From 2eb0071689ebe4157269d78cd1b7401b5f81f45b Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Sat, 10 Jul 2021 20:38:44 +0200 Subject: [PATCH 001/133] use O_TRUNC when file locking is disabled, avoid ftruncate syscall (#55456) * add test project with file locking disabled via config file * use O_TRUNC when file locking is disabled, avoid ftruncate syscall * Apply suggestions from code review Co-authored-by: Stephen Toub --- .../TestUtilities/System/PlatformDetection.cs | 8 +++-- .../System.IO.FileSystem.sln | 7 ++++ .../DisabledFileLockingSwitchTests.cs | 16 ++++++++++ ...ileSystem.DisabledFileLocking.Tests.csproj | 32 +++++++++++++++++++ .../runtimeconfig.template.json | 5 +++ .../System.IO.FileSystem/tests/File/Copy.cs | 2 +- .../System.IO.FileSystem/tests/File/Create.cs | 3 +- .../tests/File/ReadWriteAllBytes.cs | 3 +- .../tests/File/ReadWriteAllBytesAsync.cs | 3 +- .../tests/File/ReadWriteAllLines.cs | 6 ++-- .../tests/File/ReadWriteAllLinesAsync.cs | 3 +- .../tests/File/ReadWriteAllText.cs | 3 +- .../tests/File/ReadWriteAllTextAsync.cs | 3 +- .../tests/FileStream/ctor_str_fm_fa_fs.cs | 3 +- .../FileStream/ctor_str_fm_fa_fs.write.cs | 3 +- ...stem.IO.FileSystem.Net5Compat.Tests.csproj | 1 + .../Win32/SafeHandles/SafeFileHandle.Unix.cs | 20 ++++++++++-- 17 files changed, 95 insertions(+), 26 deletions(-) create mode 100644 src/libraries/System.IO.FileSystem/tests/DisabledFileLockingTests/DisabledFileLockingSwitchTests.cs create mode 100644 src/libraries/System.IO.FileSystem/tests/DisabledFileLockingTests/System.IO.FileSystem.DisabledFileLocking.Tests.csproj create mode 100644 src/libraries/System.IO.FileSystem/tests/DisabledFileLockingTests/runtimeconfig.template.json diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 0bd29858a169..e11da7e8d4d9 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -70,8 +70,8 @@ public static partial class PlatformDetection public static bool IsLinqExpressionsBuiltWithIsInterpretingOnly => s_LinqExpressionsBuiltWithIsInterpretingOnly.Value; public static bool IsNotLinqExpressionsBuiltWithIsInterpretingOnly => !IsLinqExpressionsBuiltWithIsInterpretingOnly; private static readonly Lazy s_LinqExpressionsBuiltWithIsInterpretingOnly = new Lazy(GetLinqExpressionsBuiltWithIsInterpretingOnly); - private static bool GetLinqExpressionsBuiltWithIsInterpretingOnly() - { + private static bool GetLinqExpressionsBuiltWithIsInterpretingOnly() + { Type type = typeof(LambdaExpression); if (type != null) { @@ -285,6 +285,10 @@ private static Version GetICUVersion() public static bool IsNet5CompatFileStreamEnabled => _net5CompatFileStream.Value; + private static readonly Lazy s_fileLockingDisabled = new Lazy(() => GetStaticNonPublicBooleanPropertyValue("Microsoft.Win32.SafeHandles.SafeFileHandle", "DisableFileLocking")); + + public static bool IsFileLockingEnabled => IsWindows || !s_fileLockingDisabled.Value; + private static bool GetIsInContainer() { if (IsWindows) diff --git a/src/libraries/System.IO.FileSystem/System.IO.FileSystem.sln b/src/libraries/System.IO.FileSystem/System.IO.FileSystem.sln index 93f9297296e4..fd94f8bfdcac 100644 --- a/src/libraries/System.IO.FileSystem/System.IO.FileSystem.sln +++ b/src/libraries/System.IO.FileSystem/System.IO.FileSystem.sln @@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.IO.FileSystem.Net5Co EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StreamConformanceTests", "..\Common\tests\StreamConformanceTests\StreamConformanceTests.csproj", "{FEF03BCC-509F-4646-9132-9DE27FA3DA6F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.IO.FileSystem.DisabledFileLocking.Tests", "tests\DisabledFileLockingTests\System.IO.FileSystem.DisabledFileLocking.Tests.csproj", "{D20CD3B7-A332-4D47-851A-FD8C80AE10B9}" +EndProject Global GlobalSection(NestedProjects) = preSolution {D350D6E7-52F1-40A4-B646-C178F6BBB689} = {1A727AF9-4F39-4109-BB8F-813286031DC9} @@ -43,6 +45,7 @@ Global {D7DF8034-3AE5-4DEF-BCC4-6353239391BF} = {D9FB1730-B750-4C0D-8D24-8C992DEB6034} {48E07F12-8597-40DE-8A37-CCBEB9D54012} = {1A727AF9-4F39-4109-BB8F-813286031DC9} {FEF03BCC-509F-4646-9132-9DE27FA3DA6F} = {1A727AF9-4F39-4109-BB8F-813286031DC9} + {D20CD3B7-A332-4D47-851A-FD8C80AE10B9} = {1A727AF9-4F39-4109-BB8F-813286031DC9} EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -97,6 +100,10 @@ Global {FEF03BCC-509F-4646-9132-9DE27FA3DA6F}.Debug|Any CPU.Build.0 = Debug|Any CPU {FEF03BCC-509F-4646-9132-9DE27FA3DA6F}.Release|Any CPU.ActiveCfg = Release|Any CPU {FEF03BCC-509F-4646-9132-9DE27FA3DA6F}.Release|Any CPU.Build.0 = Release|Any CPU + {D20CD3B7-A332-4D47-851A-FD8C80AE10B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D20CD3B7-A332-4D47-851A-FD8C80AE10B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D20CD3B7-A332-4D47-851A-FD8C80AE10B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D20CD3B7-A332-4D47-851A-FD8C80AE10B9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/libraries/System.IO.FileSystem/tests/DisabledFileLockingTests/DisabledFileLockingSwitchTests.cs b/src/libraries/System.IO.FileSystem/tests/DisabledFileLockingTests/DisabledFileLockingSwitchTests.cs new file mode 100644 index 000000000000..736991961ac9 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/DisabledFileLockingTests/DisabledFileLockingSwitchTests.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.IO.Tests +{ + public class DisabledFileLockingSwitchTests + { + [Fact] + public static void ConfigSwitchIsHonored() + { + Assert.Equal(OperatingSystem.IsWindows(), PlatformDetection.IsFileLockingEnabled); + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/DisabledFileLockingTests/System.IO.FileSystem.DisabledFileLocking.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/DisabledFileLockingTests/System.IO.FileSystem.DisabledFileLocking.Tests.csproj new file mode 100644 index 000000000000..87afaa0fb079 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/DisabledFileLockingTests/System.IO.FileSystem.DisabledFileLocking.Tests.csproj @@ -0,0 +1,32 @@ + + + true + true + + $(NetCoreAppCurrent)-Unix + + --working-dir=/test-dir + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libraries/System.IO.FileSystem/tests/DisabledFileLockingTests/runtimeconfig.template.json b/src/libraries/System.IO.FileSystem/tests/DisabledFileLockingTests/runtimeconfig.template.json new file mode 100644 index 000000000000..c118f2a0a0a0 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/DisabledFileLockingTests/runtimeconfig.template.json @@ -0,0 +1,5 @@ +{ + "configProperties": { + "System.IO.DisableFileLocking": true + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/File/Copy.cs b/src/libraries/System.IO.FileSystem/tests/File/Copy.cs index eb2686807bc1..643d2a98cf22 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/Copy.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/Copy.cs @@ -355,7 +355,7 @@ public void WindowsAlternateDataStreamOverwrite(string defaultStream, string alt /// /// Single tests that shouldn't be duplicated by inheritance. /// - [SkipOnPlatform(TestPlatforms.Browser, "https://github.com/dotnet/runtime/issues/40867 - flock not supported")] + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsFileLockingEnabled))] public sealed class File_Copy_Single : FileSystemTest { [Fact] diff --git a/src/libraries/System.IO.FileSystem/tests/File/Create.cs b/src/libraries/System.IO.FileSystem/tests/File/Create.cs index bf0417dbfe06..fb7f4be892c7 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/Create.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/Create.cs @@ -142,8 +142,7 @@ public void InvalidDirectory() Assert.Throws(() => Create(testFile)); } - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/40065", TestPlatforms.Browser)] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsFileLockingEnabled))] public void FileInUse() { DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath()); diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs index 7f13447f37d9..54c764376633 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs @@ -80,8 +80,7 @@ public void Overwrite() Assert.Equal(overwriteBytes, File.ReadAllBytes(path)); } - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/40065", TestPlatforms.Browser)] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsFileLockingEnabled))] public void OpenFile_ThrowsIOException() { string path = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs index 111ecb545ade..748c01dbffb8 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs @@ -94,8 +94,7 @@ public async Task OverwriteAsync() Assert.Equal(overwriteBytes, await File.ReadAllBytesAsync(path)); } - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/40065", TestPlatforms.Browser)] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsFileLockingEnabled))] public async Task OpenFile_ThrowsIOExceptionAsync() { string path = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllLines.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllLines.cs index c0ba0787d577..99c94b450790 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllLines.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllLines.cs @@ -77,8 +77,7 @@ public virtual void Overwrite() Assert.Equal(overwriteLines, Read(path)); } - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/40065", TestPlatforms.Browser)] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsFileLockingEnabled))] public void OpenFile_ThrowsIOException() { string path = GetTestFilePath(); @@ -267,8 +266,7 @@ public virtual void Overwrite() Assert.Equal(overwriteLines, Read(path)); } - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/40065", TestPlatforms.Browser)] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsFileLockingEnabled))] public void OpenFile_ThrowsIOException() { string path = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllLinesAsync.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllLinesAsync.cs index 76d1541d60c5..b2a1288639db 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllLinesAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllLinesAsync.cs @@ -75,8 +75,7 @@ public virtual async Task OverwriteAsync() Assert.Equal(overwriteLines, await ReadAsync(path)); } - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/40065", TestPlatforms.Browser)] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsFileLockingEnabled))] public async Task OpenFile_ThrowsIOExceptionAsync() { string path = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllText.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllText.cs index d24a8445e6d7..1dbe30384965 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllText.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllText.cs @@ -84,8 +84,7 @@ public virtual void Overwrite() Assert.Equal(overwriteLines, Read(path)); } - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/40065", TestPlatforms.Browser)] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsFileLockingEnabled))] public void OpenFile_ThrowsIOException() { string path = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllTextAsync.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllTextAsync.cs index 3481bff97b4f..3ad76c272fff 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllTextAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllTextAsync.cs @@ -83,8 +83,7 @@ public virtual async Task OverwriteAsync() Assert.Equal(overwriteLines, await ReadAsync(path)); } - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/40065", TestPlatforms.Browser)] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsFileLockingEnabled))] public async Task OpenFile_ThrowsIOExceptionAsync() { string path = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.cs index 386b2992ee3e..205e2940e7ec 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.cs @@ -120,10 +120,9 @@ public void FileShareOpenOrCreate() } } - [Theory] [InlineData(FileMode.Create)] [InlineData(FileMode.Truncate)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/40065", TestPlatforms.Browser)] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsFileLockingEnabled))] public void NoTruncateOnFileShareViolation(FileMode fileMode) { string fileName = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.write.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.write.cs index ae93555bbe83..6ee1c6fc95c8 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.write.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.write.cs @@ -37,8 +37,7 @@ public void FileShareWriteExisting() } } - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/40065", TestPlatforms.Browser)] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsFileLockingEnabled))] public void FileShareWithoutWriteThrows() { string fileName = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj index dd67a63fed17..745ff60368b2 100644 --- a/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj +++ b/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj @@ -8,6 +8,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs index 10dd1eabee01..ee1def85b9e0 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs @@ -206,15 +206,29 @@ private static Interop.Sys.OpenFlags PreOpenConfigurationFromOptions(FileMode mo { default: case FileMode.Open: // Open maps to the default behavior for open(...). No flags needed. - case FileMode.Truncate: // We truncate the file after getting the lock + break; + case FileMode.Truncate: + if (DisableFileLocking) + { + // if we don't lock the file, we can truncate it when opening + // otherwise we truncate the file after getting the lock + flags |= Interop.Sys.OpenFlags.O_TRUNC; + } break; case FileMode.Append: // Append is the same as OpenOrCreate, except that we'll also separately jump to the end later case FileMode.OpenOrCreate: - case FileMode.Create: // We truncate the file after getting the lock flags |= Interop.Sys.OpenFlags.O_CREAT; break; + case FileMode.Create: + flags |= Interop.Sys.OpenFlags.O_CREAT; + if (DisableFileLocking) + { + flags |= Interop.Sys.OpenFlags.O_TRUNC; + } + break; + case FileMode.CreateNew: flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL); break; @@ -292,7 +306,7 @@ private void Init(string path, FileMode mode, FileAccess access, FileShare share ignoreNotSupported: true); // just a hint. } - if (mode == FileMode.Create || mode == FileMode.Truncate) + if ((mode == FileMode.Create || mode == FileMode.Truncate) && !DisableFileLocking) { // Truncate the file now if the file mode requires it. This ensures that the file only will be truncated // if opened successfully. From 960e7b352d5f4166193b2ca35c2f627ce0b60332 Mon Sep 17 00:00:00 2001 From: Juan Hoyos Date: Sat, 10 Jul 2021 13:01:37 -0700 Subject: [PATCH 002/133] Disable tests blocking CI (#55446) * Turn off test FlushAsync_ThrowsIfWriterReaderWithException * Turn off System.Net.NameResolution.Tests.GetHost* tests for SLES --- .../tests/FlushAsyncTests.cs | 2 +- .../FunctionalTests/GetHostByNameTest.cs | 15 ++++++++++++- .../tests/FunctionalTests/GetHostEntryTest.cs | 22 +++++++++++++++---- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.IO.Pipelines/tests/FlushAsyncTests.cs b/src/libraries/System.IO.Pipelines/tests/FlushAsyncTests.cs index 9647a47055b1..fa3db4c91ae4 100644 --- a/src/libraries/System.IO.Pipelines/tests/FlushAsyncTests.cs +++ b/src/libraries/System.IO.Pipelines/tests/FlushAsyncTests.cs @@ -22,7 +22,7 @@ public void FlushAsync_ReturnsCompletedTaskWhenMaxSizeIfZero() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/50957", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/55435")] public async Task FlushAsync_ThrowsIfWriterReaderWithException() { void ThrowTestException() diff --git a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostByNameTest.cs b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostByNameTest.cs index 94dba8bdb7fc..2b62cb6c7719 100644 --- a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostByNameTest.cs +++ b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostByNameTest.cs @@ -4,6 +4,8 @@ #pragma warning disable 0618 // use of obsolete methods using System.Net.Sockets; + +using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace System.Net.NameResolution.Tests @@ -106,17 +108,28 @@ public void DnsObsoleteGetHostByName_IPv6String_ReturnsOnlyGivenIP() [ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] public void DnsObsoleteGetHostByName_EmptyString_ReturnsHostName() { + if (PlatformDetection.IsSLES) + { + // See https://github.com/dotnet/runtime/issues/55271 + throw new SkipTestException("SLES Tests environment is not configured for this test to work."); + } IPHostEntry entry = Dns.GetHostByName(""); // DNS labels should be compared as case insensitive for ASCII characters. See RFC 4343. Assert.Contains(Dns.GetHostName(), entry.HostName, StringComparison.OrdinalIgnoreCase); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)] [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArm64Process), nameof(PlatformDetection.IsThreadingSupported))] // [ActiveIssue("https://github.com/dotnet/runtime/issues/27622")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)] [ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] public void DnsObsoleteBeginEndGetHostByName_EmptyString_ReturnsHostName() { + if (PlatformDetection.IsSLES) + { + // See https://github.com/dotnet/runtime/issues/55271 + throw new SkipTestException("SLES Tests environment is not configured for this test to work."); + } + IPHostEntry entry = Dns.EndGetHostByName(Dns.BeginGetHostByName("", null, null)); // DNS labels should be compared as case insensitive for ASCII characters. See RFC 4343. diff --git a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs index 6fa40dd80c46..f860a2274e81 100644 --- a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs +++ b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace System.Net.NameResolution.Tests @@ -21,13 +22,18 @@ public async Task Dns_GetHostEntryAsync_IPAddress_Ok() } [ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)] - + [ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArm64Process))] // [ActiveIssue("https://github.com/dotnet/runtime/issues/27622")] [InlineData("")] [InlineData(TestSettings.LocalHost)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] public async Task Dns_GetHostEntry_HostString_Ok(string hostName) { + if (PlatformDetection.IsSLES) + { + // See https://github.com/dotnet/runtime/issues/55271 + throw new SkipTestException("SLES Tests environment is not configured for this test to work."); + } + try { await TestGetHostEntryAsync(() => Task.FromResult(Dns.GetHostEntry(hostName))); @@ -72,12 +78,20 @@ public async Task Dns_GetHostEntry_HostString_Ok(string hostName) } [ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArm64Process))] // [ActiveIssue("https://github.com/dotnet/runtime/issues/27622")] [InlineData("")] [InlineData(TestSettings.LocalHost)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - public async Task Dns_GetHostEntryAsync_HostString_Ok(string hostName) => + public async Task Dns_GetHostEntryAsync_HostString_Ok(string hostName) + { + if (PlatformDetection.IsSLES) + { + // See https://github.com/dotnet/runtime/issues/55271 + throw new SkipTestException("SLES Tests environment is not configured for this test to work."); + } + await TestGetHostEntryAsync(() => Dns.GetHostEntryAsync(hostName)); + } [Fact] public async Task Dns_GetHostEntryAsync_IPString_Ok() => From fce412be7779966533b94ba879e98e4eecbd94fc Mon Sep 17 00:00:00 2001 From: Yauk <33248368+Yauk@users.noreply.github.com> Date: Sat, 10 Jul 2021 15:23:25 -0700 Subject: [PATCH 003/133] Support filtering ObjectAllocated callback for pinned object heap allocation only (#55448) * Prototype allocation profiler * Add callback for pinned objects * Fix the build issue caused by corprof.idl change * Improve the test * Misc changes for the tests Co-authored-by: Andrew Au Co-authored-by: Yauk Jia --- src/coreclr/inc/corprof.idl | 3 + src/coreclr/inc/profilepriv.inl | 15 ++++ src/coreclr/pal/prebuilt/inc/corprof.h | 1 + src/coreclr/vm/eeprofinterfaces.inl | 8 ++ src/coreclr/vm/gchelpers.cpp | 3 +- src/tests/profiler/gc/gcallocate.cs | 33 +++++++++ src/tests/profiler/gc/gcallocate.csproj | 23 ++++++ src/tests/profiler/native/CMakeLists.txt | 1 + src/tests/profiler/native/classfactory.cpp | 2 + .../gcallocateprofiler/gcallocateprofiler.cpp | 73 +++++++++++++++++++ .../gcallocateprofiler/gcallocateprofiler.h | 26 +++++++ .../gcbasicprofiler/gcbasicprofiler.cpp | 4 +- .../profiler/native/gcprofiler/gcprofiler.cpp | 2 +- 13 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 src/tests/profiler/gc/gcallocate.cs create mode 100644 src/tests/profiler/gc/gcallocate.csproj create mode 100644 src/tests/profiler/native/gcallocateprofiler/gcallocateprofiler.cpp create mode 100644 src/tests/profiler/native/gcallocateprofiler/gcallocateprofiler.h diff --git a/src/coreclr/inc/corprof.idl b/src/coreclr/inc/corprof.idl index 8fc965a84f6b..d1c58f96cf97 100644 --- a/src/coreclr/inc/corprof.idl +++ b/src/coreclr/inc/corprof.idl @@ -667,6 +667,9 @@ typedef enum COR_PRF_HIGH_MONITOR_EVENT_PIPE = 0x00000080, + // Enables the pinned object allocation monitoring. + COR_PRF_HIGH_MONITOR_PINNEDOBJECT_ALLOCATED = 0x00000100, + COR_PRF_HIGH_ALLOWABLE_AFTER_ATTACH = COR_PRF_HIGH_IN_MEMORY_SYMBOLS_UPDATED | COR_PRF_HIGH_MONITOR_DYNAMIC_FUNCTION_UNLOADS | COR_PRF_HIGH_BASIC_GC | diff --git a/src/coreclr/inc/profilepriv.inl b/src/coreclr/inc/profilepriv.inl index 08ba58f5623f..e99591c5ffd1 100644 --- a/src/coreclr/inc/profilepriv.inl +++ b/src/coreclr/inc/profilepriv.inl @@ -1860,6 +1860,21 @@ inline BOOL CORProfilerTrackLargeAllocations() (&g_profControlBlock)->globalEventMask.IsEventMaskHighSet(COR_PRF_HIGH_MONITOR_LARGEOBJECT_ALLOCATED)); } +inline BOOL CORProfilerTrackPinnedAllocations() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + CANNOT_TAKE_LOCK; + } + CONTRACTL_END; + + return + (CORProfilerPresent() && + (&g_profControlBlock)->globalEventMask.IsEventMaskHighSet(COR_PRF_HIGH_MONITOR_PINNEDOBJECT_ALLOCATED)); +} + inline BOOL CORProfilerEnableRejit() { CONTRACTL diff --git a/src/coreclr/pal/prebuilt/inc/corprof.h b/src/coreclr/pal/prebuilt/inc/corprof.h index 85ce86870bf8..e82623d0c09f 100644 --- a/src/coreclr/pal/prebuilt/inc/corprof.h +++ b/src/coreclr/pal/prebuilt/inc/corprof.h @@ -574,6 +574,7 @@ enum __MIDL___MIDL_itf_corprof_0000_0000_0006 COR_PRF_HIGH_REQUIRE_PROFILE_IMAGE = 0, COR_PRF_HIGH_MONITOR_LARGEOBJECT_ALLOCATED = 0x40, COR_PRF_HIGH_MONITOR_EVENT_PIPE = 0x80, + COR_PRF_HIGH_MONITOR_PINNEDOBJECT_ALLOCATED = 0x100, COR_PRF_HIGH_ALLOWABLE_AFTER_ATTACH = ( ( ( ( ( COR_PRF_HIGH_IN_MEMORY_SYMBOLS_UPDATED | COR_PRF_HIGH_MONITOR_DYNAMIC_FUNCTION_UNLOADS ) | COR_PRF_HIGH_BASIC_GC ) | COR_PRF_HIGH_MONITOR_GC_MOVED_OBJECTS ) | COR_PRF_HIGH_MONITOR_LARGEOBJECT_ALLOCATED ) | COR_PRF_HIGH_MONITOR_EVENT_PIPE ) , COR_PRF_HIGH_ALLOWABLE_NOTIFICATION_PROFILER = ( ( ( ( ( ( COR_PRF_HIGH_IN_MEMORY_SYMBOLS_UPDATED | COR_PRF_HIGH_MONITOR_DYNAMIC_FUNCTION_UNLOADS ) | COR_PRF_HIGH_DISABLE_TIERED_COMPILATION ) | COR_PRF_HIGH_BASIC_GC ) | COR_PRF_HIGH_MONITOR_GC_MOVED_OBJECTS ) | COR_PRF_HIGH_MONITOR_LARGEOBJECT_ALLOCATED ) | COR_PRF_HIGH_MONITOR_EVENT_PIPE ) , COR_PRF_HIGH_MONITOR_IMMUTABLE = COR_PRF_HIGH_DISABLE_TIERED_COMPILATION diff --git a/src/coreclr/vm/eeprofinterfaces.inl b/src/coreclr/vm/eeprofinterfaces.inl index 250b3700f801..da6e97883296 100644 --- a/src/coreclr/vm/eeprofinterfaces.inl +++ b/src/coreclr/vm/eeprofinterfaces.inl @@ -31,5 +31,13 @@ FORCEINLINE BOOL TrackLargeAllocations() #endif // PROFILING_SUPPORTED } +FORCEINLINE BOOL TrackPinnedAllocations() +{ +#ifdef PROFILING_SUPPORTED + return CORProfilerTrackPinnedAllocations(); +#else + return FALSE; +#endif // PROFILING_SUPPORTED +} #endif diff --git a/src/coreclr/vm/gchelpers.cpp b/src/coreclr/vm/gchelpers.cpp index 0cecfc624a74..01ffd5305d9f 100644 --- a/src/coreclr/vm/gchelpers.cpp +++ b/src/coreclr/vm/gchelpers.cpp @@ -324,7 +324,8 @@ void PublishObjectAndNotify(TObj* &orObject, GC_ALLOC_FLAGS flags) // Notify the profiler of the allocation // do this after initializing bounds so callback has size information if (TrackAllocations() || - (TrackLargeAllocations() && flags & GC_ALLOC_LARGE_OBJECT_HEAP)) + (TrackLargeAllocations() && flags & GC_ALLOC_LARGE_OBJECT_HEAP) || + (TrackPinnedAllocations() && flags & GC_ALLOC_PINNED_OBJECT_HEAP)) { OBJECTREF objref = ObjectToOBJECTREF((Object*)orObject); GCPROTECT_BEGIN(objref); diff --git a/src/tests/profiler/gc/gcallocate.cs b/src/tests/profiler/gc/gcallocate.cs new file mode 100644 index 000000000000..b3fbfd9d29cf --- /dev/null +++ b/src/tests/profiler/gc/gcallocate.cs @@ -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. + +using System; +using System.Threading; + +namespace Profiler.Tests +{ + class GCAllocateTests + { + static readonly Guid GcAllocateEventsProfilerGuid = new Guid("55b9554d-6115-45a2-be1e-c80f7fa35369"); + + public static int RunTest(String[] args) + { + int[] large = new int[100000]; + int[] pinned = GC.AllocateArray(32, true); + Console.WriteLine("Test Passed"); + return 100; + } + + public static int Main(string[] args) + { + if (args.Length > 0 && args[0].Equals("RunTest", StringComparison.OrdinalIgnoreCase)) + { + return RunTest(args); + } + + return ProfilerTestRunner.Run(profileePath: System.Reflection.Assembly.GetExecutingAssembly().Location, + testName: "GCCallbacksAllocate", + profilerClsid: GcAllocateEventsProfilerGuid); + } + } +} diff --git a/src/tests/profiler/gc/gcallocate.csproj b/src/tests/profiler/gc/gcallocate.csproj new file mode 100644 index 000000000000..b2fbdc913b04 --- /dev/null +++ b/src/tests/profiler/gc/gcallocate.csproj @@ -0,0 +1,23 @@ + + + .NETCoreApp + exe + BuildAndRun + true + 0 + true + + true + + true + + + + + + + + diff --git a/src/tests/profiler/native/CMakeLists.txt b/src/tests/profiler/native/CMakeLists.txt index 9a0561c3e6d5..e068fdc1ea3c 100644 --- a/src/tests/profiler/native/CMakeLists.txt +++ b/src/tests/profiler/native/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES eventpipeprofiler/eventpipereadingprofiler.cpp eventpipeprofiler/eventpipewritingprofiler.cpp eventpipeprofiler/eventpipemetadatareader.cpp + gcallocateprofiler/gcallocateprofiler.cpp gcbasicprofiler/gcbasicprofiler.cpp gcprofiler/gcprofiler.cpp getappdomainstaticaddress/getappdomainstaticaddress.cpp diff --git a/src/tests/profiler/native/classfactory.cpp b/src/tests/profiler/native/classfactory.cpp index b41f6ba7bc9e..fe999926de5b 100644 --- a/src/tests/profiler/native/classfactory.cpp +++ b/src/tests/profiler/native/classfactory.cpp @@ -6,6 +6,7 @@ #include "eventpipeprofiler/eventpipereadingprofiler.h" #include "eventpipeprofiler/eventpipewritingprofiler.h" #include "getappdomainstaticaddress/getappdomainstaticaddress.h" +#include "gcallocateprofiler/gcallocateprofiler.h" #include "gcbasicprofiler/gcbasicprofiler.h" #include "gcprofiler/gcprofiler.h" #include "metadatagetdispenser/metadatagetdispenser.h" @@ -61,6 +62,7 @@ HRESULT STDMETHODCALLTYPE ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFI //A little simplistic, we create an instance of every profiler, then return the one whose CLSID matches Profiler* profilers[] = { + new GCAllocateProfiler(), new GCBasicProfiler(), new ReJITProfiler(), new EventPipeReadingProfiler(), diff --git a/src/tests/profiler/native/gcallocateprofiler/gcallocateprofiler.cpp b/src/tests/profiler/native/gcallocateprofiler/gcallocateprofiler.cpp new file mode 100644 index 000000000000..20e029704301 --- /dev/null +++ b/src/tests/profiler/native/gcallocateprofiler/gcallocateprofiler.cpp @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "gcallocateprofiler.h" + +GUID GCAllocateProfiler::GetClsid() +{ + // {55b9554d-6115-45a2-be1e-c80f7fa35369} + GUID clsid = { 0x55b9554d, 0x6115, 0x45a2,{ 0xbe, 0x1e, 0xc8, 0x0f, 0x7f, 0xa3, 0x53, 0x69 } }; + return clsid; +} + +HRESULT GCAllocateProfiler::Initialize(IUnknown* pICorProfilerInfoUnk) +{ + Profiler::Initialize(pICorProfilerInfoUnk); + + HRESULT hr = S_OK; + if (FAILED(hr = pCorProfilerInfo->SetEventMask2(COR_PRF_ENABLE_OBJECT_ALLOCATED, COR_PRF_HIGH_BASIC_GC | COR_PRF_HIGH_MONITOR_LARGEOBJECT_ALLOCATED | COR_PRF_HIGH_MONITOR_PINNEDOBJECT_ALLOCATED))) + { + printf("FAIL: ICorProfilerInfo::SetEventMask2() failed hr=0x%x", hr); + return hr; + } + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE GCAllocateProfiler::ObjectAllocated(ObjectID objectId, ClassID classId) +{ + COR_PRF_GC_GENERATION_RANGE gen; + HRESULT hr = pCorProfilerInfo->GetObjectGeneration(objectId, &gen); + if (FAILED(hr)) + { + printf("GetObjectGeneration failed hr=0x%x\n", hr); + _failures++; + } + else if (gen.generation == COR_PRF_GC_LARGE_OBJECT_HEAP) + { + _gcLOHAllocations++; + } + else if (gen.generation == COR_PRF_GC_PINNED_OBJECT_HEAP) + { + _gcPOHAllocations++; + } + else + { + printf("Unexpected object allocation captured, gen.generation=0x%x\n", gen.generation); + _failures++; + } + + return S_OK; +} + +HRESULT GCAllocateProfiler::Shutdown() +{ + Profiler::Shutdown(); + if (_gcPOHAllocations == 0) + { + printf("There is no POH allocations\n"); + } + else if (_gcLOHAllocations == 0) + { + printf("There is no LOH allocations\n"); + } + else if (_failures == 0) + { + printf("%d LOH objects allocated\n", (int)_gcLOHAllocations); + printf("%d POH objects allocated\n", (int)_gcPOHAllocations); + printf("PROFILER TEST PASSES\n"); + } + fflush(stdout); + + return S_OK; +} diff --git a/src/tests/profiler/native/gcallocateprofiler/gcallocateprofiler.h b/src/tests/profiler/native/gcallocateprofiler/gcallocateprofiler.h new file mode 100644 index 000000000000..65fb3b16240e --- /dev/null +++ b/src/tests/profiler/native/gcallocateprofiler/gcallocateprofiler.h @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include "../profiler.h" + +class GCAllocateProfiler : public Profiler +{ +public: + GCAllocateProfiler() : Profiler(), + _gcLOHAllocations(0), + _gcPOHAllocations(0), + _failures(0) + {} + + virtual GUID GetClsid(); + virtual HRESULT STDMETHODCALLTYPE Initialize(IUnknown* pICorProfilerInfoUnk); + virtual HRESULT STDMETHODCALLTYPE ObjectAllocated(ObjectID objectId, ClassID classId); + virtual HRESULT STDMETHODCALLTYPE Shutdown(); + +private: + std::atomic _gcLOHAllocations; + std::atomic _gcPOHAllocations; + std::atomic _failures; +}; diff --git a/src/tests/profiler/native/gcbasicprofiler/gcbasicprofiler.cpp b/src/tests/profiler/native/gcbasicprofiler/gcbasicprofiler.cpp index a9f28011eb02..6d377e6d115b 100644 --- a/src/tests/profiler/native/gcbasicprofiler/gcbasicprofiler.cpp +++ b/src/tests/profiler/native/gcbasicprofiler/gcbasicprofiler.cpp @@ -15,7 +15,7 @@ HRESULT GCBasicProfiler::Initialize(IUnknown* pICorProfilerInfoUnk) Profiler::Initialize(pICorProfilerInfoUnk); HRESULT hr = S_OK; - if (FAILED(hr = pCorProfilerInfo->SetEventMask2(0, 0x10))) + if (FAILED(hr = pCorProfilerInfo->SetEventMask2(0, COR_PRF_HIGH_BASIC_GC))) { _failures++; printf("FAIL: ICorProfilerInfo::SetEventMask2() failed hr=0x%x", hr); @@ -31,7 +31,7 @@ HRESULT GCBasicProfiler::Shutdown() if (_gcStarts == 0) { - printf("GCBasicProfiler::Shutdown: FAIL: Expected GarbaseCollectionStarted to be called\n"); + printf("GCBasicProfiler::Shutdown: FAIL: Expected GarbageCollectionStarted to be called\n"); } else if (_gcFinishes == 0) { diff --git a/src/tests/profiler/native/gcprofiler/gcprofiler.cpp b/src/tests/profiler/native/gcprofiler/gcprofiler.cpp index 0fa298f59f3b..42666a2d8eac 100644 --- a/src/tests/profiler/native/gcprofiler/gcprofiler.cpp +++ b/src/tests/profiler/native/gcprofiler/gcprofiler.cpp @@ -31,7 +31,7 @@ HRESULT GCProfiler::Shutdown() if (_gcStarts == 0) { - printf("GCProfiler::Shutdown: FAIL: Expected GarbaseCollectionStarted to be called\n"); + printf("GCProfiler::Shutdown: FAIL: Expected GarbageCollectionStarted to be called\n"); } else if (_gcFinishes == 0) { From 8fdc82909aefb4768ddce545d5352958e4874f8e Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sat, 10 Jul 2021 15:55:27 -0700 Subject: [PATCH 004/133] Propagators Support (#55419) --- ...em.Diagnostics.DiagnosticSourceActivity.cs | 15 +- ...System.Diagnostics.DiagnosticSource.csproj | 4 + .../System/Diagnostics/LegacyPropagator.cs | 189 +++++ .../System/Diagnostics/NoOutputPropagator.cs | 23 + .../Diagnostics/PassThroughPropagator.cs | 59 ++ .../System/Diagnostics/TextMapPropagator.cs | 141 ++++ .../tests/PropagatorTests.cs | 664 ++++++++++++++++++ ....Diagnostics.DiagnosticSource.Tests.csproj | 1 + 8 files changed, 1095 insertions(+), 1 deletion(-) create mode 100644 src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LegacyPropagator.cs create mode 100644 src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/NoOutputPropagator.cs create mode 100644 src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/PassThroughPropagator.cs create mode 100644 src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/TextMapPropagator.cs create mode 100644 src/libraries/System.Diagnostics.DiagnosticSource/tests/PropagatorTests.cs diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs index 482a58bb414e..7e50f732e421 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs @@ -253,7 +253,20 @@ public sealed class ActivityListener : IDisposable public System.Diagnostics.SampleActivity? SampleUsingParentId { get { throw null; } set { throw null; } } public System.Diagnostics.SampleActivity? Sample { get { throw null; } set { throw null; } } public void Dispose() { throw null; } - } + } + public abstract class TextMapPropagator + { + public delegate void PropagatorGetterCallback(object? carrier, string fieldName, out string? fieldValue, out System.Collections.Generic.IEnumerable? fieldValues); + public delegate void PropagatorSetterCallback(object? carrier, string fieldName, string fieldValue); + public abstract System.Collections.Generic.IReadOnlyCollection Fields { get; } + public abstract void Inject(Activity? activity, object? carrier, PropagatorSetterCallback? setter); + public abstract void ExtractTraceIdAndState(object? carrier, PropagatorGetterCallback? getter, out string? traceId, out string? traceState); + public abstract System.Collections.Generic.IEnumerable>? ExtractBaggage(object? carrier, PropagatorGetterCallback? getter); + public static TextMapPropagator Current { get; set; } + public static TextMapPropagator CreateDefaultPropagator() { throw null; } + public static TextMapPropagator CreatePassThroughPropagator() { throw null; } + public static TextMapPropagator CreateNoOutputPropagator() { throw null; } + } } namespace System.Diagnostics.Metrics diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj index e1dca02a19d4..e4a9cbd28d7f 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj @@ -39,7 +39,11 @@ + + + + diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LegacyPropagator.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LegacyPropagator.cs new file mode 100644 index 000000000000..a469dd5b56b6 --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LegacyPropagator.cs @@ -0,0 +1,189 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace System.Diagnostics +{ + internal sealed class LegacyPropagator : TextMapPropagator + { + internal static TextMapPropagator Instance { get; } = new LegacyPropagator(); + + public override IReadOnlyCollection Fields { get; } = new ReadOnlyCollection(new[] { TraceParent, RequestId, TraceState, Baggage, CorrelationContext }); + + public override void Inject(Activity? activity, object? carrier, PropagatorSetterCallback? setter) + { + if (activity is null || setter is null) + { + return; + } + + string? id = activity.Id; + if (id is null) + { + return; + } + + if (activity.IdFormat == ActivityIdFormat.W3C) + { + setter(carrier, TraceParent, id); + if (!string.IsNullOrEmpty(activity.TraceStateString)) + { + setter(carrier, TraceState, activity.TraceStateString); + } + } + else + { + setter(carrier, RequestId, id); + } + + InjectBaggage(carrier, activity.Baggage, setter); + } + + public override void ExtractTraceIdAndState(object? carrier, PropagatorGetterCallback? getter, out string? traceId, out string? traceState) + { + if (getter is null) + { + traceId = null; + traceState = null; + return; + } + + getter(carrier, TraceParent, out traceId, out _); + if (traceId is null) + { + getter(carrier, RequestId, out traceId, out _); + } + + getter(carrier, TraceState, out traceState, out _); + } + + public override IEnumerable>? ExtractBaggage(object? carrier, PropagatorGetterCallback? getter) + { + if (getter is null) + { + return null; + } + + getter(carrier, Baggage, out string? theBaggage, out _); + + IEnumerable>? baggage = null; + if (theBaggage is null || !TryExtractBaggage(theBaggage, out baggage)) + { + getter(carrier, CorrelationContext, out theBaggage, out _); + if (theBaggage is not null) + { + TryExtractBaggage(theBaggage, out baggage); + } + } + + return baggage; + } + + internal static bool TryExtractBaggage(string baggageString, out IEnumerable>? baggage) + { + baggage = null; + List>? baggageList = null; + + if (string.IsNullOrEmpty(baggageString)) + { + return true; + } + + int currentIndex = 0; + + do + { + // Skip spaces + while (currentIndex < baggageString.Length && (baggageString[currentIndex] == Space || baggageString[currentIndex] == Tab)) + { + currentIndex++; + } + + if (currentIndex >= baggageString.Length) + { + break; // No Key exist + } + + int keyStart = currentIndex; + + // Search end of the key + while (currentIndex < baggageString.Length && baggageString[currentIndex] != Space && baggageString[currentIndex] != Tab && baggageString[currentIndex] != '=') + { + currentIndex++; + } + + if (currentIndex >= baggageString.Length) + { + break; + } + + int keyEnd = currentIndex; + + if (baggageString[currentIndex] != '=') + { + // Skip Spaces + while (currentIndex < baggageString.Length && (baggageString[currentIndex] == Space || baggageString[currentIndex] == Tab)) + { + currentIndex++; + } + + if (currentIndex >= baggageString.Length) + { + break; // Wrong key format + } + + if (baggageString[currentIndex] != '=') + { + break; // wrong key format. + } + } + + currentIndex++; + + // Skip spaces + while (currentIndex < baggageString.Length && (baggageString[currentIndex] == Space || baggageString[currentIndex] == Tab)) + { + currentIndex++; + } + + if (currentIndex >= baggageString.Length) + { + break; // Wrong value format + } + + int valueStart = currentIndex; + + // Search end of the value + while (currentIndex < baggageString.Length && baggageString[currentIndex] != Space && baggageString[currentIndex] != Tab && + baggageString[currentIndex] != Comma && baggageString[currentIndex] != Semicolon) + { + currentIndex++; + } + + if (keyStart < keyEnd && valueStart < currentIndex) + { + baggageList ??= new(); + + // Insert in reverse order for asp.net compatability. + baggageList.Insert(0, new KeyValuePair( + WebUtility.UrlDecode(baggageString.Substring(keyStart, keyEnd - keyStart)).Trim(s_trimmingSpaceCharacters), + WebUtility.UrlDecode(baggageString.Substring(valueStart, currentIndex - valueStart)).Trim(s_trimmingSpaceCharacters))); + } + + // Skip to end of values + while (currentIndex < baggageString.Length && baggageString[currentIndex] != Comma) + { + currentIndex++; + } + + currentIndex++; // Move to next key-value entry + } while (currentIndex < baggageString.Length); + + baggage = baggageList; + return baggageList != null; + } + } +} diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/NoOutputPropagator.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/NoOutputPropagator.cs new file mode 100644 index 000000000000..f9d503a8f1c9 --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/NoOutputPropagator.cs @@ -0,0 +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.Collections.Generic; + +namespace System.Diagnostics +{ + internal sealed class NoOutputPropagator : TextMapPropagator + { + internal static TextMapPropagator Instance { get; } = new NoOutputPropagator(); + + public override IReadOnlyCollection Fields { get; } = LegacyPropagator.Instance.Fields; + + public override void Inject(Activity? activity, object? carrier, PropagatorSetterCallback? setter) + { + // nothing to do. + } + + public override void ExtractTraceIdAndState(object? carrier, PropagatorGetterCallback? getter, out string? traceId, out string? traceState) => LegacyPropagator.Instance.ExtractTraceIdAndState(carrier, getter, out traceId, out traceState); + + public override IEnumerable>? ExtractBaggage(object? carrier, PropagatorGetterCallback? getter) => LegacyPropagator.Instance.ExtractBaggage(carrier, getter); + } +} diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/PassThroughPropagator.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/PassThroughPropagator.cs new file mode 100644 index 000000000000..01bf821a5cbc --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/PassThroughPropagator.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Diagnostics +{ + internal sealed class PassThroughPropagator : TextMapPropagator + { + internal static TextMapPropagator Instance { get; } = new PassThroughPropagator(); + + public override IReadOnlyCollection Fields { get; } = LegacyPropagator.Instance.Fields; + + public override void Inject(Activity? activity, object? carrier, PropagatorSetterCallback? setter) + { + if (setter is null) + { + return; + } + + GetRootId(out string? parentId, out string? traceState, out bool isW3c, out IEnumerable>? baggage); + if (parentId is null) + { + return; + } + + setter(carrier, isW3c ? TraceParent : RequestId, parentId); + + if (!string.IsNullOrEmpty(traceState)) + { + setter(carrier, TraceState, traceState); + } + + if (baggage is not null) + { + InjectBaggage(carrier, baggage, setter); + } + } + + public override void ExtractTraceIdAndState(object? carrier, PropagatorGetterCallback? getter, out string? traceId, out string? traceState) => LegacyPropagator.Instance.ExtractTraceIdAndState(carrier, getter, out traceId, out traceState); + + public override IEnumerable>? ExtractBaggage(object? carrier, PropagatorGetterCallback? getter) => LegacyPropagator.Instance.ExtractBaggage(carrier, getter); + + private static void GetRootId(out string? parentId, out string? traceState, out bool isW3c, out IEnumerable>? baggage) + { + Activity? activity = Activity.Current; + + while (activity?.Parent is Activity parent) + { + activity = parent; + } + + traceState = activity?.TraceStateString; + parentId = activity?.ParentId ?? activity?.Id; + isW3c = parentId is not null ? Activity.TryConvertIdToContext(parentId, traceState, out _) : false; + baggage = activity?.Baggage; + } + } +} diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/TextMapPropagator.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/TextMapPropagator.cs new file mode 100644 index 000000000000..0dab622a2983 --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/TextMapPropagator.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net; +using System.Text; +using System.Diagnostics; +using System.Collections.Generic; + +namespace System.Diagnostics +{ + /// + /// An implementation of TextMapPropagator determines if and how distributed context information is encoded and decoded as it traverses the network. + /// The encoding can be transported over any network protocol that supports string key-value pairs. For example when using HTTP, each key value pair is an HTTP header. + /// TextMapPropagator inject values into and extracts values from carriers as string key/value pairs. + /// + public abstract class TextMapPropagator + { + private static TextMapPropagator s_current = CreateDefaultPropagator(); + + /// + /// The callback that is used in propagators' extract methods. The callback is invoked to lookup the value of a named field. + /// + /// Carrier is the medium used by Propagators to read values from. + /// The propagation field name. + /// An output string to receive the value corresponds to the input fieldName. This should return non null value if there is only one value for the input field name. + /// An output collection of strings to receive the values corresponds to the input fieldName. This should return non null value if there are more than one value for the input field name. + public delegate void PropagatorGetterCallback(object? carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues); + + /// + /// The callback that is used in propagators' inject methods. This callback is invoked to set the value of a named field. + /// Propagators may invoke it multiple times in order to set multiple fields. + /// + /// Carrier is the medium used by Propagators to write values to. + /// The propagation field name. + /// The value corresponds to the input fieldName. + public delegate void PropagatorSetterCallback(object? carrier, string fieldName, string fieldValue); + + /// + /// The set of field names this propagator is likely to read or write. + /// + /// Returns list of fields that will be used by the TextMapPropagator. + public abstract IReadOnlyCollection Fields { get; } + + /// + /// Injects the trace values stroed in the object into a carrier. For example, into the headers of an HTTP request. + /// + /// The Activity object has the distributed context to inject to the carrier. + /// Carrier is the medium in which the distributed context will be stored. + /// The callback will be invoked to set a named key/value pair on the carrier. + public abstract void Inject(Activity? activity, object? carrier, PropagatorSetterCallback? setter); + + /// + /// Extracts trace Id and trace state from an incoming request represented by the carrier. For example, from the headers of an HTTP request. + /// + /// Carrier is the medium from which values will be read. + /// The callback will be invoked to get the propagation trace Id and trace state from carrier. + /// The extracted trace Id from the carrier. + /// The extracted trace state from the carrier. + public abstract void ExtractTraceIdAndState(object? carrier, PropagatorGetterCallback? getter, out string? traceId, out string? traceState); + + /// + /// Extracts the baggage key-value pair list from an incoming request represented by the carrier. For example, from the headers of an HTTP request. + /// + /// Carrier is the medium from which values will be read. + /// The callback will be invoked to get the propagation baggage list from carrier. + /// Returns the extracted key-value pair list from the carrier. + public abstract IEnumerable>? ExtractBaggage(object? carrier, PropagatorGetterCallback? getter); + + /// + /// Get or set the process wide propagator object which used as the current selected propagator. + /// + public static TextMapPropagator Current + { + get + { + Debug.Assert(s_current is not null); + return s_current; + } + + set + { + s_current = value ?? throw new ArgumentNullException(nameof(value)); + } + } + + /// + /// returns the default propagator object which Current property will be initialized with. + /// + /// + /// CreateDefaultPropagator will create a propagator instance that can inject and extract the headers with field names "tarcestate", + /// "traceparent" of the identifiers which are formatted as W3C trace parent, "Request-Id" of the identifiers which are formatted as a hierarchical identifier. + /// The returned propagator can inject the baggage key-value pair list with header name "Correlation-Context" and it can extract the baggage values mapped to header names "Correlation-Context" and "baggage". + /// + public static TextMapPropagator CreateDefaultPropagator() => LegacyPropagator.Instance; + + /// + /// Returns a propagator which attempts to act transparently, emitting the same data on outbound network requests that was received on the in-bound request. + /// When encoding the outbound message, this propagator uses information from the request's root Activity, ignoring any intermediate Activities that may have been created while processing the request. + /// + public static TextMapPropagator CreatePassThroughPropagator() => PassThroughPropagator.Instance; + + /// + /// Returns a propagator which does not transmit any distributed context information in outbound network messages. + /// + public static TextMapPropagator CreateNoOutputPropagator() => NoOutputPropagator.Instance; + + // internal stuff + + internal static void InjectBaggage(object? carrier, IEnumerable> baggage, PropagatorSetterCallback setter) + { + using (IEnumerator> e = baggage.GetEnumerator()) + { + if (e.MoveNext()) + { + StringBuilder baggageList = new StringBuilder(); + + do + { + KeyValuePair item = e.Current; + baggageList.Append(WebUtility.UrlEncode(item.Key)).Append('=').Append(WebUtility.UrlEncode(item.Value)).Append(CommaWithSpace); + } while (e.MoveNext()); + + setter(carrier, CorrelationContext, baggageList.ToString(0, baggageList.Length - 2)); + } + } + } + + internal const string TraceParent = "traceparent"; + internal const string RequestId = "Request-Id"; + internal const string TraceState = "tracestate"; + internal const string Baggage = "baggage"; + internal const string CorrelationContext = "Correlation-Context"; + internal const char Space = ' '; + internal const char Tab = (char)9; + internal const char Comma = ','; + internal const char Semicolon = ';'; + internal const string CommaWithSpace = ", "; + + internal static readonly char [] s_trimmingSpaceCharacters = new char[] { Space, Tab }; + } +} diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/PropagatorTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/PropagatorTests.cs new file mode 100644 index 000000000000..a0ade495467d --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/PropagatorTests.cs @@ -0,0 +1,664 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net; +using System.Linq; +using System.Collections.Generic; +using Microsoft.DotNet.RemoteExecutor; +using Xunit; + +namespace System.Diagnostics.Tests +{ + public class PropagatorTests + { + internal const string TraceParent = "traceparent"; + internal const string RequestId = "Request-Id"; + internal const string TraceState = "tracestate"; + internal const string Baggage = "baggage"; + internal const string CorrelationContext = "Correlation-Context"; + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void TestAllPropagators() + { + RemoteExecutor.Invoke(() => { + Assert.NotNull(TextMapPropagator.Current); + + // + // Default Propagator + // + + Assert.Same(TextMapPropagator.CreateDefaultPropagator(), TextMapPropagator.Current); + + TestDefaultPropagatorUsingW3CActivity( + TextMapPropagator.Current, + "Legacy1=true", + new List>() { new KeyValuePair(" LegacyKey1 ", " LegacyValue1 ") }); + + TestDefaultPropagatorUsingHierarchicalActivity( + TextMapPropagator.Current, + "Legacy2=true", + new List>() { new KeyValuePair("LegacyKey2", "LegacyValue2") }); + + TestFields(TextMapPropagator.Current); + + // + // NoOutput Propagator + // + + TextMapPropagator.Current = TextMapPropagator.CreateNoOutputPropagator(); + Assert.NotNull(TextMapPropagator.Current); + TestNoOutputPropagatorUsingHierarchicalActivity( + TextMapPropagator.Current, + "ActivityState=1", + new List>() { new KeyValuePair("B1", "V1"), new KeyValuePair(" B2 ", " V2 ")}); + + TestNoOutputPropagatorUsingHierarchicalActivity( + TextMapPropagator.Current, + "ActivityState=2", + null); + + TestNoOutputPropagatorUsingW3CActivity( + TextMapPropagator.Current, + "ActivityState=1", + new List>() { new KeyValuePair(" B3 ", " V3"), new KeyValuePair(" B4 ", " V4 "), new KeyValuePair("B5", "V5")}); + + TestNoOutputPropagatorUsingW3CActivity( + TextMapPropagator.Current, + "ActivityState=2", + null); + + TestFields(TextMapPropagator.Current); + + // + // Pass Through Propagator + // + + TextMapPropagator.Current = TextMapPropagator.CreatePassThroughPropagator(); + Assert.NotNull(TextMapPropagator.Current); + TestPassThroughPropagatorUsingHierarchicalActivityWithParentChain( + TextMapPropagator.Current, + "PassThrough=true", + new List>() { new KeyValuePair("PassThroughKey1", "PassThroughValue1"), new KeyValuePair("PassThroughKey2", "PassThroughValue2")}); + + TestPassThroughPropagatorUsingHierarchicalActivityWithParentId( + TextMapPropagator.Current, + "PassThrough1=true", + new List>() { new KeyValuePair("PassThroughKey3", "PassThroughValue3"), new KeyValuePair(" PassThroughKey4 ", " PassThroughValue4 ")}); + + TestPassThroughPropagatorUsingW3CActivity( + TextMapPropagator.Current, + "PassThrough2=1", + new List>() { new KeyValuePair(" PassThroughKey4 ", " PassThroughValue4 ") }); + + TestPassThroughPropagatorWithNullCurrent(TextMapPropagator.Current); + + TestFields(TextMapPropagator.Current); + + // + // Test Current + // + + Assert.Throws(() => TextMapPropagator.Current = null); + + }).Dispose(); + } + + private void TestDefaultPropagatorUsingW3CActivity(TextMapPropagator propagator, string state, IEnumerable> baggage) + { + using Activity a = CreateW3CActivity("LegacyW3C1", "LegacyW3CState=1", baggage); + using Activity b = CreateW3CActivity("LegacyW3C2", "LegacyW3CState=2", baggage); + + Assert.NotSame(Activity.Current, a); + + TestDefaultPropagatorUsing(a, propagator, state, baggage); + + Assert.Same(Activity.Current, b); + + TestDefaultPropagatorUsing(Activity.Current, propagator, state, baggage); + } + + private void TestDefaultPropagatorUsingHierarchicalActivity(TextMapPropagator propagator, string state, IEnumerable> baggage) + { + using Activity a = CreateHierarchicalActivity("LegacyHierarchical1", null, "LegacyHierarchicalState=1", baggage); + using Activity b = CreateHierarchicalActivity("LegacyHierarchical2", null, "LegacyHierarchicalState=2", baggage); + + Assert.NotSame(Activity.Current, a); + + TestDefaultPropagatorUsing(a, propagator, state, baggage); + + Assert.Same(Activity.Current, b); + + TestDefaultPropagatorUsing(Activity.Current, propagator, state, baggage); + } + + private void TestDefaultPropagatorUsing(Activity a, TextMapPropagator propagator, string state, IEnumerable> baggage) + { + // Test with non-current + propagator.Inject(a, null, (object carrier, string fieldName, string value) => + { + if (fieldName == TraceParent && a.IdFormat == ActivityIdFormat.W3C) + { + Assert.Equal(a.Id, value); + return; + } + + if (fieldName == RequestId && a.IdFormat != ActivityIdFormat.W3C) + { + Assert.Equal(a.Id, value); + return; + } + + if (fieldName == TraceState) + { + Assert.Equal(a.TraceStateString, value); + return; + } + + if (fieldName == CorrelationContext) + { + Assert.Equal(GetFormattedBaggage(a.Baggage), value); + return; + } + + Assert.False(true, $"Encountered wrong header name '{fieldName}'"); + }); + + TestDefaultExtraction(propagator, a); + TestBaggageExtraction(propagator, a); + } + + private void TestNoOutputPropagatorUsingHierarchicalActivity(TextMapPropagator propagator, string state, IEnumerable> baggage) + { + using Activity a = CreateHierarchicalActivity("NoOutputHierarchical", null, state, baggage); + + propagator.Inject(a, null, (object carrier, string fieldName, string value) => + { + Assert.False(true, $"Not expected to have the setter callback be called in the NoOutput propgator."); + }); + + TestDefaultExtraction(propagator, a); + + TestBaggageExtraction(propagator, a); + } + + private void TestNoOutputPropagatorUsingW3CActivity(TextMapPropagator propagator, string state, IEnumerable> baggage) + { + using Activity a = CreateW3CActivity("NoOutputW3C", state, baggage); + + propagator.Inject(a, null, (object carrier, string fieldName, string value) => + { + Assert.False(true, $"Not expected to have the setter callback be called in the NoOutput propgator."); + }); + + TestDefaultExtraction(propagator, a); + + TestBaggageExtraction(propagator, a); + } + + private void TestPassThroughPropagatorUsingHierarchicalActivityWithParentChain(TextMapPropagator propagator, string state, IEnumerable> baggage) + { + using Activity a = CreateHierarchicalActivity("PassThrough", null, state, baggage); + using Activity b = CreateHierarchicalActivity("PassThroughChild1", null, state + "1", new List>() { new KeyValuePair("Child1Key", "Child1Value") } ); + using Activity c = CreateHierarchicalActivity("PassThroughChild2", null, state + "2", new List>() { new KeyValuePair("Child2Key", "Child2Value") } ); + + propagator.Inject(a, null, (object carrier, string fieldName, string value) => + { + if (fieldName == TraceParent) + { + Assert.False(true, $"Unexpected to inject a TraceParent with Hierarchical Activity."); + return; + } + + if (fieldName == RequestId) + { + Assert.Equal(a.Id, value); + return; + } + + if (fieldName == TraceState) + { + Assert.Equal(a.TraceStateString, value); + return; + } + + if (fieldName == CorrelationContext) + { + Assert.Equal(GetFormattedBaggage(a.Baggage), value); + return; + } + + Assert.False(true, $"Encountered wrong header name '{fieldName}'"); + }); + + TestDefaultExtraction(propagator, a); + TestDefaultExtraction(propagator, b); + TestDefaultExtraction(propagator, c); + + TestBaggageExtraction(propagator, a); + TestBaggageExtraction(propagator, b); + TestBaggageExtraction(propagator, c); + } + + private void TestPassThroughPropagatorUsingHierarchicalActivityWithParentId(TextMapPropagator propagator, string state, IEnumerable> baggage) + { + using Activity a = CreateHierarchicalActivity("PassThrough", "Parent1", state, baggage); + using Activity b = CreateHierarchicalActivity("PassThroughChild1", "Parent2", state + "1", new List>() { new KeyValuePair("Child1Key", "Child1Value") } ); + using Activity c = CreateHierarchicalActivity("PassThroughChild2", "Parent3", state + "2", new List>() { new KeyValuePair("Child2Key", "Child2Value") } ); + + propagator.Inject(a, null, (object carrier, string fieldName, string value) => + { + if (fieldName == TraceParent) + { + Assert.False(true, $"Unexpected to inject a TraceParent with Hierarchical Activity."); + return; + } + + if (fieldName == RequestId) + { + Assert.Equal(c.ParentId, value); + return; + } + + if (fieldName == TraceState) + { + Assert.Equal(c.TraceStateString, value); + return; + } + + if (fieldName == CorrelationContext) + { + Assert.Equal(GetFormattedBaggage(c.Baggage), value); + return; + } + + Assert.False(true, $"Encountered wrong header name '{fieldName}'"); + }); + + TestDefaultExtraction(propagator, a); + TestDefaultExtraction(propagator, b); + TestDefaultExtraction(propagator, c); + + TestBaggageExtraction(propagator, a); + TestBaggageExtraction(propagator, b); + TestBaggageExtraction(propagator, c); + } + + private void TestPassThroughPropagatorUsingW3CActivity(TextMapPropagator propagator, string state, IEnumerable> baggage) + { + using Activity a = CreateW3CActivity("PassThroughW3C", "PassThroughW3CState=1", baggage); + + propagator.Inject(a, null, (object carrier, string fieldName, string value) => + { + if (fieldName == TraceParent) + { + Assert.Equal(a.Id, value); + return; + } + + if (fieldName == TraceState) + { + Assert.Equal(a.TraceStateString, value); + return; + } + + if (fieldName == CorrelationContext) + { + Assert.Equal(GetFormattedBaggage(a.Baggage), value); + return; + } + + Assert.False(true, $"Encountered wrong header name '{fieldName}'"); + }); + + TestDefaultExtraction(propagator, a); + TestBaggageExtraction(propagator, a); + } + + private void TestPassThroughPropagatorWithNullCurrent(TextMapPropagator propagator) + { + Activity.Current = null; + + propagator.Inject(null, null, (object carrier, string fieldName, string value) => + { + Assert.False(true, $"PassThroughPropagator shouldn't inject anything if the Activity.Current is null"); + }); + + using Activity a = CreateW3CActivity("PassThroughNotNull", "", null); + + propagator.Inject(a, null, (object carrier, string fieldName, string value) => + { + if (fieldName == TraceParent) + { + Assert.Equal(a.Id, value); + return; + } + + Assert.False(true, $"Encountered wrong header name '{fieldName}'"); + }); + } + + private void TestDefaultExtraction(TextMapPropagator propagator, Activity a) + { + bool traceParentEncountered = false; + + propagator.ExtractTraceIdAndState(null, (object carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => + { + Assert.Null(carrier); + fieldValues = null; + fieldValue = null; + + if (fieldName == TraceParent) + { + if (a.IdFormat == ActivityIdFormat.W3C) + { + fieldValue = a.Id; + } + else + { + traceParentEncountered = true; + } + return; + } + + if (fieldName == RequestId) + { + if (a.IdFormat == ActivityIdFormat.W3C) + { + Assert.True(false, $"Not expected to get RequestId as we expect the request handled using TraceParenet."); + } + else + { + Assert.True(traceParentEncountered, $"Expected to get TraceParent request before getting RequestId."); + fieldValue = a.Id; + } + + return; + } + + if (fieldName == TraceState) + { + fieldValue = a.TraceStateString; + return; + } + + Assert.False(true, $"Encountered wrong header name '{fieldName}'"); + }, out string? traceId, out string? traceState); + + Assert.Equal(a.Id, traceId); + Assert.Equal(a.TraceStateString, traceState); + } + + private void TestBaggageExtraction(TextMapPropagator propagator, Activity a) + { + bool baggageEncountered = false; + + IEnumerable>? b = propagator.ExtractBaggage(null, (object carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => + { + Assert.Null(carrier); + fieldValue = null; + fieldValues = null; + + if (fieldName == Baggage) + { + if (a.IdFormat == ActivityIdFormat.W3C) + { + fieldValue = GetFormattedBaggage(a.Baggage); + } + else + { + baggageEncountered = true; + } + + return; + } + + if (fieldName == CorrelationContext && a.IdFormat != ActivityIdFormat.W3C) + { + Assert.True(baggageEncountered, $"Expected to get Baggage request before getting Correlation-Context."); + fieldValue = GetFormattedBaggage(a.Baggage); + return; + } + + Assert.False(true, $"Encountered wrong header name '{fieldName}'"); + }); + + Assert.Equal(GetFormattedBaggage(a.Baggage, false, true), GetFormattedBaggage(b, true)); + } + + private void TestFields(TextMapPropagator propagator) + { + Assert.True(propagator.Fields.Contains(TraceParent)); + Assert.True(propagator.Fields.Contains(RequestId)); + Assert.True(propagator.Fields.Contains(TraceState)); + Assert.True(propagator.Fields.Contains(Baggage)); + Assert.True(propagator.Fields.Contains(CorrelationContext)); + } + + internal static string GetFormattedBaggage(IEnumerable>? b, bool flipOrder = false, bool trimSpaces = false) + { + string formattedBaggage = ""; + + if (b is null) + { + return formattedBaggage; + } + List> list = new List>(b); + + int startIndex = flipOrder ? list.Count - 1 : 0; + int exitIndex = flipOrder ? -1 : list.Count; + int step = flipOrder ? -1 : 1; + + for (int i = startIndex; i != exitIndex; i += step) + { + string key = trimSpaces ? list[i].Key.Trim() : list[i].Key; + string value = trimSpaces ? list[i].Value.Trim() : list[i].Value; + + formattedBaggage += (formattedBaggage.Length > 0 ? ", " : "") + WebUtility.UrlEncode(key) + "=" + WebUtility.UrlEncode(value); + } + + return formattedBaggage; + } + + private Activity CreateHierarchicalActivity(string name, string parentId, string state, IEnumerable>? baggage) + { + Activity a = new Activity(name); + a.SetIdFormat(ActivityIdFormat.Hierarchical); + + if (baggage is not null) + { + foreach (KeyValuePair kvp in baggage) + { + a.SetBaggage(kvp.Key, kvp.Value); + } + } + + a.TraceStateString = state; + + if (parentId is not null) + { + a.SetParentId(parentId); + } + a.Start(); + + return a; + } + + private Activity CreateW3CActivity(string name, string state, IEnumerable>? baggage) + { + Activity a = new Activity(name); + a.SetIdFormat(ActivityIdFormat.W3C); + + if (baggage is not null) + { + foreach (KeyValuePair kvp in baggage) + { + a.SetBaggage(kvp.Key, kvp.Value); + } + } + + a.TraceStateString = state; + a.Start(); + + return a; + } + + internal static IEnumerable>? ParseBaggage(string baggageString) + { + if (baggageString is null) + { + return null; + } + + List> list = new(); + string [] parts = baggageString.Split(','); + + foreach (string part in parts) + { + string [] baggageItem = part.Split('='); + + if (baggageItem.Length != 2) + { + return null; // Invalid format + } + + list.Add(new KeyValuePair(WebUtility.UrlDecode(baggageItem[0]).Trim(), WebUtility.UrlDecode(baggageItem[1]).Trim())); + } + + return list; + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void TestCustomPropagator() + { + RemoteExecutor.Invoke(() => { + + TextMapPropagator.Current = new CustomPropagator(); + using Activity a = CreateW3CActivity("CustomW3C1", "CustomW3CState=1", new List>() { new KeyValuePair(" CustomKey1 ", " CustomValue1 ") }); + + string traceParent = "x-" + a.Id ; + string traceState = "x-" + a.TraceStateString; + string baggageString = "x=y, " + GetFormattedBaggage(a.Baggage); + + TextMapPropagator.Current.Inject(a, null, (object carrier, string fieldName, string value) => + { + if (fieldName == CustomPropagator.XTraceParent) + { + Assert.Equal(traceParent, value); + return; + } + + if (fieldName == CustomPropagator.XTraceState) + { + Assert.Equal(traceState, value); + return; + } + + if (fieldName == CustomPropagator.XBaggage) + { + Assert.Equal(baggageString, value); + return; + } + + Assert.False(true, $"Encountered wrong header name '{fieldName}' in the Custom Propagator"); + }); + + TextMapPropagator.Current.ExtractTraceIdAndState(null, (object carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => + { + fieldValues = null; + fieldValue = null; + + if (fieldName == CustomPropagator.XTraceParent) + { + fieldValue = traceParent; + return; + } + + if (fieldName == CustomPropagator.XTraceState) + { + fieldValue = traceState; + return; + } + + Assert.False(true, $"Encountered wrong header name '{fieldName}' in the Custom propagator"); + }, out string? traceId, out string? state); + + Assert.Equal(traceParent, traceId); + Assert.Equal(traceState, state); + + IEnumerable>? b = TextMapPropagator.Current.ExtractBaggage(null, (object carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => + { + Assert.Null(carrier); + fieldValue = null; + fieldValues = null; + + if (fieldName == CustomPropagator.XBaggage) + { + fieldValue = baggageString; + return; + } + + Assert.False(true, $"Encountered wrong header name '{fieldName}' in custom propagator"); + }); + + Assert.Equal(2, b.Count()); + Assert.Equal(new KeyValuePair("x", "y"), b.ElementAt(0)); + Assert.Equal(new KeyValuePair("CustomKey1", "CustomValue1"), b.ElementAt(1)); + + }).Dispose(); + } + + internal class CustomPropagator : TextMapPropagator + { + internal const string XTraceParent = "x-traceparent"; + internal const string XTraceState = "x-tracestate"; + internal const string XBaggage = "x-baggage"; + + public override IReadOnlyCollection Fields { get; } = new[] { XTraceParent, XTraceState, XBaggage}; + + public override void Inject(Activity? activity, object? carrier, PropagatorSetterCallback? setter) + { + if (activity is null || carrier is null) + { + return; + } + + setter(carrier, XTraceParent, "x-" + activity.Id); + + if (!string.IsNullOrEmpty(activity.TraceStateString)) + { + setter(carrier, XTraceState, "x-" + activity.TraceStateString); + } + + if (activity.Baggage.Count() > 0) + { + setter(carrier, XBaggage, "x=y, " + PropagatorTests.GetFormattedBaggage(activity.Baggage)); + } + } + + public override void ExtractTraceIdAndState(object? carrier, PropagatorGetterCallback? getter, out string? traceId, out string? traceState) + { + if (getter is null) + { + traceId = null; + traceState = null; + return; + } + + getter(carrier, XTraceParent, out traceId, out _); + getter(carrier, XTraceState, out traceState, out _); + } + + public override IEnumerable>? ExtractBaggage(object? carrier, PropagatorGetterCallback? getter) + { + if (getter is null) + { + return null; + } + + getter(carrier, XBaggage, out string? theBaggage, out _); + + return PropagatorTests.ParseBaggage(theBaggage); + } + } + } +} diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj index 3d38a9493178..24c84423e663 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj @@ -28,6 +28,7 @@ + From 21c251623d45e2a753cd687c0551ec327f837d51 Mon Sep 17 00:00:00 2001 From: Noah Falk Date: Sat, 10 Jul 2021 17:10:48 -0700 Subject: [PATCH 005/133] Fixing MetricEventSource tests (#55385) * Fixing MetricEventSource tests I've seen two recent non-deterministic failures: 1. After starting the EventListener there is a delay of one collection interval (previously 0.3 seconds) where we expect calls to counter.Add and Histogram.Record() to update stats. On a fast machine this code would run in under a millisecond but in some test runs it still wasn't happening fast enough. 2. We were seeing error events from a previous test get observed in a later test because session id was being ignored for error events. Fixes: 1. Added an initial collection interval where no counter APIs will be invoked and the test will delay up to 60 seconds waiting for this. Hopefully this delay makes it more likely that the Add/Record APIs are ready to execute promptly once the 2nd interval begins. I also increased the intervals to 1 second. If that still isn't sufficient we can either further increase the collection intervals or disable the tests. I also moved these tests to outerloop because they are slow and noisy on the console, but it may have a side benefit lessening the impact if there are future timing related failures. 2. Tightened up the session id matching so only error events with empty id or the expected id are allowed. --- .../tests/MetricEventSourceTests.cs | 186 ++++++++++-------- 1 file changed, 103 insertions(+), 83 deletions(-) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs index c63aaa1a8712..9cd9501d8c5e 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs @@ -16,6 +16,8 @@ namespace System.Diagnostics.Metrics.Tests public class MetricEventSourceTests { ITestOutputHelper _output; + const double IntervalSecs = 1; + static readonly TimeSpan s_waitForEventTimeout = TimeSpan.FromSeconds(60); public MetricEventSourceTests(ITestOutputHelper output) { @@ -23,6 +25,7 @@ public MetricEventSourceTests(ITestOutputHelper output) } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] public void EventSourcePublishesTimeSeriesWithEmptyMetadata() { using Meter meter = new Meter("TestMeter1"); @@ -34,15 +37,15 @@ public void EventSourcePublishesTimeSeriesWithEmptyMetadata() Histogram h = meter.CreateHistogram("histogram1"); EventWrittenEventArgs[] events; - double intervalSecs = 0.3; - using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, intervalSecs, "TestMeter1")) + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter1")) { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); c.Add(5); h.Record(19); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 1); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); c.Add(12); h.Record(26); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 2); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); events = listener.Events.ToArray(); } @@ -52,10 +55,11 @@ public void EventSourcePublishesTimeSeriesWithEmptyMetadata() AssertCounterEventsPresent(events, meter.Name, oc.Name, "", "", "", "7"); AssertGaugeEventsPresent(events, meter.Name, og.Name, "", "", "9", "18"); AssertHistogramEventsPresent(events, meter.Name, h.Name, "", "", "0.5=19;0.95=19;0.99=19", "0.5=26;0.95=26;0.99=26"); - AssertCollectStartStopEventsPresent(events, intervalSecs, 2); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] public void EventSourcePublishesTimeSeriesWithMetadata() { using Meter meter = new Meter("TestMeter2"); @@ -67,28 +71,29 @@ public void EventSourcePublishesTimeSeriesWithMetadata() Histogram h = meter.CreateHistogram("histogram1", "a unit", "the description"); EventWrittenEventArgs[] events; - double intervalSecs = 0.3; - using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, intervalSecs, "TestMeter2")) + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter2")) { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); c.Add(5); h.Record(19); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 1); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); c.Add(12); h.Record(26); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 2); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); events = listener.Events.ToArray(); } AssertBeginInstrumentReportingEventsPresent(events, c, oc, og, h); AssertInitialEnumerationCompleteEventPresent(events); AssertCounterEventsPresent(events, meter.Name, c.Name, "", c.Unit, "5", "12"); - AssertCounterEventsPresent(events, meter.Name, oc.Name, "", oc.Unit, "", "7"); - AssertGaugeEventsPresent(events, meter.Name, og.Name, "", og.Unit, "9", "18"); + AssertCounterEventsPresent(events, meter.Name, oc.Name, "", oc.Unit, "", "7", "7"); + AssertGaugeEventsPresent(events, meter.Name, og.Name, "", og.Unit, "9", "18", "27"); AssertHistogramEventsPresent(events, meter.Name, h.Name, "", h.Unit, "0.5=19;0.95=19;0.99=19", "0.5=26;0.95=26;0.99=26"); - AssertCollectStartStopEventsPresent(events, intervalSecs, 2); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] public void EventSourcePublishesTimeSeriesForLateMeter() { // this ensures the MetricsEventSource exists when the listener tries to query @@ -102,10 +107,9 @@ public void EventSourcePublishesTimeSeriesForLateMeter() Histogram h; EventWrittenEventArgs[] events; - double intervalSecs = 0.3; - using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, intervalSecs, "TestMeter3")) + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter3")) { - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 1); + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); // the Meter is created after the EventSource was already monitoring meter = new Meter("TestMeter3"); @@ -118,10 +122,10 @@ public void EventSourcePublishesTimeSeriesForLateMeter() c.Add(5); h.Record(19); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 2); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); c.Add(12); h.Record(26); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 3); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); events = listener.Events.ToArray(); } @@ -131,7 +135,7 @@ public void EventSourcePublishesTimeSeriesForLateMeter() AssertCounterEventsPresent(events, meter.Name, oc.Name, "", "", "", "7"); AssertGaugeEventsPresent(events, meter.Name, og.Name, "", "", "9", "18"); AssertHistogramEventsPresent(events, meter.Name, h.Name, "", "", "0.5=19;0.95=19;0.99=19", "0.5=26;0.95=26;0.99=26"); - AssertCollectStartStopEventsPresent(events, intervalSecs, 3); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); } finally { @@ -140,6 +144,7 @@ public void EventSourcePublishesTimeSeriesForLateMeter() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] public void EventSourcePublishesTimeSeriesForLateInstruments() { // this ensures the MetricsEventSource exists when the listener tries to query @@ -150,10 +155,9 @@ public void EventSourcePublishesTimeSeriesForLateInstruments() Histogram h; EventWrittenEventArgs[] events; - double intervalSecs = 0.3; - using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, intervalSecs, "TestMeter4")) + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter4")) { - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 1); + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); // Instruments are created after the EventSource was already monitoring c = meter.CreateCounter("counter1"); @@ -165,10 +169,10 @@ public void EventSourcePublishesTimeSeriesForLateInstruments() c.Add(5); h.Record(19); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 2); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); c.Add(12); h.Record(26); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 3); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); events = listener.Events.ToArray(); } @@ -178,10 +182,11 @@ public void EventSourcePublishesTimeSeriesForLateInstruments() AssertCounterEventsPresent(events, meter.Name, oc.Name, "", "", "", "7"); AssertGaugeEventsPresent(events, meter.Name, og.Name, "", "", "9", "18"); AssertHistogramEventsPresent(events, meter.Name, h.Name, "", "", "0.5=19;0.95=19;0.99=19", "0.5=26;0.95=26;0.99=26"); - AssertCollectStartStopEventsPresent(events, intervalSecs, 3); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] public void EventSourcePublishesTimeSeriesWithTags() { using Meter meter = new Meter("TestMeter5"); @@ -209,20 +214,21 @@ public void EventSourcePublishesTimeSeriesWithTags() Histogram h = meter.CreateHistogram("histogram1"); EventWrittenEventArgs[] events; - double intervalSecs = 0.3; - using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, intervalSecs, "TestMeter5")) + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter5")) { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5, new KeyValuePair("Color", "red")); c.Add(6, new KeyValuePair("Color", "blue")); h.Record(19, new KeyValuePair("Size", 123)); h.Record(20, new KeyValuePair("Size", 124)); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 1); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); c.Add(12, new KeyValuePair("Color", "red")); c.Add(13, new KeyValuePair("Color", "blue")); h.Record(26, new KeyValuePair("Size", 123)); h.Record(27, new KeyValuePair("Size", 124)); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 2); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); events = listener.Events.ToArray(); } @@ -236,11 +242,12 @@ public void EventSourcePublishesTimeSeriesWithTags() AssertGaugeEventsPresent(events, meter.Name, og.Name, "Color=blue,Size=4", "", "18", "36"); AssertHistogramEventsPresent(events, meter.Name, h.Name, "Size=123", "", "0.5=19;0.95=19;0.99=19", "0.5=26;0.95=26;0.99=26"); AssertHistogramEventsPresent(events, meter.Name, h.Name, "Size=124", "", "0.5=20;0.95=20;0.99=20", "0.5=27;0.95=27;0.99=27"); - AssertCollectStartStopEventsPresent(events, intervalSecs, 2); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] public void EventSourceFiltersInstruments() { using Meter meterA = new Meter("TestMeterA"); @@ -257,10 +264,11 @@ public void EventSourceFiltersInstruments() Counter c3c = meterC.CreateCounter("counter3"); EventWrittenEventArgs[] events; - double intervalSecs = 0.3; - using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, intervalSecs, + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeterA\\counter3;TestMeterB\\counter1;TestMeterC\\counter2;TestMeterB;TestMeterC\\counter3")) { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c1a.Add(1); c2a.Add(1); c3a.Add(1); @@ -270,7 +278,7 @@ public void EventSourceFiltersInstruments() c1c.Add(1); c2c.Add(1); c3c.Add(1); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 1); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); c1a.Add(2); c2a.Add(2); @@ -281,7 +289,7 @@ public void EventSourceFiltersInstruments() c1c.Add(2); c2c.Add(2); c3c.Add(2); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 2); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); events = listener.Events.ToArray(); } @@ -296,10 +304,11 @@ public void EventSourceFiltersInstruments() AssertCounterEventsNotPresent(events, meterA.Name, c1a.Name, ""); AssertCounterEventsNotPresent(events, meterA.Name, c2a.Name, ""); AssertCounterEventsNotPresent(events, meterC.Name, c1c.Name, ""); - AssertCollectStartStopEventsPresent(events, intervalSecs, 2); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] public void EventSourcePublishesMissingDataPoints() { using Meter meter = new Meter("TestMeter6"); @@ -310,7 +319,7 @@ public void EventSourcePublishesMissingDataPoints() { counterState += 7; counterCollectInterval++; - if ((counterCollectInterval % 2) == 1) + if ((counterCollectInterval % 2) == 0) { return new Measurement[] { new Measurement(counterState) }; } @@ -326,7 +335,7 @@ public void EventSourcePublishesMissingDataPoints() { gaugeState += 9; gaugeCollectInterval++; - if ((gaugeCollectInterval % 2) == 1) + if ((gaugeCollectInterval % 2) == 0) { return new Measurement[] { new Measurement(gaugeState) }; } @@ -339,19 +348,20 @@ public void EventSourcePublishesMissingDataPoints() Histogram h = meter.CreateHistogram("histogram1"); EventWrittenEventArgs[] events; - double intervalSecs = 0.3; - using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, intervalSecs, "TestMeter6")) + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter6")) { + // no measurements in interval 1 + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); c.Add(5); h.Record(19); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 1); - // no measurements in interval 2 - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 2); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + // no measurements in interval 3 + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); c.Add(12); h.Record(26); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 3); - // no measurements in interval 4 - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 4); + listener.WaitForCollectionStop(s_waitForEventTimeout, 4); + // no measurements in interval 5 + listener.WaitForCollectionStop(s_waitForEventTimeout, 5); events = listener.Events.ToArray(); } @@ -359,12 +369,13 @@ public void EventSourcePublishesMissingDataPoints() AssertInitialEnumerationCompleteEventPresent(events); AssertCounterEventsPresent(events, meter.Name, c.Name, "", "", "5", "0", "12"); AssertCounterEventsPresent(events, meter.Name, oc.Name, "", "", "", "0", "14", "0"); - AssertGaugeEventsPresent(events, meter.Name, og.Name, "", "", "9", "", "27", ""); + AssertGaugeEventsPresent(events, meter.Name, og.Name, "", "", "18", "", "36", ""); AssertHistogramEventsPresent(events, meter.Name, h.Name, "", "", "0.5=19;0.95=19;0.99=19", "", "0.5=26;0.95=26;0.99=26", ""); - AssertCollectStartStopEventsPresent(events, intervalSecs, 4); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 5); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] public void EventSourcePublishesEndEventsOnNewListener() { using Meter meter = new Meter("TestMeter7"); @@ -376,32 +387,33 @@ public void EventSourcePublishesEndEventsOnNewListener() Histogram h = meter.CreateHistogram("histogram1", "a unit", "the description"); EventWrittenEventArgs[] events; - double intervalSecs = 0.3; - using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, intervalSecs, "TestMeter7")) + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter7")) { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); c.Add(5); h.Record(19); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 1); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); c.Add(12); h.Record(26); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 2); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); // some alternate listener starts listening - using MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, intervalSecs, "ADifferentMeter"); - listener.WaitForEndInstrumentReporting(TimeSpan.FromSeconds(5), 4); + using MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "ADifferentMeter"); + listener.WaitForEndInstrumentReporting(s_waitForEventTimeout, 4); events = listener.Events.ToArray(); } AssertBeginInstrumentReportingEventsPresent(events, c, oc, og, h); AssertCounterEventsPresent(events, meter.Name, c.Name, "", c.Unit, "5", "12"); - AssertCounterEventsPresent(events, meter.Name, oc.Name, "", oc.Unit, "", "7"); - AssertGaugeEventsPresent(events, meter.Name, og.Name, "", og.Unit, "9", "18"); + AssertCounterEventsPresent(events, meter.Name, oc.Name, "", oc.Unit, "", "7", "7"); + AssertGaugeEventsPresent(events, meter.Name, og.Name, "", og.Unit, "9", "18", "27"); AssertHistogramEventsPresent(events, meter.Name, h.Name, "", h.Unit, "0.5=19;0.95=19;0.99=19", "0.5=26;0.95=26;0.99=26"); - AssertCollectStartStopEventsPresent(events, intervalSecs, 2); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); AssertEndInstrumentReportingEventsPresent(events, c, oc, og, h); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] public void EventSourcePublishesEndEventsOnMeterDispose() { using Meter meterA = new Meter("TestMeter8"); @@ -414,36 +426,37 @@ public void EventSourcePublishesEndEventsOnMeterDispose() Histogram h = meterB.CreateHistogram("histogram1", "a unit", "the description"); EventWrittenEventArgs[] events; - double intervalSecs = 0.3; - using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, intervalSecs, "TestMeter8;TestMeter9")) + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter8;TestMeter9")) { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); c.Add(5); h.Record(19); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 1); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); c.Add(12); h.Record(26); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 2); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); meterA.Dispose(); - listener.WaitForEndInstrumentReporting(TimeSpan.FromSeconds(5), 3); + listener.WaitForEndInstrumentReporting(s_waitForEventTimeout, 3); h.Record(21); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 3); + listener.WaitForCollectionStop(s_waitForEventTimeout, 4); events = listener.Events.ToArray(); } AssertBeginInstrumentReportingEventsPresent(events, c, oc, og, h); AssertInitialEnumerationCompleteEventPresent(events); AssertCounterEventsPresent(events, meterA.Name, c.Name, "", c.Unit, "5", "12"); - AssertCounterEventsPresent(events, meterA.Name, oc.Name, "", oc.Unit, "", "7"); - AssertGaugeEventsPresent(events, meterA.Name, og.Name, "", og.Unit, "9", "18"); + AssertCounterEventsPresent(events, meterA.Name, oc.Name, "", oc.Unit, "", "7", "7"); + AssertGaugeEventsPresent(events, meterA.Name, og.Name, "", og.Unit, "9", "18", "27"); AssertHistogramEventsPresent(events, meterB.Name, h.Name, "", h.Unit, "0.5=19;0.95=19;0.99=19", "0.5=26;0.95=26;0.99=26", "0.5=21;0.95=21;0.99=21"); - AssertCollectStartStopEventsPresent(events, intervalSecs, 3); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 4); AssertEndInstrumentReportingEventsPresent(events, c, oc, og); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] public void EventSourcePublishesInstruments() { using Meter meterA = new Meter("TestMeter10"); @@ -458,7 +471,7 @@ public void EventSourcePublishesInstruments() EventWrittenEventArgs[] events; using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.InstrumentPublishing, null, "")) { - listener.WaitForEnumerationComplete(TimeSpan.FromSeconds(5)); + listener.WaitForEnumerationComplete(s_waitForEventTimeout); events = listener.Events.ToArray(); } @@ -467,6 +480,7 @@ public void EventSourcePublishesInstruments() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] public void EventSourcePublishesAllDataTypes() { using Meter meter = new Meter("TestMeter12"); @@ -479,9 +493,10 @@ public void EventSourcePublishesAllDataTypes() Counter d = meter.CreateCounter("counterDouble"); EventWrittenEventArgs[] events; - double intervalSecs = 0.3; - using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, intervalSecs, "TestMeter12")) + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter12")) { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + i.Add(1_234_567); s.Add(21_432); b.Add(1); @@ -497,7 +512,7 @@ public void EventSourcePublishesAllDataTypes() dec.Add(1); f.Add(1); d.Add(1); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 1); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); i.Add(1_234_567); s.Add(21_432); @@ -514,7 +529,7 @@ public void EventSourcePublishesAllDataTypes() dec.Add(1); f.Add(1); d.Add(1); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 2); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); events = listener.Events.ToArray(); } @@ -527,10 +542,11 @@ public void EventSourcePublishesAllDataTypes() AssertCounterEventsPresent(events, meter.Name, dec.Name, "", "", "123456789012346", "123456789012346"); AssertCounterEventsPresent(events, meter.Name, f.Name, "", "", "123457.7890625", "123457.7890625"); AssertCounterEventsPresent(events, meter.Name, d.Name, "", "", "87654321987655.4", "87654321987655.4"); - AssertCollectStartStopEventsPresent(events, intervalSecs, 2); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] public void EventSourceEnforcesTimeSeriesLimit() { using Meter meter = new Meter("TestMeter13"); @@ -538,20 +554,21 @@ public void EventSourceEnforcesTimeSeriesLimit() EventWrittenEventArgs[] events; - double intervalSecs = 0.3; - using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, intervalSecs, 2, 50, "TestMeter13")) + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, 2, 50, "TestMeter13")) { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5, new KeyValuePair("Color", "red")); c.Add(6, new KeyValuePair("Color", "blue")); c.Add(7, new KeyValuePair("Color", "green")); c.Add(8, new KeyValuePair("Color", "yellow")); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 1); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); c.Add(12, new KeyValuePair("Color", "red")); c.Add(13, new KeyValuePair("Color", "blue")); c.Add(14, new KeyValuePair("Color", "green")); c.Add(15, new KeyValuePair("Color", "yellow")); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 2); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); events = listener.Events.ToArray(); } @@ -562,10 +579,11 @@ public void EventSourceEnforcesTimeSeriesLimit() AssertTimeSeriesLimitPresent(events); AssertCounterEventsNotPresent(events, meter.Name, c.Name, "Color=green"); AssertCounterEventsNotPresent(events, meter.Name, c.Name, "Color=yellow"); - AssertCollectStartStopEventsPresent(events, intervalSecs, 2); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] public void EventSourceEnforcesHistogramLimit() { using Meter meter = new Meter("TestMeter14"); @@ -573,20 +591,21 @@ public void EventSourceEnforcesHistogramLimit() EventWrittenEventArgs[] events; - double intervalSecs = 0.3; - using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, intervalSecs, 50, 2, "TestMeter14")) + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, 50, 2, "TestMeter14")) { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + h.Record(5, new KeyValuePair("Color", "red")); h.Record(6, new KeyValuePair("Color", "blue")); h.Record(7, new KeyValuePair("Color", "green")); h.Record(8, new KeyValuePair("Color", "yellow")); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 1); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); h.Record(12, new KeyValuePair("Color", "red")); h.Record(13, new KeyValuePair("Color", "blue")); h.Record(14, new KeyValuePair("Color", "green")); h.Record(15, new KeyValuePair("Color", "yellow")); - listener.WaitForCollectionStop(TimeSpan.FromSeconds(5), 2); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); events = listener.Events.ToArray(); } @@ -597,7 +616,7 @@ public void EventSourceEnforcesHistogramLimit() AssertHistogramLimitPresent(events); AssertHistogramEventsNotPresent(events, meter.Name, h.Name, "Color=green"); AssertHistogramEventsNotPresent(events, meter.Name, h.Name, "Color=yellow"); - AssertCollectStartStopEventsPresent(events, intervalSecs, 2); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); } private void AssertBeginInstrumentReportingEventsPresent(EventWrittenEventArgs[] events, params Instrument[] expectedInstruments) @@ -746,8 +765,8 @@ private void AssertGaugeEventsPresent(EventWrittenEventArgs[] events, string met Assert.True(filteredEvents.Length >= expectedValues.Length); for (int i = 0; i < expectedValues.Length; i++) { - Assert.Equal(filteredEvents[i].Unit, expectedUnit); - Assert.Equal(filteredEvents[i].Value, expectedValues[i]); + Assert.Equal(expectedUnit, filteredEvents[i].Unit); + Assert.Equal(expectedValues[i], filteredEvents[i].Value); } } @@ -906,7 +925,8 @@ protected override void OnEventSourceCreated(EventSource eventSource) protected override void OnEventWritten(EventWrittenEventArgs eventData) { - if(eventData.EventName != "Message" && eventData.EventName != "Error" && eventData.Payload[0].ToString() != _sessionId) + string sessionId = eventData.Payload[0].ToString(); + if(sessionId != "" && sessionId != _sessionId) { return; } From 83a4d3cc02fb04fce17b24fc09b3cdf77a12ba51 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Sat, 10 Jul 2021 21:04:54 -0700 Subject: [PATCH 006/133] Relax SystemTrustCertificateWithCustomRootTrust test --- .../tests/ChainTests.cs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/ChainTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/ChainTests.cs index 8e9db6ed6958..135eed056df9 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/ChainTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/ChainTests.cs @@ -245,6 +245,7 @@ public static void BuildChainExtraStoreUntrustedRoot() public static void SystemTrustCertificateWithCustomRootTrust(bool addCertificateToCustomRootTrust) { using (var microsoftDotCom = new X509Certificate2(TestData.MicrosoftDotComSslCertBytes)) + using (var microsoftDotComIssuer = new X509Certificate2(TestData.MicrosoftDotComIssuerBytes)) using (var testCert = new X509Certificate2(TestFiles.ChainPfxFile, TestData.ChainPfxPassword)) using (var chainHolder = new ChainHolder()) { @@ -252,6 +253,7 @@ public static void SystemTrustCertificateWithCustomRootTrust(bool addCertificate chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; chain.ChainPolicy.VerificationTime = microsoftDotCom.NotBefore.AddSeconds(1); chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; + chain.ChainPolicy.ExtraStore.Add(microsoftDotComIssuer); if (addCertificateToCustomRootTrust) { @@ -269,16 +271,29 @@ public static void SystemTrustCertificateWithCustomRootTrust(bool addCertificate { Assert.False(chain.Build(microsoftDotCom)); - // Linux and Windows do not search the default system root stores when CustomRootTrust is enabled + // Historically, Windows has not searched system stores when CustomRootTrust is enabled. + // That seems to have recently (as of 2021-07-09) changed. + + Assert.InRange(chain.ChainElements.Count, 2, 3); + + if (chain.ChainElements.Count < 3) + { + Assert.Equal(X509ChainStatusFlags.PartialChain, chain.AllStatusFlags()); + } + else + { + Assert.Equal(X509ChainStatusFlags.UntrustedRoot, chain.AllStatusFlags()); + } + + // Check some known conditions. + if (PlatformDetection.UsesAppleCrypto) { Assert.Equal(3, chain.ChainElements.Count); - Assert.Equal(X509ChainStatusFlags.UntrustedRoot, chain.AllStatusFlags()); } - else + else if (OperatingSystem.IsLinux()) { Assert.Equal(2, chain.ChainElements.Count); - Assert.Equal(X509ChainStatusFlags.PartialChain, chain.AllStatusFlags()); } } } From 6a47ecf4a8ee670356f5e554f08afb0b32cdac9a Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Sun, 11 Jul 2021 16:46:53 +0200 Subject: [PATCH 007/133] W^X support (#54954) * W^X support This change is the last part of enabling the W^X support. It adds the actual executable allocator that handles all double mapped memory allocations and creating the writeable mappings. The platform specific functionality is placed in a new minipal that is going to be a basis for future removal of Windows APIs usage from the native runtime. The last state of the change was tested on all the platforms that we support using coreclr pri 1 tests with both the W^X enabled and disabled using the COMPlus_EnableWriteXorExecute variable. The debugger changes were tested using the managed debugger testing suite on Windows x64, x86 and on Apple Silicon so far. Further testing on other platforms is in progress. * Replace LeafLock in UMEntryThunkFreeList by a new lock * Also allocate LoaderHeapFreeBlock from regular heap. * Set the W^X default to disabled --- src/coreclr/CMakeLists.txt | 3 + src/coreclr/clrdefinitions.cmake | 4 - src/coreclr/debug/ee/arm64/arm64walker.cpp | 9 +- src/coreclr/debug/ee/controller.cpp | 47 +- src/coreclr/debug/ee/controller.h | 18 +- src/coreclr/debug/ee/debugger.cpp | 30 +- src/coreclr/debug/ee/debugger.h | 32 +- src/coreclr/debug/inc/amd64/primitives.h | 22 +- src/coreclr/debug/inc/arm/primitives.h | 13 +- src/coreclr/debug/inc/arm64/primitives.h | 6 +- src/coreclr/debug/inc/i386/primitives.h | 13 +- .../dlls/mscoree/coreclr/CMakeLists.txt | 1 + src/coreclr/inc/CrstTypes.def | 7 + src/coreclr/inc/clrconfigvalues.h | 4 + src/coreclr/inc/crsttypes.h | 218 ++--- src/coreclr/inc/executableallocator.h | 201 ++++- src/coreclr/inc/jithelpers.h | 12 +- src/coreclr/inc/utilcode.h | 29 - src/coreclr/minipal/CMakeLists.txt | 7 + src/coreclr/minipal/Unix/CMakeLists.txt | 4 + src/coreclr/minipal/Unix/doublemapping.cpp | 211 +++++ src/coreclr/minipal/Windows/CMakeLists.txt | 4 + src/coreclr/minipal/Windows/doublemapping.cpp | 205 +++++ src/coreclr/minipal/minipal.h | 78 ++ src/coreclr/utilcode/CMakeLists.txt | 1 + src/coreclr/utilcode/executableallocator.cpp | 755 ++++++++++++++++++ src/coreclr/utilcode/loaderheap.cpp | 119 +-- src/coreclr/utilcode/util.cpp | 162 ---- src/coreclr/vm/CMakeLists.txt | 4 +- src/coreclr/vm/amd64/JitHelpers_Fast.asm | 79 +- src/coreclr/vm/amd64/jithelpers_fast.S | 26 +- src/coreclr/vm/amd64/jitinterfaceamd64.cpp | 20 +- src/coreclr/vm/arm/armsinglestepper.cpp | 30 +- src/coreclr/vm/arm/asmhelpers.S | 10 + src/coreclr/vm/arm/asmhelpers.asm | 12 + src/coreclr/vm/arm/cgencpu.h | 13 + src/coreclr/vm/arm/stubs.cpp | 23 +- src/coreclr/vm/arm64/arm64singlestepper.cpp | 12 +- src/coreclr/vm/arm64/asmhelpers.S | 10 - src/coreclr/vm/arm64/asmhelpers.asm | 35 +- src/coreclr/vm/arm64/cgencpu.h | 13 + src/coreclr/vm/arm64/stubs.cpp | 10 +- src/coreclr/vm/ceemain.cpp | 9 +- src/coreclr/vm/class.cpp | 5 +- src/coreclr/vm/codeman.cpp | 27 +- src/coreclr/vm/comcallablewrapper.cpp | 18 +- src/coreclr/vm/comcallablewrapper.h | 4 + src/coreclr/vm/comdelegate.cpp | 2 +- src/coreclr/vm/dllimportcallback.cpp | 2 +- src/coreclr/vm/dynamicmethod.cpp | 7 +- src/coreclr/vm/excep.cpp | 2 - src/coreclr/vm/exceptionhandling.cpp | 6 - src/coreclr/vm/gccover.cpp | 4 +- src/coreclr/vm/i386/jithelp.S | 30 +- src/coreclr/vm/i386/jithelp.asm | 35 +- src/coreclr/vm/i386/jitinterfacex86.cpp | 84 +- src/coreclr/vm/i386/stublinkerx86.cpp | 2 +- src/coreclr/vm/i386/stublinkerx86.h | 10 +- src/coreclr/vm/jitinterface.cpp | 2 +- src/coreclr/vm/jitinterface.h | 42 +- src/coreclr/vm/loaderallocator.cpp | 17 +- src/coreclr/vm/loaderallocator.inl | 6 - src/coreclr/vm/method.cpp | 40 - src/coreclr/vm/precode.cpp | 4 +- src/coreclr/vm/stackwalk.cpp | 2 - src/coreclr/vm/stublink.cpp | 14 +- src/coreclr/vm/stublink.h | 2 +- src/coreclr/vm/threads.cpp | 123 ++- src/coreclr/vm/threads.h | 19 +- src/coreclr/vm/virtualcallstub.cpp | 14 +- 70 files changed, 2258 insertions(+), 786 deletions(-) create mode 100644 src/coreclr/minipal/CMakeLists.txt create mode 100644 src/coreclr/minipal/Unix/CMakeLists.txt create mode 100644 src/coreclr/minipal/Unix/doublemapping.cpp create mode 100644 src/coreclr/minipal/Windows/CMakeLists.txt create mode 100644 src/coreclr/minipal/Windows/doublemapping.cpp create mode 100644 src/coreclr/minipal/minipal.h create mode 100644 src/coreclr/utilcode/executableallocator.cpp diff --git a/src/coreclr/CMakeLists.txt b/src/coreclr/CMakeLists.txt index 78aa96947352..b4a485934270 100644 --- a/src/coreclr/CMakeLists.txt +++ b/src/coreclr/CMakeLists.txt @@ -119,6 +119,8 @@ add_subdirectory(pal/prebuilt/inc) add_subdirectory(debug/debug-pal) +add_subdirectory(minipal) + if(CLR_CMAKE_TARGET_WIN32) add_subdirectory(gc/sample) endif() @@ -171,6 +173,7 @@ include_directories("classlibnative/cryptography") include_directories("classlibnative/inc") include_directories("${GENERATED_INCLUDE_DIR}") include_directories("hosts/inc") +include_directories("minipal") if(CLR_CMAKE_TARGET_WIN32 AND FEATURE_EVENT_TRACE) include_directories("${GENERATED_INCLUDE_DIR}/etw") diff --git a/src/coreclr/clrdefinitions.cmake b/src/coreclr/clrdefinitions.cmake index eeb421cac4c2..0485ff99a99e 100644 --- a/src/coreclr/clrdefinitions.cmake +++ b/src/coreclr/clrdefinitions.cmake @@ -224,10 +224,6 @@ if(CLR_CMAKE_TARGET_WIN32) endif(CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_I386) endif(CLR_CMAKE_TARGET_WIN32) -if(CLR_CMAKE_TARGET_OSX) - add_definitions(-DFEATURE_WRITEBARRIER_COPY) -endif(CLR_CMAKE_TARGET_OSX) - if (NOT CLR_CMAKE_TARGET_ARCH_I386 OR NOT CLR_CMAKE_TARGET_WIN32) add_compile_definitions($<$>>:FEATURE_EH_FUNCLETS>) endif (NOT CLR_CMAKE_TARGET_ARCH_I386 OR NOT CLR_CMAKE_TARGET_WIN32) diff --git a/src/coreclr/debug/ee/arm64/arm64walker.cpp b/src/coreclr/debug/ee/arm64/arm64walker.cpp index ae6e8c1fc293..6c4dee934970 100644 --- a/src/coreclr/debug/ee/arm64/arm64walker.cpp +++ b/src/coreclr/debug/ee/arm64/arm64walker.cpp @@ -171,7 +171,14 @@ BYTE* NativeWalker::SetupOrSimulateInstructionForPatchSkip(T_CONTEXT * context, { CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypass, 0xd503201f); //Add Nop in buffer - m_pSharedPatchBypassBuffer->RipTargetFixup = ip; //Control Flow simulation alone is done DebuggerPatchSkip::TriggerExceptionHook +#if defined(HOST_OSX) && defined(HOST_ARM64) + ExecutableWriterHolder ripTargetFixupWriterHolder(&m_pSharedPatchBypassBuffer->RipTargetFixup, sizeof(UINT_PTR)); + UINT_PTR *pRipTargetFixupRW = ripTargetFixupWriterHolder.GetRW(); +#else // HOST_OSX && HOST_ARM64 + UINT_PTR *pRipTargetFixupRW = &m_pSharedPatchBypassBuffer->RipTargetFixup; +#endif // HOST_OSX && HOST_ARM64 + + *pRipTargetFixupRW = ip; //Control Flow simulation alone is done DebuggerPatchSkip::TriggerExceptionHook LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x is a Control Flow instr \n", opcode)); if (walk == WALK_CALL) //initialize Lr diff --git a/src/coreclr/debug/ee/controller.cpp b/src/coreclr/debug/ee/controller.cpp index b17ae8f11500..f9304d16ab07 100644 --- a/src/coreclr/debug/ee/controller.cpp +++ b/src/coreclr/debug/ee/controller.cpp @@ -84,8 +84,13 @@ SharedPatchBypassBuffer* DebuggerControllerPatch::GetOrCreateSharedPatchBypassBu if (m_pSharedPatchBypassBuffer == NULL) { void *pSharedPatchBypassBufferRX = g_pDebugger->GetInteropSafeExecutableHeap()->Alloc(sizeof(SharedPatchBypassBuffer)); +#if defined(HOST_OSX) && defined(HOST_ARM64) ExecutableWriterHolder sharedPatchBypassBufferWriterHolder((SharedPatchBypassBuffer*)pSharedPatchBypassBufferRX, sizeof(SharedPatchBypassBuffer)); - new (sharedPatchBypassBufferWriterHolder.GetRW()) SharedPatchBypassBuffer(); + void *pSharedPatchBypassBufferRW = sharedPatchBypassBufferWriterHolder.GetRW(); +#else // HOST_OSX && HOST_ARM64 + void *pSharedPatchBypassBufferRW = pSharedPatchBypassBufferRX; +#endif // HOST_OSX && HOST_ARM64 + new (pSharedPatchBypassBufferRW) SharedPatchBypassBuffer(); m_pSharedPatchBypassBuffer = (SharedPatchBypassBuffer*)pSharedPatchBypassBufferRX; _ASSERTE(m_pSharedPatchBypassBuffer); @@ -4351,7 +4356,15 @@ DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread, // m_pSharedPatchBypassBuffer = patch->GetOrCreateSharedPatchBypassBuffer(); - BYTE* patchBypass = m_pSharedPatchBypassBuffer->PatchBypass; +#if defined(HOST_OSX) && defined(HOST_ARM64) + ExecutableWriterHolder sharedPatchBypassBufferWriterHolder((SharedPatchBypassBuffer*)m_pSharedPatchBypassBuffer, sizeof(SharedPatchBypassBuffer)); + SharedPatchBypassBuffer *pSharedPatchBypassBufferRW = sharedPatchBypassBufferWriterHolder.GetRW(); +#else // HOST_OSX && HOST_ARM64 + SharedPatchBypassBuffer *pSharedPatchBypassBufferRW = m_pSharedPatchBypassBuffer; +#endif // HOST_OSX && HOST_ARM64 + + BYTE* patchBypassRX = m_pSharedPatchBypassBuffer->PatchBypass; + BYTE* patchBypassRW = pSharedPatchBypassBufferRW->PatchBypass; LOG((LF_CORDB, LL_INFO10000, "DPS::DPS: Patch skip for opcode 0x%.4x at address %p buffer allocated at 0x%.8x\n", patch->opcode, patch->address, m_pSharedPatchBypassBuffer)); // Copy the instruction block over to the patch skip @@ -4367,19 +4380,19 @@ DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread, // the 2nd skip executes the new jump-stamp code and not the original method prologue code. Copying // the code every time ensures that we have the most up-to-date version of the code in the buffer. _ASSERTE( patch->IsBound() ); - CopyInstructionBlock(patchBypass, (const BYTE *)patch->address); + CopyInstructionBlock(patchBypassRW, (const BYTE *)patch->address); // Technically, we could create a patch skipper for an inactive patch, but we rely on the opcode being // set here. _ASSERTE( patch->IsActivated() ); - CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypass, patch->opcode); + CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypassRW, patch->opcode); LOG((LF_CORDB, LL_EVERYTHING, "SetInstruction was called\n")); // // Look at instruction to get some attributes // - NativeWalker::DecodeInstructionForPatchSkip(patchBypass, &(m_instrAttrib)); + NativeWalker::DecodeInstructionForPatchSkip(patchBypassRX, &(m_instrAttrib)); #if defined(TARGET_AMD64) @@ -4395,33 +4408,33 @@ DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread, // Populate the RIP-relative buffer with the current value if needed // - BYTE* bufferBypass = m_pSharedPatchBypassBuffer->BypassBuffer; + BYTE* bufferBypassRW = pSharedPatchBypassBufferRW->BypassBuffer; // Overwrite the *signed* displacement. - int dwOldDisp = *(int*)(&patchBypass[m_instrAttrib.m_dwOffsetToDisp]); + int dwOldDisp = *(int*)(&patchBypassRX[m_instrAttrib.m_dwOffsetToDisp]); int dwNewDisp = offsetof(SharedPatchBypassBuffer, BypassBuffer) - (offsetof(SharedPatchBypassBuffer, PatchBypass) + m_instrAttrib.m_cbInstr); - *(int*)(&patchBypass[m_instrAttrib.m_dwOffsetToDisp]) = dwNewDisp; + *(int*)(&patchBypassRW[m_instrAttrib.m_dwOffsetToDisp]) = dwNewDisp; // This could be an LEA, which we'll just have to change into a MOV // and copy the original address - if (((patchBypass[0] == 0x4C) || (patchBypass[0] == 0x48)) && (patchBypass[1] == 0x8d)) + if (((patchBypassRX[0] == 0x4C) || (patchBypassRX[0] == 0x48)) && (patchBypassRX[1] == 0x8d)) { - patchBypass[1] = 0x8b; // MOV reg, mem + patchBypassRW[1] = 0x8b; // MOV reg, mem _ASSERTE((int)sizeof(void*) <= SharedPatchBypassBuffer::cbBufferBypass); - *(void**)bufferBypass = (void*)(patch->address + m_instrAttrib.m_cbInstr + dwOldDisp); + *(void**)bufferBypassRW = (void*)(patch->address + m_instrAttrib.m_cbInstr + dwOldDisp); } else { _ASSERTE(m_instrAttrib.m_cOperandSize <= SharedPatchBypassBuffer::cbBufferBypass); // Copy the data into our buffer. - memcpy(bufferBypass, patch->address + m_instrAttrib.m_cbInstr + dwOldDisp, m_instrAttrib.m_cOperandSize); + memcpy(bufferBypassRW, patch->address + m_instrAttrib.m_cbInstr + dwOldDisp, m_instrAttrib.m_cOperandSize); if (m_instrAttrib.m_fIsWrite) { // save the actual destination address and size so when we TriggerSingleStep() we can update the value - m_pSharedPatchBypassBuffer->RipTargetFixup = (UINT_PTR)(patch->address + m_instrAttrib.m_cbInstr + dwOldDisp); - m_pSharedPatchBypassBuffer->RipTargetFixupSize = m_instrAttrib.m_cOperandSize; + pSharedPatchBypassBufferRW->RipTargetFixup = (UINT_PTR)(patch->address + m_instrAttrib.m_cbInstr + dwOldDisp); + pSharedPatchBypassBufferRW->RipTargetFixupSize = m_instrAttrib.m_cOperandSize; } } } @@ -4490,17 +4503,17 @@ DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread, #else // FEATURE_EMULATE_SINGLESTEP #ifdef TARGET_ARM64 - patchBypass = NativeWalker::SetupOrSimulateInstructionForPatchSkip(context, m_pSharedPatchBypassBuffer, (const BYTE *)patch->address, patch->opcode); + patchBypassRX = NativeWalker::SetupOrSimulateInstructionForPatchSkip(context, m_pSharedPatchBypassBuffer, (const BYTE *)patch->address, patch->opcode); #endif //TARGET_ARM64 //set eip to point to buffer... - SetIP(context, (PCODE)patchBypass); + SetIP(context, (PCODE)patchBypassRX); if (context ==(T_CONTEXT*) &c) thread->SetThreadContext(&c); - LOG((LF_CORDB, LL_INFO10000, "DPS::DPS Bypass at 0x%p for opcode %p \n", patchBypass, patch->opcode)); + LOG((LF_CORDB, LL_INFO10000, "DPS::DPS Bypass at 0x%p for opcode %p \n", patchBypassRX, patch->opcode)); // // Turn on single step (if the platform supports it) so we can diff --git a/src/coreclr/debug/ee/controller.h b/src/coreclr/debug/ee/controller.h index 12b1106f7a4b..6996439c31fb 100644 --- a/src/coreclr/debug/ee/controller.h +++ b/src/coreclr/debug/ee/controller.h @@ -266,14 +266,28 @@ class SharedPatchBypassBuffer LONG AddRef() { - LONG newRefCount = InterlockedIncrement(&m_refCount); +#if !defined(DACCESS_COMPILE) && defined(HOST_OSX) && defined(HOST_ARM64) + ExecutableWriterHolder refCountWriterHolder(&m_refCount, sizeof(LONG)); + LONG *pRefCountRW = refCountWriterHolder.GetRW(); +#else // !DACCESS_COMPILE && HOST_OSX && HOST_ARM64 + LONG *pRefCountRW = &m_refCount; +#endif // !DACCESS_COMPILE && HOST_OSX && HOST_ARM64 + + LONG newRefCount = InterlockedIncrement(pRefCountRW); _ASSERTE(newRefCount > 0); return newRefCount; } LONG Release() { - LONG newRefCount = InterlockedDecrement(&m_refCount); +#if !DACCESS_COMPILE && HOST_OSX && HOST_ARM64 + ExecutableWriterHolder refCountWriterHolder(&m_refCount, sizeof(LONG)); + LONG *pRefCountRW = refCountWriterHolder.GetRW(); +#else // !DACCESS_COMPILE && HOST_OSX && HOST_ARM64 + LONG *pRefCountRW = &m_refCount; +#endif // !DACCESS_COMPILE && HOST_OSX && HOST_ARM64 + + LONG newRefCount = InterlockedDecrement(pRefCountRW); _ASSERTE(newRefCount >= 0); if (newRefCount == 0) diff --git a/src/coreclr/debug/ee/debugger.cpp b/src/coreclr/debug/ee/debugger.cpp index 53ee5555ace4..e4563a31757f 100644 --- a/src/coreclr/debug/ee/debugger.cpp +++ b/src/coreclr/debug/ee/debugger.cpp @@ -1317,13 +1317,19 @@ DebuggerEval::DebuggerEval(CONTEXT * pContext, DebuggerIPCE_FuncEvalInfo * pEval // Allocate the breakpoint instruction info in executable memory. void *bpInfoSegmentRX = g_pDebugger->GetInteropSafeExecutableHeap()->Alloc(sizeof(DebuggerEvalBreakpointInfoSegment)); + +#if !defined(DBI_COMPILE) && !defined(DACCESS_COMPILE) && defined(HOST_OSX) && defined(HOST_ARM64) ExecutableWriterHolder bpInfoSegmentWriterHolder((DebuggerEvalBreakpointInfoSegment*)bpInfoSegmentRX, sizeof(DebuggerEvalBreakpointInfoSegment)); - new (bpInfoSegmentWriterHolder.GetRW()) DebuggerEvalBreakpointInfoSegment(this); + DebuggerEvalBreakpointInfoSegment *bpInfoSegmentRW = bpInfoSegmentWriterHolder.GetRW(); +#else // !DBI_COMPILE && !DACCESS_COMPILE && HOST_OSX && HOST_ARM64 + DebuggerEvalBreakpointInfoSegment *bpInfoSegmentRW = (DebuggerEvalBreakpointInfoSegment*)bpInfoSegmentRX; +#endif // !DBI_COMPILE && !DACCESS_COMPILE && HOST_OSX && HOST_ARM64 + new (bpInfoSegmentRW) DebuggerEvalBreakpointInfoSegment(this); m_bpInfoSegment = (DebuggerEvalBreakpointInfoSegment*)bpInfoSegmentRX; // This must be non-zero so that the saved opcode is non-zero, and on IA64 we want it to be 0x16 // so that we can have a breakpoint instruction in any slot in the bundle. - bpInfoSegmentWriterHolder.GetRW()->m_breakpointInstruction[0] = 0x16; + bpInfoSegmentRW->m_breakpointInstruction[0] = 0x16; #if defined(TARGET_ARM) USHORT *bp = (USHORT*)&m_bpInfoSegment->m_breakpointInstruction; *bp = CORDbg_BREAK_INSTRUCTION; @@ -16234,6 +16240,7 @@ void Debugger::ReleaseDebuggerDataLock(Debugger *pDebugger) } #endif // DACCESS_COMPILE +#ifndef DACCESS_COMPILE /* ------------------------------------------------------------------------ * * Functions for DebuggerHeap executable memory allocations * ------------------------------------------------------------------------ */ @@ -16378,6 +16385,7 @@ void* DebuggerHeapExecutableMemoryAllocator::GetPointerToChunkWithUsageUpdate(De return page->GetPointerToChunk(chunkNumber); } +#endif // DACCESS_COMPILE /* ------------------------------------------------------------------------ * * DebuggerHeap impl @@ -16412,7 +16420,7 @@ void DebuggerHeap::Destroy() m_hHeap = NULL; } #endif -#ifndef HOST_WINDOWS +#if !defined(HOST_WINDOWS) && !defined(DACCESS_COMPILE) if (m_execMemAllocator != NULL) { delete m_execMemAllocator; @@ -16439,6 +16447,8 @@ HRESULT DebuggerHeap::Init(BOOL fExecutable) } CONTRACTL_END; +#ifndef DACCESS_COMPILE + // Have knob catch if we don't want to lazy init the debugger. _ASSERTE(!g_DbgShouldntUseDebugger); m_fExecutable = fExecutable; @@ -16472,7 +16482,9 @@ HRESULT DebuggerHeap::Init(BOOL fExecutable) return E_OUTOFMEMORY; } } -#endif +#endif + +#endif // !DACCESS_COMPILE return S_OK; } @@ -16549,7 +16561,10 @@ void *DebuggerHeap::Alloc(DWORD size) size += sizeof(InteropHeapCanary); #endif - void *ret; + void *ret = NULL; + +#ifndef DACCESS_COMPILE + #ifdef USE_INTEROPSAFE_HEAP _ASSERTE(m_hHeap != NULL); ret = ::HeapAlloc(m_hHeap, HEAP_ZERO_MEMORY, size); @@ -16585,7 +16600,7 @@ void *DebuggerHeap::Alloc(DWORD size) InteropHeapCanary * pCanary = InteropHeapCanary::GetFromRawAddr(ret); ret = pCanary->GetUserAddr(); #endif - +#endif // !DACCESS_COMPILE return ret; } @@ -16638,6 +16653,8 @@ void DebuggerHeap::Free(void *pMem) } CONTRACTL_END; +#ifndef DACCESS_COMPILE + #ifdef USE_INTEROPSAFE_CANARY // Check for canary @@ -16673,6 +16690,7 @@ void DebuggerHeap::Free(void *pMem) #endif // HOST_WINDOWS } #endif +#endif // !DACCESS_COMPILE } #ifndef DACCESS_COMPILE diff --git a/src/coreclr/debug/ee/debugger.h b/src/coreclr/debug/ee/debugger.h index f16f8cd6d9d9..5503de245909 100644 --- a/src/coreclr/debug/ee/debugger.h +++ b/src/coreclr/debug/ee/debugger.h @@ -1054,6 +1054,8 @@ constexpr uint64_t CHUNKS_PER_DEBUGGERHEAP=(DEBUGGERHEAP_PAGESIZE / EXPECTED_CHU constexpr uint64_t MAX_CHUNK_MASK=((1ull << CHUNKS_PER_DEBUGGERHEAP) - 1); constexpr uint64_t BOOKKEEPING_CHUNK_MASK (1ull << (CHUNKS_PER_DEBUGGERHEAP - 1)); +#ifndef DACCESS_COMPILE + // Forward declaration struct DebuggerHeapExecutableMemoryPage; @@ -1110,8 +1112,13 @@ struct DECLSPEC_ALIGN(DEBUGGERHEAP_PAGESIZE) DebuggerHeapExecutableMemoryPage inline void SetNextPage(DebuggerHeapExecutableMemoryPage* nextPage) { +#if defined(HOST_OSX) && defined(HOST_ARM64) ExecutableWriterHolder debuggerHeapPageWriterHolder(this, sizeof(DebuggerHeapExecutableMemoryPage)); - debuggerHeapPageWriterHolder.GetRW()->chunks[0].bookkeeping.nextPage = nextPage; + DebuggerHeapExecutableMemoryPage *pHeapPageRW = debuggerHeapPageWriterHolder.GetRW(); +#else + DebuggerHeapExecutableMemoryPage *pHeapPageRW = this; +#endif + pHeapPageRW->chunks[0].bookkeeping.nextPage = nextPage; } inline uint64_t GetPageOccupancy() const @@ -1124,8 +1131,13 @@ struct DECLSPEC_ALIGN(DEBUGGERHEAP_PAGESIZE) DebuggerHeapExecutableMemoryPage // Can't unset the bookmark chunk! ASSERT((newOccupancy & BOOKKEEPING_CHUNK_MASK) != 0); ASSERT(newOccupancy <= MAX_CHUNK_MASK); +#if defined(HOST_OSX) && defined(HOST_ARM64) ExecutableWriterHolder debuggerHeapPageWriterHolder(this, sizeof(DebuggerHeapExecutableMemoryPage)); - debuggerHeapPageWriterHolder.GetRW()->chunks[0].bookkeeping.pageOccupancy = newOccupancy; + DebuggerHeapExecutableMemoryPage *pHeapPageRW = debuggerHeapPageWriterHolder.GetRW(); +#else + DebuggerHeapExecutableMemoryPage *pHeapPageRW = this; +#endif + pHeapPageRW->chunks[0].bookkeeping.pageOccupancy = newOccupancy; } inline void* GetPointerToChunk(int chunkNum) const @@ -1136,14 +1148,18 @@ struct DECLSPEC_ALIGN(DEBUGGERHEAP_PAGESIZE) DebuggerHeapExecutableMemoryPage DebuggerHeapExecutableMemoryPage() { - ExecutableWriterHolder debuggerHeapPageWriterHolder(this, sizeof(DebuggerHeapExecutableMemoryPage)); - SetPageOccupancy(BOOKKEEPING_CHUNK_MASK); // only the first bit is set. +#if defined(HOST_OSX) && defined(HOST_ARM64) + ExecutableWriterHolder debuggerHeapPageWriterHolder(this, sizeof(DebuggerHeapExecutableMemoryPage)); + DebuggerHeapExecutableMemoryPage *pHeapPageRW = debuggerHeapPageWriterHolder.GetRW(); +#else + DebuggerHeapExecutableMemoryPage *pHeapPageRW = this; +#endif for (uint8_t i = 1; i < CHUNKS_PER_DEBUGGERHEAP; i++) { ASSERT(i != 0); - debuggerHeapPageWriterHolder.GetRW()->chunks[i].data.startOfPage = this; - debuggerHeapPageWriterHolder.GetRW()->chunks[i].data.chunkNumber = i; + pHeapPageRW->chunks[i].data.startOfPage = this; + pHeapPageRW->chunks[i].data.chunkNumber = i; } } @@ -1190,6 +1206,8 @@ class DebuggerHeapExecutableMemoryAllocator Crst m_execMemAllocMutex; }; +#endif // DACCESS_COMPILE + // ------------------------------------------------------------------------ * // DebuggerHeap class // For interop debugging, we need a heap that: @@ -1201,6 +1219,8 @@ class DebuggerHeapExecutableMemoryAllocator #define USE_INTEROPSAFE_HEAP #endif +class DebuggerHeapExecutableMemoryAllocator; + class DebuggerHeap { public: diff --git a/src/coreclr/debug/inc/amd64/primitives.h b/src/coreclr/debug/inc/amd64/primitives.h index d8d14b24b542..9d363938519c 100644 --- a/src/coreclr/debug/inc/amd64/primitives.h +++ b/src/coreclr/debug/inc/amd64/primitives.h @@ -12,10 +12,6 @@ #ifndef PRIMITIVES_H_ #define PRIMITIVES_H_ -#if !defined(DBI_COMPILE) && !defined(DACCESS_COMPILE) -#include "executableallocator.h" -#endif - #ifndef CORDB_ADDRESS_TYPE typedef const BYTE CORDB_ADDRESS_TYPE; typedef DPTR(CORDB_ADDRESS_TYPE) PTR_CORDB_ADDRESS_TYPE; @@ -191,14 +187,7 @@ inline void CORDbgInsertBreakpoint(UNALIGNED CORDB_ADDRESS_TYPE *address) { LIMITED_METHOD_CONTRACT; -#if !defined(DBI_COMPILE) && !defined(DACCESS_COMPILE) - ExecutableWriterHolder breakpointWriterHolder(address, CORDbg_BREAK_INSTRUCTION_SIZE); - UNALIGNED CORDB_ADDRESS_TYPE* addressRW = breakpointWriterHolder.GetRW(); -#else // !DBI_COMPILE && !DACCESS_COMPILE - UNALIGNED CORDB_ADDRESS_TYPE* addressRW = address; -#endif // !DBI_COMPILE && !DACCESS_COMPILE - - *((unsigned char*)addressRW) = 0xCC; // int 3 (single byte patch) + *((unsigned char*)address) = 0xCC; // int 3 (single byte patch) FlushInstructionCache(GetCurrentProcess(), address, 1); } @@ -209,14 +198,7 @@ inline void CORDbgSetInstruction(UNALIGNED CORDB_ADDRESS_TYPE* address, // In a DAC build, this function assumes the input is an host address. LIMITED_METHOD_DAC_CONTRACT; -#if !defined(DBI_COMPILE) && !defined(DACCESS_COMPILE) - ExecutableWriterHolder instructionWriterHolder(address, sizeof(unsigned char)); - UNALIGNED CORDB_ADDRESS_TYPE* addressRW = instructionWriterHolder.GetRW(); -#else // !DBI_COMPILE && !DACCESS_COMPILE - UNALIGNED CORDB_ADDRESS_TYPE* addressRW = address; -#endif // !DBI_COMPILE && !DACCESS_COMPILE - - *((unsigned char*)addressRW) = + *((unsigned char*)address) = (unsigned char) instruction; // setting one byte is important FlushInstructionCache(GetCurrentProcess(), address, 1); diff --git a/src/coreclr/debug/inc/arm/primitives.h b/src/coreclr/debug/inc/arm/primitives.h index c4e2d28602e5..269281eb006b 100644 --- a/src/coreclr/debug/inc/arm/primitives.h +++ b/src/coreclr/debug/inc/arm/primitives.h @@ -12,10 +12,6 @@ #ifndef PRIMITIVES_H_ #define PRIMITIVES_H_ -#if !defined(DBI_COMPILE) && !defined(DACCESS_COMPILE) -#include "executableallocator.h" -#endif - #ifndef THUMB_CODE #define THUMB_CODE 1 #endif @@ -163,14 +159,7 @@ inline void CORDbgSetInstruction(CORDB_ADDRESS_TYPE* address, // In a DAC build, this function assumes the input is an host address. LIMITED_METHOD_DAC_CONTRACT; -#if !defined(DBI_COMPILE) && !defined(DACCESS_COMPILE) - ExecutableWriterHolder instructionWriterHolder(address, sizeof(PRD_TYPE)); - CORDB_ADDRESS_TYPE* addressRW = instructionWriterHolder.GetRW(); -#else // !DBI_COMPILE && !DACCESS_COMPILE - CORDB_ADDRESS_TYPE* addressRW = address; -#endif // !DBI_COMPILE && !DACCESS_COMPILE - - CORDB_ADDRESS ptraddr = (CORDB_ADDRESS)addressRW; + CORDB_ADDRESS ptraddr = (CORDB_ADDRESS)address; _ASSERTE(ptraddr & THUMB_CODE); ptraddr &= ~THUMB_CODE; diff --git a/src/coreclr/debug/inc/arm64/primitives.h b/src/coreclr/debug/inc/arm64/primitives.h index 4f4c3f7bcd8f..05c03c7b3094 100644 --- a/src/coreclr/debug/inc/arm64/primitives.h +++ b/src/coreclr/debug/inc/arm64/primitives.h @@ -150,13 +150,13 @@ inline void CORDbgSetInstruction(CORDB_ADDRESS_TYPE* address, // In a DAC build, this function assumes the input is an host address. LIMITED_METHOD_DAC_CONTRACT; -#if !defined(DBI_COMPILE) && !defined(DACCESS_COMPILE) +#if !defined(DBI_COMPILE) && !defined(DACCESS_COMPILE) && defined(HOST_OSX) ExecutableWriterHolder instructionWriterHolder((LPVOID)address, sizeof(PRD_TYPE)); ULONGLONG ptraddr = dac_cast(instructionWriterHolder.GetRW()); -#else // !DBI_COMPILE && !DACCESS_COMPILE +#else // !DBI_COMPILE && !DACCESS_COMPILE && HOST_OSX ULONGLONG ptraddr = dac_cast(address); -#endif // !DBI_COMPILE && !DACCESS_COMPILE +#endif // !DBI_COMPILE && !DACCESS_COMPILE && HOST_OSX *(PRD_TYPE *)ptraddr = instruction; FlushInstructionCache(GetCurrentProcess(), address, diff --git a/src/coreclr/debug/inc/i386/primitives.h b/src/coreclr/debug/inc/i386/primitives.h index 313b42c5a197..2f228b3a3a9a 100644 --- a/src/coreclr/debug/inc/i386/primitives.h +++ b/src/coreclr/debug/inc/i386/primitives.h @@ -12,10 +12,6 @@ #ifndef PRIMITIVES_H_ #define PRIMITIVES_H_ -#if !defined(DBI_COMPILE) && !defined(DACCESS_COMPILE) -#include "executableallocator.h" -#endif - typedef const BYTE CORDB_ADDRESS_TYPE; typedef DPTR(CORDB_ADDRESS_TYPE) PTR_CORDB_ADDRESS_TYPE; @@ -151,14 +147,7 @@ inline void CORDbgInsertBreakpoint(UNALIGNED CORDB_ADDRESS_TYPE *address) { LIMITED_METHOD_CONTRACT; -#if !defined(DBI_COMPILE) && !defined(DACCESS_COMPILE) - ExecutableWriterHolder breakpointWriterHolder(address, CORDbg_BREAK_INSTRUCTION_SIZE); - UNALIGNED CORDB_ADDRESS_TYPE* addressRW = breakpointWriterHolder.GetRW(); -#else // !DBI_COMPILE && !DACCESS_COMPILE - UNALIGNED CORDB_ADDRESS_TYPE* addressRW = address; -#endif // !DBI_COMPILE && !DACCESS_COMPILE - - *((unsigned char*)addressRW) = 0xCC; // int 3 (single byte patch) + *((unsigned char*)address) = 0xCC; // int 3 (single byte patch) FlushInstructionCache(GetCurrentProcess(), address, 1); } diff --git a/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt b/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt index fae55ecdc3ea..9b8e4b649864 100644 --- a/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt +++ b/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt @@ -109,6 +109,7 @@ set(CORECLR_LIBRARIES v3binder System.Globalization.Native-Static interop + coreclrminipal ) if(CLR_CMAKE_TARGET_WIN32) diff --git a/src/coreclr/inc/CrstTypes.def b/src/coreclr/inc/CrstTypes.def index c48872a0b942..c7266df7dbb0 100644 --- a/src/coreclr/inc/CrstTypes.def +++ b/src/coreclr/inc/CrstTypes.def @@ -201,6 +201,10 @@ End Crst Exception End +Crst ExecutableAllocatorLock + AcquiredAfter LoaderHeap ArgBasedStubCache UMEntryThunkFreeListLock +End + Crst ExecuteManRangeLock End @@ -505,6 +509,9 @@ Crst TypeEquivalenceMap AcquiredBefore LoaderHeap End +Crst UMEntryThunkFreeListLock +End + Crst UniqueStack AcquiredBefore LoaderHeap End diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index 0d2a1db98e47..e2f1a63a20fe 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -737,6 +737,10 @@ RETAIL_CONFIG_STRING_INFO(EXTERNAL_DOTNET_DiagnosticPorts, W("DiagnosticPorts"), RETAIL_CONFIG_STRING_INFO(INTERNAL_LTTngConfig, W("LTTngConfig"), "Configuration for LTTng.") RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_LTTng, W("LTTng"), 1, "If COMPlus_LTTng is set to 0, this will prevent the LTTng library from being loaded at runtime") +// +// Executable code +// +RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableWriteXorExecute, W("EnableWriteXorExecute"), 0, "Enable W^X for executable memory."); #ifdef FEATURE_GDBJIT /// diff --git a/src/coreclr/inc/crsttypes.h b/src/coreclr/inc/crsttypes.h index a1bab2ecb906..7be482c48bb5 100644 --- a/src/coreclr/inc/crsttypes.h +++ b/src/coreclr/inc/crsttypes.h @@ -49,92 +49,94 @@ enum CrstType CrstEventPipe = 31, CrstEventStore = 32, CrstException = 33, - CrstExecuteManRangeLock = 34, - CrstExternalObjectContextCache = 35, - CrstFCall = 36, - CrstFuncPtrStubs = 37, - CrstFusionAppCtx = 38, - CrstGCCover = 39, - CrstGlobalStrLiteralMap = 40, - CrstHandleTable = 41, - CrstHostAssemblyMap = 42, - CrstHostAssemblyMapAdd = 43, - CrstIbcProfile = 44, - CrstIJWFixupData = 45, - CrstIJWHash = 46, - CrstILStubGen = 47, - CrstInlineTrackingMap = 48, - CrstInstMethodHashTable = 49, - CrstInterop = 50, - CrstInteropData = 51, - CrstIsJMCMethod = 52, - CrstISymUnmanagedReader = 53, - CrstJit = 54, - CrstJitGenericHandleCache = 55, - CrstJitInlineTrackingMap = 56, - CrstJitPatchpoint = 57, - CrstJitPerf = 58, - CrstJumpStubCache = 59, - CrstLeafLock = 60, - CrstListLock = 61, - CrstLoaderAllocator = 62, - CrstLoaderAllocatorReferences = 63, - CrstLoaderHeap = 64, - CrstManagedObjectWrapperMap = 65, - CrstMethodDescBackpatchInfoTracker = 66, - CrstModule = 67, - CrstModuleFixup = 68, - CrstModuleLookupTable = 69, - CrstMulticoreJitHash = 70, - CrstMulticoreJitManager = 71, - CrstNativeImageEagerFixups = 72, - CrstNativeImageLoad = 73, - CrstNls = 74, - CrstNotifyGdb = 75, - CrstObjectList = 76, - CrstPEImage = 77, - CrstPendingTypeLoadEntry = 78, - CrstPgoData = 79, - CrstPinnedByrefValidation = 80, - CrstProfilerGCRefDataFreeList = 81, - CrstProfilingAPIStatus = 82, - CrstRCWCache = 83, - CrstRCWCleanupList = 84, - CrstReadyToRunEntryPointToMethodDescMap = 85, - CrstReflection = 86, - CrstReJITGlobalRequest = 87, - CrstRetThunkCache = 88, - CrstSavedExceptionInfo = 89, - CrstSaveModuleProfileData = 90, - CrstSecurityStackwalkCache = 91, - CrstSigConvert = 92, - CrstSingleUseLock = 93, - CrstSpecialStatics = 94, - CrstStackSampler = 95, - CrstStressLog = 96, - CrstStubCache = 97, - CrstStubDispatchCache = 98, - CrstStubUnwindInfoHeapSegments = 99, - CrstSyncBlockCache = 100, - CrstSyncHashLock = 101, - CrstSystemBaseDomain = 102, - CrstSystemDomain = 103, - CrstSystemDomainDelayedUnloadList = 104, - CrstThreadIdDispenser = 105, - CrstThreadpoolTimerQueue = 106, - CrstThreadpoolWaitThreads = 107, - CrstThreadpoolWorker = 108, - CrstThreadStore = 109, - CrstTieredCompilation = 110, - CrstTypeEquivalenceMap = 111, - CrstTypeIDMap = 112, - CrstUMEntryThunkCache = 113, - CrstUniqueStack = 114, - CrstUnresolvedClassLock = 115, - CrstUnwindInfoTableLock = 116, - CrstVSDIndirectionCellLock = 117, - CrstWrapperTemplate = 118, - kNumberOfCrstTypes = 119 + CrstExecutableAllocatorLock = 34, + CrstExecuteManRangeLock = 35, + CrstExternalObjectContextCache = 36, + CrstFCall = 37, + CrstFuncPtrStubs = 38, + CrstFusionAppCtx = 39, + CrstGCCover = 40, + CrstGlobalStrLiteralMap = 41, + CrstHandleTable = 42, + CrstHostAssemblyMap = 43, + CrstHostAssemblyMapAdd = 44, + CrstIbcProfile = 45, + CrstIJWFixupData = 46, + CrstIJWHash = 47, + CrstILStubGen = 48, + CrstInlineTrackingMap = 49, + CrstInstMethodHashTable = 50, + CrstInterop = 51, + CrstInteropData = 52, + CrstIsJMCMethod = 53, + CrstISymUnmanagedReader = 54, + CrstJit = 55, + CrstJitGenericHandleCache = 56, + CrstJitInlineTrackingMap = 57, + CrstJitPatchpoint = 58, + CrstJitPerf = 59, + CrstJumpStubCache = 60, + CrstLeafLock = 61, + CrstListLock = 62, + CrstLoaderAllocator = 63, + CrstLoaderAllocatorReferences = 64, + CrstLoaderHeap = 65, + CrstManagedObjectWrapperMap = 66, + CrstMethodDescBackpatchInfoTracker = 67, + CrstModule = 68, + CrstModuleFixup = 69, + CrstModuleLookupTable = 70, + CrstMulticoreJitHash = 71, + CrstMulticoreJitManager = 72, + CrstNativeImageEagerFixups = 73, + CrstNativeImageLoad = 74, + CrstNls = 75, + CrstNotifyGdb = 76, + CrstObjectList = 77, + CrstPEImage = 78, + CrstPendingTypeLoadEntry = 79, + CrstPgoData = 80, + CrstPinnedByrefValidation = 81, + CrstProfilerGCRefDataFreeList = 82, + CrstProfilingAPIStatus = 83, + CrstRCWCache = 84, + CrstRCWCleanupList = 85, + CrstReadyToRunEntryPointToMethodDescMap = 86, + CrstReflection = 87, + CrstReJITGlobalRequest = 88, + CrstRetThunkCache = 89, + CrstSavedExceptionInfo = 90, + CrstSaveModuleProfileData = 91, + CrstSecurityStackwalkCache = 92, + CrstSigConvert = 93, + CrstSingleUseLock = 94, + CrstSpecialStatics = 95, + CrstStackSampler = 96, + CrstStressLog = 97, + CrstStubCache = 98, + CrstStubDispatchCache = 99, + CrstStubUnwindInfoHeapSegments = 100, + CrstSyncBlockCache = 101, + CrstSyncHashLock = 102, + CrstSystemBaseDomain = 103, + CrstSystemDomain = 104, + CrstSystemDomainDelayedUnloadList = 105, + CrstThreadIdDispenser = 106, + CrstThreadpoolTimerQueue = 107, + CrstThreadpoolWaitThreads = 108, + CrstThreadpoolWorker = 109, + CrstThreadStore = 110, + CrstTieredCompilation = 111, + CrstTypeEquivalenceMap = 112, + CrstTypeIDMap = 113, + CrstUMEntryThunkCache = 114, + CrstUMEntryThunkFreeListLock = 115, + CrstUniqueStack = 116, + CrstUnresolvedClassLock = 117, + CrstUnwindInfoTableLock = 118, + CrstVSDIndirectionCellLock = 119, + CrstWrapperTemplate = 120, + kNumberOfCrstTypes = 121 }; #endif // __CRST_TYPES_INCLUDED @@ -147,11 +149,11 @@ int g_rgCrstLevelMap[] = { 10, // CrstAppDomainCache 14, // CrstAppDomainHandleTable - 0, // CrstArgBasedStubCache + 3, // CrstArgBasedStubCache 0, // CrstAssemblyList 12, // CrstAssemblyLoader - 3, // CrstAvailableClass - 4, // CrstAvailableParamTypes + 4, // CrstAvailableClass + 5, // CrstAvailableParamTypes 7, // CrstBaseDomain -1, // CrstCCompRC 13, // CrstClassFactInfoHash @@ -160,7 +162,7 @@ int g_rgCrstLevelMap[] = 6, // CrstCodeFragmentHeap 9, // CrstCodeVersioning 0, // CrstCOMCallWrapper - 4, // CrstCOMWrapperCache + 5, // CrstCOMWrapperCache 3, // CrstDataTest1 0, // CrstDataTest2 0, // CrstDbgTransport @@ -179,9 +181,10 @@ int g_rgCrstLevelMap[] = 18, // CrstEventPipe 0, // CrstEventStore 0, // CrstException + 0, // CrstExecutableAllocatorLock 0, // CrstExecuteManRangeLock 0, // CrstExternalObjectContextCache - 3, // CrstFCall + 4, // CrstFCall 7, // CrstFuncPtrStubs 10, // CrstFusionAppCtx 10, // CrstGCCover @@ -196,25 +199,25 @@ int g_rgCrstLevelMap[] = 3, // CrstInlineTrackingMap 17, // CrstInstMethodHashTable 20, // CrstInterop - 4, // CrstInteropData + 5, // CrstInteropData 0, // CrstIsJMCMethod 7, // CrstISymUnmanagedReader 11, // CrstJit 0, // CrstJitGenericHandleCache 16, // CrstJitInlineTrackingMap - 3, // CrstJitPatchpoint + 4, // CrstJitPatchpoint -1, // CrstJitPerf 6, // CrstJumpStubCache 0, // CrstLeafLock -1, // CrstListLock 15, // CrstLoaderAllocator 16, // CrstLoaderAllocatorReferences - 0, // CrstLoaderHeap + 3, // CrstLoaderHeap 3, // CrstManagedObjectWrapperMap 14, // CrstMethodDescBackpatchInfoTracker - 4, // CrstModule + 5, // CrstModule 15, // CrstModuleFixup - 3, // CrstModuleLookupTable + 4, // CrstModuleLookupTable 0, // CrstMulticoreJitHash 13, // CrstMulticoreJitManager 0, // CrstNativeImageEagerFixups @@ -222,22 +225,22 @@ int g_rgCrstLevelMap[] = 0, // CrstNls 0, // CrstNotifyGdb 2, // CrstObjectList - 4, // CrstPEImage + 5, // CrstPEImage 19, // CrstPendingTypeLoadEntry - 3, // CrstPgoData + 4, // CrstPgoData 0, // CrstPinnedByrefValidation 0, // CrstProfilerGCRefDataFreeList 0, // CrstProfilingAPIStatus - 3, // CrstRCWCache + 4, // CrstRCWCache 0, // CrstRCWCleanupList 10, // CrstReadyToRunEntryPointToMethodDescMap 8, // CrstReflection 17, // CrstReJITGlobalRequest - 3, // CrstRetThunkCache + 4, // CrstRetThunkCache 3, // CrstSavedExceptionInfo 0, // CrstSaveModuleProfileData 0, // CrstSecurityStackwalkCache - 3, // CrstSigConvert + 4, // CrstSigConvert 5, // CrstSingleUseLock 0, // CrstSpecialStatics 0, // CrstStackSampler @@ -247,7 +250,7 @@ int g_rgCrstLevelMap[] = 4, // CrstStubUnwindInfoHeapSegments 3, // CrstSyncBlockCache 0, // CrstSyncHashLock - 4, // CrstSystemBaseDomain + 5, // CrstSystemBaseDomain 13, // CrstSystemDomain 0, // CrstSystemDomainDelayedUnloadList 0, // CrstThreadIdDispenser @@ -256,13 +259,14 @@ int g_rgCrstLevelMap[] = 13, // CrstThreadpoolWorker 12, // CrstThreadStore 8, // CrstTieredCompilation - 3, // CrstTypeEquivalenceMap + 4, // CrstTypeEquivalenceMap 10, // CrstTypeIDMap - 3, // CrstUMEntryThunkCache - 3, // CrstUniqueStack + 4, // CrstUMEntryThunkCache + 3, // CrstUMEntryThunkFreeListLock + 4, // CrstUniqueStack 7, // CrstUnresolvedClassLock 3, // CrstUnwindInfoTableLock - 3, // CrstVSDIndirectionCellLock + 4, // CrstVSDIndirectionCellLock 3, // CrstWrapperTemplate }; @@ -303,6 +307,7 @@ LPCSTR g_rgCrstNameMap[] = "CrstEventPipe", "CrstEventStore", "CrstException", + "CrstExecutableAllocatorLock", "CrstExecuteManRangeLock", "CrstExternalObjectContextCache", "CrstFCall", @@ -383,6 +388,7 @@ LPCSTR g_rgCrstNameMap[] = "CrstTypeEquivalenceMap", "CrstTypeIDMap", "CrstUMEntryThunkCache", + "CrstUMEntryThunkFreeListLock", "CrstUniqueStack", "CrstUnresolvedClassLock", "CrstUnwindInfoTableLock", diff --git a/src/coreclr/inc/executableallocator.h b/src/coreclr/inc/executableallocator.h index ce0c6c22f890..101178f9a4ef 100644 --- a/src/coreclr/inc/executableallocator.h +++ b/src/coreclr/inc/executableallocator.h @@ -11,6 +11,191 @@ #include "utilcode.h" #include "ex.h" +#include "minipal.h" + +#ifndef DACCESS_COMPILE + +// This class is responsible for allocation of all the executable memory in the runtime. +class ExecutableAllocator +{ + // RX address range block descriptor + struct BlockRX + { + // Next block in a linked list + BlockRX* next; + // Base address of the block + void* baseRX; + // Size of the block + size_t size; + // Offset of the block in the shared memory + size_t offset; + }; + + // RW address range block descriptor + struct BlockRW + { + // Next block in a linked list + BlockRW* next; + // Base address of the RW mapping of the block + void* baseRW; + // Base address of the RX mapping of the block + void* baseRX; + // Size of the block + size_t size; + // Usage reference count of the RW block. RW blocks can be reused + // when multiple mappings overlap in the VA space at the same time + // (even from multiple threads) + size_t refCount; + }; + + typedef void (*FatalErrorHandler)(UINT errorCode, LPCWSTR pszMessage); + + // Instance of the allocator + static ExecutableAllocator* g_instance; + + // Callback to the runtime to report fatal errors + static FatalErrorHandler g_fatalErrorHandler; + +#if USE_UPPER_ADDRESS + // Preferred region to allocate the code in. + static BYTE* g_codeMinAddr; + static BYTE* g_codeMaxAddr; + static BYTE* g_codeAllocStart; + // Next address to try to allocate for code in the preferred region. + static BYTE* g_codeAllocHint; +#endif // USE_UPPER_ADDRESS + + // Caches the COMPlus_EnableWXORX setting + static bool g_isWXorXEnabled; + + // Head of the linked list of all RX blocks that were allocated by this allocator + BlockRX* m_pFirstBlockRX = NULL; + + // Head of the linked list of free RX blocks that were allocated by this allocator and then backed out + BlockRX* m_pFirstFreeBlockRX = NULL; + + // Head of the linked list of currently mapped RW blocks + BlockRW* m_pFirstBlockRW = NULL; + + // Handle of the double mapped memory mapper + void *m_doubleMemoryMapperHandle = NULL; + + // Maximum size of executable memory this allocator can allocate + size_t m_maxExecutableCodeSize; + + // First free offset in the underlying shared memory. It is not used + // for platforms that don't use shared memory. + size_t m_freeOffset = 0; + + // Last RW mapping cached so that it can be reused for the next mapping + // request if it goes into the same range. + BlockRW* m_cachedMapping = NULL; + + // Synchronization of the public allocator methods + CRITSEC_COOKIE m_CriticalSection; + + // Update currently cached mapping. If the passed in block is the same as the one + // in the cache, it keeps it cached. Otherwise it destroys the currently cached one + // and replaces it by the passed in one. + void UpdateCachedMapping(BlockRW *pBlock); + + // Find existing RW block that maps the whole specified range of RX memory. + // Return NULL if no such block exists. + void* FindRWBlock(void* baseRX, size_t size); + + // Add RW block to the list of existing RW blocks + bool AddRWBlock(void* baseRW, void* baseRX, size_t size); + + // Remove RW block from the list of existing RW blocks and return the base + // address and size the underlying memory was mapped at. + // Return false if no existing RW block contains the passed in address. + bool RemoveRWBlock(void* pRW, void** pUnmapAddress, size_t* pUnmapSize); + + // Find a free block with the closest size >= the requested size. + // Returns NULL if no such block exists. + BlockRX* FindBestFreeBlock(size_t size); + + // Return memory mapping granularity. + static size_t Granularity(); + + // Allocate a block of executable memory of the specified size. + // It doesn't acquire the actual virtual memory, just the + // range of the underlying shared memory. + BlockRX* AllocateBlock(size_t size, bool* pIsFreeBlock); + + // Backout the block allocated by AllocateBlock in case of an + // error. + void BackoutBlock(BlockRX* pBlock, bool isFreeBlock); + + // Allocate range of offsets in the underlying shared memory + bool AllocateOffset(size_t* pOffset, size_t size); + + // Add RX block to the linked list of existing blocks + void AddRXBlock(BlockRX *pBlock); + + // Return true if double mapping is enabled. + static bool IsDoubleMappingEnabled(); + + // Initialize the allocator instance + bool Initialize(); + +public: + + // Return the ExecuteAllocator singleton instance + static ExecutableAllocator* Instance(); + + // Initialize the static members of the Executable allocator and allocate + // and initialize the instance of it. + static HRESULT StaticInitialize(FatalErrorHandler fatalErrorHandler); + + // Destroy the allocator + ~ExecutableAllocator(); + + // Return true if W^X is enabled + static bool IsWXORXEnabled(); + + // Use this function to initialize the g_codeAllocHint + // during startup. base is runtime .dll base address, + // size is runtime .dll virtual size. + static void InitCodeAllocHint(size_t base, size_t size, int randomPageOffset); + + // Use this function to reset the g_codeAllocHint + // after unloading an AppDomain + static void ResetCodeAllocHint(); + + // Returns TRUE if p is located in near clr.dll that allows us + // to use rel32 IP-relative addressing modes. + static bool IsPreferredExecutableRange(void* p); + + // Reserve the specified amount of virtual address space for executable mapping. + void* Reserve(size_t size); + + // Reserve the specified amount of virtual address space for executable mapping. + // The reserved range must be within the loAddress and hiAddress. If it is not + // possible to reserve memory in such range, the method returns NULL. + void* ReserveWithinRange(size_t size, const void* loAddress, const void* hiAddress); + + // Reserve the specified amount of virtual address space for executable mapping + // exactly at the given address. + void* ReserveAt(void* baseAddressRX, size_t size); + + // Commit the specified range of memory. The memory can be committed as executable (RX) + // or non-executable (RW) based on the passed in isExecutable flag. The non-executable + // allocations are used to allocate data structures that need to be close to the + // executable code due to memory addressing performance related reasons. + void* Commit(void* pStart, size_t size, bool isExecutable); + + // Release the executable memory block starting at the passed in address that was allocated + // by one of the ReserveXXX methods. + void Release(void* pRX); + + // Map the specified block of executable memory as RW + void* MapRW(void* pRX, size_t size); + + // Unmap the RW mapping at the specified address + void UnmapRW(void* pRW); +}; + // Holder class to map read-execute memory as read-write so that it can be modified without using read-write-execute mapping. // At the moment the implementation is dummy, returning the same addresses for both cases and expecting them to be read-write-execute. // The class uses the move semantics to ensure proper unmapping in case of re-assigning of the holder value. @@ -30,13 +215,17 @@ class ExecutableWriterHolder void Unmap() { +#if defined(HOST_OSX) && defined(HOST_ARM64) && !defined(DACCESS_COMPILE) if (m_addressRX != NULL) { - // TODO: mapping / unmapping for targets using double memory mapping will be added with the double mapped allocator addition -#if defined(HOST_OSX) && defined(HOST_ARM64) && !defined(DACCESS_COMPILE) PAL_JitWriteProtect(false); -#endif } +#else + if (m_addressRX != m_addressRW) + { + ExecutableAllocator::Instance()->UnmapRW((void*)m_addressRW); + } +#endif } public: @@ -62,9 +251,11 @@ class ExecutableWriterHolder ExecutableWriterHolder(T* addressRX, size_t size) { m_addressRX = addressRX; +#if defined(HOST_OSX) && defined(HOST_ARM64) m_addressRW = addressRX; -#if defined(HOST_OSX) && defined(HOST_ARM64) && !defined(DACCESS_COMPILE) PAL_JitWriteProtect(true); +#else + m_addressRW = (T *)ExecutableAllocator::Instance()->MapRW((void*)addressRX, size); #endif } @@ -79,3 +270,5 @@ class ExecutableWriterHolder return m_addressRW; } }; + +#endif // !DACCESS_COMPILE diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index fb65ea9fa613..3c42f0850850 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -302,12 +302,12 @@ #endif // !FEATURE_EH_FUNCLETS #ifdef TARGET_X86 - JITHELPER(CORINFO_HELP_ASSIGN_REF_EAX, JIT_WriteBarrierEAX, CORINFO_HELP_SIG_NO_ALIGN_STUB) - JITHELPER(CORINFO_HELP_ASSIGN_REF_EBX, JIT_WriteBarrierEBX, CORINFO_HELP_SIG_NO_ALIGN_STUB) - JITHELPER(CORINFO_HELP_ASSIGN_REF_ECX, JIT_WriteBarrierECX, CORINFO_HELP_SIG_NO_ALIGN_STUB) - JITHELPER(CORINFO_HELP_ASSIGN_REF_ESI, JIT_WriteBarrierESI, CORINFO_HELP_SIG_NO_ALIGN_STUB) - JITHELPER(CORINFO_HELP_ASSIGN_REF_EDI, JIT_WriteBarrierEDI, CORINFO_HELP_SIG_NO_ALIGN_STUB) - JITHELPER(CORINFO_HELP_ASSIGN_REF_EBP, JIT_WriteBarrierEBP, CORINFO_HELP_SIG_NO_ALIGN_STUB) + DYNAMICJITHELPER(CORINFO_HELP_ASSIGN_REF_EAX, JIT_WriteBarrierEAX, CORINFO_HELP_SIG_NO_ALIGN_STUB) + DYNAMICJITHELPER(CORINFO_HELP_ASSIGN_REF_EBX, JIT_WriteBarrierEBX, CORINFO_HELP_SIG_NO_ALIGN_STUB) + DYNAMICJITHELPER(CORINFO_HELP_ASSIGN_REF_ECX, JIT_WriteBarrierECX, CORINFO_HELP_SIG_NO_ALIGN_STUB) + DYNAMICJITHELPER(CORINFO_HELP_ASSIGN_REF_ESI, JIT_WriteBarrierESI, CORINFO_HELP_SIG_NO_ALIGN_STUB) + DYNAMICJITHELPER(CORINFO_HELP_ASSIGN_REF_EDI, JIT_WriteBarrierEDI, CORINFO_HELP_SIG_NO_ALIGN_STUB) + DYNAMICJITHELPER(CORINFO_HELP_ASSIGN_REF_EBP, JIT_WriteBarrierEBP, CORINFO_HELP_SIG_NO_ALIGN_STUB) JITHELPER(CORINFO_HELP_CHECKED_ASSIGN_REF_EAX, JIT_CheckedWriteBarrierEAX, CORINFO_HELP_SIG_NO_ALIGN_STUB) JITHELPER(CORINFO_HELP_CHECKED_ASSIGN_REF_EBX, JIT_CheckedWriteBarrierEBX, CORINFO_HELP_SIG_NO_ALIGN_STUB) diff --git a/src/coreclr/inc/utilcode.h b/src/coreclr/inc/utilcode.h index a47034ee2e05..77df9dfa94d2 100644 --- a/src/coreclr/inc/utilcode.h +++ b/src/coreclr/inc/utilcode.h @@ -1014,35 +1014,6 @@ void SplitPath(__in SString const &path, #define CLRGetTickCount64() GetTickCount64() -// -// Use this function to initialize the s_CodeAllocHint -// during startup. base is runtime .dll base address, -// size is runtime .dll virtual size. -// -void InitCodeAllocHint(SIZE_T base, SIZE_T size, int randomPageOffset); - - -// -// Use this function to reset the s_CodeAllocHint -// after unloading an AppDomain -// -void ResetCodeAllocHint(); - -// -// Returns TRUE if p is located in near clr.dll that allows us -// to use rel32 IP-relative addressing modes. -// -BOOL IsPreferredExecutableRange(void * p); - -// -// Allocate free memory that will be used for executable code -// Handles the special requirements that we have on 64-bit platforms -// where we want the executable memory to be located near mscorwks -// -BYTE * ClrVirtualAllocExecutable(SIZE_T dwSize, - DWORD flAllocationType, - DWORD flProtect); - // // Allocate free memory within the range [pMinAddr..pMaxAddr] using // ClrVirtualQuery to find free memory and ClrVirtualAlloc to allocate it. diff --git a/src/coreclr/minipal/CMakeLists.txt b/src/coreclr/minipal/CMakeLists.txt new file mode 100644 index 000000000000..3096237d2a2f --- /dev/null +++ b/src/coreclr/minipal/CMakeLists.txt @@ -0,0 +1,7 @@ +include_directories(.) +if (CLR_CMAKE_HOST_UNIX) + add_subdirectory(Unix) +else (CLR_CMAKE_HOST_UNIX) + add_subdirectory(Windows) +endif (CLR_CMAKE_HOST_UNIX) + diff --git a/src/coreclr/minipal/Unix/CMakeLists.txt b/src/coreclr/minipal/Unix/CMakeLists.txt new file mode 100644 index 000000000000..b56b5017d375 --- /dev/null +++ b/src/coreclr/minipal/Unix/CMakeLists.txt @@ -0,0 +1,4 @@ +add_library(coreclrminipal + STATIC + doublemapping.cpp +) diff --git a/src/coreclr/minipal/Unix/doublemapping.cpp b/src/coreclr/minipal/Unix/doublemapping.cpp new file mode 100644 index 000000000000..a50b326861aa --- /dev/null +++ b/src/coreclr/minipal/Unix/doublemapping.cpp @@ -0,0 +1,211 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef TARGET_LINUX +#include +#include // __NR_memfd_create +#endif // TARGET_LINUX +#include "minipal.h" + +#if defined(TARGET_OSX) && defined(TARGET_AMD64) +#include +#endif // TARGET_OSX && TARGET_AMD64 + +#ifndef TARGET_OSX + +#ifdef TARGET_64BIT +static const off_t MaxDoubleMappedSize = 2048ULL*1024*1024*1024; +#else +static const off_t MaxDoubleMappedSize = UINT_MAX; +#endif + +#ifdef TARGET_LINUX +#define memfd_create(...) syscall(__NR_memfd_create, __VA_ARGS__) +#endif // TARGET_LINUX + +#endif // TARGET_OSX + +bool VMToOSInterface::CreateDoubleMemoryMapper(void** pHandle, size_t *pMaxExecutableCodeSize) +{ +#ifndef TARGET_OSX + +#ifdef TARGET_FREEBSD + int fd = shm_open(SHM_ANON, O_RDWR | O_CREAT, S_IRWXU); +#else // TARGET_FREEBSD + int fd = memfd_create("doublemapper", MFD_CLOEXEC); +#endif // TARGET_FREEBSD + + if (fd == -1) + { + return false; + } + + if (ftruncate(fd, MaxDoubleMappedSize) == -1) + { + close(fd); + return false; + } + + *pMaxExecutableCodeSize = MaxDoubleMappedSize; + *pHandle = (void*)(size_t)fd; +#else // !TARGET_OSX + *pMaxExecutableCodeSize = SIZE_MAX; + *pHandle = NULL; +#endif // !TARGET_OSX + + return true; +} + +void VMToOSInterface::DestroyDoubleMemoryMapper(void *mapperHandle) +{ +#ifndef TARGET_OSX + close((int)(size_t)mapperHandle); +#endif +} + +extern "C" void* PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange(const void* lpBeginAddress, const void* lpEndAddress, size_t dwSize); + +#ifdef TARGET_OSX +bool IsMapJitFlagNeeded() +{ + static volatile int isMapJitFlagNeeded = -1; + + if (isMapJitFlagNeeded == -1) + { + int mapJitFlagCheckResult = 0; + int pageSize = sysconf(_SC_PAGE_SIZE); + // Try to map a page with read-write-execute protection. It should fail on Mojave hardened runtime and higher. + void* testPage = mmap(NULL, pageSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (testPage == MAP_FAILED && (errno == EACCES)) + { + // The mapping has failed with EACCES, check if making the same mapping with MAP_JIT flag works + testPage = mmap(NULL, pageSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE | MAP_JIT, -1, 0); + if (testPage != MAP_FAILED) + { + mapJitFlagCheckResult = 1; + } + } + + if (testPage != MAP_FAILED) + { + munmap(testPage, pageSize); + } + + isMapJitFlagNeeded = mapJitFlagCheckResult; + } + + return (bool)isMapJitFlagNeeded; +} +#endif // TARGET_OSX + +void* VMToOSInterface::ReserveDoubleMappedMemory(void *mapperHandle, size_t offset, size_t size, const void *rangeStart, const void* rangeEnd) +{ + int fd = (int)(size_t)mapperHandle; + + if (rangeStart != NULL || rangeEnd != NULL) + { + void* result = PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange(rangeStart, rangeEnd, size); +#ifndef TARGET_OSX + if (result != NULL) + { + // Map the shared memory over the range reserved from the executable memory allocator. + result = mmap(result, size, PROT_NONE, MAP_SHARED | MAP_FIXED, fd, offset); + if (result == MAP_FAILED) + { + assert(false); + result = NULL; + } + } +#endif // TARGET_OSX + + return result; + } + +#ifndef TARGET_OSX + void* result = mmap(NULL, size, PROT_NONE, MAP_SHARED, fd, offset); +#else + int mmapFlags = MAP_ANON | MAP_PRIVATE; + if (IsMapJitFlagNeeded()) + { + mmapFlags |= MAP_JIT; + } + void* result = mmap(NULL, size, PROT_NONE, mmapFlags, -1, 0); +#endif + if (result == MAP_FAILED) + { + assert(false); + result = NULL; + } + return result; +} + +void *VMToOSInterface::CommitDoubleMappedMemory(void* pStart, size_t size, bool isExecutable) +{ + if (mprotect(pStart, size, isExecutable ? (PROT_READ | PROT_EXEC) : (PROT_READ | PROT_WRITE)) == -1) + { + return NULL; + } + + return pStart; +} + +bool VMToOSInterface::ReleaseDoubleMappedMemory(void *mapperHandle, void* pStart, size_t offset, size_t size) +{ +#ifndef TARGET_OSX + int fd = (int)(size_t)mapperHandle; + mmap(pStart, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, offset); + memset(pStart, 0, size); +#endif // TARGET_OSX + return munmap(pStart, size) != -1; +} + +void* VMToOSInterface::GetRWMapping(void *mapperHandle, void* pStart, size_t offset, size_t size) +{ +#ifndef TARGET_OSX + int fd = (int)(size_t)mapperHandle; + return mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset); +#else // TARGET_OSX +#ifdef TARGET_AMD64 + vm_address_t startRW; + vm_prot_t curProtection, maxProtection; + kern_return_t kr = vm_remap(mach_task_self(), &startRW, size, 0, VM_FLAGS_ANYWHERE | VM_FLAGS_RANDOM_ADDR, + mach_task_self(), (vm_address_t)pStart, FALSE, &curProtection, &maxProtection, VM_INHERIT_NONE); + + if (kr != KERN_SUCCESS) + { + return NULL; + } + + int st = mprotect((void*)startRW, size, PROT_READ | PROT_WRITE); + if (st == -1) + { + munmap((void*)startRW, size); + return NULL; + } + + return (void*)startRW; +#else // TARGET_AMD64 + // This method should not be called on OSX ARM64 + assert(false); + return NULL; +#endif // TARGET_AMD64 +#endif // TARGET_OSX +} + +bool VMToOSInterface::ReleaseRWMapping(void* pStart, size_t size) +{ + return munmap(pStart, size) != -1; +} diff --git a/src/coreclr/minipal/Windows/CMakeLists.txt b/src/coreclr/minipal/Windows/CMakeLists.txt new file mode 100644 index 000000000000..b56b5017d375 --- /dev/null +++ b/src/coreclr/minipal/Windows/CMakeLists.txt @@ -0,0 +1,4 @@ +add_library(coreclrminipal + STATIC + doublemapping.cpp +) diff --git a/src/coreclr/minipal/Windows/doublemapping.cpp b/src/coreclr/minipal/Windows/doublemapping.cpp new file mode 100644 index 000000000000..e265f1d139ad --- /dev/null +++ b/src/coreclr/minipal/Windows/doublemapping.cpp @@ -0,0 +1,205 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +#include +#include +#include +#include "minipal.h" + +#define HIDWORD(_qw) ((ULONG)((_qw) >> 32)) +#define LODWORD(_qw) ((ULONG)(_qw)) + +#ifdef TARGET_64BIT +static const uint64_t MaxDoubleMappedSize = 2048ULL*1024*1024*1024; +#else +static const uint64_t MaxDoubleMappedSize = UINT_MAX; +#endif + +#define VIRTUAL_ALLOC_RESERVE_GRANULARITY (64*1024) // 0x10000 (64 KB) +inline size_t ALIGN_UP( size_t val, size_t alignment ) +{ + // alignment must be a power of 2 for this implementation to work (need modulo otherwise) + assert( 0 == (alignment & (alignment - 1)) ); + size_t result = (val + (alignment - 1)) & ~(alignment - 1); + assert( result >= val ); // check for overflow + return result; +} + +template inline T ALIGN_UP(T val, size_t alignment) +{ + return (T)ALIGN_UP((size_t)val, alignment); +} + +inline void *GetTopMemoryAddress(void) +{ + static void *result; // = NULL; + if( NULL == result ) + { + SYSTEM_INFO sysInfo; + GetSystemInfo( &sysInfo ); + result = sysInfo.lpMaximumApplicationAddress; + } + return result; +} + +inline void *GetBotMemoryAddress(void) +{ + static void *result; // = NULL; + if( NULL == result ) + { + SYSTEM_INFO sysInfo; + GetSystemInfo( &sysInfo ); + result = sysInfo.lpMinimumApplicationAddress; + } + return result; +} + +#define TOP_MEMORY (GetTopMemoryAddress()) +#define BOT_MEMORY (GetBotMemoryAddress()) + +bool VMToOSInterface::CreateDoubleMemoryMapper(void **pHandle, size_t *pMaxExecutableCodeSize) +{ + *pMaxExecutableCodeSize = (size_t)MaxDoubleMappedSize; + *pHandle = CreateFileMapping( + INVALID_HANDLE_VALUE, // use paging file + NULL, // default security + PAGE_EXECUTE_READWRITE | SEC_RESERVE, // read/write/execute access + HIDWORD(MaxDoubleMappedSize), // maximum object size (high-order DWORD) + LODWORD(MaxDoubleMappedSize), // maximum object size (low-order DWORD) + NULL); + + return *pHandle != NULL; +} + +void VMToOSInterface::DestroyDoubleMemoryMapper(void *mapperHandle) +{ + CloseHandle((HANDLE)mapperHandle); +} + +void* VMToOSInterface::ReserveDoubleMappedMemory(void *mapperHandle, size_t offset, size_t size, const void *pMinAddr, const void* pMaxAddr) +{ + BYTE *pResult = nullptr; // our return value; + + if (size == 0) + { + return nullptr; + } + + // + // First lets normalize the pMinAddr and pMaxAddr values + // + // If pMinAddr is NULL then set it to BOT_MEMORY + if ((pMinAddr == 0) || (pMinAddr < (BYTE *) BOT_MEMORY)) + { + pMinAddr = (BYTE *) BOT_MEMORY; + } + + // If pMaxAddr is NULL then set it to TOP_MEMORY + if ((pMaxAddr == 0) || (pMaxAddr > (BYTE *) TOP_MEMORY)) + { + pMaxAddr = (BYTE *) TOP_MEMORY; + } + + // If pMaxAddr is not greater than pMinAddr we can not make an allocation + if (pMaxAddr <= pMinAddr) + { + return nullptr; + } + + // If pMinAddr is BOT_MEMORY and pMaxAddr is TOP_MEMORY + // then we can call ClrVirtualAlloc instead + if ((pMinAddr == (BYTE *) BOT_MEMORY) && (pMaxAddr == (BYTE *) TOP_MEMORY)) + { + return (BYTE*)MapViewOfFile((HANDLE)mapperHandle, + FILE_MAP_EXECUTE | FILE_MAP_READ | FILE_MAP_WRITE, + HIDWORD((int64_t)offset), + LODWORD((int64_t)offset), + size); + } + + // We will do one scan from [pMinAddr .. pMaxAddr] + // First align the tryAddr up to next 64k base address. + // See docs for VirtualAllocEx and lpAddress and 64k alignment for reasons. + // + BYTE * tryAddr = (BYTE *)ALIGN_UP((BYTE *)pMinAddr, VIRTUAL_ALLOC_RESERVE_GRANULARITY); + bool virtualQueryFailed = false; + bool faultInjected = false; + unsigned virtualQueryCount = 0; + + // Now scan memory and try to find a free block of the size requested. + while ((tryAddr + size) <= (BYTE *) pMaxAddr) + { + MEMORY_BASIC_INFORMATION mbInfo; + + // Use VirtualQuery to find out if this address is MEM_FREE + // + virtualQueryCount++; + if (!VirtualQuery((LPCVOID)tryAddr, &mbInfo, sizeof(mbInfo))) + { + // Exit and return nullptr if the VirtualQuery call fails. + virtualQueryFailed = true; + break; + } + + // Is there enough memory free from this start location? + // Note that for most versions of UNIX the mbInfo.RegionSize returned will always be 0 + if ((mbInfo.State == MEM_FREE) && + (mbInfo.RegionSize >= (SIZE_T) size || mbInfo.RegionSize == 0)) + { + // Try reserving the memory using VirtualAlloc now + pResult = (BYTE*)MapViewOfFileEx((HANDLE)mapperHandle, + FILE_MAP_EXECUTE | FILE_MAP_READ | FILE_MAP_WRITE, + HIDWORD((int64_t)offset), + LODWORD((int64_t)offset), + size, + tryAddr); + + // Normally this will be successful + // + if (pResult != nullptr) + { + // return pResult + break; + } + + // We might fail in a race. So just move on to next region and continue trying + tryAddr = tryAddr + VIRTUAL_ALLOC_RESERVE_GRANULARITY; + } + else + { + // Try another section of memory + tryAddr = max(tryAddr + VIRTUAL_ALLOC_RESERVE_GRANULARITY, + (BYTE*) mbInfo.BaseAddress + mbInfo.RegionSize); + } + } + + return pResult; +} + +void *VMToOSInterface::CommitDoubleMappedMemory(void* pStart, size_t size, bool isExecutable) +{ + return VirtualAlloc(pStart, size, MEM_COMMIT, isExecutable ? PAGE_EXECUTE_READ : PAGE_READWRITE); +} + +bool VMToOSInterface::ReleaseDoubleMappedMemory(void *mapperHandle, void* pStart, size_t offset, size_t size) +{ + // Zero the memory before the unmapping + VirtualAlloc(pStart, size, MEM_COMMIT, PAGE_READWRITE); + memset(pStart, 0, size); + return UnmapViewOfFile(pStart); +} + +void* VMToOSInterface::GetRWMapping(void *mapperHandle, void* pStart, size_t offset, size_t size) +{ + return (BYTE*)MapViewOfFile((HANDLE)mapperHandle, + FILE_MAP_READ | FILE_MAP_WRITE, + HIDWORD((int64_t)offset), + LODWORD((int64_t)offset), + size); +} + +bool VMToOSInterface::ReleaseRWMapping(void* pStart, size_t size) +{ + return UnmapViewOfFile(pStart); +} diff --git a/src/coreclr/minipal/minipal.h b/src/coreclr/minipal/minipal.h new file mode 100644 index 000000000000..39098f9bc129 --- /dev/null +++ b/src/coreclr/minipal/minipal.h @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// +#include + +// Interface between the runtime and platform specific functionality +class VMToOSInterface +{ +private: + ~VMToOSInterface() {} +public: + // Create double mapped memory mapper + // Parameters: + // pHandle - receives handle of the double mapped memory mapper + // pMaxExecutableCodeSize - receives the maximum executable memory size it can map + // Return: + // true if it succeeded, false if it failed + static bool CreateDoubleMemoryMapper(void **pHandle, size_t *pMaxExecutableCodeSize); + + // Destroy the double mapped memory mapper represented by the passed in handle + // Parameters: + // mapperHandle - handle of the double mapped memory mapper to destroy + static void DestroyDoubleMemoryMapper(void *mapperHandle); + + // Reserve a block of memory that can be double mapped. + // Parameters: + // mapperHandle - handle of the double mapped memory mapper to use + // offset - offset in the underlying shared memory + // size - size of the block to reserve + // rangeStart + // rangeEnd - Requests reserving virtual memory in the specified range. + // Setting both rangeStart and rangeEnd to 0 means that the + // requested range is not limited. + // When a specific range is requested, it is obligatory. + // Return: + // starting virtual address of the reserved memory or NULL if it failed + static void* ReserveDoubleMappedMemory(void *mapperHandle, size_t offset, size_t size, const void *rangeStart, const void* rangeEnd); + + // Commit a block of memory in the range previously reserved by the ReserveDoubleMappedMemory + // Parameters: + // pStart - start address of the virtual address range to commit + // size - size of the memory block to commit + // isExecutable - true means that the mapping should be RX, false means RW + // Return: + // Committed range start + static void* CommitDoubleMappedMemory(void* pStart, size_t size, bool isExecutable); + + // Release a block of virtual memory previously commited by the CommitDoubleMappedMemory + // Parameters: + // mapperHandle - handle of the double mapped memory mapper to use + // pStart - start address of the virtual address range to release. It must be one + // that was previously returned by the CommitDoubleMappedMemory + // offset - offset in the underlying shared memory + // size - size of the memory block to release + // Return: + // true if it succeeded, false if it failed + static bool ReleaseDoubleMappedMemory(void *mapperHandle, void* pStart, size_t offset, size_t size); + + // Get a RW mapping for the RX block specified by the arguments + // Parameters: + // mapperHandle - handle of the double mapped memory mapper to use + // pStart - start address of the RX virtual address range. + // offset - offset in the underlying shared memory + // size - size of the memory block to map as RW + // Return: + // Starting virtual address of the RW mapping. + static void* GetRWMapping(void *mapperHandle, void* pStart, size_t offset, size_t size); + + // Release RW mapping of the block specified by the arguments + // Parameters: + // pStart - Start address of the RW virtual address range. It must be an address + // previously returned by the GetRWMapping. + // size - Size of the memory block to release. It must be the size previously + // passed to the GetRWMapping that returned the pStart. + // Return: + // true if it succeeded, false if it failed + static bool ReleaseRWMapping(void* pStart, size_t size); +}; diff --git a/src/coreclr/utilcode/CMakeLists.txt b/src/coreclr/utilcode/CMakeLists.txt index 1ae433adbfd8..8c57742cb631 100644 --- a/src/coreclr/utilcode/CMakeLists.txt +++ b/src/coreclr/utilcode/CMakeLists.txt @@ -69,6 +69,7 @@ endif(CLR_CMAKE_TARGET_WIN32) set(UTILCODE_SOURCES ${UTILCODE_COMMON_SOURCES} + executableallocator.cpp ) set(UTILCODE_DAC_SOURCES diff --git a/src/coreclr/utilcode/executableallocator.cpp b/src/coreclr/utilcode/executableallocator.cpp new file mode 100644 index 000000000000..ac4c326c8378 --- /dev/null +++ b/src/coreclr/utilcode/executableallocator.cpp @@ -0,0 +1,755 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pedecoder.h" +#include "executableallocator.h" + +#if USE_UPPER_ADDRESS +// Preferred region to allocate the code in. +BYTE * ExecutableAllocator::g_codeMinAddr; +BYTE * ExecutableAllocator::g_codeMaxAddr; +BYTE * ExecutableAllocator::g_codeAllocStart; +// Next address to try to allocate for code in the preferred region. +BYTE * ExecutableAllocator::g_codeAllocHint; +#endif // USE_UPPER_ADDRESS + +bool ExecutableAllocator::g_isWXorXEnabled = false; + +ExecutableAllocator::FatalErrorHandler ExecutableAllocator::g_fatalErrorHandler = NULL; + +ExecutableAllocator* ExecutableAllocator::g_instance = NULL; + +bool ExecutableAllocator::IsDoubleMappingEnabled() +{ + LIMITED_METHOD_CONTRACT; + +#if defined(HOST_OSX) && defined(HOST_ARM64) + return false; +#else + return g_isWXorXEnabled; +#endif +} + +bool ExecutableAllocator::IsWXORXEnabled() +{ + LIMITED_METHOD_CONTRACT; + +#if defined(HOST_OSX) && defined(HOST_ARM64) + return true; +#else + return g_isWXorXEnabled; +#endif +} + +extern SYSTEM_INFO g_SystemInfo; + +size_t ExecutableAllocator::Granularity() +{ + LIMITED_METHOD_CONTRACT; + + return g_SystemInfo.dwAllocationGranularity; +} + +// Use this function to initialize the g_codeAllocHint +// during startup. base is runtime .dll base address, +// size is runtime .dll virtual size. +void ExecutableAllocator::InitCodeAllocHint(size_t base, size_t size, int randomPageOffset) +{ +#if USE_UPPER_ADDRESS + +#ifdef _DEBUG + // If GetForceRelocs is enabled we don't constrain the pMinAddr + if (PEDecoder::GetForceRelocs()) + return; +#endif + + // + // If we are using the UPPER_ADDRESS space (on Win64) + // then for any code heap that doesn't specify an address + // range using [pMinAddr..pMaxAddr] we place it in the + // upper address space + // This enables us to avoid having to use long JumpStubs + // to reach the code for our ngen-ed images. + // Which are also placed in the UPPER_ADDRESS space. + // + SIZE_T reach = 0x7FFF0000u; + + // We will choose the preferred code region based on the address of clr.dll. The JIT helpers + // in clr.dll are the most heavily called functions. + g_codeMinAddr = (base + size > reach) ? (BYTE *)(base + size - reach) : (BYTE *)0; + g_codeMaxAddr = (base + reach > base) ? (BYTE *)(base + reach) : (BYTE *)-1; + + BYTE * pStart; + + if (g_codeMinAddr <= (BYTE *)CODEHEAP_START_ADDRESS && + (BYTE *)CODEHEAP_START_ADDRESS < g_codeMaxAddr) + { + // clr.dll got loaded at its preferred base address? (OS without ASLR - pre-Vista) + // Use the code head start address that does not cause collisions with NGen images. + // This logic is coupled with scripts that we use to assign base addresses. + pStart = (BYTE *)CODEHEAP_START_ADDRESS; + } + else + if (base > UINT32_MAX) + { + // clr.dll got address assigned by ASLR? + // Try to occupy the space as far as possible to minimize collisions with other ASLR assigned + // addresses. Do not start at g_codeMinAddr exactly so that we can also reach common native images + // that can be placed at higher addresses than clr.dll. + pStart = g_codeMinAddr + (g_codeMaxAddr - g_codeMinAddr) / 8; + } + else + { + // clr.dll missed the base address? + // Try to occupy the space right after it. + pStart = (BYTE *)(base + size); + } + + // Randomize the address space + pStart += GetOsPageSize() * randomPageOffset; + + g_codeAllocStart = pStart; + g_codeAllocHint = pStart; +#endif +} + +// Use this function to reset the g_codeAllocHint +// after unloading an AppDomain +void ExecutableAllocator::ResetCodeAllocHint() +{ + LIMITED_METHOD_CONTRACT; +#if USE_UPPER_ADDRESS + g_codeAllocHint = g_codeAllocStart; +#endif +} + +// Returns TRUE if p is located in near clr.dll that allows us +// to use rel32 IP-relative addressing modes. +bool ExecutableAllocator::IsPreferredExecutableRange(void * p) +{ + LIMITED_METHOD_CONTRACT; +#if USE_UPPER_ADDRESS + if (g_codeMinAddr <= (BYTE *)p && (BYTE *)p < g_codeMaxAddr) + return true; +#endif + return false; +} + +ExecutableAllocator* ExecutableAllocator::Instance() +{ + LIMITED_METHOD_CONTRACT; + return g_instance; +} + +ExecutableAllocator::~ExecutableAllocator() +{ + if (IsDoubleMappingEnabled()) + { + VMToOSInterface::DestroyDoubleMemoryMapper(m_doubleMemoryMapperHandle); + } +} + +HRESULT ExecutableAllocator::StaticInitialize(FatalErrorHandler fatalErrorHandler) +{ + LIMITED_METHOD_CONTRACT; + + g_fatalErrorHandler = fatalErrorHandler; + g_isWXorXEnabled = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_EnableWriteXorExecute) != 0; + g_instance = new (nothrow) ExecutableAllocator(); + if (g_instance == NULL) + { + return E_OUTOFMEMORY; + } + + if (!g_instance->Initialize()) + { + return E_FAIL; + } + + return S_OK; +} + +bool ExecutableAllocator::Initialize() +{ + LIMITED_METHOD_CONTRACT; + + if (IsDoubleMappingEnabled()) + { + if (!VMToOSInterface::CreateDoubleMemoryMapper(&m_doubleMemoryMapperHandle, &m_maxExecutableCodeSize)) + { + return false; + } + + m_CriticalSection = ClrCreateCriticalSection(CrstExecutableAllocatorLock,CrstFlags(CRST_UNSAFE_ANYMODE | CRST_DEBUGGER_THREAD)); + } + + return true; +} + +//#define ENABLE_CACHED_MAPPINGS + +void ExecutableAllocator::UpdateCachedMapping(BlockRW* pBlock) +{ + LIMITED_METHOD_CONTRACT; +#ifdef ENABLE_CACHED_MAPPINGS + if (m_cachedMapping == NULL) + { + m_cachedMapping = pBlock; + pBlock->refCount++; + } + else if (m_cachedMapping != pBlock) + { + void* unmapAddress = NULL; + size_t unmapSize; + + if (!RemoveRWBlock(m_cachedMapping->baseRW, &unmapAddress, &unmapSize)) + { + g_fatalErrorHandler(COR_E_EXECUTIONENGINE, W("The RW block to unmap was not found")); + } + if (unmapAddress && !VMToOSInterface::ReleaseRWMapping(unmapAddress, unmapSize)) + { + g_fatalErrorHandler(COR_E_EXECUTIONENGINE, W("Releasing the RW mapping failed")); + } + m_cachedMapping = pBlock; + pBlock->refCount++; + } +#endif // ENABLE_CACHED_MAPPINGS +} + +void* ExecutableAllocator::FindRWBlock(void* baseRX, size_t size) +{ + LIMITED_METHOD_CONTRACT; + + for (BlockRW* pBlock = m_pFirstBlockRW; pBlock != NULL; pBlock = pBlock->next) + { + if (pBlock->baseRX <= baseRX && ((size_t)baseRX + size) <= ((size_t)pBlock->baseRX + pBlock->size)) + { + pBlock->refCount++; + UpdateCachedMapping(pBlock); + + return (BYTE*)pBlock->baseRW + ((size_t)baseRX - (size_t)pBlock->baseRX); + } + } + + return NULL; +} + +bool ExecutableAllocator::AddRWBlock(void* baseRW, void* baseRX, size_t size) +{ + LIMITED_METHOD_CONTRACT; + + for (BlockRW* pBlock = m_pFirstBlockRW; pBlock != NULL; pBlock = pBlock->next) + { + if (pBlock->baseRX <= baseRX && ((size_t)baseRX + size) <= ((size_t)pBlock->baseRX + pBlock->size)) + { + break; + } + } + + // The new "nothrow" below failure is handled as fail fast since it is not recoverable + PERMANENT_CONTRACT_VIOLATION(FaultViolation, ReasonContractInfrastructure); + + BlockRW* pBlockRW = new (nothrow) BlockRW(); + if (pBlockRW == NULL) + { + g_fatalErrorHandler(COR_E_EXECUTIONENGINE, W("The RW block metadata cannot be allocated")); + return false; + } + + pBlockRW->baseRW = baseRW; + pBlockRW->baseRX = baseRX; + pBlockRW->size = size; + pBlockRW->next = m_pFirstBlockRW; + pBlockRW->refCount = 1; + m_pFirstBlockRW = pBlockRW; + + UpdateCachedMapping(pBlockRW); + + return true; +} + +bool ExecutableAllocator::RemoveRWBlock(void* pRW, void** pUnmapAddress, size_t* pUnmapSize) +{ + LIMITED_METHOD_CONTRACT; + + BlockRW* pPrevBlockRW = NULL; + for (BlockRW* pBlockRW = m_pFirstBlockRW; pBlockRW != NULL; pBlockRW = pBlockRW->next) + { + if (pBlockRW->baseRW <= pRW && (size_t)pRW < ((size_t)pBlockRW->baseRW + pBlockRW->size)) + { + // found + pBlockRW->refCount--; + if (pBlockRW->refCount != 0) + { + *pUnmapAddress = NULL; + return true; + } + + if (pPrevBlockRW == NULL) + { + m_pFirstBlockRW = pBlockRW->next; + } + else + { + pPrevBlockRW->next = pBlockRW->next; + } + + *pUnmapAddress = pBlockRW->baseRW; + *pUnmapSize = pBlockRW->size; + + delete pBlockRW; + return true; + } + + pPrevBlockRW = pBlockRW; + } + + return false; +} + +bool ExecutableAllocator::AllocateOffset(size_t* pOffset, size_t size) +{ + LIMITED_METHOD_CONTRACT; + + size_t offset = m_freeOffset; + size_t newFreeOffset = offset + size; + + if (newFreeOffset > m_maxExecutableCodeSize) + { + return false; + } + + m_freeOffset = newFreeOffset; + + *pOffset = offset; + + return true; +} + +void ExecutableAllocator::AddRXBlock(BlockRX* pBlock) +{ + LIMITED_METHOD_CONTRACT; + + pBlock->next = m_pFirstBlockRX; + m_pFirstBlockRX = pBlock; +} + +void* ExecutableAllocator::Commit(void* pStart, size_t size, bool isExecutable) +{ + LIMITED_METHOD_CONTRACT; + + if (IsDoubleMappingEnabled()) + { + return VMToOSInterface::CommitDoubleMappedMemory(pStart, size, isExecutable); + } + else + { + return ClrVirtualAlloc(pStart, size, MEM_COMMIT, isExecutable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE); + } +} + +void ExecutableAllocator::Release(void* pRX) +{ + LIMITED_METHOD_CONTRACT; + + if (IsDoubleMappingEnabled()) + { + CRITSEC_Holder csh(m_CriticalSection); + + // Locate the RX block corresponding to the pRX and remove it from the linked list + BlockRX* pBlock; + BlockRX* pPrevBlock = NULL; + + for (pBlock = m_pFirstBlockRX; pBlock != NULL; pBlock = pBlock->next) + { + if (pRX == pBlock->baseRX) + { + if (pPrevBlock == NULL) + { + m_pFirstBlockRX = pBlock->next; + } + else + { + pPrevBlock->next = pBlock->next; + } + + break; + } + pPrevBlock = pBlock; + } + + if (pBlock != NULL) + { + VMToOSInterface::ReleaseDoubleMappedMemory(m_doubleMemoryMapperHandle, pRX, pBlock->offset, pBlock->size); + // Put the released block into the free block list + pBlock->baseRX = NULL; + pBlock->next = m_pFirstFreeBlockRX; + m_pFirstFreeBlockRX = pBlock; + } + else + { + // The block was not found, which should never happen. + g_fatalErrorHandler(COR_E_EXECUTIONENGINE, W("The RX block to release was not found")); + } + } + else + { + ClrVirtualFree(pRX, 0, MEM_RELEASE); + } +} + +// Find a free block with the closest size >= the requested size. +// Returns NULL if no such block exists. +ExecutableAllocator::BlockRX* ExecutableAllocator::FindBestFreeBlock(size_t size) +{ + LIMITED_METHOD_CONTRACT; + + BlockRX* pPrevBlock = NULL; + BlockRX* pPrevBestBlock = NULL; + BlockRX* pBestBlock = NULL; + BlockRX* pBlock = m_pFirstFreeBlockRX; + + while (pBlock != NULL) + { + if (pBlock->size >= size) + { + if (pBestBlock != NULL) + { + if (pBlock->size < pBestBlock->size) + { + pPrevBestBlock = pPrevBlock; + pBestBlock = pBlock; + } + } + else + { + pPrevBestBlock = pPrevBlock; + pBestBlock = pBlock; + } + } + pPrevBlock = pBlock; + pBlock = pBlock->next; + } + + if (pBestBlock != NULL) + { + if (pPrevBestBlock != NULL) + { + pPrevBestBlock->next = pBestBlock->next; + } + else + { + m_pFirstFreeBlockRX = pBestBlock->next; + } + + pBestBlock->next = NULL; + } + + return pBestBlock; +} + +// Allocate a new block of executable memory and the related descriptor structure. +// First try to get it from the free blocks and if there is no suitable free block, +// allocate a new one. +ExecutableAllocator::BlockRX* ExecutableAllocator::AllocateBlock(size_t size, bool* pIsFreeBlock) +{ + LIMITED_METHOD_CONTRACT; + + size_t offset; + BlockRX* block = FindBestFreeBlock(size); + *pIsFreeBlock = (block != NULL); + + if (block == NULL) + { + if (!AllocateOffset(&offset, size)) + { + return NULL; + } + + block = new (nothrow) BlockRX(); + if (block == NULL) + { + return NULL; + } + + block->offset = offset; + block->size = size; + } + + return block; +} + +// Backout a previously allocated block. The block is added to the free blocks list and +// reused for later allocation requests. +void ExecutableAllocator::BackoutBlock(BlockRX* pBlock, bool isFreeBlock) +{ + LIMITED_METHOD_CONTRACT; + + if (!isFreeBlock) + { + m_freeOffset -= pBlock->size; + delete pBlock; + } + else + { + pBlock->next = m_pFirstFreeBlockRX; + m_pFirstFreeBlockRX = pBlock; + } +} + +// Reserve executable memory within the specified virtual address space range. If it is not possible to +// reserve memory in that range, the method returns NULL and nothing is allocated. +void* ExecutableAllocator::ReserveWithinRange(size_t size, const void* loAddress, const void* hiAddress) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE((size & (Granularity() - 1)) == 0); + if (IsDoubleMappingEnabled()) + { + CRITSEC_Holder csh(m_CriticalSection); + + bool isFreeBlock; + BlockRX* block = AllocateBlock(size, &isFreeBlock); + if (block == NULL) + { + return NULL; + } + + void *result = VMToOSInterface::ReserveDoubleMappedMemory(m_doubleMemoryMapperHandle, block->offset, size, loAddress, hiAddress); + + if (result != NULL) + { + block->baseRX = result; + AddRXBlock(block); + } + else + { + BackoutBlock(block, isFreeBlock); + } + + return result; + } + else + { + DWORD allocationType = MEM_RESERVE; +#ifdef HOST_UNIX + // Tell PAL to use the executable memory allocator to satisfy this request for virtual memory. + // This will allow us to place JIT'ed code close to the coreclr library + // and thus improve performance by avoiding jump stubs in managed code. + allocationType |= MEM_RESERVE_EXECUTABLE; +#endif + return ClrVirtualAllocWithinRange((const BYTE*)loAddress, (const BYTE*)hiAddress, size, allocationType, PAGE_NOACCESS); + } +} + +// Reserve executable memory. On Windows it tries to use the allocation hints to +// allocate memory close to the previously allocated executable memory and loaded +// executable files. +void* ExecutableAllocator::Reserve(size_t size) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE((size & (Granularity() - 1)) == 0); + + BYTE *result = NULL; + +#if USE_UPPER_ADDRESS + // + // If we are using the UPPER_ADDRESS space (on Win64) + // then for any heap that will contain executable code + // we will place it in the upper address space + // + // This enables us to avoid having to use JumpStubs + // to reach the code for our ngen-ed images on x64, + // since they are also placed in the UPPER_ADDRESS space. + // + BYTE * pHint = g_codeAllocHint; + + if (size <= (SIZE_T)(g_codeMaxAddr - g_codeMinAddr) && pHint != NULL) + { + // Try to allocate in the preferred region after the hint + result = (BYTE*)ReserveWithinRange(size, pHint, g_codeMaxAddr); + if (result != NULL) + { + g_codeAllocHint = result + size; + } + else + { + // Try to allocate in the preferred region before the hint + result = (BYTE*)ReserveWithinRange(size, g_codeMinAddr, pHint + size); + + if (result != NULL) + { + g_codeAllocHint = result + size; + } + + g_codeAllocHint = NULL; + } + } + + // Fall through to +#endif // USE_UPPER_ADDRESS + + if (result == NULL) + { + if (IsDoubleMappingEnabled()) + { + CRITSEC_Holder csh(m_CriticalSection); + + bool isFreeBlock; + BlockRX* block = AllocateBlock(size, &isFreeBlock); + if (block == NULL) + { + return NULL; + } + + result = (BYTE*)VMToOSInterface::ReserveDoubleMappedMemory(m_doubleMemoryMapperHandle, block->offset, size, 0, 0); + + if (result != NULL) + { + block->baseRX = result; + AddRXBlock(block); + } + else + { + BackoutBlock(block, isFreeBlock); + } + } + else + { + DWORD allocationType = MEM_RESERVE; +#ifdef HOST_UNIX + // Tell PAL to use the executable memory allocator to satisfy this request for virtual memory. + // This will allow us to place JIT'ed code close to the coreclr library + // and thus improve performance by avoiding jump stubs in managed code. + allocationType |= MEM_RESERVE_EXECUTABLE; +#endif + result = (BYTE*)ClrVirtualAlloc(NULL, size, allocationType, PAGE_NOACCESS); + } + } + + return result; +} + +// Reserve a block of executable memory at the specified virtual address. If it is not +// possible, the method returns NULL. +void* ExecutableAllocator::ReserveAt(void* baseAddressRX, size_t size) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE((size & (Granularity() - 1)) == 0); + + if (IsDoubleMappingEnabled()) + { + CRITSEC_Holder csh(m_CriticalSection); + + bool isFreeBlock; + BlockRX* block = AllocateBlock(size, &isFreeBlock); + if (block == NULL) + { + return NULL; + } + + void* result = VMToOSInterface::ReserveDoubleMappedMemory(m_doubleMemoryMapperHandle, block->offset, size, baseAddressRX, baseAddressRX); + + if (result != NULL) + { + block->baseRX = result; + AddRXBlock(block); + } + else + { + BackoutBlock(block, isFreeBlock); + } + + return result; + } + else + { + return VirtualAlloc(baseAddressRX, size, MEM_RESERVE, PAGE_NOACCESS); + } +} + +// Map an executable memory block as writeable. If there is already a mapping +// covering the specified block, return that mapping instead of creating a new one. +// Return starting address of the writeable mapping. +void* ExecutableAllocator::MapRW(void* pRX, size_t size) +{ + LIMITED_METHOD_CONTRACT; + + if (!IsDoubleMappingEnabled()) + { + return pRX; + } + + CRITSEC_Holder csh(m_CriticalSection); + + void* result = FindRWBlock(pRX, size); + if (result != NULL) + { + return result; + } + + for (BlockRX* pBlock = m_pFirstBlockRX; pBlock != NULL; pBlock = pBlock->next) + { + if (pRX >= pBlock->baseRX && ((size_t)pRX + size) <= ((size_t)pBlock->baseRX + pBlock->size)) + { + // Offset of the RX address in the originally allocated block + size_t offset = (size_t)pRX - (size_t)pBlock->baseRX; + // Offset of the RX address that will start the newly mapped block + size_t mapOffset = ALIGN_DOWN(offset, Granularity()); + // Size of the block we will map + size_t mapSize = ALIGN_UP(offset - mapOffset + size, Granularity()); + void* pRW = VMToOSInterface::GetRWMapping(m_doubleMemoryMapperHandle, (BYTE*)pBlock->baseRX + mapOffset, pBlock->offset + mapOffset, mapSize); + + if (pRW == NULL) + { + g_fatalErrorHandler(COR_E_EXECUTIONENGINE, W("Failed to create RW mapping for RX memory")); + } + + AddRWBlock(pRW, (BYTE*)pBlock->baseRX + mapOffset, mapSize); + + return (void*)((size_t)pRW + (offset - mapOffset)); + } + else if (pRX >= pBlock->baseRX && pRX < (void*)((size_t)pBlock->baseRX + pBlock->size)) + { + g_fatalErrorHandler(COR_E_EXECUTIONENGINE, W("Attempting to RW map a block that crosses the end of the allocated RX range")); + } + else if (pRX < pBlock->baseRX && (void*)((size_t)pRX + size) > pBlock->baseRX) + { + g_fatalErrorHandler(COR_E_EXECUTIONENGINE, W("Attempting to map a block that crosses the beginning of the allocated range")); + } + } + + // The executable memory block was not found, so we cannot provide the writeable mapping. + g_fatalErrorHandler(COR_E_EXECUTIONENGINE, W("The RX block to map as RW was not found")); + return NULL; +} + +// Unmap writeable mapping at the specified address. The address must be an address +// returned by the MapRW method. +void ExecutableAllocator::UnmapRW(void* pRW) +{ + LIMITED_METHOD_CONTRACT; + + if (!IsDoubleMappingEnabled()) + { + return; + } + + CRITSEC_Holder csh(m_CriticalSection); + _ASSERTE(pRW != NULL); + + void* unmapAddress = NULL; + size_t unmapSize; + + if (!RemoveRWBlock(pRW, &unmapAddress, &unmapSize)) + { + g_fatalErrorHandler(COR_E_EXECUTIONENGINE, W("The RW block to unmap was not found")); + } + + if (unmapAddress && !VMToOSInterface::ReleaseRWMapping(unmapAddress, unmapSize)) + { + g_fatalErrorHandler(COR_E_EXECUTIONENGINE, W("Releasing the RW mapping failed")); + } +} diff --git a/src/coreclr/utilcode/loaderheap.cpp b/src/coreclr/utilcode/loaderheap.cpp index adaf07d8f582..b3b381b2f9be 100644 --- a/src/coreclr/utilcode/loaderheap.cpp +++ b/src/coreclr/utilcode/loaderheap.cpp @@ -695,15 +695,21 @@ size_t AllocMem_TotalSize(size_t dwRequestedSize, UnlockedLoaderHeap *pHeap); struct LoaderHeapFreeBlock { public: - LoaderHeapFreeBlock *m_pNext; // Pointer to next block on free list - size_t m_dwSize; // Total size of this block (including this header) -//! Try not to grow the size of this structure. It places a minimum size on LoaderHeap allocations. + LoaderHeapFreeBlock *m_pNext; // Pointer to next block on free list + size_t m_dwSize; // Total size of this block + void *m_pBlockAddress; // Virtual address of the block +#ifndef DACCESS_COMPILE static void InsertFreeBlock(LoaderHeapFreeBlock **ppHead, void *pMem, size_t dwTotalSize, UnlockedLoaderHeap *pHeap) { STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_GC_NOTRIGGER; + // The new "nothrow" below failure is handled in a non-fault way, so + // make sure that callers with FORBID_FAULT can call this method without + // firing the contract violation assert. + PERMANENT_CONTRACT_VIOLATION(FaultViolation, ReasonContractInfrastructure); + LOADER_HEAP_BEGIN_TRAP_FAULT // It's illegal to insert a free block that's smaller than the minimum sized allocation - @@ -722,19 +728,30 @@ struct LoaderHeapFreeBlock } #endif - INDEBUG(memset(pMem, 0xcc, dwTotalSize);) - LoaderHeapFreeBlock *pNewBlock = (LoaderHeapFreeBlock*)pMem; - pNewBlock->m_pNext = *ppHead; - pNewBlock->m_dwSize = dwTotalSize; - *ppHead = pNewBlock; + void* pMemRW = pMem; + ExecutableWriterHolder memWriterHolder; + if (pHeap->IsExecutable()) + { + memWriterHolder = ExecutableWriterHolder(pMem, dwTotalSize); + pMemRW = memWriterHolder.GetRW(); + } - MergeBlock(pNewBlock, pHeap); + INDEBUG(memset(pMemRW, 0xcc, dwTotalSize);) + LoaderHeapFreeBlock *pNewBlock = new (nothrow) LoaderHeapFreeBlock; + // If we fail allocating the LoaderHeapFreeBlock, ignore the failure and don't insert the free block at all. + if (pNewBlock != NULL) + { + pNewBlock->m_pNext = *ppHead; + pNewBlock->m_dwSize = dwTotalSize; + pNewBlock->m_pBlockAddress = pMem; + *ppHead = pNewBlock; + MergeBlock(pNewBlock, pHeap); + } LOADER_HEAP_END_TRAP_FAULT } - - static void *AllocFromFreeList(LoaderHeapFreeBlock **ppHead, size_t dwSize, BOOL fRemoveFromFreeList, UnlockedLoaderHeap *pHeap) + static void *AllocFromFreeList(LoaderHeapFreeBlock **ppHead, size_t dwSize, UnlockedLoaderHeap *pHeap) { STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_GC_NOTRIGGER; @@ -751,23 +768,19 @@ struct LoaderHeapFreeBlock size_t dwCurSize = pCur->m_dwSize; if (dwCurSize == dwSize) { - pResult = pCur; + pResult = pCur->m_pBlockAddress; // Exact match. Hooray! - if (fRemoveFromFreeList) - { - *ppWalk = pCur->m_pNext; - } + *ppWalk = pCur->m_pNext; + delete pCur; break; } else if (dwCurSize > dwSize && (dwCurSize - dwSize) >= AllocMem_TotalSize(1, pHeap)) { // Partial match. Ok... - pResult = pCur; - if (fRemoveFromFreeList) - { - *ppWalk = pCur->m_pNext; - InsertFreeBlock(ppWalk, ((BYTE*)pCur) + dwSize, dwCurSize - dwSize, pHeap ); - } + pResult = pCur->m_pBlockAddress; + *ppWalk = pCur->m_pNext; + InsertFreeBlock(ppWalk, ((BYTE*)pCur->m_pBlockAddress) + dwSize, dwCurSize - dwSize, pHeap ); + delete pCur; break; } @@ -777,19 +790,22 @@ struct LoaderHeapFreeBlock ppWalk = &( pCur->m_pNext ); } - if (pResult && fRemoveFromFreeList) + if (pResult) { + void *pResultRW = pResult; + ExecutableWriterHolder resultWriterHolder; + if (pHeap->IsExecutable()) + { + resultWriterHolder = ExecutableWriterHolder(pResult, dwSize); + pResultRW = resultWriterHolder.GetRW(); + } // Callers of loaderheap assume allocated memory is zero-inited so we must preserve this invariant! - memset(pResult, 0, dwSize); + memset(pResultRW, 0, dwSize); } LOADER_HEAP_END_TRAP_FAULT return pResult; - - - } - private: // Try to merge pFreeBlock with its immediate successor. Return TRUE if a merge happened. FALSE if no merge happened. static BOOL MergeBlock(LoaderHeapFreeBlock *pFreeBlock, UnlockedLoaderHeap *pHeap) @@ -803,7 +819,7 @@ struct LoaderHeapFreeBlock LoaderHeapFreeBlock *pNextBlock = pFreeBlock->m_pNext; size_t dwSize = pFreeBlock->m_dwSize; - if (pNextBlock == NULL || ((BYTE*)pNextBlock) != (((BYTE*)pFreeBlock) + dwSize)) + if (pNextBlock == NULL || ((BYTE*)pNextBlock->m_pBlockAddress) != (((BYTE*)pFreeBlock->m_pBlockAddress) + dwSize)) { result = FALSE; } @@ -811,9 +827,17 @@ struct LoaderHeapFreeBlock { size_t dwCombinedSize = dwSize + pNextBlock->m_dwSize; LoaderHeapFreeBlock *pNextNextBlock = pNextBlock->m_pNext; - INDEBUG(memset(pFreeBlock, 0xcc, dwCombinedSize);) + void *pMemRW = pFreeBlock->m_pBlockAddress; + ExecutableWriterHolder memWriterHolder; + if (pHeap->IsExecutable()) + { + memWriterHolder = ExecutableWriterHolder(pFreeBlock->m_pBlockAddress, dwCombinedSize); + pMemRW = memWriterHolder.GetRW(); + } + INDEBUG(memset(pMemRW, 0xcc, dwCombinedSize);) pFreeBlock->m_pNext = pNextNextBlock; pFreeBlock->m_dwSize = dwCombinedSize; + delete pNextBlock; result = TRUE; } @@ -822,7 +846,7 @@ struct LoaderHeapFreeBlock return result; } - +#endif // DACCESS_COMPILE }; @@ -840,8 +864,7 @@ struct LoaderHeapFreeBlock // - z bytes of pad (DEBUG-ONLY) (where "z" is just enough to pointer-align the following byte) // - a bytes of tag (DEBUG-ONLY) (where "a" is sizeof(LoaderHeapValidationTag) // -// - b bytes of pad (if total size after all this < sizeof(LoaderHeapFreeBlock), pad enough to make it the size of LoaderHeapFreeBlock) -// - c bytes of pad (where "c" is just enough to pointer-align the following byte) +// - b bytes of pad (where "b" is just enough to pointer-align the following byte) // // ==> Following address is always pointer-aligned //===================================================================================== @@ -862,10 +885,6 @@ inline size_t AllocMem_TotalSize(size_t dwRequestedSize, UnlockedLoaderHeap *pHe #ifdef _DEBUG dwSize += sizeof(LoaderHeapValidationTag); #endif - if (dwSize < sizeof(LoaderHeapFreeBlock)) - { - dwSize = sizeof(LoaderHeapFreeBlock); - } } dwSize = ((dwSize + ALLOC_ALIGN_CONSTANT) & (~ALLOC_ALIGN_CONSTANT)); @@ -977,9 +996,7 @@ UnlockedLoaderHeap::~UnlockedLoaderHeap() if (fReleaseMemory) { - BOOL fSuccess; - fSuccess = ClrVirtualFree(pVirtualAddress, 0, MEM_RELEASE); - _ASSERTE(fSuccess); + ExecutableAllocator::Instance()->Release(pVirtualAddress); } delete pSearch; @@ -987,9 +1004,7 @@ UnlockedLoaderHeap::~UnlockedLoaderHeap() if (m_reservedBlock.m_fReleaseMemory) { - BOOL fSuccess; - fSuccess = ClrVirtualFree(m_reservedBlock.pVirtualAddress, 0, MEM_RELEASE); - _ASSERTE(fSuccess); + ExecutableAllocator::Instance()->Release(m_reservedBlock.pVirtualAddress); } INDEBUG(s_dwNumInstancesOfLoaderHeaps --;) @@ -1058,7 +1073,7 @@ void ReleaseReservedMemory(BYTE* value) { if (value) { - ClrVirtualFree(value, 0, MEM_RELEASE); + ExecutableAllocator::Instance()->Release(value); } } @@ -1114,7 +1129,9 @@ BOOL UnlockedLoaderHeap::UnlockedReservePages(size_t dwSizeToCommit) // Reserve pages // - pData = ClrVirtualAllocExecutable(dwSizeToReserve, MEM_RESERVE, PAGE_NOACCESS); + // Reserve the memory for even non-executable stuff close to the executable code, as it has profound effect + // on e.g. a static variable access performance. + pData = (BYTE *)ExecutableAllocator::Instance()->Reserve(dwSizeToReserve); if (pData == NULL) { return FALSE; @@ -1140,7 +1157,7 @@ BOOL UnlockedLoaderHeap::UnlockedReservePages(size_t dwSizeToCommit) } // Commit first set of pages, since it will contain the LoaderHeapBlock - void *pTemp = ClrVirtualAlloc(pData, dwSizeToCommit, MEM_COMMIT, (m_Options & LHF_EXECUTABLE) ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE); + void *pTemp = ExecutableAllocator::Instance()->Commit(pData, dwSizeToCommit, (m_Options & LHF_EXECUTABLE)); if (pTemp == NULL) { //_ASSERTE(!"Unable to ClrVirtualAlloc commit in a loaderheap"); @@ -1213,7 +1230,7 @@ BOOL UnlockedLoaderHeap::GetMoreCommittedPages(size_t dwMinSize) dwSizeToCommit = ALIGN_UP(dwSizeToCommit, GetOsPageSize()); // Yes, so commit the desired number of reserved pages - void *pData = ClrVirtualAlloc(m_pPtrToEndOfCommittedRegion, dwSizeToCommit, MEM_COMMIT, (m_Options & LHF_EXECUTABLE) ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE); + void *pData = ExecutableAllocator::Instance()->Commit(m_pPtrToEndOfCommittedRegion, dwSizeToCommit, (m_Options & LHF_EXECUTABLE)); if (pData == NULL) return FALSE; @@ -1316,7 +1333,7 @@ void *UnlockedLoaderHeap::UnlockedAllocMem_NoThrow(size_t dwSize { // Any memory available on the free list? - void *pData = LoaderHeapFreeBlock::AllocFromFreeList(&m_pFirstFreeBlock, dwSize, TRUE /*fRemoveFromFreeList*/, this); + void *pData = LoaderHeapFreeBlock::AllocFromFreeList(&m_pFirstFreeBlock, dwSize, this); if (!pData) { // Enough bytes available in committed region? @@ -1518,8 +1535,6 @@ void UnlockedLoaderHeap::UnlockedBackoutMem(void *pMem, if (m_pAllocPtr == ( ((BYTE*)pMem) + dwSize )) { - // Cool. This was the last block allocated. We can just undo the allocation instead - // of going to the freelist. void *pMemRW = pMem; ExecutableWriterHolder memWriterHolder; if (m_Options & LHF_EXECUTABLE) @@ -1527,6 +1542,9 @@ void UnlockedLoaderHeap::UnlockedBackoutMem(void *pMem, memWriterHolder = ExecutableWriterHolder(pMem, dwSize); pMemRW = memWriterHolder.GetRW(); } + + // Cool. This was the last block allocated. We can just undo the allocation instead + // of going to the freelist. memset(pMemRW, 0x00, dwSize); // Fill freed region with 0 m_pAllocPtr = (BYTE*)pMem; } @@ -1534,7 +1552,6 @@ void UnlockedLoaderHeap::UnlockedBackoutMem(void *pMem, { LoaderHeapFreeBlock::InsertFreeBlock(&m_pFirstFreeBlock, pMem, dwSize, this); } - } diff --git a/src/coreclr/utilcode/util.cpp b/src/coreclr/utilcode/util.cpp index 0026d1f619f1..e7b1755b2b1c 100644 --- a/src/coreclr/utilcode/util.cpp +++ b/src/coreclr/utilcode/util.cpp @@ -352,168 +352,6 @@ HRESULT FakeCoCreateInstanceEx(REFCLSID rclsid, return hr; } -#if USE_UPPER_ADDRESS -static BYTE * s_CodeMinAddr; // Preferred region to allocate the code in. -static BYTE * s_CodeMaxAddr; -static BYTE * s_CodeAllocStart; -static BYTE * s_CodeAllocHint; // Next address to try to allocate for code in the preferred region. -#endif - -// -// Use this function to initialize the s_CodeAllocHint -// during startup. base is runtime .dll base address, -// size is runtime .dll virtual size. -// -void InitCodeAllocHint(SIZE_T base, SIZE_T size, int randomPageOffset) -{ -#if USE_UPPER_ADDRESS - -#ifdef _DEBUG - // If GetForceRelocs is enabled we don't constrain the pMinAddr - if (PEDecoder::GetForceRelocs()) - return; -#endif - -// - // If we are using the UPPER_ADDRESS space (on Win64) - // then for any code heap that doesn't specify an address - // range using [pMinAddr..pMaxAddr] we place it in the - // upper address space - // This enables us to avoid having to use long JumpStubs - // to reach the code for our ngen-ed images. - // Which are also placed in the UPPER_ADDRESS space. - // - SIZE_T reach = 0x7FFF0000u; - - // We will choose the preferred code region based on the address of clr.dll. The JIT helpers - // in clr.dll are the most heavily called functions. - s_CodeMinAddr = (base + size > reach) ? (BYTE *)(base + size - reach) : (BYTE *)0; - s_CodeMaxAddr = (base + reach > base) ? (BYTE *)(base + reach) : (BYTE *)-1; - - BYTE * pStart; - - if (s_CodeMinAddr <= (BYTE *)CODEHEAP_START_ADDRESS && - (BYTE *)CODEHEAP_START_ADDRESS < s_CodeMaxAddr) - { - // clr.dll got loaded at its preferred base address? (OS without ASLR - pre-Vista) - // Use the code head start address that does not cause collisions with NGen images. - // This logic is coupled with scripts that we use to assign base addresses. - pStart = (BYTE *)CODEHEAP_START_ADDRESS; - } - else - if (base > UINT32_MAX) - { - // clr.dll got address assigned by ASLR? - // Try to occupy the space as far as possible to minimize collisions with other ASLR assigned - // addresses. Do not start at s_CodeMinAddr exactly so that we can also reach common native images - // that can be placed at higher addresses than clr.dll. - pStart = s_CodeMinAddr + (s_CodeMaxAddr - s_CodeMinAddr) / 8; - } - else - { - // clr.dll missed the base address? - // Try to occupy the space right after it. - pStart = (BYTE *)(base + size); - } - - // Randomize the address space - pStart += GetOsPageSize() * randomPageOffset; - - s_CodeAllocStart = pStart; - s_CodeAllocHint = pStart; -#endif -} - -// -// Use this function to reset the s_CodeAllocHint -// after unloading an AppDomain -// -void ResetCodeAllocHint() -{ - LIMITED_METHOD_CONTRACT; -#if USE_UPPER_ADDRESS - s_CodeAllocHint = s_CodeAllocStart; -#endif -} - -// -// Returns TRUE if p is located in near clr.dll that allows us -// to use rel32 IP-relative addressing modes. -// -BOOL IsPreferredExecutableRange(void * p) -{ - LIMITED_METHOD_CONTRACT; -#if USE_UPPER_ADDRESS - if (s_CodeMinAddr <= (BYTE *)p && (BYTE *)p < s_CodeMaxAddr) - return TRUE; -#endif - return FALSE; -} - -// -// Allocate free memory that will be used for executable code -// Handles the special requirements that we have on 64-bit platforms -// where we want the executable memory to be located near clr.dll -// -BYTE * ClrVirtualAllocExecutable(SIZE_T dwSize, - DWORD flAllocationType, - DWORD flProtect) -{ - CONTRACTL - { - NOTHROW; - } - CONTRACTL_END; - -#if USE_UPPER_ADDRESS - // - // If we are using the UPPER_ADDRESS space (on Win64) - // then for any heap that will contain executable code - // we will place it in the upper address space - // - // This enables us to avoid having to use JumpStubs - // to reach the code for our ngen-ed images on x64, - // since they are also placed in the UPPER_ADDRESS space. - // - BYTE * pHint = s_CodeAllocHint; - - if (dwSize <= (SIZE_T)(s_CodeMaxAddr - s_CodeMinAddr) && pHint != NULL) - { - // Try to allocate in the preferred region after the hint - BYTE * pResult = ClrVirtualAllocWithinRange(pHint, s_CodeMaxAddr, dwSize, flAllocationType, flProtect); - - if (pResult != NULL) - { - s_CodeAllocHint = pResult + dwSize; - return pResult; - } - - // Try to allocate in the preferred region before the hint - pResult = ClrVirtualAllocWithinRange(s_CodeMinAddr, pHint + dwSize, dwSize, flAllocationType, flProtect); - - if (pResult != NULL) - { - s_CodeAllocHint = pResult + dwSize; - return pResult; - } - - s_CodeAllocHint = NULL; - } - - // Fall through to -#endif // USE_UPPER_ADDRESS - -#ifdef HOST_UNIX - // Tell PAL to use the executable memory allocator to satisfy this request for virtual memory. - // This will allow us to place JIT'ed code close to the coreclr library - // and thus improve performance by avoiding jump stubs in managed code. - flAllocationType |= MEM_RESERVE_EXECUTABLE; -#endif // HOST_UNIX - - return (BYTE *) ClrVirtualAlloc (NULL, dwSize, flAllocationType, flProtect); - -} - // // Allocate free memory with specific alignment. // diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 1d682d2a428b..9c2cb3df0b7e 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -833,7 +833,6 @@ elseif(CLR_CMAKE_TARGET_ARCH_ARM) set(VM_SOURCES_DAC_AND_WKS_ARCH ${ARCH_SOURCES_DIR}/exceparm.cpp ${ARCH_SOURCES_DIR}/stubs.cpp - ${ARCH_SOURCES_DIR}/armsinglestepper.cpp ) set(VM_HEADERS_DAC_AND_WKS_ARCH @@ -844,6 +843,7 @@ elseif(CLR_CMAKE_TARGET_ARCH_ARM) set(VM_SOURCES_WKS_ARCH ${ARCH_SOURCES_DIR}/profiler.cpp + ${ARCH_SOURCES_DIR}/armsinglestepper.cpp exceptionhandling.cpp gcinfodecoder.cpp ) @@ -868,7 +868,7 @@ elseif(CLR_CMAKE_TARGET_ARCH_ARM64) ) if(CLR_CMAKE_HOST_UNIX) - list(APPEND VM_SOURCES_DAC_AND_WKS_ARCH + list(APPEND VM_SOURCES_WKS_ARCH ${ARCH_SOURCES_DIR}/arm64singlestepper.cpp ) endif(CLR_CMAKE_HOST_UNIX) diff --git a/src/coreclr/vm/amd64/JitHelpers_Fast.asm b/src/coreclr/vm/amd64/JitHelpers_Fast.asm index 82a301bb0cbd..219597eb350c 100644 --- a/src/coreclr/vm/amd64/JitHelpers_Fast.asm +++ b/src/coreclr/vm/amd64/JitHelpers_Fast.asm @@ -51,37 +51,6 @@ endif extern JIT_InternalThrow:proc -; There is an even more optimized version of these helpers possible which takes -; advantage of knowledge of which way the ephemeral heap is growing to only do 1/2 -; that check (this is more significant in the JIT_WriteBarrier case). -; -; Additionally we can look into providing helpers which will take the src/dest from -; specific registers (like x86) which _could_ (??) make for easier register allocation -; for the JIT64, however it might lead to having to have some nasty code that treats -; these guys really special like... :(. -; -; Version that does the move, checks whether or not it's in the GC and whether or not -; it needs to have it's card updated -; -; void JIT_CheckedWriteBarrier(Object** dst, Object* src) -LEAF_ENTRY JIT_CheckedWriteBarrier, _TEXT - - ; When WRITE_BARRIER_CHECK is defined _NotInHeap will write the reference - ; but if it isn't then it will just return. - ; - ; See if this is in GCHeap - cmp rcx, [g_lowest_address] - jb NotInHeap - cmp rcx, [g_highest_address] - jnb NotInHeap - - jmp JIT_WriteBarrier - - NotInHeap: - ; See comment above about possible AV - mov [rcx], rdx - ret -LEAF_END_MARKED JIT_CheckedWriteBarrier, _TEXT ; Mark start of the code region that we patch at runtime LEAF_ENTRY JIT_PatchedCodeStart, _TEXT @@ -99,7 +68,8 @@ LEAF_ENTRY JIT_WriteBarrier, _TEXT ifdef _DEBUG ; In debug builds, this just contains jump to the debug version of the write barrier by default - jmp JIT_WriteBarrier_Debug + mov rax, JIT_WriteBarrier_Debug + jmp rax endif ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP @@ -388,6 +358,51 @@ endif ret LEAF_END_MARKED JIT_ByRefWriteBarrier, _TEXT +Section segment para 'DATA' + + align 16 + + public JIT_WriteBarrier_Loc +JIT_WriteBarrier_Loc: + dq 0 + +LEAF_ENTRY JIT_WriteBarrier_Callable, _TEXT + ; JIT_WriteBarrier(Object** dst, Object* src) + jmp QWORD PTR [JIT_WriteBarrier_Loc] +LEAF_END JIT_WriteBarrier_Callable, _TEXT + +; There is an even more optimized version of these helpers possible which takes +; advantage of knowledge of which way the ephemeral heap is growing to only do 1/2 +; that check (this is more significant in the JIT_WriteBarrier case). +; +; Additionally we can look into providing helpers which will take the src/dest from +; specific registers (like x86) which _could_ (??) make for easier register allocation +; for the JIT64, however it might lead to having to have some nasty code that treats +; these guys really special like... :(. +; +; Version that does the move, checks whether or not it's in the GC and whether or not +; it needs to have it's card updated +; +; void JIT_CheckedWriteBarrier(Object** dst, Object* src) +LEAF_ENTRY JIT_CheckedWriteBarrier, _TEXT + + ; When WRITE_BARRIER_CHECK is defined _NotInHeap will write the reference + ; but if it isn't then it will just return. + ; + ; See if this is in GCHeap + cmp rcx, [g_lowest_address] + jb NotInHeap + cmp rcx, [g_highest_address] + jnb NotInHeap + + jmp QWORD PTR [JIT_WriteBarrier_Loc] + + NotInHeap: + ; See comment above about possible AV + mov [rcx], rdx + ret +LEAF_END_MARKED JIT_CheckedWriteBarrier, _TEXT + ; The following helper will access ("probe") a word on each page of the stack ; starting with the page right beneath rsp down to the one pointed to by r11. ; The procedure is needed to make sure that the "guard" page is pushed down below the allocated stack frame. diff --git a/src/coreclr/vm/amd64/jithelpers_fast.S b/src/coreclr/vm/amd64/jithelpers_fast.S index a13afb487851..8109886d0c96 100644 --- a/src/coreclr/vm/amd64/jithelpers_fast.S +++ b/src/coreclr/vm/amd64/jithelpers_fast.S @@ -32,26 +32,14 @@ LEAF_ENTRY JIT_CheckedWriteBarrier, _TEXT // See if this is in GCHeap PREPARE_EXTERNAL_VAR g_lowest_address, rax cmp rdi, [rax] -#ifdef FEATURE_WRITEBARRIER_COPY // jb NotInHeap .byte 0x72, 0x12 -#else - // jb NotInHeap - .byte 0x72, 0x0e -#endif PREPARE_EXTERNAL_VAR g_highest_address, rax cmp rdi, [rax] -#ifdef FEATURE_WRITEBARRIER_COPY // jnb NotInHeap .byte 0x73, 0x06 jmp [rip + C_FUNC(JIT_WriteBarrier_Loc)] -#else - // jnb NotInHeap - .byte 0x73, 0x02 - // jmp C_FUNC(JIT_WriteBarrier) - .byte 0xeb, 0x05 -#endif NotInHeap: // See comment above about possible AV @@ -398,11 +386,17 @@ LEAF_ENTRY JIT_ByRefWriteBarrier, _TEXT ret LEAF_END_MARKED JIT_ByRefWriteBarrier, _TEXT -#ifdef FEATURE_WRITEBARRIER_COPY // When JIT_WriteBarrier is copied into an allocated page, // helpers use this global variable to jump to it. This variable is set in InitThreadManager. - .global _JIT_WriteBarrier_Loc - .zerofill __DATA,__common,_JIT_WriteBarrier_Loc,8,3 + .global C_FUNC(JIT_WriteBarrier_Loc) +#ifdef TARGET_OSX + .zerofill __DATA,__common,C_FUNC(JIT_WriteBarrier_Loc),8,3 +#else + .data + C_FUNC(JIT_WriteBarrier_Loc): + .quad 0 + .text +#endif // ------------------------------------------------------------------ // __declspec(naked) void F_CALL_CONV JIT_WriteBarrier_Callable(Object **dst, Object* val) @@ -412,8 +406,6 @@ LEAF_ENTRY JIT_WriteBarrier_Callable, _TEXT jmp [rip + C_FUNC(JIT_WriteBarrier_Loc)] LEAF_END JIT_WriteBarrier_Callable, _TEXT -#endif // FEATURE_WRITEBARRIER_COPY - // The following helper will access ("probe") a word on each page of the stack // starting with the page right beneath rsp down to the one pointed to by r11. diff --git a/src/coreclr/vm/amd64/jitinterfaceamd64.cpp b/src/coreclr/vm/amd64/jitinterfaceamd64.cpp index 38bff78a54cb..02b023777b8a 100644 --- a/src/coreclr/vm/amd64/jitinterfaceamd64.cpp +++ b/src/coreclr/vm/amd64/jitinterfaceamd64.cpp @@ -293,7 +293,10 @@ int WriteBarrierManager::ChangeWriteBarrierTo(WriteBarrierType newWriteBarrier, // the memcpy must come before the switch statment because the asserts inside the switch // are actually looking into the JIT_WriteBarrier buffer - memcpy(GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier), (LPVOID)GetCurrentWriteBarrierCode(), GetCurrentWriteBarrierSize()); + { + ExecutableWriterHolder writeBarrierWriterHolder(GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier), GetCurrentWriteBarrierSize()); + memcpy(writeBarrierWriterHolder.GetRW(), (LPVOID)GetCurrentWriteBarrierCode(), GetCurrentWriteBarrierSize()); + } switch (newWriteBarrier) { @@ -544,7 +547,8 @@ int WriteBarrierManager::UpdateEphemeralBounds(bool isRuntimeSuspended) // Change immediate if different from new g_ephermeral_high. if (*(UINT64*)m_pUpperBoundImmediate != (size_t)g_ephemeral_high) { - *(UINT64*)m_pUpperBoundImmediate = (size_t)g_ephemeral_high; + ExecutableWriterHolder upperBoundWriterHolder((UINT64*)m_pUpperBoundImmediate, sizeof(UINT64)); + *upperBoundWriterHolder.GetRW() = (size_t)g_ephemeral_high; stompWBCompleteActions |= SWB_ICACHE_FLUSH; } } @@ -557,7 +561,8 @@ int WriteBarrierManager::UpdateEphemeralBounds(bool isRuntimeSuspended) // Change immediate if different from new g_ephermeral_low. if (*(UINT64*)m_pLowerBoundImmediate != (size_t)g_ephemeral_low) { - *(UINT64*)m_pLowerBoundImmediate = (size_t)g_ephemeral_low; + ExecutableWriterHolder lowerBoundImmediateWriterHolder((UINT64*)m_pLowerBoundImmediate, sizeof(UINT64)); + *lowerBoundImmediateWriterHolder.GetRW() = (size_t)g_ephemeral_low; stompWBCompleteActions |= SWB_ICACHE_FLUSH; } break; @@ -609,7 +614,8 @@ int WriteBarrierManager::UpdateWriteWatchAndCardTableLocations(bool isRuntimeSus #endif // FEATURE_SVR_GC if (*(UINT64*)m_pWriteWatchTableImmediate != (size_t)g_sw_ww_table) { - *(UINT64*)m_pWriteWatchTableImmediate = (size_t)g_sw_ww_table; + ExecutableWriterHolder writeWatchTableImmediateWriterHolder((UINT64*)m_pWriteWatchTableImmediate, sizeof(UINT64)); + *writeWatchTableImmediateWriterHolder.GetRW() = (size_t)g_sw_ww_table; stompWBCompleteActions |= SWB_ICACHE_FLUSH; } break; @@ -621,14 +627,16 @@ int WriteBarrierManager::UpdateWriteWatchAndCardTableLocations(bool isRuntimeSus if (*(UINT64*)m_pCardTableImmediate != (size_t)g_card_table) { - *(UINT64*)m_pCardTableImmediate = (size_t)g_card_table; + ExecutableWriterHolder cardTableImmediateWriterHolder((UINT64*)m_pCardTableImmediate, sizeof(UINT64)); + *cardTableImmediateWriterHolder.GetRW() = (size_t)g_card_table; stompWBCompleteActions |= SWB_ICACHE_FLUSH; } #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES if (*(UINT64*)m_pCardBundleTableImmediate != (size_t)g_card_bundle_table) { - *(UINT64*)m_pCardBundleTableImmediate = (size_t)g_card_bundle_table; + ExecutableWriterHolder cardBundleTableImmediateWriterHolder((UINT64*)m_pCardBundleTableImmediate, sizeof(UINT64)); + *cardBundleTableImmediateWriterHolder.GetRW() = (size_t)g_card_bundle_table; stompWBCompleteActions |= SWB_ICACHE_FLUSH; } #endif diff --git a/src/coreclr/vm/arm/armsinglestepper.cpp b/src/coreclr/vm/arm/armsinglestepper.cpp index 79317263b222..f9e718ae5420 100644 --- a/src/coreclr/vm/arm/armsinglestepper.cpp +++ b/src/coreclr/vm/arm/armsinglestepper.cpp @@ -97,11 +97,7 @@ ArmSingleStepper::ArmSingleStepper() ArmSingleStepper::~ArmSingleStepper() { #if !defined(DACCESS_COMPILE) -#ifdef TARGET_UNIX SystemDomain::GetGlobalLoaderAllocator()->GetExecutableHeap()->BackoutMem(m_rgCode, kMaxCodeBuffer * sizeof(WORD)); -#else - DeleteExecutable(m_rgCode); -#endif #endif } @@ -110,11 +106,7 @@ void ArmSingleStepper::Init() #if !defined(DACCESS_COMPILE) if (m_rgCode == NULL) { -#ifdef TARGET_UNIX m_rgCode = (WORD *)(void *)SystemDomain::GetGlobalLoaderAllocator()->GetExecutableHeap()->AllocMem(S_SIZE_T(kMaxCodeBuffer * sizeof(WORD))); -#else - m_rgCode = new (executable) WORD[kMaxCodeBuffer]; -#endif } #endif } @@ -287,6 +279,8 @@ void ArmSingleStepper::Apply(T_CONTEXT *pCtx) DWORD idxNextInstruction = 0; + ExecutableWriterHolder codeWriterHolder(m_rgCode, kMaxCodeBuffer * sizeof(m_rgCode[0])); + if (m_originalITState.InITBlock() && !ConditionHolds(pCtx, m_originalITState.CurrentCondition())) { LOG((LF_CORDB, LL_INFO100000, "ArmSingleStepper: Case 1: ITState::Clear;\n")); @@ -295,7 +289,7 @@ void ArmSingleStepper::Apply(T_CONTEXT *pCtx) // to execute. We'll put the correct value back during fixup. ITState::Clear(pCtx); m_fSkipIT = true; - m_rgCode[idxNextInstruction++] = kBreakpointOp; + codeWriterHolder.GetRW()[idxNextInstruction++] = kBreakpointOp; } else if (TryEmulate(pCtx, opcode1, opcode2, false)) { @@ -308,8 +302,8 @@ void ArmSingleStepper::Apply(T_CONTEXT *pCtx) m_fEmulate = true; // Set breakpoints to stop the execution. This will get us right back here. - m_rgCode[idxNextInstruction++] = kBreakpointOp; - m_rgCode[idxNextInstruction++] = kBreakpointOp; + codeWriterHolder.GetRW()[idxNextInstruction++] = kBreakpointOp; + codeWriterHolder.GetRW()[idxNextInstruction++] = kBreakpointOp; } else { @@ -323,24 +317,24 @@ void ArmSingleStepper::Apply(T_CONTEXT *pCtx) // guarantee one of them will be hit (we don't care which one -- the fixup code will update // the PC and IT state to make it look as though the CPU just executed the current // instruction). - m_rgCode[idxNextInstruction++] = opcode1; + codeWriterHolder.GetRW()[idxNextInstruction++] = opcode1; if (Is32BitInstruction(opcode1)) - m_rgCode[idxNextInstruction++] = opcode2; + codeWriterHolder.GetRW()[idxNextInstruction++] = opcode2; - m_rgCode[idxNextInstruction++] = kBreakpointOp; - m_rgCode[idxNextInstruction++] = kBreakpointOp; - m_rgCode[idxNextInstruction++] = kBreakpointOp; + codeWriterHolder.GetRW()[idxNextInstruction++] = kBreakpointOp; + codeWriterHolder.GetRW()[idxNextInstruction++] = kBreakpointOp; + codeWriterHolder.GetRW()[idxNextInstruction++] = kBreakpointOp; } // Always terminate the redirection buffer with a breakpoint. - m_rgCode[idxNextInstruction++] = kBreakpointOp; + codeWriterHolder.GetRW()[idxNextInstruction++] = kBreakpointOp; _ASSERTE(idxNextInstruction <= kMaxCodeBuffer); // Set the thread up so it will redirect to our buffer when execution resumes. pCtx->Pc = ((DWORD)(DWORD_PTR)m_rgCode) | THUMB_CODE; // Make sure the CPU sees the updated contents of the buffer. - FlushInstructionCache(GetCurrentProcess(), m_rgCode, sizeof(m_rgCode)); + FlushInstructionCache(GetCurrentProcess(), m_rgCode, kMaxCodeBuffer * sizeof(m_rgCode[0])); // Done, set the state. m_state = Applied; diff --git a/src/coreclr/vm/arm/asmhelpers.S b/src/coreclr/vm/arm/asmhelpers.S index 930395b56dc7..3faa8fe36846 100644 --- a/src/coreclr/vm/arm/asmhelpers.S +++ b/src/coreclr/vm/arm/asmhelpers.S @@ -978,6 +978,16 @@ g_rgWriteBarrierDescriptors: .global g_rgWriteBarrierDescriptors +// ------------------------------------------------------------------ +// __declspec(naked) void F_CALL_CONV JIT_WriteBarrier_Callable(Object **dst, Object* val) + LEAF_ENTRY JIT_WriteBarrier_Callable + + // Branch to the write barrier + ldr r2, =JIT_WriteBarrier_Loc // or R3? See targetarm.h + ldr pc, [r2] + + LEAF_END JIT_WriteBarrier_Callable + #ifdef FEATURE_READYTORUN NESTED_ENTRY DelayLoad_MethodCall_FakeProlog, _TEXT, NoHandler diff --git a/src/coreclr/vm/arm/asmhelpers.asm b/src/coreclr/vm/arm/asmhelpers.asm index d20540e62090..82596e66693d 100644 --- a/src/coreclr/vm/arm/asmhelpers.asm +++ b/src/coreclr/vm/arm/asmhelpers.asm @@ -1724,6 +1724,18 @@ tempReg SETS "$tmpReg" END_WRITE_BARRIERS + IMPORT JIT_WriteBarrier_Loc + +; ------------------------------------------------------------------ +; __declspec(naked) void F_CALL_CONV JIT_WriteBarrier_Callable(Object **dst, Object* val) + LEAF_ENTRY JIT_WriteBarrier_Callable + + ; Branch to the write barrier + ldr r2, =JIT_WriteBarrier_Loc ; or R3? See targetarm.h + ldr pc, [r2] + + LEAF_END + #ifdef FEATURE_READYTORUN NESTED_ENTRY DelayLoad_MethodCall_FakeProlog diff --git a/src/coreclr/vm/arm/cgencpu.h b/src/coreclr/vm/arm/cgencpu.h index 88d0c6802b69..425c28655843 100644 --- a/src/coreclr/vm/arm/cgencpu.h +++ b/src/coreclr/vm/arm/cgencpu.h @@ -1069,6 +1069,7 @@ struct StubPrecode { return m_pTarget; } +#ifndef DACCESS_COMPILE void ResetTargetInterlocked() { CONTRACTL @@ -1095,6 +1096,7 @@ struct StubPrecode { return (TADDR)InterlockedCompareExchange( (LONG*)&precodeWriterHolder.GetRW()->m_pTarget, (LONG)target, (LONG)expected) == expected; } +#endif // !DACCESS_COMPILE #ifdef FEATURE_PREJIT void Fixup(DataImage *image); @@ -1167,6 +1169,13 @@ struct FixupPrecode { return dac_cast(this) + (m_PrecodeChunkIndex + 1) * sizeof(FixupPrecode); } + size_t GetSizeRW() + { + LIMITED_METHOD_CONTRACT; + + return GetBase() + sizeof(void*) - dac_cast(this); + } + TADDR GetMethodDesc(); PCODE GetTarget() @@ -1175,6 +1184,7 @@ struct FixupPrecode { return m_pTarget; } +#ifndef DACCESS_COMPILE void ResetTargetInterlocked() { CONTRACTL @@ -1201,6 +1211,7 @@ struct FixupPrecode { return (TADDR)InterlockedCompareExchange( (LONG*)&precodeWriterHolder.GetRW()->m_pTarget, (LONG)target, (LONG)expected) == expected; } +#endif // !DACCESS_COMPILE static BOOL IsFixupPrecodeByASM(PCODE addr) { @@ -1256,6 +1267,7 @@ struct ThisPtrRetBufPrecode { return m_pTarget; } +#ifndef DACCESS_COMPILE BOOL SetTargetInterlocked(TADDR target, TADDR expected) { CONTRACTL @@ -1268,6 +1280,7 @@ struct ThisPtrRetBufPrecode { ExecutableWriterHolder precodeWriterHolder(this, sizeof(ThisPtrRetBufPrecode)); return FastInterlockCompareExchange((LONG*)&precodeWriterHolder.GetRW()->m_pTarget, (LONG)target, (LONG)expected) == (LONG)expected; } +#endif // !DACCESS_COMPILE }; typedef DPTR(ThisPtrRetBufPrecode) PTR_ThisPtrRetBufPrecode; diff --git a/src/coreclr/vm/arm/stubs.cpp b/src/coreclr/vm/arm/stubs.cpp index aac3e25b1814..6e62df237033 100644 --- a/src/coreclr/vm/arm/stubs.cpp +++ b/src/coreclr/vm/arm/stubs.cpp @@ -329,16 +329,28 @@ void ComputeWriteBarrierRange(BYTE ** ppbStart, DWORD * pcbLength) { DWORD size = (PBYTE)JIT_PatchedWriteBarrierLast - (PBYTE)JIT_PatchedWriteBarrierStart; *ppbStart = (PBYTE)JIT_PatchedWriteBarrierStart; + if (IsWriteBarrierCopyEnabled()) + { + *ppbStart = GetWriteBarrierCodeLocation(*ppbStart); + } *pcbLength = size; } void CopyWriteBarrier(PCODE dstCode, PCODE srcCode, PCODE endCode) { - TADDR dst = PCODEToPINSTR(dstCode); + TADDR dst = (TADDR)PCODEToPINSTR((PCODE)GetWriteBarrierCodeLocation((void*)dstCode)); TADDR src = PCODEToPINSTR(srcCode); TADDR end = PCODEToPINSTR(endCode); size_t size = (PBYTE)end - (PBYTE)src; + + ExecutableWriterHolder writeBarrierWriterHolder; + if (IsWriteBarrierCopyEnabled()) + { + writeBarrierWriterHolder = ExecutableWriterHolder((void*)dst, size); + dst = (TADDR)writeBarrierWriterHolder.GetRW(); + } + memcpy((PVOID)dst, (PVOID)src, size); } @@ -419,7 +431,7 @@ void UpdateGCWriteBarriers(bool postGrow = false) } #define GWB_PATCH_OFFSET(_global) \ if (pDesc->m_dw_##_global##_offset != 0xffff) \ - PutThumb2Mov32((UINT16*)(to + pDesc->m_dw_##_global##_offset - 1), (UINT32)(dac_cast(_global))); + PutThumb2Mov32((UINT16*)(to + pDesc->m_dw_##_global##_offset), (UINT32)(dac_cast(_global))); // Iterate through the write barrier patch table created in the .clrwb section // (see write barrier asm code) @@ -431,6 +443,13 @@ void UpdateGCWriteBarriers(bool postGrow = false) PBYTE to = FindWBMapping(pDesc->m_pFuncStart); if(to) { + to = (PBYTE)PCODEToPINSTR((PCODE)GetWriteBarrierCodeLocation(to)); + ExecutableWriterHolder barrierWriterHolder; + if (IsWriteBarrierCopyEnabled()) + { + barrierWriterHolder = ExecutableWriterHolder(to, pDesc->m_pFuncEnd - pDesc->m_pFuncStart); + to = barrierWriterHolder.GetRW(); + } GWB_PATCH_OFFSET(g_lowest_address); GWB_PATCH_OFFSET(g_highest_address); GWB_PATCH_OFFSET(g_ephemeral_low); diff --git a/src/coreclr/vm/arm64/arm64singlestepper.cpp b/src/coreclr/vm/arm64/arm64singlestepper.cpp index d45925311a33..6c1764647c9f 100644 --- a/src/coreclr/vm/arm64/arm64singlestepper.cpp +++ b/src/coreclr/vm/arm64/arm64singlestepper.cpp @@ -46,11 +46,7 @@ Arm64SingleStepper::Arm64SingleStepper() Arm64SingleStepper::~Arm64SingleStepper() { #if !defined(DACCESS_COMPILE) -#ifdef TARGET_UNIX SystemDomain::GetGlobalLoaderAllocator()->GetExecutableHeap()->BackoutMem(m_rgCode, kMaxCodeBuffer * sizeof(uint32_t)); -#else - DeleteExecutable(m_rgCode); -#endif #endif } @@ -59,11 +55,7 @@ void Arm64SingleStepper::Init() #if !defined(DACCESS_COMPILE) if (m_rgCode == NULL) { -#ifdef TARGET_UNIX m_rgCode = (uint32_t *)(void *)SystemDomain::GetGlobalLoaderAllocator()->GetExecutableHeap()->AllocMem(S_SIZE_T(kMaxCodeBuffer * sizeof(uint32_t))); -#else - m_rgCode = new (executable) uint32_t[kMaxCodeBuffer]; -#endif } #endif } @@ -207,7 +199,7 @@ void Arm64SingleStepper::Apply(T_CONTEXT *pCtx) unsigned int idxNextInstruction = 0; - ExecutableWriterHolder codeWriterHolder(m_rgCode, sizeof(m_rgCode)); + ExecutableWriterHolder codeWriterHolder(m_rgCode, kMaxCodeBuffer * sizeof(m_rgCode[0])); if (TryEmulate(pCtx, opcode, false)) { @@ -230,7 +222,7 @@ void Arm64SingleStepper::Apply(T_CONTEXT *pCtx) pCtx->Pc = (uint64_t)m_rgCode; // Make sure the CPU sees the updated contents of the buffer. - FlushInstructionCache(GetCurrentProcess(), m_rgCode, sizeof(m_rgCode)); + FlushInstructionCache(GetCurrentProcess(), m_rgCode, kMaxCodeBuffer * sizeof(m_rgCode[0])); // Done, set the state. m_state = Applied; diff --git a/src/coreclr/vm/arm64/asmhelpers.S b/src/coreclr/vm/arm64/asmhelpers.S index e6b47d07b2b0..8ef66586cd22 100644 --- a/src/coreclr/vm/arm64/asmhelpers.S +++ b/src/coreclr/vm/arm64/asmhelpers.S @@ -270,13 +270,9 @@ LOCAL_LABEL(EphemeralCheckEnabled): ldr x7, [x12] // Update wbs state -#ifdef FEATURE_WRITEBARRIER_COPY PREPARE_EXTERNAL_VAR JIT_WriteBarrier_Table_Loc, x12 ldr x12, [x12] add x12, x12, x9 -#else // FEATURE_WRITEBARRIER_COPY - adr x12, LOCAL_LABEL(wbs_begin) -#endif // FEATURE_WRITEBARRIER_COPY stp x0, x1, [x12], 16 stp x2, x3, [x12], 16 @@ -295,16 +291,10 @@ LEAF_ENTRY JIT_WriteBarrier_Callable, _TEXT mov x14, x0 // x14 = dst mov x15, x1 // x15 = val -#ifdef FEATURE_WRITEBARRIER_COPY -LOCAL_LABEL(Branch_JIT_WriteBarrier_Copy): // Branch to the write barrier PREPARE_EXTERNAL_VAR JIT_WriteBarrier_Loc, x17 ldr x17, [x17] br x17 -#else // FEATURE_WRITEBARRIER_COPY - // Branch to the write barrier - b C_FUNC(JIT_WriteBarrier) -#endif // FEATURE_WRITEBARRIER_COPY LEAF_END JIT_WriteBarrier_Callable, _TEXT .balign 64 // Align to power of two at least as big as patchable literal pool so that it fits optimally in cache line diff --git a/src/coreclr/vm/arm64/asmhelpers.asm b/src/coreclr/vm/arm64/asmhelpers.asm index ffbeb9fd1acb..17d3a676940b 100644 --- a/src/coreclr/vm/arm64/asmhelpers.asm +++ b/src/coreclr/vm/arm64/asmhelpers.asm @@ -61,6 +61,10 @@ #ifdef FEATURE_COMINTEROP IMPORT CLRToCOMWorker #endif // FEATURE_COMINTEROP + + IMPORT JIT_WriteBarrier_Table_Loc + IMPORT JIT_WriteBarrier_Loc + TEXTAREA ;; LPVOID __stdcall GetCurrentIP(void); @@ -308,6 +312,7 @@ ThePreStubPatchLabel ; x12 will be used for pointers mov x8, x0 + mov x9, x1 adrp x12, g_card_table ldr x0, [x12, g_card_table] @@ -346,7 +351,9 @@ EphemeralCheckEnabled ldr x7, [x12, g_highest_address] ; Update wbs state - adr x12, wbs_begin + adrp x12, JIT_WriteBarrier_Table_Loc + ldr x12, [x12, JIT_WriteBarrier_Table_Loc] + add x12, x12, x9 stp x0, x1, [x12], 16 stp x2, x3, [x12], 16 stp x4, x5, [x12], 16 @@ -355,9 +362,11 @@ EphemeralCheckEnabled EPILOG_RESTORE_REG_PAIR fp, lr, #16! EPILOG_RETURN + WRITE_BARRIER_END JIT_UpdateWriteBarrierState + ; Begin patchable literal pool ALIGN 64 ; Align to power of two at least as big as patchable literal pool so that it fits optimally in cache line - + WRITE_BARRIER_ENTRY JIT_WriteBarrier_Table wbs_begin wbs_card_table DCQ 0 @@ -375,14 +384,7 @@ wbs_lowest_address DCQ 0 wbs_highest_address DCQ 0 - - WRITE_BARRIER_END JIT_UpdateWriteBarrierState - -; ------------------------------------------------------------------ -; End of the writeable code region - LEAF_ENTRY JIT_PatchedCodeLast - ret lr - LEAF_END + WRITE_BARRIER_END JIT_WriteBarrier_Table ; void JIT_ByRefWriteBarrier ; On entry: @@ -546,6 +548,12 @@ Exit ret lr WRITE_BARRIER_END JIT_WriteBarrier +; ------------------------------------------------------------------ +; End of the writeable code region + LEAF_ENTRY JIT_PatchedCodeLast + ret lr + LEAF_END + #ifdef FEATURE_PREJIT ;------------------------------------------------ ; VirtualMethodFixupStub @@ -1417,9 +1425,10 @@ CallHelper2 mov x14, x0 ; x14 = dst mov x15, x1 ; x15 = val - ; Branch to the write barrier (which is already correctly overwritten with - ; single or multi-proc code based on the current CPU - b JIT_WriteBarrier + ; Branch to the write barrier + adrp x17, JIT_WriteBarrier_Loc + ldr x17, [x17, JIT_WriteBarrier_Loc] + br x17 LEAF_END diff --git a/src/coreclr/vm/arm64/cgencpu.h b/src/coreclr/vm/arm64/cgencpu.h index 83e56cfb9f9b..0641d89ff1a9 100644 --- a/src/coreclr/vm/arm64/cgencpu.h +++ b/src/coreclr/vm/arm64/cgencpu.h @@ -597,6 +597,7 @@ struct StubPrecode { return m_pTarget; } +#ifndef DACCESS_COMPILE void ResetTargetInterlocked() { CONTRACTL @@ -623,6 +624,7 @@ struct StubPrecode { return (TADDR)InterlockedCompareExchange64( (LONGLONG*)&precodeWriterHolder.GetRW()->m_pTarget, (TADDR)target, (TADDR)expected) == expected; } +#endif // !DACCESS_COMPILE #ifdef FEATURE_PREJIT void Fixup(DataImage *image); @@ -715,6 +717,13 @@ struct FixupPrecode { return dac_cast(this) + (m_PrecodeChunkIndex + 1) * sizeof(FixupPrecode); } + size_t GetSizeRW() + { + LIMITED_METHOD_CONTRACT; + + return GetBase() + sizeof(void*) - dac_cast(this); + } + TADDR GetMethodDesc(); PCODE GetTarget() @@ -723,6 +732,7 @@ struct FixupPrecode { return m_pTarget; } +#ifndef DACCESS_COMPILE void ResetTargetInterlocked() { CONTRACTL @@ -749,6 +759,7 @@ struct FixupPrecode { return (TADDR)InterlockedCompareExchange64( (LONGLONG*)&precodeWriterHolder.GetRW()->m_pTarget, (TADDR)target, (TADDR)expected) == expected; } +#endif // !DACCESS_COMPILE static BOOL IsFixupPrecodeByASM(PCODE addr) { @@ -797,6 +808,7 @@ struct ThisPtrRetBufPrecode { return m_pTarget; } +#ifndef DACCESS_COMPILE BOOL SetTargetInterlocked(TADDR target, TADDR expected) { CONTRACTL @@ -810,6 +822,7 @@ struct ThisPtrRetBufPrecode { return (TADDR)InterlockedCompareExchange64( (LONGLONG*)&precodeWriterHolder.GetRW()->m_pTarget, (TADDR)target, (TADDR)expected) == expected; } +#endif // !DACCESS_COMPILE }; typedef DPTR(ThisPtrRetBufPrecode) PTR_ThisPtrRetBufPrecode; diff --git a/src/coreclr/vm/arm64/stubs.cpp b/src/coreclr/vm/arm64/stubs.cpp index 54cf1c492754..12d56ddb9867 100644 --- a/src/coreclr/vm/arm64/stubs.cpp +++ b/src/coreclr/vm/arm64/stubs.cpp @@ -1067,8 +1067,14 @@ extern "C" void STDCALL JIT_PatchedCodeLast(); static void UpdateWriteBarrierState(bool skipEphemeralCheck) { BYTE *writeBarrierCodeStart = GetWriteBarrierCodeLocation((void*)JIT_PatchedCodeStart); - ExecutableWriterHolder writeBarrierWriterHolder(writeBarrierCodeStart, (BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart); - JIT_UpdateWriteBarrierState(GCHeapUtilities::IsServerHeap(), writeBarrierWriterHolder.GetRW() - writeBarrierCodeStart); + BYTE *writeBarrierCodeStartRW = writeBarrierCodeStart; + ExecutableWriterHolder writeBarrierWriterHolder; + if (IsWriteBarrierCopyEnabled()) + { + writeBarrierWriterHolder = ExecutableWriterHolder(writeBarrierCodeStart, (BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart); + writeBarrierCodeStartRW = writeBarrierWriterHolder.GetRW(); + } + JIT_UpdateWriteBarrierState(GCHeapUtilities::IsServerHeap(), writeBarrierCodeStartRW - writeBarrierCodeStart); } void InitJITHelpers1() diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index cdc5925234af..b60aac924d2e 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -607,6 +607,11 @@ void EESocketCleanupHelper(bool isExecutingOnAltStack) #endif // TARGET_UNIX #endif // CROSSGEN_COMPILE +void FatalErrorHandler(UINT errorCode, LPCWSTR pszMessage) +{ + EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(errorCode, pszMessage); +} + void EEStartupHelper() { CONTRACTL @@ -670,6 +675,8 @@ void EEStartupHelper() // This needs to be done before the EE has started InitializeStartupFlags(); + IfFailGo(ExecutableAllocator::StaticInitialize(FatalErrorHandler)); + ThreadpoolMgr::StaticInitialize(); MethodDescBackpatchInfoTracker::StaticInitialize(); @@ -824,7 +831,7 @@ void EEStartupHelper() g_runtimeLoadedBaseAddress = (SIZE_T)pe.GetBase(); g_runtimeVirtualSize = (SIZE_T)pe.GetVirtualSize(); - InitCodeAllocHint(g_runtimeLoadedBaseAddress, g_runtimeVirtualSize, GetRandomInt(64)); + ExecutableAllocator::InitCodeAllocHint(g_runtimeLoadedBaseAddress, g_runtimeVirtualSize, GetRandomInt(64)); } #endif // !TARGET_UNIX diff --git a/src/coreclr/vm/class.cpp b/src/coreclr/vm/class.cpp index 02feec829a76..5c5004f56860 100644 --- a/src/coreclr/vm/class.cpp +++ b/src/coreclr/vm/class.cpp @@ -153,7 +153,9 @@ void EEClass::Destruct(MethodTable * pOwningMT) if (pDelegateEEClass->m_pStaticCallStub) { - BOOL fStubDeleted = pDelegateEEClass->m_pStaticCallStub->DecRef(); + ExecutableWriterHolder stubWriterHolder(pDelegateEEClass->m_pStaticCallStub, sizeof(Stub)); + BOOL fStubDeleted = stubWriterHolder.GetRW()->DecRef(); + if (fStubDeleted) { DelegateInvokeStubManager::g_pManager->RemoveStub(pDelegateEEClass->m_pStaticCallStub); @@ -167,7 +169,6 @@ void EEClass::Destruct(MethodTable * pOwningMT) // it is owned by the m_pMulticastStubCache, not by the class // - it is shared across classes. So we don't decrement // its ref count here - delete pDelegateEEClass->m_pUMThunkMarshInfo; } #ifdef FEATURE_COMINTEROP diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index 37220786fedd..78721292a3e9 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -2139,8 +2139,7 @@ VOID EEJitManager::EnsureJumpStubReserve(BYTE * pImageBase, SIZE_T imageSize, SI return; // Unable to allocate the reserve - give up } - pNewReserve->m_ptr = ClrVirtualAllocWithinRange(loAddrCurrent, hiAddrCurrent, - allocChunk, MEM_RESERVE, PAGE_NOACCESS); + pNewReserve->m_ptr = (BYTE*)ExecutableAllocator::Instance()->ReserveWithinRange(allocChunk, loAddrCurrent, hiAddrCurrent); if (pNewReserve->m_ptr != NULL) break; @@ -2231,8 +2230,7 @@ HeapList* LoaderCodeHeap::CreateCodeHeap(CodeHeapRequestInfo *pInfo, LoaderHeap if (!pInfo->getThrowOnOutOfMemoryWithinRange() && PEDecoder::GetForceRelocs()) RETURN NULL; #endif - pBaseAddr = ClrVirtualAllocWithinRange(loAddr, hiAddr, - reserveSize, MEM_RESERVE, PAGE_NOACCESS); + pBaseAddr = (BYTE*)ExecutableAllocator::Instance()->ReserveWithinRange(reserveSize, loAddr, hiAddr); if (!pBaseAddr) { @@ -2251,7 +2249,7 @@ HeapList* LoaderCodeHeap::CreateCodeHeap(CodeHeapRequestInfo *pInfo, LoaderHeap } else { - pBaseAddr = ClrVirtualAllocExecutable(reserveSize, MEM_RESERVE, PAGE_NOACCESS); + pBaseAddr = (BYTE*)ExecutableAllocator::Instance()->Reserve(reserveSize); if (!pBaseAddr) ThrowOutOfMemory(); } @@ -2686,15 +2684,14 @@ void EEJitManager::allocCode(MethodDesc* pMD, size_t blockSize, size_t reserveFo *pAllocatedSize = sizeof(CodeHeader) + totalSize; -#if defined(HOST_OSX) && defined(HOST_ARM64) -#define FEATURE_WXORX -#endif - -#ifdef FEATURE_WXORX - pCodeHdrRW = (CodeHeader *)new BYTE[*pAllocatedSize]; -#else - pCodeHdrRW = pCodeHdr; -#endif + if (ExecutableAllocator::IsWXORXEnabled()) + { + pCodeHdrRW = (CodeHeader *)new BYTE[*pAllocatedSize]; + } + else + { + pCodeHdrRW = pCodeHdr; + } #ifdef USE_INDIRECT_CODEHEADER if (requestInfo.IsDynamicDomain()) @@ -3347,7 +3344,7 @@ void EEJitManager::Unload(LoaderAllocator *pAllocator) } } - ResetCodeAllocHint(); + ExecutableAllocator::ResetCodeAllocHint(); } EEJitManager::DomainCodeHeapList::DomainCodeHeapList() diff --git a/src/coreclr/vm/comcallablewrapper.cpp b/src/coreclr/vm/comcallablewrapper.cpp index 8b95dac8cdd7..499880dc16dd 100644 --- a/src/coreclr/vm/comcallablewrapper.cpp +++ b/src/coreclr/vm/comcallablewrapper.cpp @@ -3183,12 +3183,11 @@ void ComMethodTable::Cleanup() if (m_pDispatchInfo) delete m_pDispatchInfo; - if (m_pMDescr) - DeleteExecutable(m_pMDescr); if (m_pITypeInfo && !g_fProcessDetach) SafeRelease(m_pITypeInfo); - DeleteExecutable(this); + // The m_pMDescr and the current instance is allocated from the related LoaderAllocator + // so no cleanup is needed here. } @@ -3214,7 +3213,7 @@ void ComMethodTable::LayOutClassMethodTable() SLOT *pComVtable; unsigned cbPrevSlots = 0; unsigned cbAlloc = 0; - NewExecutableHolder pMDMemoryPtr = NULL; + AllocMemHolder pMDMemoryPtr; BYTE* pMethodDescMemory = NULL; size_t writeableOffset = 0; unsigned cbNumParentVirtualMethods = 0; @@ -3321,7 +3320,7 @@ void ComMethodTable::LayOutClassMethodTable() cbAlloc = cbMethodDescs; if (cbAlloc > 0) { - pMDMemoryPtr = (BYTE*) new (executable) BYTE[cbAlloc + sizeof(UINT_PTR)]; + pMDMemoryPtr = m_pMT->GetLoaderAllocator()->GetStubHeap()->AllocMem(S_SIZE_T(cbAlloc + sizeof(UINT_PTR))); pMethodDescMemory = pMDMemoryPtr; methodDescMemoryWriteableHolder = ExecutableWriterHolder(pMethodDescMemory, cbAlloc + sizeof(UINT_PTR)); @@ -3703,7 +3702,6 @@ BOOL ComMethodTable::LayOutInterfaceMethodTable(MethodTable* pClsMT) // Method descs are at the end of the vtable // m_cbSlots interfaces methods + IUnk methods pMethodDescMemory = (BYTE *)&pComVtable[m_cbSlots]; - for (i = 0; i < cbSlots; i++) { ComCallMethodDesc* pNewMD = (ComCallMethodDesc *) (pMethodDescMemory + COMMETHOD_PREPAD); @@ -4495,13 +4493,12 @@ ComMethodTable* ComCallWrapperTemplate::CreateComMethodTableForClass(MethodTable if (cbToAlloc.IsOverflow()) ThrowHR(COR_E_OVERFLOW); - NewExecutableHolder pComMT = (ComMethodTable*) new (executable) BYTE[cbToAlloc.Value()]; + AllocMemHolder pComMT(pClassMT->GetLoaderAllocator()->GetStubHeap()->AllocMem(S_SIZE_T(cbToAlloc.Value()))); _ASSERTE(!cbNewSlots.IsOverflow() && !cbTotalSlots.IsOverflow() && !cbVtable.IsOverflow()); ExecutableWriterHolder comMTWriterHolder(pComMT, cbToAlloc.Value()); ComMethodTable* pComMTRW = comMTWriterHolder.GetRW(); - // set up the header pComMTRW->m_ptReserved = (SLOT)(size_t)0xDEADC0FF; // reserved pComMTRW->m_pMT = pClassMT; // pointer to the class method table @@ -4573,7 +4570,7 @@ ComMethodTable* ComCallWrapperTemplate::CreateComMethodTableForInterface(MethodT if (cbToAlloc.IsOverflow()) ThrowHR(COR_E_OVERFLOW); - NewExecutableHolder pComMT = (ComMethodTable*) new (executable) BYTE[cbToAlloc.Value()]; + AllocMemHolder pComMT(pInterfaceMT->GetLoaderAllocator()->GetStubHeap()->AllocMem(S_SIZE_T(cbToAlloc.Value()))); _ASSERTE(!cbVtable.IsOverflow() && !cbMethDescs.IsOverflow()); @@ -4639,7 +4636,8 @@ ComMethodTable* ComCallWrapperTemplate::CreateComMethodTableForBasic(MethodTable unsigned cbVtable = cbExtraSlots * sizeof(SLOT); unsigned cbToAlloc = sizeof(ComMethodTable) + cbVtable; - NewExecutableHolder pComMT = (ComMethodTable*) new (executable) BYTE[cbToAlloc]; + AllocMemHolder pComMT(pMT->GetLoaderAllocator()->GetStubHeap()->AllocMem(S_SIZE_T(cbToAlloc))); + ExecutableWriterHolder comMTWriterHolder(pComMT, cbToAlloc); ComMethodTable* pComMTRW = comMTWriterHolder.GetRW(); diff --git a/src/coreclr/vm/comcallablewrapper.h b/src/coreclr/vm/comcallablewrapper.h index 2581ddf832fd..0f1e4b878e4c 100644 --- a/src/coreclr/vm/comcallablewrapper.h +++ b/src/coreclr/vm/comcallablewrapper.h @@ -499,6 +499,7 @@ struct ComMethodTable // Accessor for the IDispatch information. DispatchInfo* GetDispatchInfo(); +#ifndef DACCESS_COMPILE LONG AddRef() { LIMITED_METHOD_CONTRACT; @@ -527,6 +528,7 @@ struct ComMethodTable return cbRef; } +#endif // DACCESS_COMPILE CorIfaceAttr GetInterfaceType() { @@ -746,6 +748,7 @@ struct ComMethodTable } +#ifndef DACCESS_COMPILE inline REFIID GetIID() { // Cannot use a normal CONTRACT since the return type is ref type which @@ -768,6 +771,7 @@ struct ComMethodTable return m_IID; } +#endif // DACCESS_COMPILE void CheckParentComVisibility(BOOL fForIDispatch) { diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index b6c17260a130..1b61e16dec5d 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -1253,7 +1253,7 @@ LPVOID COMDelegate::ConvertToCallback(OBJECTREF pDelegateObj) { GCX_PREEMP(); - pUMThunkMarshInfo = new UMThunkMarshInfo(); + pUMThunkMarshInfo = (UMThunkMarshInfo*)(void*)pMT->GetLoaderAllocator()->GetStubHeap()->AllocMem(S_SIZE_T(sizeof(UMThunkMarshInfo))); ExecutableWriterHolder uMThunkMarshInfoWriterHolder(pUMThunkMarshInfo, sizeof(UMThunkMarshInfo)); uMThunkMarshInfoWriterHolder.GetRW()->LoadTimeInit(pInvokeMeth); diff --git a/src/coreclr/vm/dllimportcallback.cpp b/src/coreclr/vm/dllimportcallback.cpp index 4a88f81df521..4f3cf879d10a 100644 --- a/src/coreclr/vm/dllimportcallback.cpp +++ b/src/coreclr/vm/dllimportcallback.cpp @@ -41,7 +41,7 @@ class UMEntryThunkFreeList { WRAPPER_NO_CONTRACT; - m_crst.Init(CrstLeafLock, CRST_UNSAFE_ANYMODE); + m_crst.Init(CrstUMEntryThunkFreeListLock, CRST_UNSAFE_ANYMODE); } UMEntryThunk *GetUMEntryThunk() diff --git a/src/coreclr/vm/dynamicmethod.cpp b/src/coreclr/vm/dynamicmethod.cpp index 9dae86aca937..541d88dc1688 100644 --- a/src/coreclr/vm/dynamicmethod.cpp +++ b/src/coreclr/vm/dynamicmethod.cpp @@ -403,8 +403,7 @@ HeapList* HostCodeHeap::InitializeHeapList(CodeHeapRequestInfo *pInfo) if (pInfo->m_loAddr != NULL || pInfo->m_hiAddr != NULL) { - m_pBaseAddr = ClrVirtualAllocWithinRange(pInfo->m_loAddr, pInfo->m_hiAddr, - ReserveBlockSize, MEM_RESERVE, PAGE_NOACCESS); + m_pBaseAddr = (BYTE*)ExecutableAllocator::Instance()->ReserveWithinRange(ReserveBlockSize, pInfo->m_loAddr, pInfo->m_hiAddr); if (!m_pBaseAddr) { if (pInfo->getThrowOnOutOfMemoryWithinRange()) @@ -417,7 +416,7 @@ HeapList* HostCodeHeap::InitializeHeapList(CodeHeapRequestInfo *pInfo) // top up the ReserveBlockSize to suggested minimum ReserveBlockSize = max(ReserveBlockSize, pInfo->getReserveSize()); - m_pBaseAddr = ClrVirtualAllocExecutable(ReserveBlockSize, MEM_RESERVE, PAGE_NOACCESS); + m_pBaseAddr = (BYTE*)ExecutableAllocator::Instance()->Reserve(ReserveBlockSize); if (!m_pBaseAddr) ThrowOutOfMemory(); } @@ -749,7 +748,7 @@ HostCodeHeap::TrackAllocation* HostCodeHeap::AllocMemory_NoThrow(size_t header, if (m_pLastAvailableCommittedAddr + sizeToCommit <= m_pBaseAddr + m_TotalBytesAvailable) { - if (NULL == ClrVirtualAlloc(m_pLastAvailableCommittedAddr, sizeToCommit, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) + if (NULL == ExecutableAllocator::Instance()->Commit(m_pLastAvailableCommittedAddr, sizeToCommit, true /* isExecutable */)) { LOG((LF_BCL, LL_ERROR, "CodeHeap [0x%p] - VirtualAlloc failed\n", this)); return NULL; diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index a1fdf255a5ce..6bf5efcc8028 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -6699,14 +6699,12 @@ AdjustContextForJITHelpers( PCODE ip = GetIP(pContext); -#ifdef FEATURE_WRITEBARRIER_COPY if (IsIPInWriteBarrierCodeCopy(ip)) { // Pretend we were executing the barrier function at its original location so that the unwinder can unwind the frame ip = AdjustWriteBarrierIP(ip); SetIP(pContext, ip); } -#endif // FEATURE_WRITEBARRIER_COPY #ifdef FEATURE_DATABREAKPOINT diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index 7fff234ca85e..4af702fab149 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -4694,14 +4694,12 @@ VOID DECLSPEC_NORETURN UnwindManagedExceptionPass1(PAL_SEHException& ex, CONTEXT break; } -#ifdef FEATURE_WRITEBARRIER_COPY if (IsIPInWriteBarrierCodeCopy(controlPc)) { // Pretend we were executing the barrier function at its original location so that the unwinder can unwind the frame controlPc = AdjustWriteBarrierIP(controlPc); SetIP(frameContext, controlPc); } -#endif // FEATURE_WRITEBARRIER_COPY UINT_PTR sp = GetSP(frameContext); @@ -5174,13 +5172,11 @@ BOOL IsSafeToHandleHardwareException(PCONTEXT contextRecord, PEXCEPTION_RECORD e { PCODE controlPc = GetIP(contextRecord); -#ifdef FEATURE_WRITEBARRIER_COPY if (IsIPInWriteBarrierCodeCopy(controlPc)) { // Pretend we were executing the barrier function at its original location controlPc = AdjustWriteBarrierIP(controlPc); } -#endif // FEATURE_WRITEBARRIER_COPY return g_fEEStarted && ( exceptionRecord->ExceptionCode == STATUS_BREAKPOINT || @@ -5259,14 +5255,12 @@ BOOL HandleHardwareException(PAL_SEHException* ex) { GCX_COOP(); // Must be cooperative to modify frame chain. -#ifdef FEATURE_WRITEBARRIER_COPY if (IsIPInWriteBarrierCodeCopy(controlPc)) { // Pretend we were executing the barrier function at its original location so that the unwinder can unwind the frame controlPc = AdjustWriteBarrierIP(controlPc); SetIP(ex->GetContextRecord(), controlPc); } -#endif // FEATURE_WRITEBARRIER_COPY if (IsIPInMarkedJitHelper(controlPc)) { diff --git a/src/coreclr/vm/gccover.cpp b/src/coreclr/vm/gccover.cpp index be856dbe1a63..9ce0cc676f7a 100644 --- a/src/coreclr/vm/gccover.cpp +++ b/src/coreclr/vm/gccover.cpp @@ -1258,9 +1258,9 @@ void RemoveGcCoverageInterrupt(TADDR instrPtr, BYTE * savedInstrPtr, GCCoverageI { ExecutableWriterHolder instrPtrWriterHolder((void*)instrPtr, 4); #ifdef TARGET_ARM - if (GetARMInstructionLength(savedInstrPtr) == 2) + if (GetARMInstructionLength(savedInstrPtr) == 2) *(WORD *)instrPtrWriterHolder.GetRW() = *(WORD *)savedInstrPtr; - else + else *(DWORD *)instrPtrWriterHolder.GetRW() = *(DWORD *)savedInstrPtr; #elif defined(TARGET_ARM64) *(DWORD *)instrPtrWriterHolder.GetRW() = *(DWORD *)savedInstrPtr; diff --git a/src/coreclr/vm/i386/jithelp.S b/src/coreclr/vm/i386/jithelp.S index facce7cacd3e..dc56da1d1779 100644 --- a/src/coreclr/vm/i386/jithelp.S +++ b/src/coreclr/vm/i386/jithelp.S @@ -377,10 +377,27 @@ LEAF_ENTRY JIT_WriteBarrierGroup, _TEXT ret LEAF_END JIT_WriteBarrierGroup, _TEXT -#ifdef FEATURE_USE_ASM_GC_WRITE_BARRIERS -// ******************************************************************************* -// Write barrier wrappers with fcall calling convention -// + .data + .align 4 + .global C_FUNC(JIT_WriteBarrierEAX_Loc) +C_FUNC(JIT_WriteBarrierEAX_Loc): + .word 0 + .text + +LEAF_ENTRY JIT_WriteBarrier_Callable, _TEXT + mov eax, edx + mov edx, ecx + push eax + call 1f +1: + pop eax +2: + add eax, offset _GLOBAL_OFFSET_TABLE_+1 // (2b - 1b) + mov eax, dword ptr [eax + C_FUNC(JIT_WriteBarrierEAX_Loc)@GOT] + xchg eax, dword ptr [esp] + ret +LEAF_END JIT_WriteBarrier_Callable, _TEXT + .macro UniversalWriteBarrierHelper name .align 4 @@ -392,6 +409,11 @@ LEAF_END JIT_\name, _TEXT .endm +#ifdef FEATURE_USE_ASM_GC_WRITE_BARRIERS +// ******************************************************************************* +// Write barrier wrappers with fcall calling convention +// + // Only define these if we're using the ASM GC write barriers; if this flag is not defined, // we'll use C++ versions of these write barriers. UniversalWriteBarrierHelper CheckedWriteBarrier diff --git a/src/coreclr/vm/i386/jithelp.asm b/src/coreclr/vm/i386/jithelp.asm index 3743ac3cbe02..3650b3f2afd6 100644 --- a/src/coreclr/vm/i386/jithelp.asm +++ b/src/coreclr/vm/i386/jithelp.asm @@ -411,15 +411,13 @@ ENDM ;******************************************************************************* ; Write barrier wrappers with fcall calling convention ; -UniversalWriteBarrierHelper MACRO name + + .data ALIGN 4 -PUBLIC @JIT_&name&@8 -@JIT_&name&@8 PROC - mov eax,edx - mov edx,ecx - jmp _JIT_&name&EAX@0 -@JIT_&name&@8 ENDP -ENDM + public _JIT_WriteBarrierEAX_Loc +_JIT_WriteBarrierEAX_Loc dd 0 + + .code ; WriteBarrierStart and WriteBarrierEnd are used to determine bounds of ; WriteBarrier functions so can determine if got AV in them. @@ -429,6 +427,25 @@ _JIT_WriteBarrierGroup@0 PROC ret _JIT_WriteBarrierGroup@0 ENDP + ALIGN 4 +PUBLIC @JIT_WriteBarrier_Callable@8 +@JIT_WriteBarrier_Callable@8 PROC + mov eax,edx + mov edx,ecx + jmp DWORD PTR [_JIT_WriteBarrierEAX_Loc] + +@JIT_WriteBarrier_Callable@8 ENDP + +UniversalWriteBarrierHelper MACRO name + ALIGN 4 +PUBLIC @JIT_&name&@8 +@JIT_&name&@8 PROC + mov eax,edx + mov edx,ecx + jmp _JIT_&name&EAX@0 +@JIT_&name&@8 ENDP +ENDM + ifdef FEATURE_USE_ASM_GC_WRITE_BARRIERS ; Only define these if we're using the ASM GC write barriers; if this flag is not defined, ; we'll use C++ versions of these write barriers. @@ -1233,6 +1250,8 @@ fremloopd: ; PatchedCodeStart and PatchedCodeEnd are used to determine bounds of patched code. ; + ALIGN 4 + _JIT_PatchedCodeStart@0 proc public ret _JIT_PatchedCodeStart@0 endp diff --git a/src/coreclr/vm/i386/jitinterfacex86.cpp b/src/coreclr/vm/i386/jitinterfacex86.cpp index 0e366bdbd1a8..0467f347aaac 100644 --- a/src/coreclr/vm/i386/jitinterfacex86.cpp +++ b/src/coreclr/vm/i386/jitinterfacex86.cpp @@ -1050,10 +1050,18 @@ void InitJITHelpers1() { BYTE * pfunc = (BYTE *) JIT_WriteBarrierReg_PreGrow; - BYTE * pBuf = (BYTE *)c_rgWriteBarriers[iBarrier]; + BYTE * pBuf = GetWriteBarrierCodeLocation((BYTE *)c_rgWriteBarriers[iBarrier]); int reg = c_rgWriteBarrierRegs[iBarrier]; - memcpy(pBuf, pfunc, 34); + BYTE * pBufRW = pBuf; + ExecutableWriterHolder barrierWriterHolder; + if (IsWriteBarrierCopyEnabled()) + { + barrierWriterHolder = ExecutableWriterHolder(pBuf, 34); + pBufRW = barrierWriterHolder.GetRW(); + } + + memcpy(pBufRW, pfunc, 34); // assert the copied code ends in a ret to make sure we got the right length _ASSERTE(pBuf[33] == 0xC3); @@ -1069,24 +1077,24 @@ void InitJITHelpers1() _ASSERTE(pBuf[0] == 0x89); // Update the reg field (bits 3..5) of the ModR/M byte of this instruction - pBuf[1] &= 0xc7; - pBuf[1] |= reg << 3; + pBufRW[1] &= 0xc7; + pBufRW[1] |= reg << 3; // Second instruction to patch is cmp reg, imm32 (low bound) _ASSERTE(pBuf[2] == 0x81); // Here the lowest three bits in ModR/M field are the register - pBuf[3] &= 0xf8; - pBuf[3] |= reg; + pBufRW[3] &= 0xf8; + pBufRW[3] |= reg; #ifdef WRITE_BARRIER_CHECK // Don't do the fancy optimization just jump to the old one // Use the slow one from time to time in a debug build because // there are some good asserts in the unoptimized one if ((g_pConfig->GetHeapVerifyLevel() & EEConfig::HEAPVERIFY_BARRIERCHECK) || DEBUG_RANDOM_BARRIER_CHECK) { - pfunc = &pBuf[0]; + pfunc = &pBufRW[0]; *pfunc++ = 0xE9; // JMP c_rgDebugWriteBarriers[iBarrier] - *((DWORD*) pfunc) = (BYTE*) c_rgDebugWriteBarriers[iBarrier] - (pfunc + sizeof(DWORD)); + *((DWORD*) pfunc) = (BYTE*) c_rgDebugWriteBarriers[iBarrier] - (&pBuf[1] + sizeof(DWORD)); } #endif // WRITE_BARRIER_CHECK } @@ -1132,7 +1140,7 @@ void ValidateWriteBarrierHelpers() #endif // WRITE_BARRIER_CHECK // first validate the PreGrow helper - BYTE* pWriteBarrierFunc = reinterpret_cast(JIT_WriteBarrierEAX); + BYTE* pWriteBarrierFunc = GetWriteBarrierCodeLocation(reinterpret_cast(JIT_WriteBarrierEAX)); // ephemeral region DWORD* pLocation = reinterpret_cast(&pWriteBarrierFunc[AnyGrow_EphemeralLowerBound]); @@ -1170,7 +1178,7 @@ void ValidateWriteBarrierHelpers() #endif //CODECOVERAGE /*********************************************************************/ -#define WriteBarrierIsPreGrow() (((BYTE *)JIT_WriteBarrierEAX)[10] == 0xc1) +#define WriteBarrierIsPreGrow() ((GetWriteBarrierCodeLocation((BYTE *)JIT_WriteBarrierEAX))[10] == 0xc1) /*********************************************************************/ @@ -1188,20 +1196,28 @@ int StompWriteBarrierEphemeral(bool /* isRuntimeSuspended */) #ifdef WRITE_BARRIER_CHECK // Don't do the fancy optimization if we are checking write barrier - if (((BYTE *)JIT_WriteBarrierEAX)[0] == 0xE9) // we are using slow write barrier + if ((GetWriteBarrierCodeLocation((BYTE *)JIT_WriteBarrierEAX))[0] == 0xE9) // we are using slow write barrier return stompWBCompleteActions; #endif // WRITE_BARRIER_CHECK // Update the lower bound. for (int iBarrier = 0; iBarrier < NUM_WRITE_BARRIERS; iBarrier++) { - BYTE * pBuf = (BYTE *)c_rgWriteBarriers[iBarrier]; + BYTE * pBuf = GetWriteBarrierCodeLocation((BYTE *)c_rgWriteBarriers[iBarrier]); + + BYTE * pBufRW = pBuf; + ExecutableWriterHolder barrierWriterHolder; + if (IsWriteBarrierCopyEnabled()) + { + barrierWriterHolder = ExecutableWriterHolder(pBuf, 42); + pBufRW = barrierWriterHolder.GetRW(); + } // assert there is in fact a cmp r/m32, imm32 there _ASSERTE(pBuf[2] == 0x81); // Update the immediate which is the lower bound of the ephemeral generation - size_t *pfunc = (size_t *) &pBuf[AnyGrow_EphemeralLowerBound]; + size_t *pfunc = (size_t *) &pBufRW[AnyGrow_EphemeralLowerBound]; //avoid trivial self modifying code if (*pfunc != (size_t) g_ephemeral_low) { @@ -1214,7 +1230,7 @@ int StompWriteBarrierEphemeral(bool /* isRuntimeSuspended */) _ASSERTE(pBuf[10] == 0x81); // Update the upper bound if we are using the PostGrow thunk. - pfunc = (size_t *) &pBuf[PostGrow_EphemeralUpperBound]; + pfunc = (size_t *) &pBufRW[PostGrow_EphemeralUpperBound]; //avoid trivial self modifying code if (*pfunc != (size_t) g_ephemeral_high) { @@ -1244,7 +1260,7 @@ int StompWriteBarrierResize(bool isRuntimeSuspended, bool bReqUpperBoundsCheck) #ifdef WRITE_BARRIER_CHECK // Don't do the fancy optimization if we are checking write barrier - if (((BYTE *)JIT_WriteBarrierEAX)[0] == 0xE9) // we are using slow write barrier + if ((GetWriteBarrierCodeLocation((BYTE *)JIT_WriteBarrierEAX))[0] == 0xE9) // we are using slow write barrier return stompWBCompleteActions; #endif // WRITE_BARRIER_CHECK @@ -1253,12 +1269,20 @@ int StompWriteBarrierResize(bool isRuntimeSuspended, bool bReqUpperBoundsCheck) for (int iBarrier = 0; iBarrier < NUM_WRITE_BARRIERS; iBarrier++) { - BYTE * pBuf = (BYTE *)c_rgWriteBarriers[iBarrier]; + BYTE * pBuf = GetWriteBarrierCodeLocation((BYTE *)c_rgWriteBarriers[iBarrier]); int reg = c_rgWriteBarrierRegs[iBarrier]; size_t *pfunc; - // Check if we are still using the pre-grow version of the write barrier. + BYTE * pBufRW = pBuf; + ExecutableWriterHolder barrierWriterHolder; + if (IsWriteBarrierCopyEnabled()) + { + barrierWriterHolder = ExecutableWriterHolder(pBuf, 42); + pBufRW = barrierWriterHolder.GetRW(); + } + + // Check if we are still using the pre-grow version of the write barrier. if (bWriteBarrierIsPreGrow) { // Check if we need to use the upper bounds checking barrier stub. @@ -1271,7 +1295,7 @@ int StompWriteBarrierResize(bool isRuntimeSuspended, bool bReqUpperBoundsCheck) } pfunc = (size_t *) JIT_WriteBarrierReg_PostGrow; - memcpy(pBuf, pfunc, 42); + memcpy(pBufRW, pfunc, 42); // assert the copied code ends in a ret to make sure we got the right length _ASSERTE(pBuf[41] == 0xC3); @@ -1287,35 +1311,35 @@ int StompWriteBarrierResize(bool isRuntimeSuspended, bool bReqUpperBoundsCheck) _ASSERTE(pBuf[0] == 0x89); // Update the reg field (bits 3..5) of the ModR/M byte of this instruction - pBuf[1] &= 0xc7; - pBuf[1] |= reg << 3; + pBufRW[1] &= 0xc7; + pBufRW[1] |= reg << 3; // Second instruction to patch is cmp reg, imm32 (low bound) _ASSERTE(pBuf[2] == 0x81); // Here the lowest three bits in ModR/M field are the register - pBuf[3] &= 0xf8; - pBuf[3] |= reg; + pBufRW[3] &= 0xf8; + pBufRW[3] |= reg; // Third instruction to patch is another cmp reg, imm32 (high bound) _ASSERTE(pBuf[10] == 0x81); // Here the lowest three bits in ModR/M field are the register - pBuf[11] &= 0xf8; - pBuf[11] |= reg; + pBufRW[11] &= 0xf8; + pBufRW[11] |= reg; bStompWriteBarrierEphemeral = true; // What we're trying to update is the offset field of a // cmp offset[edx], 0ffh instruction _ASSERTE(pBuf[22] == 0x80); - pfunc = (size_t *) &pBuf[PostGrow_CardTableFirstLocation]; + pfunc = (size_t *) &pBufRW[PostGrow_CardTableFirstLocation]; *pfunc = (size_t) g_card_table; // What we're trying to update is the offset field of a // mov offset[edx], 0ffh instruction _ASSERTE(pBuf[34] == 0xC6); - pfunc = (size_t *) &pBuf[PostGrow_CardTableSecondLocation]; + pfunc = (size_t *) &pBufRW[PostGrow_CardTableSecondLocation]; } else @@ -1324,14 +1348,14 @@ int StompWriteBarrierResize(bool isRuntimeSuspended, bool bReqUpperBoundsCheck) // cmp offset[edx], 0ffh instruction _ASSERTE(pBuf[14] == 0x80); - pfunc = (size_t *) &pBuf[PreGrow_CardTableFirstLocation]; + pfunc = (size_t *) &pBufRW[PreGrow_CardTableFirstLocation]; *pfunc = (size_t) g_card_table; // What we're trying to update is the offset field of a // mov offset[edx], 0ffh instruction _ASSERTE(pBuf[26] == 0xC6); - pfunc = (size_t *) &pBuf[PreGrow_CardTableSecondLocation]; + pfunc = (size_t *) &pBufRW[PreGrow_CardTableSecondLocation]; } } else @@ -1340,13 +1364,13 @@ int StompWriteBarrierResize(bool isRuntimeSuspended, bool bReqUpperBoundsCheck) // cmp offset[edx], 0ffh instruction _ASSERTE(pBuf[22] == 0x80); - pfunc = (size_t *) &pBuf[PostGrow_CardTableFirstLocation]; + pfunc = (size_t *) &pBufRW[PostGrow_CardTableFirstLocation]; *pfunc = (size_t) g_card_table; // What we're trying to update is the offset field of a // mov offset[edx], 0ffh instruction _ASSERTE(pBuf[34] == 0xC6); - pfunc = (size_t *) &pBuf[PostGrow_CardTableSecondLocation]; + pfunc = (size_t *) &pBufRW[PostGrow_CardTableSecondLocation]; } // Stick in the adjustment value. diff --git a/src/coreclr/vm/i386/stublinkerx86.cpp b/src/coreclr/vm/i386/stublinkerx86.cpp index 61c5dfd90cbf..564363053fc6 100644 --- a/src/coreclr/vm/i386/stublinkerx86.cpp +++ b/src/coreclr/vm/i386/stublinkerx86.cpp @@ -4829,7 +4829,7 @@ VOID StubLinkerCPU::EmitArrayOpStub(const ArrayOpScript* pArrayOpScript) X86EmitOp(0x8d, kEDX, elemBaseReg, elemOfs, elemScaledReg, elemScale); // call JIT_Writeable_Thunks_Buf.WriteBarrierReg[0] (== EAX) - X86EmitCall(NewExternalCodeLabel((LPVOID) &JIT_WriteBarrierEAX), 0); + X86EmitCall(NewExternalCodeLabel((LPVOID) GetWriteBarrierCodeLocation(&JIT_WriteBarrierEAX)), 0); } else #else // TARGET_AMD64 diff --git a/src/coreclr/vm/i386/stublinkerx86.h b/src/coreclr/vm/i386/stublinkerx86.h index af5244d07719..564c999975e7 100644 --- a/src/coreclr/vm/i386/stublinkerx86.h +++ b/src/coreclr/vm/i386/stublinkerx86.h @@ -536,7 +536,7 @@ struct StubPrecode { return rel32Decode(PTR_HOST_MEMBER_TADDR(StubPrecode, this, m_rel32)); } - +#ifndef DACCESS_COMPILE void ResetTargetInterlocked() { CONTRACTL @@ -562,6 +562,7 @@ struct StubPrecode { ExecutableWriterHolder rel32Holder(&m_rel32, 4); return rel32SetInterlocked(&m_rel32, rel32Holder.GetRW(), target, expected, (MethodDesc*)GetMethodDesc()); } +#endif // !DACCESS_COMPILE }; IN_TARGET_64BIT(static_assert_no_msg(offsetof(StubPrecode, m_movR10) == OFFSETOF_PRECODE_TYPE);) IN_TARGET_64BIT(static_assert_no_msg(offsetof(StubPrecode, m_type) == OFFSETOF_PRECODE_TYPE_MOV_R10);) @@ -646,6 +647,13 @@ struct FixupPrecode { return dac_cast(this) + (m_PrecodeChunkIndex + 1) * sizeof(FixupPrecode); } + size_t GetSizeRW() + { + LIMITED_METHOD_CONTRACT; + + return GetBase() + sizeof(void*) - dac_cast(this); + } + TADDR GetMethodDesc(); #else // HAS_FIXUP_PRECODE_CHUNKS TADDR GetMethodDesc() diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index a1e4d93d881d..882e2c29cef0 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -11875,7 +11875,7 @@ WORD CEEJitInfo::getRelocTypeHint(void * target) if (m_fAllowRel32) { // The JIT calls this method for data addresses only. It always uses REL32s for direct code targets. - if (IsPreferredExecutableRange(target)) + if (ExecutableAllocator::IsPreferredExecutableRange(target)) return IMAGE_REL_BASED_REL32; } #endif // TARGET_AMD64 diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index ca9d03c2141d..e071d0717d17 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -238,15 +238,10 @@ extern "C" FCDECL2(Object*, ChkCastAny_NoCacheLookup, CORINFO_CLASS_HANDLE type, extern "C" FCDECL2(Object*, IsInstanceOfAny_NoCacheLookup, CORINFO_CLASS_HANDLE type, Object* obj); extern "C" FCDECL2(LPVOID, Unbox_Helper, CORINFO_CLASS_HANDLE type, Object* obj); -#if defined(TARGET_ARM64) || defined(FEATURE_WRITEBARRIER_COPY) // ARM64 JIT_WriteBarrier uses speciall ABI and thus is not callable directly // Copied write barriers must be called at a different location extern "C" FCDECL2(VOID, JIT_WriteBarrier_Callable, Object **dst, Object *ref); #define WriteBarrier_Helper JIT_WriteBarrier_Callable -#else -// in other cases the regular JIT helper is callable. -#define WriteBarrier_Helper JIT_WriteBarrier -#endif extern "C" FCDECL1(void, JIT_InternalThrow, unsigned exceptNum); extern "C" FCDECL1(void*, JIT_InternalThrowFromHelper, unsigned exceptNum); @@ -344,28 +339,25 @@ EXTERN_C FCDECL2_VV(UINT64, JIT_LRsz, UINT64 num, int shift); #ifdef TARGET_X86 +#define ENUM_X86_WRITE_BARRIER_REGISTERS() \ + X86_WRITE_BARRIER_REGISTER(EAX) \ + X86_WRITE_BARRIER_REGISTER(ECX) \ + X86_WRITE_BARRIER_REGISTER(EBX) \ + X86_WRITE_BARRIER_REGISTER(ESI) \ + X86_WRITE_BARRIER_REGISTER(EDI) \ + X86_WRITE_BARRIER_REGISTER(EBP) + extern "C" { - void STDCALL JIT_CheckedWriteBarrierEAX(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_CheckedWriteBarrierEBX(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_CheckedWriteBarrierECX(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_CheckedWriteBarrierESI(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_CheckedWriteBarrierEDI(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_CheckedWriteBarrierEBP(); // JIThelp.asm/JIThelp.s - - void STDCALL JIT_DebugWriteBarrierEAX(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_DebugWriteBarrierEBX(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_DebugWriteBarrierECX(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_DebugWriteBarrierESI(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_DebugWriteBarrierEDI(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_DebugWriteBarrierEBP(); // JIThelp.asm/JIThelp.s - - void STDCALL JIT_WriteBarrierEAX(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_WriteBarrierEBX(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_WriteBarrierECX(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_WriteBarrierESI(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_WriteBarrierEDI(); // JIThelp.asm/JIThelp.s - void STDCALL JIT_WriteBarrierEBP(); // JIThelp.asm/JIThelp.s + +// JIThelp.asm/JIThelp.s +#define X86_WRITE_BARRIER_REGISTER(reg) \ + void STDCALL JIT_CheckedWriteBarrier##reg(); \ + void STDCALL JIT_DebugWriteBarrier##reg(); \ + void STDCALL JIT_WriteBarrier##reg(); + + ENUM_X86_WRITE_BARRIER_REGISTERS() +#undef X86_WRITE_BARRIER_REGISTER void STDCALL JIT_WriteBarrierGroup(); void STDCALL JIT_WriteBarrierGroup_End(); diff --git a/src/coreclr/vm/loaderallocator.cpp b/src/coreclr/vm/loaderallocator.cpp index 4f222be4a2c0..0a77e4445f06 100644 --- a/src/coreclr/vm/loaderallocator.cpp +++ b/src/coreclr/vm/loaderallocator.cpp @@ -1137,7 +1137,7 @@ void LoaderAllocator::Init(BaseDomain *pDomain, BYTE *pExecutableHeapMemory) _ASSERTE(dwTotalReserveMemSize <= VIRTUAL_ALLOC_RESERVE_GRANULARITY); #endif - BYTE * initReservedMem = ClrVirtualAllocExecutable(dwTotalReserveMemSize, MEM_RESERVE, PAGE_NOACCESS); + BYTE * initReservedMem = (BYTE*)ExecutableAllocator::Instance()->Reserve(dwTotalReserveMemSize); m_InitialReservedMemForLoaderHeaps = initReservedMem; @@ -1672,18 +1672,25 @@ void AssemblyLoaderAllocator::SetCollectible() { CONTRACTL { - THROWS; + NOTHROW; } CONTRACTL_END; m_IsCollectible = true; -#ifndef DACCESS_COMPILE - m_pShuffleThunkCache = new ShuffleThunkCache(m_pStubHeap); -#endif } #ifndef DACCESS_COMPILE +void AssemblyLoaderAllocator::Init(AppDomain* pAppDomain) +{ + m_Id.Init(); + LoaderAllocator::Init((BaseDomain *)pAppDomain); + if (IsCollectible()) + { + m_pShuffleThunkCache = new ShuffleThunkCache(m_pStubHeap); + } +} + #ifndef CROSSGEN_COMPILE AssemblyLoaderAllocator::~AssemblyLoaderAllocator() diff --git a/src/coreclr/vm/loaderallocator.inl b/src/coreclr/vm/loaderallocator.inl index a826675ccc93..993732d4010f 100644 --- a/src/coreclr/vm/loaderallocator.inl +++ b/src/coreclr/vm/loaderallocator.inl @@ -21,12 +21,6 @@ inline void GlobalLoaderAllocator::Init(BaseDomain *pDomain) LoaderAllocator::Init(pDomain, m_ExecutableHeapInstance); } -inline void AssemblyLoaderAllocator::Init(AppDomain* pAppDomain) -{ - m_Id.Init(); - LoaderAllocator::Init((BaseDomain *)pAppDomain); -} - inline BOOL LoaderAllocatorID::Equals(LoaderAllocatorID *pId) { LIMITED_METHOD_CONTRACT; diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index bd3984d8697c..db308ab208a8 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -4188,46 +4188,6 @@ c_CentralJumpCode = { }; #include -#elif defined(TARGET_AMD64) - -#include -static const struct CentralJumpCode { - BYTE m_movzxRAX[4]; - BYTE m_shlEAX[4]; - BYTE m_movRAX[2]; - MethodDesc* m_pBaseMD; - BYTE m_addR10RAX[3]; - BYTE m_jmp[1]; - INT32 m_rel32; - - inline void Setup(CentralJumpCode* pCodeRX, MethodDesc* pMD, PCODE target, LoaderAllocator *pLoaderAllocator) { - WRAPPER_NO_CONTRACT; - m_pBaseMD = pMD; - m_rel32 = rel32UsingJumpStub(&pCodeRX->m_rel32, target, pMD, pLoaderAllocator); - } - - inline BOOL CheckTarget(TADDR target) { - WRAPPER_NO_CONTRACT; - TADDR addr = rel32Decode(PTR_HOST_MEMBER_TADDR(CentralJumpCode, this, m_rel32)); - if (*PTR_BYTE(addr) == 0x48 && - *PTR_BYTE(addr+1) == 0xB8 && - *PTR_BYTE(addr+10) == 0xFF && - *PTR_BYTE(addr+11) == 0xE0) - { - addr = *PTR_TADDR(addr+2); - } - return (addr == target); - } -} -c_CentralJumpCode = { - { 0x48, 0x0F, 0xB6, 0xC0 }, // movzx rax,al - { 0x48, 0xC1, 0xE0, MethodDesc::ALIGNMENT_SHIFT }, // shl rax, MethodDesc::ALIGNMENT_SHIFT - { 0x49, 0xBA }, NULL, // mov r10, pBaseMD - { 0x4C, 0x03, 0xD0 }, // add r10,rax - { 0xE9 }, 0 // jmp PreStub -}; -#include - #elif defined(TARGET_ARM) #include diff --git a/src/coreclr/vm/precode.cpp b/src/coreclr/vm/precode.cpp index 80731c191e73..0bd2bd657f9a 100644 --- a/src/coreclr/vm/precode.cpp +++ b/src/coreclr/vm/precode.cpp @@ -480,7 +480,9 @@ void Precode::Reset() #ifdef HAS_FIXUP_PRECODE_CHUNKS if (t == PRECODE_FIXUP) { - size = sizeof(FixupPrecode) + sizeof(PTR_MethodDesc); + // The writeable size the Init method accesses is dynamic depending on + // the FixupPrecode members. + size = ((FixupPrecode*)this)->GetSizeRW(); } else #endif diff --git a/src/coreclr/vm/stackwalk.cpp b/src/coreclr/vm/stackwalk.cpp index 0971334af4d3..e61802b98495 100644 --- a/src/coreclr/vm/stackwalk.cpp +++ b/src/coreclr/vm/stackwalk.cpp @@ -713,14 +713,12 @@ UINT_PTR Thread::VirtualUnwindToFirstManagedCallFrame(T_CONTEXT* pContext) // get our caller's PSP, or our caller's caller's SP. while (!ExecutionManager::IsManagedCode(uControlPc)) { -#ifdef FEATURE_WRITEBARRIER_COPY if (IsIPInWriteBarrierCodeCopy(uControlPc)) { // Pretend we were executing the barrier function at its original location so that the unwinder can unwind the frame uControlPc = AdjustWriteBarrierIP(uControlPc); SetIP(pContext, uControlPc); } -#endif // FEATURE_WRITEBARRIER_COPY #ifndef TARGET_UNIX uControlPc = VirtualUnwindCallFrame(pContext); diff --git a/src/coreclr/vm/stublink.cpp b/src/coreclr/vm/stublink.cpp index 04a33e398261..304cb4fb35b4 100644 --- a/src/coreclr/vm/stublink.cpp +++ b/src/coreclr/vm/stublink.cpp @@ -846,7 +846,7 @@ Stub *StubLinker::Link(LoaderHeap *pHeap, DWORD flags) ); ASSERT(pStub != NULL); - bool fSuccess = EmitStub(pStub, globalsize, pHeap); + bool fSuccess = EmitStub(pStub, globalsize, size, pHeap); #ifdef STUBLINKER_GENERATES_UNWIND_INFO if (fSuccess) @@ -1007,13 +1007,13 @@ int StubLinker::CalculateSize(int* pGlobalSize) return globalsize + datasize; } -bool StubLinker::EmitStub(Stub* pStub, int globalsize, LoaderHeap* pHeap) +bool StubLinker::EmitStub(Stub* pStub, int globalsize, int totalSize, LoaderHeap* pHeap) { STANDARD_VM_CONTRACT; BYTE *pCode = (BYTE*)(pStub->GetBlob()); - ExecutableWriterHolder stubWriterHolder(pStub, sizeof(Stub)); + ExecutableWriterHolder stubWriterHolder(pStub, sizeof(Stub) + totalSize); Stub *pStubRW = stubWriterHolder.GetRW(); BYTE *pCodeRW = (BYTE*)(pStubRW->GetBlob()); @@ -2013,11 +2013,7 @@ VOID Stub::DeleteStub() FillMemory(this+1, m_numCodeBytes, 0xcc); #endif -#ifndef TARGET_UNIX - DeleteExecutable((BYTE*)GetAllocationBase()); -#else delete [] (BYTE*)GetAllocationBase(); -#endif } } @@ -2124,11 +2120,7 @@ Stub* Stub::NewStub(PTR_VOID pCode, DWORD flags) BYTE *pBlock; if (pHeap == NULL) { -#ifndef TARGET_UNIX - pBlock = new (executable) BYTE[totalSize]; -#else pBlock = new BYTE[totalSize]; -#endif } else { diff --git a/src/coreclr/vm/stublink.h b/src/coreclr/vm/stublink.h index 94326f9962ea..9613fd48f687 100644 --- a/src/coreclr/vm/stublink.h +++ b/src/coreclr/vm/stublink.h @@ -395,7 +395,7 @@ class StubLinker // Writes out the code element into memory following the // stub object. - bool EmitStub(Stub* pStub, int globalsize, LoaderHeap* pHeap); + bool EmitStub(Stub* pStub, int globalsize, int totalSize, LoaderHeap* pHeap); CodeRun *GetLastCodeRunIfAny(); diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index fa93110399d3..2c55f8770b01 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -1078,18 +1078,30 @@ DWORD_PTR Thread::OBJREF_HASH = OBJREF_TABSIZE; extern "C" void STDCALL JIT_PatchedCodeStart(); extern "C" void STDCALL JIT_PatchedCodeLast(); -#ifdef FEATURE_WRITEBARRIER_COPY - static void* s_barrierCopy = NULL; BYTE* GetWriteBarrierCodeLocation(VOID* barrier) { - return (BYTE*)s_barrierCopy + ((BYTE*)barrier - (BYTE*)JIT_PatchedCodeStart); + if (IsWriteBarrierCopyEnabled()) + { + return (BYTE*)PINSTRToPCODE((TADDR)s_barrierCopy + ((TADDR)barrier - (TADDR)JIT_PatchedCodeStart)); + } + else + { + return (BYTE*)barrier; + } } BOOL IsIPInWriteBarrierCodeCopy(PCODE controlPc) { - return (s_barrierCopy <= (void*)controlPc && (void*)controlPc < ((BYTE*)s_barrierCopy + ((BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart))); + if (IsWriteBarrierCopyEnabled()) + { + return (s_barrierCopy <= (void*)controlPc && (void*)controlPc < ((BYTE*)s_barrierCopy + ((BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart))); + } + else + { + return FALSE; + } } PCODE AdjustWriteBarrierIP(PCODE controlPc) @@ -1100,14 +1112,21 @@ PCODE AdjustWriteBarrierIP(PCODE controlPc) return (PCODE)JIT_PatchedCodeStart + (controlPc - (PCODE)s_barrierCopy); } +#ifdef TARGET_X86 +extern "C" void *JIT_WriteBarrierEAX_Loc; +#else extern "C" void *JIT_WriteBarrier_Loc; +#endif + #ifdef TARGET_ARM64 extern "C" void (*JIT_WriteBarrier_Table)(); extern "C" void *JIT_WriteBarrier_Loc = 0; extern "C" void *JIT_WriteBarrier_Table_Loc = 0; #endif // TARGET_ARM64 -#endif // FEATURE_WRITEBARRIER_COPY +#ifdef TARGET_ARM +extern "C" void *JIT_WriteBarrier_Loc = 0; +#endif // TARGET_ARM #ifndef TARGET_UNIX // g_TlsIndex is only used by the DAC. Disable optimizations around it to prevent it from getting optimized out. @@ -1138,50 +1157,80 @@ void InitThreadManager() _ASSERTE_ALL_BUILDS("clr/src/VM/threads.cpp", (BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart > (ptrdiff_t)0); _ASSERTE_ALL_BUILDS("clr/src/VM/threads.cpp", (BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart < (ptrdiff_t)GetOsPageSize()); -#ifdef FEATURE_WRITEBARRIER_COPY - s_barrierCopy = ClrVirtualAlloc(NULL, g_SystemInfo.dwAllocationGranularity, MEM_COMMIT, PAGE_EXECUTE_READWRITE); - if (s_barrierCopy == NULL) + if (IsWriteBarrierCopyEnabled()) { - _ASSERTE(!"ClrVirtualAlloc of GC barrier code page failed"); - COMPlusThrowWin32(); - } + s_barrierCopy = ExecutableAllocator::Instance()->Reserve(g_SystemInfo.dwAllocationGranularity); + ExecutableAllocator::Instance()->Commit(s_barrierCopy, g_SystemInfo.dwAllocationGranularity, true); + if (s_barrierCopy == NULL) + { + _ASSERTE(!"Allocation of GC barrier code page failed"); + COMPlusThrowWin32(); + } - { - size_t writeBarrierSize = (BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart; - ExecutableWriterHolder barrierWriterHolder(s_barrierCopy, writeBarrierSize); - memcpy(barrierWriterHolder.GetRW(), (BYTE*)JIT_PatchedCodeStart, writeBarrierSize); - } + { + size_t writeBarrierSize = (BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart; + ExecutableWriterHolder barrierWriterHolder(s_barrierCopy, writeBarrierSize); + memcpy(barrierWriterHolder.GetRW(), (BYTE*)JIT_PatchedCodeStart, writeBarrierSize); + } - // Store the JIT_WriteBarrier copy location to a global variable so that helpers - // can jump to it. - JIT_WriteBarrier_Loc = GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier); + // Store the JIT_WriteBarrier copy location to a global variable so that helpers + // can jump to it. +#ifdef TARGET_X86 + JIT_WriteBarrierEAX_Loc = GetWriteBarrierCodeLocation((void*)JIT_WriteBarrierEAX); - SetJitHelperFunction(CORINFO_HELP_ASSIGN_REF, GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier)); +#define X86_WRITE_BARRIER_REGISTER(reg) \ + SetJitHelperFunction(CORINFO_HELP_ASSIGN_REF_##reg, GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier##reg)); \ + ETW::MethodLog::StubInitialized((ULONGLONG)GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier##reg), W("@WriteBarrier" #reg)); -#ifdef TARGET_ARM64 - // Store the JIT_WriteBarrier_Table copy location to a global variable so that it can be updated. - JIT_WriteBarrier_Table_Loc = GetWriteBarrierCodeLocation((void*)&JIT_WriteBarrier_Table); + ENUM_X86_WRITE_BARRIER_REGISTERS() - SetJitHelperFunction(CORINFO_HELP_CHECKED_ASSIGN_REF, GetWriteBarrierCodeLocation((void*)JIT_CheckedWriteBarrier)); - SetJitHelperFunction(CORINFO_HELP_ASSIGN_BYREF, GetWriteBarrierCodeLocation((void*)JIT_ByRefWriteBarrier)); -#endif // TARGET_ARM64 +#undef X86_WRITE_BARRIER_REGISTER -#else // FEATURE_WRITEBARRIER_COPY +#else // TARGET_X86 + JIT_WriteBarrier_Loc = GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier); +#endif // TARGET_X86 + SetJitHelperFunction(CORINFO_HELP_ASSIGN_REF, GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier)); + ETW::MethodLog::StubInitialized((ULONGLONG)GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier), W("@WriteBarrier")); - // I am using virtual protect to cover the entire range that this code falls in. - // +#ifdef TARGET_ARM64 + // Store the JIT_WriteBarrier_Table copy location to a global variable so that it can be updated. + JIT_WriteBarrier_Table_Loc = GetWriteBarrierCodeLocation((void*)&JIT_WriteBarrier_Table); +#endif // TARGET_ARM64 - // We could reset it to non-writeable inbetween GCs and such, but then we'd have to keep on re-writing back and forth, - // so instead we'll leave it writable from here forward. +#if defined(TARGET_ARM64) || defined(TARGET_ARM) + SetJitHelperFunction(CORINFO_HELP_CHECKED_ASSIGN_REF, GetWriteBarrierCodeLocation((void*)JIT_CheckedWriteBarrier)); + ETW::MethodLog::StubInitialized((ULONGLONG)GetWriteBarrierCodeLocation((void*)JIT_CheckedWriteBarrier), W("@CheckedWriteBarrier")); + SetJitHelperFunction(CORINFO_HELP_ASSIGN_BYREF, GetWriteBarrierCodeLocation((void*)JIT_ByRefWriteBarrier)); + ETW::MethodLog::StubInitialized((ULONGLONG)GetWriteBarrierCodeLocation((void*)JIT_ByRefWriteBarrier), W("@ByRefWriteBarrier")); +#endif // TARGET_ARM64 || TARGET_ARM - DWORD oldProt; - if (!ClrVirtualProtect((void *)JIT_PatchedCodeStart, (BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart, - PAGE_EXECUTE_READWRITE, &oldProt)) + } + else { - _ASSERTE(!"ClrVirtualProtect of code page failed"); - COMPlusThrowWin32(); + // I am using virtual protect to cover the entire range that this code falls in. + // + + // We could reset it to non-writeable inbetween GCs and such, but then we'd have to keep on re-writing back and forth, + // so instead we'll leave it writable from here forward. + + DWORD oldProt; + if (!ClrVirtualProtect((void *)JIT_PatchedCodeStart, (BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart, + PAGE_EXECUTE_READWRITE, &oldProt)) + { + _ASSERTE(!"ClrVirtualProtect of code page failed"); + COMPlusThrowWin32(); + } + +#ifdef TARGET_X86 + JIT_WriteBarrierEAX_Loc = (void*)JIT_WriteBarrierEAX; +#else + JIT_WriteBarrier_Loc = (void*)JIT_WriteBarrier; +#endif +#ifdef TARGET_ARM64 + // Store the JIT_WriteBarrier_Table copy location to a global variable so that it can be updated. + JIT_WriteBarrier_Table_Loc = (void*)&JIT_WriteBarrier_Table; +#endif // TARGET_ARM64 } -#endif // FEATURE_WRITEBARRIER_COPY #ifndef TARGET_UNIX _ASSERTE(GetThreadNULLOk() == NULL); diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index d18b21d58f95..7d600dab5eda 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -6271,18 +6271,23 @@ class ThreadStateNCStackHolder BOOL Debug_IsLockedViaThreadSuspension(); -#ifdef FEATURE_WRITEBARRIER_COPY +inline BOOL IsWriteBarrierCopyEnabled() +{ +#ifdef DACCESS_COMPILE + return FALSE; +#else // DACCESS_COMPILE +#ifdef HOST_OSX + return TRUE; +#else + return ExecutableAllocator::IsWXORXEnabled(); +#endif +#endif // DACCESS_COMPILE +} BYTE* GetWriteBarrierCodeLocation(VOID* barrier); BOOL IsIPInWriteBarrierCodeCopy(PCODE controlPc); PCODE AdjustWriteBarrierIP(PCODE controlPc); -#else // FEATURE_WRITEBARRIER_COPY - -#define GetWriteBarrierCodeLocation(barrier) ((BYTE*)(barrier)) - -#endif // FEATURE_WRITEBARRIER_COPY - #if !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE) extern thread_local Thread* t_pStackWalkerWalkingThread; #define SET_THREAD_TYPE_STACKWALKER(pThread) t_pStackWalkerWalkingThread = pThread diff --git a/src/coreclr/vm/virtualcallstub.cpp b/src/coreclr/vm/virtualcallstub.cpp index 95d568d641c7..3af4c52afc9b 100644 --- a/src/coreclr/vm/virtualcallstub.cpp +++ b/src/coreclr/vm/virtualcallstub.cpp @@ -641,7 +641,7 @@ void VirtualCallStubManager::Init(BaseDomain *pDomain, LoaderAllocator *pLoaderA dwTotalReserveMemSize); } - initReservedMem = ClrVirtualAllocExecutable (dwTotalReserveMemSize, MEM_RESERVE, PAGE_NOACCESS); + initReservedMem = (BYTE*)ExecutableAllocator::Instance()->Reserve(dwTotalReserveMemSize); m_initialReservedMemForHeaps = (BYTE *) initReservedMem; @@ -2766,11 +2766,7 @@ DispatchHolder *VirtualCallStubManager::GenerateDispatchStub(PCODE ad } #endif - ExecutableWriterHolder dispatchWriterHolder(holder, sizeof(DispatchHolder) -#ifdef TARGET_AMD64 - + sizeof(DispatchStubShort) -#endif - ); + ExecutableWriterHolder dispatchWriterHolder(holder, dispatchHolderSize); dispatchWriterHolder.GetRW()->Initialize(holder, addrOfCode, addrOfFail, (size_t)pMTExpected @@ -2833,9 +2829,9 @@ DispatchHolder *VirtualCallStubManager::GenerateDispatchStubLong(PCODE } CONTRACT_END; //allocate from the requisite heap and copy the template over it. - DispatchHolder * holder = (DispatchHolder*) (void*) - dispatch_heap->AllocAlignedMem(DispatchHolder::GetHolderSize(DispatchStub::e_TYPE_LONG), CODE_SIZE_ALIGN); - ExecutableWriterHolder dispatchWriterHolder(holder, sizeof(DispatchHolder) + sizeof(DispatchStubLong)); + size_t dispatchHolderSize = DispatchHolder::GetHolderSize(DispatchStub::e_TYPE_LONG); + DispatchHolder * holder = (DispatchHolder*) (void*)dispatch_heap->AllocAlignedMem(dispatchHolderSize, CODE_SIZE_ALIGN); + ExecutableWriterHolder dispatchWriterHolder(holder, dispatchHolderSize); dispatchWriterHolder.GetRW()->Initialize(holder, addrOfCode, addrOfFail, From bb3982239e11d559f839aba445ea7d698084e623 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sun, 11 Jul 2021 08:24:08 -0700 Subject: [PATCH 008/133] Adding more tests for the generic math feature (#55377) * Fixing some issues in the generic math implementation * Adding generic math tests for integer types --- .../System.Private.CoreLib/src/System/Byte.cs | 12 +- .../System.Private.CoreLib/src/System/Char.cs | 12 +- .../src/System/Decimal.cs | 8 +- .../src/System/Double.cs | 8 +- .../System.Private.CoreLib/src/System/Half.cs | 8 +- .../src/System/IInteger.cs | 4 +- .../src/System/Int16.cs | 18 +- .../src/System/Int32.cs | 8 +- .../src/System/Int64.cs | 16 +- .../src/System/IntPtr.cs | 38 +- .../src/System/SByte.cs | 18 +- .../src/System/Single.cs | 8 +- .../src/System/UInt16.cs | 12 +- .../src/System/UInt32.cs | 16 +- .../src/System/UInt64.cs | 16 +- .../src/System/UIntPtr.cs | 42 +- .../System.Runtime/ref/System.Runtime.cs | 44 +- .../tests/System.Runtime.Tests.csproj | 22 +- .../tests/System/ByteTests.GenericMath.cs | 1175 +++++++++++ .../tests/System/CharTests.GenericMath.cs | 1057 ++++++++++ .../tests/System/GenericMathHelpers.cs | 228 +++ .../tests/System/GenericMathTests.cs | 73 - .../tests/System/Int16Tests.GenericMath.cs | 1181 +++++++++++ .../tests/System/Int32GenericMathTests.cs | 35 - .../tests/System/Int32Tests.GenericMath.cs | 1181 +++++++++++ .../tests/System/Int64Tests.GenericMath.cs | 1181 +++++++++++ .../tests/System/IntPtrTests.GenericMath.cs | 1746 +++++++++++++++++ .../tests/System/SByteTests.GenericMath.cs | 1181 +++++++++++ .../tests/System/UInt16Tests.GenericMath.cs | 1175 +++++++++++ .../tests/System/UInt32Tests.GenericMath.cs | 1175 +++++++++++ .../tests/System/UInt64Tests.GenericMath.cs | 1175 +++++++++++ .../tests/System/UIntPtrTests.GenericMath.cs | 1695 ++++++++++++++++ 32 files changed, 14313 insertions(+), 255 deletions(-) create mode 100644 src/libraries/System.Runtime/tests/System/ByteTests.GenericMath.cs create mode 100644 src/libraries/System.Runtime/tests/System/CharTests.GenericMath.cs create mode 100644 src/libraries/System.Runtime/tests/System/GenericMathHelpers.cs delete mode 100644 src/libraries/System.Runtime/tests/System/GenericMathTests.cs create mode 100644 src/libraries/System.Runtime/tests/System/Int16Tests.GenericMath.cs delete mode 100644 src/libraries/System.Runtime/tests/System/Int32GenericMathTests.cs create mode 100644 src/libraries/System.Runtime/tests/System/Int32Tests.GenericMath.cs create mode 100644 src/libraries/System.Runtime/tests/System/Int64Tests.GenericMath.cs create mode 100644 src/libraries/System.Runtime/tests/System/IntPtrTests.GenericMath.cs create mode 100644 src/libraries/System.Runtime/tests/System/SByteTests.GenericMath.cs create mode 100644 src/libraries/System.Runtime/tests/System/UInt16Tests.GenericMath.cs create mode 100644 src/libraries/System.Runtime/tests/System/UInt32Tests.GenericMath.cs create mode 100644 src/libraries/System.Runtime/tests/System/UInt64Tests.GenericMath.cs create mode 100644 src/libraries/System.Runtime/tests/System/UIntPtrTests.GenericMath.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/Byte.cs b/src/libraries/System.Private.CoreLib/src/System/Byte.cs index 39bfeae2a7e4..3d5b448159a6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Byte.cs @@ -313,11 +313,11 @@ static byte IBinaryInteger.PopCount(byte value) => (byte)BitOperations.PopCount(value); [RequiresPreviewFeatures] - static byte IBinaryInteger.RotateLeft(byte value, byte rotateAmount) + static byte IBinaryInteger.RotateLeft(byte value, int rotateAmount) => (byte)((value << (rotateAmount & 7)) | (value >> ((8 - rotateAmount) & 7))); [RequiresPreviewFeatures] - static byte IBinaryInteger.RotateRight(byte value, byte rotateAmount) + static byte IBinaryInteger.RotateRight(byte value, int rotateAmount) => (byte)((value >> (rotateAmount & 7)) | (value << ((8 - rotateAmount) & 7))); [RequiresPreviewFeatures] @@ -382,11 +382,11 @@ static byte IBinaryNumber.Log2(byte value) [RequiresPreviewFeatures] static byte IDecrementOperators.operator --(byte value) - => value--; + => --value; // [RequiresPreviewFeatures] // static checked byte IDecrementOperators.operator --(byte value) - // => checked(value--); + // => checked(--value); // // IDivisionOperators @@ -418,11 +418,11 @@ static byte IBinaryNumber.Log2(byte value) [RequiresPreviewFeatures] static byte IIncrementOperators.operator ++(byte value) - => value++; + => ++value; // [RequiresPreviewFeatures] // static checked byte IIncrementOperators.operator ++(byte value) - // => checked(value++); + // => checked(++value); // // IMinMaxValue diff --git a/src/libraries/System.Private.CoreLib/src/System/Char.cs b/src/libraries/System.Private.CoreLib/src/System/Char.cs index 2f58b54b7460..26ff3399fe10 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Char.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Char.cs @@ -1093,11 +1093,11 @@ static char IBinaryInteger.PopCount(char value) => (char)BitOperations.PopCount(value); [RequiresPreviewFeatures] - static char IBinaryInteger.RotateLeft(char value, char rotateAmount) + static char IBinaryInteger.RotateLeft(char value, int rotateAmount) => (char)((value << (rotateAmount & 15)) | (value >> ((16 - rotateAmount) & 15))); [RequiresPreviewFeatures] - static char IBinaryInteger.RotateRight(char value, char rotateAmount) + static char IBinaryInteger.RotateRight(char value, int rotateAmount) => (char)((value >> (rotateAmount & 15)) | (value << ((16 - rotateAmount) & 15))); [RequiresPreviewFeatures] @@ -1162,11 +1162,11 @@ static char IBinaryNumber.Log2(char value) [RequiresPreviewFeatures] static char IDecrementOperators.operator --(char value) - => value--; + => --value; // [RequiresPreviewFeatures] // static checked char IDecrementOperators.operator --(char value) - // => checked(value--); + // => checked(--value); // // IDivisionOperators @@ -1198,11 +1198,11 @@ static char IBinaryNumber.Log2(char value) [RequiresPreviewFeatures] static char IIncrementOperators.operator ++(char value) - => value++; + => ++value; // [RequiresPreviewFeatures] // static checked char IIncrementOperators.operator ++(char value) - // => checked(value++); + // => checked(++value); // // IMinMaxValue diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs index e5f26f2ff014..5c4f30795cd6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs @@ -1126,11 +1126,11 @@ object IConvertible.ToType(Type type, IFormatProvider? provider) [RequiresPreviewFeatures] static decimal IDecrementOperators.operator --(decimal value) - => value--; + => --value; // [RequiresPreviewFeatures] // static checked decimal IDecrementOperators.operator --(decimal value) - // => checked(value--); + // => checked(--value); // // IDivisionOperators @@ -1162,11 +1162,11 @@ object IConvertible.ToType(Type type, IFormatProvider? provider) [RequiresPreviewFeatures] static decimal IIncrementOperators.operator ++(decimal value) - => value++; + => ++value; // [RequiresPreviewFeatures] // static checked decimal IIncrementOperators.operator ++(decimal value) - // => checked(value++); + // => checked(++value); // // IMinMaxValue diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index 85d5f8c36f11..10178510f633 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -550,11 +550,11 @@ static double IBinaryNumber.Log2(double value) [RequiresPreviewFeatures] static double IDecrementOperators.operator --(double value) - => value--; + => --value; // [RequiresPreviewFeatures] // static checked double IDecrementOperators.operator --(double value) - // => checked(value--); + // => checked(--value); // // IDivisionOperators @@ -828,11 +828,11 @@ static double IFloatingPoint.Truncate(double x) [RequiresPreviewFeatures] static double IIncrementOperators.operator ++(double value) - => value++; + => ++value; // [RequiresPreviewFeatures] // static checked double IIncrementOperators.operator ++(double value) - // => checked(value++); + // => checked(++value); // // IMinMaxValue diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index ea64c10f8353..c70cb712bfe6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -802,7 +802,7 @@ static Half IBinaryNumber.Log2(Half value) static Half IDecrementOperators.operator --(Half value) { var tmp = (float)value; - tmp--; + --tmp; return (Half)tmp; } @@ -810,7 +810,7 @@ static Half IBinaryNumber.Log2(Half value) // static checked Half IDecrementOperators.operator --(Half value) // { // var tmp = (float)value; - // tmp--; + // --tmp; // return (Half)tmp; // } @@ -1132,7 +1132,7 @@ static Half IFloatingPoint.Truncate(Half x) static Half IIncrementOperators.operator ++(Half value) { var tmp = (float)value; - tmp++; + ++tmp; return (Half)tmp; } @@ -1140,7 +1140,7 @@ static Half IFloatingPoint.Truncate(Half x) // static checked Half IIncrementOperators.operator ++(Half value) // { // var tmp = (float)value; - // tmp++; + // ++tmp; // return (Half)tmp; // } diff --git a/src/libraries/System.Private.CoreLib/src/System/IInteger.cs b/src/libraries/System.Private.CoreLib/src/System/IInteger.cs index a93b439625e9..89ec036bb147 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IInteger.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IInteger.cs @@ -31,13 +31,13 @@ public interface IBinaryInteger /// The value which is rotated left by . /// The amount by which is rotated left. /// The result of rotating left by . - static abstract TSelf RotateLeft(TSelf value, TSelf rotateAmount); + static abstract TSelf RotateLeft(TSelf value, int rotateAmount); /// Rotates a value right by a given amount. /// The value which is rotated right by . /// The amount by which is rotated right. /// The result of rotating right by . - static abstract TSelf RotateRight(TSelf value, TSelf rotateAmount); + static abstract TSelf RotateRight(TSelf value, int rotateAmount); /// Computes the number of trailing zeros in a value. /// The value whose trailing zeroes are to be counted. diff --git a/src/libraries/System.Private.CoreLib/src/System/Int16.cs b/src/libraries/System.Private.CoreLib/src/System/Int16.cs index f42c84de2bd8..909b37f77b34 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int16.cs @@ -316,12 +316,12 @@ static short IBinaryInteger.PopCount(short value) => (short)BitOperations.PopCount((ushort)value); [RequiresPreviewFeatures] - static short IBinaryInteger.RotateLeft(short value, short rotateAmount) - => (short)((value << (rotateAmount & 15)) | (value >> ((16 - rotateAmount) & 15))); + static short IBinaryInteger.RotateLeft(short value, int rotateAmount) + => (short)((value << (rotateAmount & 15)) | ((ushort)value >> ((16 - rotateAmount) & 15))); [RequiresPreviewFeatures] - static short IBinaryInteger.RotateRight(short value, short rotateAmount) - => (short)((value >> (rotateAmount & 15)) | (value << ((16 - rotateAmount) & 15))); + static short IBinaryInteger.RotateRight(short value, int rotateAmount) + => (short)(((ushort)value >> (rotateAmount & 15)) | (value << ((16 - rotateAmount) & 15))); [RequiresPreviewFeatures] static short IBinaryInteger.TrailingZeroCount(short value) @@ -391,11 +391,11 @@ static short IBinaryNumber.Log2(short value) [RequiresPreviewFeatures] static short IDecrementOperators.operator --(short value) - => value--; + => --value; // [RequiresPreviewFeatures] // static checked short IDecrementOperators.operator --(short value) - // => checked(value--); + // => checked(--value); // // IDivisionOperators @@ -427,11 +427,11 @@ static short IBinaryNumber.Log2(short value) [RequiresPreviewFeatures] static short IIncrementOperators.operator ++(short value) - => value++; + => ++value; // [RequiresPreviewFeatures] // static checked short IIncrementOperators.operator ++(short value) - // => checked(value++); + // => checked(++value); // // IMinMaxValue @@ -565,7 +565,7 @@ static short INumber.CreateSaturating(TOther value) { if (typeof(TOther) == typeof(byte)) { - return (short)(object)value; + return (byte)(object)value; } else if (typeof(TOther) == typeof(char)) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Int32.cs b/src/libraries/System.Private.CoreLib/src/System/Int32.cs index d8514bc89f7e..68c6a70cdb04 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int32.cs @@ -383,11 +383,11 @@ static int IBinaryNumber.Log2(int value) [RequiresPreviewFeatures] static int IDecrementOperators.operator --(int value) - => value--; + => --value; // [RequiresPreviewFeatures] // static checked int IDecrementOperators.operator --(int value) - // => checked(value--); + // => checked(--value); // // IDivisionOperators @@ -419,11 +419,11 @@ static int IBinaryNumber.Log2(int value) [RequiresPreviewFeatures] static int IIncrementOperators.operator ++(int value) - => value++; + => ++value; // [RequiresPreviewFeatures] // static checked int IIncrementOperators.operator ++(int value) - // => checked(value++); + // => checked(++value); // // IMinMaxValue diff --git a/src/libraries/System.Private.CoreLib/src/System/Int64.cs b/src/libraries/System.Private.CoreLib/src/System/Int64.cs index c0420f5052e5..1646cf96457e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int64.cs @@ -295,12 +295,12 @@ static long IBinaryInteger.PopCount(long value) => BitOperations.PopCount((ulong)value); [RequiresPreviewFeatures] - static long IBinaryInteger.RotateLeft(long value, long rotateAmount) - => (long)BitOperations.RotateLeft((ulong)value, (int)rotateAmount); + static long IBinaryInteger.RotateLeft(long value, int rotateAmount) + => (long)BitOperations.RotateLeft((ulong)value, rotateAmount); [RequiresPreviewFeatures] - static long IBinaryInteger.RotateRight(long value, long rotateAmount) - => (long)BitOperations.RotateRight((ulong)value, (int)rotateAmount); + static long IBinaryInteger.RotateRight(long value, int rotateAmount) + => (long)BitOperations.RotateRight((ulong)value, rotateAmount); [RequiresPreviewFeatures] static long IBinaryInteger.TrailingZeroCount(long value) @@ -370,11 +370,11 @@ static long IBinaryNumber.Log2(long value) [RequiresPreviewFeatures] static long IDecrementOperators.operator --(long value) - => value--; + => --value; // [RequiresPreviewFeatures] // static checked long IDecrementOperators.operator --(long value) - // => checked(value--); + // => checked(--value); // // IDivisionOperators @@ -406,11 +406,11 @@ static long IBinaryNumber.Log2(long value) [RequiresPreviewFeatures] static long IIncrementOperators.operator ++(long value) - => value++; + => ++value; // [RequiresPreviewFeatures] // static checked long IIncrementOperators.operator ++(long value) - // => checked(value++); + // => checked(++value); // // IMinMaxValue diff --git a/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs b/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs index f64effb167ad..a9592617b2eb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs @@ -278,11 +278,11 @@ static nint IBinaryInteger.LeadingZeroCount(nint value) { if (Environment.Is64BitProcess) { - return BitOperations.LeadingZeroCount((uint)value); + return BitOperations.LeadingZeroCount((ulong)value); } else { - return BitOperations.LeadingZeroCount((ulong)value); + return BitOperations.LeadingZeroCount((uint)value); } } @@ -291,38 +291,38 @@ static nint IBinaryInteger.PopCount(nint value) { if (Environment.Is64BitProcess) { - return BitOperations.PopCount((uint)value); + return BitOperations.PopCount((ulong)value); } else { - return BitOperations.PopCount((ulong)value); + return BitOperations.PopCount((uint)value); } } [RequiresPreviewFeatures] - static nint IBinaryInteger.RotateLeft(nint value, nint rotateAmount) + static nint IBinaryInteger.RotateLeft(nint value, int rotateAmount) { if (Environment.Is64BitProcess) { - return (nint)BitOperations.RotateLeft((uint)value, (int)rotateAmount); + return (nint)BitOperations.RotateLeft((ulong)value, rotateAmount); } else { - return (nint)BitOperations.RotateLeft((ulong)value, (int)rotateAmount); + return (nint)BitOperations.RotateLeft((uint)value, rotateAmount); } } [RequiresPreviewFeatures] - static nint IBinaryInteger.RotateRight(nint value, nint rotateAmount) + static nint IBinaryInteger.RotateRight(nint value, int rotateAmount) { if (Environment.Is64BitProcess) { - return (nint)BitOperations.RotateRight((uint)value, (int)rotateAmount); + return (nint)BitOperations.RotateRight((ulong)value, rotateAmount); } else { - return (nint)BitOperations.RotateRight((ulong)value, (int)rotateAmount); + return (nint)BitOperations.RotateRight((uint)value, rotateAmount); } } @@ -331,11 +331,11 @@ static nint IBinaryInteger.TrailingZeroCount(nint value) { if (Environment.Is64BitProcess) { - return BitOperations.TrailingZeroCount((uint)value); + return BitOperations.TrailingZeroCount((ulong)value); } else { - return BitOperations.TrailingZeroCount((ulong)value); + return BitOperations.TrailingZeroCount((uint)value); } } @@ -357,11 +357,11 @@ static nint IBinaryNumber.Log2(nint value) if (Environment.Is64BitProcess) { - return BitOperations.Log2((uint)value); + return BitOperations.Log2((ulong)value); } else { - return BitOperations.Log2((ulong)value); + return BitOperations.Log2((uint)value); } } @@ -411,11 +411,11 @@ static nint IBinaryNumber.Log2(nint value) [RequiresPreviewFeatures] static nint IDecrementOperators.operator --(nint value) - => value--; + => --value; // [RequiresPreviewFeatures] // static checked nint IDecrementOperators.operator --(nint value) - // => checked(value--); + // => checked(--value); // // IDivisionOperators @@ -447,11 +447,11 @@ static nint IBinaryNumber.Log2(nint value) [RequiresPreviewFeatures] static nint IIncrementOperators.operator ++(nint value) - => value++; + => ++value; // [RequiresPreviewFeatures] // static checked nint IIncrementOperators.operator ++(nint value) - // => checked(value++); + // => checked(++value); // // IMinMaxValue @@ -585,7 +585,7 @@ static nint INumber.CreateSaturating(TOther value) { if (typeof(TOther) == typeof(byte)) { - return (nint)(object)value; + return (byte)(object)value; } else if (typeof(TOther) == typeof(char)) { diff --git a/src/libraries/System.Private.CoreLib/src/System/SByte.cs b/src/libraries/System.Private.CoreLib/src/System/SByte.cs index cec0716214ff..ce811d0cffd9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SByte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SByte.cs @@ -318,19 +318,19 @@ object IConvertible.ToType(Type type, IFormatProvider? provider) [RequiresPreviewFeatures] static sbyte IBinaryInteger.LeadingZeroCount(sbyte value) - => (sbyte)(BitOperations.LeadingZeroCount((byte)value) - 16); + => (sbyte)(BitOperations.LeadingZeroCount((byte)value) - 24); [RequiresPreviewFeatures] static sbyte IBinaryInteger.PopCount(sbyte value) => (sbyte)BitOperations.PopCount((byte)value); [RequiresPreviewFeatures] - static sbyte IBinaryInteger.RotateLeft(sbyte value, sbyte rotateAmount) - => (sbyte)((value << (rotateAmount & 7)) | (value >> ((8 - rotateAmount) & 7))); + static sbyte IBinaryInteger.RotateLeft(sbyte value, int rotateAmount) + => (sbyte)((value << (rotateAmount & 7)) | ((byte)value >> ((8 - rotateAmount) & 7))); [RequiresPreviewFeatures] - static sbyte IBinaryInteger.RotateRight(sbyte value, sbyte rotateAmount) - => (sbyte)((value >> (rotateAmount & 7)) | (value << ((8 - rotateAmount) & 7))); + static sbyte IBinaryInteger.RotateRight(sbyte value, int rotateAmount) + => (sbyte)(((byte)value >> (rotateAmount & 7)) | (value << ((8 - rotateAmount) & 7))); [RequiresPreviewFeatures] static sbyte IBinaryInteger.TrailingZeroCount(sbyte value) @@ -400,11 +400,11 @@ static sbyte IBinaryNumber.Log2(sbyte value) [RequiresPreviewFeatures] static sbyte IDecrementOperators.operator --(sbyte value) - => value--; + => --value; // [RequiresPreviewFeatures] // static checked sbyte IDecrementOperators.operator --(sbyte value) - // => checked(value--); + // => checked(--value); // // IDivisionOperators @@ -436,11 +436,11 @@ static sbyte IBinaryNumber.Log2(sbyte value) [RequiresPreviewFeatures] static sbyte IIncrementOperators.operator ++(sbyte value) - => value++; + => ++value; // [RequiresPreviewFeatures] // static checked sbyte IIncrementOperators.operator ++(sbyte value) - // => checked(value++); + // => checked(++value); // // IMinMaxValue diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index 92dc3a9b6dfb..59dbddd253cf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -542,11 +542,11 @@ static float IBinaryNumber.Log2(float value) [RequiresPreviewFeatures] static float IDecrementOperators.operator --(float value) - => value--; + => --value; // [RequiresPreviewFeatures] // static checked float IDecrementOperators.operator --(float value) - // => checked(value--); + // => checked(--value); // // IDivisionOperators @@ -820,11 +820,11 @@ static float IFloatingPoint.Truncate(float x) [RequiresPreviewFeatures] static float IIncrementOperators.operator ++(float value) - => value++; + => ++value; // [RequiresPreviewFeatures] // static checked float IIncrementOperators.operator ++(float value) - // => checked(value++); + // => checked(++value); // // IMinMaxValue diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs index 218c092ed192..49c08cc99fab 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs @@ -307,11 +307,11 @@ static ushort IBinaryInteger.PopCount(ushort value) => (ushort)BitOperations.PopCount(value); [RequiresPreviewFeatures] - static ushort IBinaryInteger.RotateLeft(ushort value, ushort rotateAmount) + static ushort IBinaryInteger.RotateLeft(ushort value, int rotateAmount) => (ushort)((value << (rotateAmount & 15)) | (value >> ((16 - rotateAmount) & 15))); [RequiresPreviewFeatures] - static ushort IBinaryInteger.RotateRight(ushort value, ushort rotateAmount) + static ushort IBinaryInteger.RotateRight(ushort value, int rotateAmount) => (ushort)((value >> (rotateAmount & 15)) | (value << ((16 - rotateAmount) & 15))); [RequiresPreviewFeatures] @@ -376,11 +376,11 @@ static ushort IBinaryNumber.Log2(ushort value) [RequiresPreviewFeatures] static ushort IDecrementOperators.operator --(ushort value) - => value--; + => --value; // [RequiresPreviewFeatures] // static checked ushort IDecrementOperators.operator --(ushort value) - // => checked(value--); + // => checked(--value); // // IDivisionOperators @@ -412,11 +412,11 @@ static ushort IBinaryNumber.Log2(ushort value) [RequiresPreviewFeatures] static ushort IIncrementOperators.operator ++(ushort value) - => value++; + => ++value; // [RequiresPreviewFeatures] // static checked ushort IIncrementOperators.operator ++(ushort value) - // => checked(value++); + // => checked(++value); // // IMinMaxValue diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs index 14d06e9b0eff..70f073b1e15b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs @@ -293,12 +293,12 @@ static uint IBinaryInteger.PopCount(uint value) => (uint)BitOperations.PopCount(value); [RequiresPreviewFeatures] - static uint IBinaryInteger.RotateLeft(uint value, uint rotateAmount) - => BitOperations.RotateLeft(value, (int)rotateAmount); + static uint IBinaryInteger.RotateLeft(uint value, int rotateAmount) + => BitOperations.RotateLeft(value, rotateAmount); [RequiresPreviewFeatures] - static uint IBinaryInteger.RotateRight(uint value, uint rotateAmount) - => BitOperations.RotateRight(value, (int)rotateAmount); + static uint IBinaryInteger.RotateRight(uint value, int rotateAmount) + => BitOperations.RotateRight(value, rotateAmount); [RequiresPreviewFeatures] static uint IBinaryInteger.TrailingZeroCount(uint value) @@ -362,11 +362,11 @@ static uint IBinaryNumber.Log2(uint value) [RequiresPreviewFeatures] static uint IDecrementOperators.operator --(uint value) - => value--; + => --value; // [RequiresPreviewFeatures] // static checked uint IDecrementOperators.operator --(uint value) - // => checked(value--); + // => checked(--value); // // IDivisionOperators @@ -398,11 +398,11 @@ static uint IBinaryNumber.Log2(uint value) [RequiresPreviewFeatures] static uint IIncrementOperators.operator ++(uint value) - => value++; + => ++value; // [RequiresPreviewFeatures] // static checked uint IIncrementOperators.operator ++(uint value) - // => checked(value++); + // => checked(++value); // // IMinMaxValue diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs index e341b0bee93a..6b2a45f1a8eb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs @@ -292,12 +292,12 @@ static ulong IBinaryInteger.PopCount(ulong value) => (ulong)BitOperations.PopCount(value); [RequiresPreviewFeatures] - static ulong IBinaryInteger.RotateLeft(ulong value, ulong rotateAmount) - => BitOperations.RotateLeft(value, (int)rotateAmount); + static ulong IBinaryInteger.RotateLeft(ulong value, int rotateAmount) + => BitOperations.RotateLeft(value, rotateAmount); [RequiresPreviewFeatures] - static ulong IBinaryInteger.RotateRight(ulong value, ulong rotateAmount) - => BitOperations.RotateRight(value, (int)rotateAmount); + static ulong IBinaryInteger.RotateRight(ulong value, int rotateAmount) + => BitOperations.RotateRight(value, rotateAmount); [RequiresPreviewFeatures] static ulong IBinaryInteger.TrailingZeroCount(ulong value) @@ -361,11 +361,11 @@ static ulong IBinaryNumber.Log2(ulong value) [RequiresPreviewFeatures] static ulong IDecrementOperators.operator --(ulong value) - => value--; + => --value; // [RequiresPreviewFeatures] // static checked ulong IDecrementOperators.operator --(ulong value) - // => checked(value--); + // => checked(--value); // // IDivisionOperators @@ -397,11 +397,11 @@ static ulong IBinaryNumber.Log2(ulong value) [RequiresPreviewFeatures] static ulong IIncrementOperators.operator ++(ulong value) - => value++; + => ++value; // [RequiresPreviewFeatures] // static checked ulong IIncrementOperators.operator ++(ulong value) - // => checked(value++); + // => checked(++value); // // IMinMaxValue diff --git a/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs b/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs index 43c549fe94e3..b413125474f5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs @@ -270,11 +270,11 @@ static nuint IBinaryInteger.LeadingZeroCount(nuint value) { if (Environment.Is64BitProcess) { - return (nuint)BitOperations.LeadingZeroCount((uint)value); + return (nuint)BitOperations.LeadingZeroCount((ulong)value); } else { - return (nuint)BitOperations.LeadingZeroCount((ulong)value); + return (nuint)BitOperations.LeadingZeroCount((uint)value); } } @@ -283,37 +283,37 @@ static nuint IBinaryInteger.PopCount(nuint value) { if (Environment.Is64BitProcess) { - return (nuint)BitOperations.PopCount((uint)value); + return (nuint)BitOperations.PopCount((ulong)value); } else { - return (nuint)BitOperations.PopCount((ulong)value); + return (nuint)BitOperations.PopCount((uint)value); } } [RequiresPreviewFeatures] - static nuint IBinaryInteger.RotateLeft(nuint value, nuint rotateAmount) + static nuint IBinaryInteger.RotateLeft(nuint value, int rotateAmount) { if (Environment.Is64BitProcess) { - return (nuint)BitOperations.RotateLeft((uint)value, (int)rotateAmount); + return (nuint)BitOperations.RotateLeft((ulong)value, rotateAmount); } else { - return (nuint)BitOperations.RotateLeft((ulong)value, (int)rotateAmount); + return (nuint)BitOperations.RotateLeft((uint)value, rotateAmount); } } [RequiresPreviewFeatures] - static nuint IBinaryInteger.RotateRight(nuint value, nuint rotateAmount) + static nuint IBinaryInteger.RotateRight(nuint value, int rotateAmount) { if (Environment.Is64BitProcess) { - return (nuint)BitOperations.RotateRight((uint)value, (int)rotateAmount); + return (nuint)BitOperations.RotateRight((ulong)value, rotateAmount); } else { - return (nuint)BitOperations.RotateRight((ulong)value, (int)rotateAmount); + return (nuint)BitOperations.RotateRight((uint)value, rotateAmount); } } @@ -322,11 +322,11 @@ static nuint IBinaryInteger.TrailingZeroCount(nuint value) { if (Environment.Is64BitProcess) { - return (nuint)BitOperations.TrailingZeroCount((uint)value); + return (nuint)BitOperations.TrailingZeroCount((ulong)value); } else { - return (nuint)BitOperations.TrailingZeroCount((ulong)value); + return (nuint)BitOperations.TrailingZeroCount((uint)value); } } @@ -339,11 +339,11 @@ static bool IBinaryNumber.IsPow2(nuint value) { if (Environment.Is64BitProcess) { - return BitOperations.IsPow2((uint)value); + return BitOperations.IsPow2((ulong)value); } else { - return BitOperations.IsPow2((ulong)value); + return BitOperations.IsPow2((uint)value); } } @@ -352,11 +352,11 @@ static nuint IBinaryNumber.Log2(nuint value) { if (Environment.Is64BitProcess) { - return (nuint)BitOperations.Log2((uint)value); + return (nuint)BitOperations.Log2((ulong)value); } else { - return (nuint)BitOperations.Log2((ulong)value); + return (nuint)BitOperations.Log2((uint)value); } } @@ -406,11 +406,11 @@ static nuint IBinaryNumber.Log2(nuint value) [RequiresPreviewFeatures] static nuint IDecrementOperators.operator --(nuint value) - => value--; + => --value; // [RequiresPreviewFeatures] // static checked nuint IDecrementOperators.operator --(nuint value) - // => checked(value--); + // => checked(--value); // // IDivisionOperators @@ -442,11 +442,11 @@ static nuint IBinaryNumber.Log2(nuint value) [RequiresPreviewFeatures] static nuint IIncrementOperators.operator ++(nuint value) - => value++; + => ++value; // [RequiresPreviewFeatures] // static checked nuint IIncrementOperators.operator ++(nuint value) - // => checked(value++); + // => checked(++value); // // IMinMaxValue @@ -661,7 +661,7 @@ static nuint INumber.CreateTruncating(TOther value) { if (typeof(TOther) == typeof(byte)) { - return (nuint)(object)value; + return (byte)(object)value; } else if (typeof(TOther) == typeof(char)) { diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 1b9b1d241b85..67e80443421d 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -777,9 +777,9 @@ public static void SetByte(System.Array array, int index, byte value) { } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static byte IBinaryInteger.PopCount(byte value) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static byte IBinaryInteger.RotateLeft(byte value, byte rotateAmount) { throw null; } + static byte IBinaryInteger.RotateLeft(byte value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static byte IBinaryInteger.RotateRight(byte value, byte rotateAmount) { throw null; } + static byte IBinaryInteger.RotateRight(byte value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static byte IBinaryInteger.TrailingZeroCount(byte value) { throw null; } @@ -997,9 +997,9 @@ public CannotUnloadAppDomainException(string? message, System.Exception? innerEx [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static char IBinaryInteger.PopCount(char value) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static char IBinaryInteger.RotateLeft(char value, char rotateAmount) { throw null; } + static char IBinaryInteger.RotateLeft(char value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static char IBinaryInteger.RotateRight(char value, char rotateAmount) { throw null; } + static char IBinaryInteger.RotateRight(char value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static char IBinaryInteger.TrailingZeroCount(char value) { throw null; } @@ -3379,8 +3379,8 @@ public partial interface IBinaryInteger : System.IBinaryNumber, Sy { static abstract TSelf LeadingZeroCount(TSelf value); static abstract TSelf PopCount(TSelf value); - static abstract TSelf RotateLeft(TSelf value, TSelf rotateAmount); - static abstract TSelf RotateRight(TSelf value, TSelf rotateAmount); + static abstract TSelf RotateLeft(TSelf value, int rotateAmount); + static abstract TSelf RotateRight(TSelf value, int rotateAmount); static abstract TSelf TrailingZeroCount(TSelf value); } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] @@ -3760,9 +3760,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static short IBinaryInteger.PopCount(short value) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static short IBinaryInteger.RotateLeft(short value, short rotateAmount) { throw null; } + static short IBinaryInteger.RotateLeft(short value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static short IBinaryInteger.RotateRight(short value, short rotateAmount) { throw null; } + static short IBinaryInteger.RotateRight(short value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static short IBinaryInteger.TrailingZeroCount(short value) { throw null; } @@ -4118,9 +4118,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static long IBinaryInteger.PopCount(long value) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static long IBinaryInteger.RotateLeft(long value, long rotateAmount) { throw null; } + static long IBinaryInteger.RotateLeft(long value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static long IBinaryInteger.RotateRight(long value, long rotateAmount) { throw null; } + static long IBinaryInteger.RotateRight(long value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static long IBinaryInteger.TrailingZeroCount(long value) { throw null; } @@ -4306,9 +4306,9 @@ void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Ser [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static nint IBinaryInteger.PopCount(nint value) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static nint IBinaryInteger.RotateLeft(nint value, nint rotateAmount) { throw null; } + static nint IBinaryInteger.RotateLeft(nint value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static nint IBinaryInteger.RotateRight(nint value, nint rotateAmount) { throw null; } + static nint IBinaryInteger.RotateRight(nint value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static nint IBinaryInteger.TrailingZeroCount(nint value) { throw null; } @@ -5269,9 +5269,9 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static sbyte IBinaryInteger.PopCount(sbyte value) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static sbyte IBinaryInteger.RotateLeft(sbyte value, sbyte rotateAmount) { throw null; } + static sbyte IBinaryInteger.RotateLeft(sbyte value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static sbyte IBinaryInteger.RotateRight(sbyte value, sbyte rotateAmount) { throw null; } + static sbyte IBinaryInteger.RotateRight(sbyte value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static sbyte IBinaryInteger.TrailingZeroCount(sbyte value) { throw null; } @@ -6979,9 +6979,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static ushort IBinaryInteger.PopCount(ushort value) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static ushort IBinaryInteger.RotateLeft(ushort value, ushort rotateAmount) { throw null; } + static ushort IBinaryInteger.RotateLeft(ushort value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static ushort IBinaryInteger.RotateRight(ushort value, ushort rotateAmount) { throw null; } + static ushort IBinaryInteger.RotateRight(ushort value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static ushort IBinaryInteger.TrailingZeroCount(ushort value) { throw null; } @@ -7156,9 +7156,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static uint IBinaryInteger.PopCount(uint value) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static uint IBinaryInteger.RotateLeft(uint value, uint rotateAmount) { throw null; } + static uint IBinaryInteger.RotateLeft(uint value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static uint IBinaryInteger.RotateRight(uint value, uint rotateAmount) { throw null; } + static uint IBinaryInteger.RotateRight(uint value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static uint IBinaryInteger.TrailingZeroCount(uint value) { throw null; } @@ -7333,9 +7333,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static ulong IBinaryInteger.PopCount(ulong value) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static ulong IBinaryInteger.RotateLeft(ulong value, ulong rotateAmount) { throw null; } + static ulong IBinaryInteger.RotateLeft(ulong value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static ulong IBinaryInteger.RotateRight(ulong value, ulong rotateAmount) { throw null; } + static ulong IBinaryInteger.RotateRight(ulong value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static ulong IBinaryInteger.TrailingZeroCount(ulong value) { throw null; } @@ -7515,9 +7515,9 @@ void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Ser [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static nuint IBinaryInteger.PopCount(nuint value) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static nuint IBinaryInteger.RotateLeft(nuint value, nuint rotateAmount) { throw null; } + static nuint IBinaryInteger.RotateLeft(nuint value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] - static nuint IBinaryInteger.RotateRight(nuint value, nuint rotateAmount) { throw null; } + static nuint IBinaryInteger.RotateRight(nuint value, int rotateAmount) { throw null; } [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] static nuint IBinaryInteger.TrailingZeroCount(nuint value) { throw null; } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index 32bbac1e9930..0f4092248ae8 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -1,12 +1,16 @@ - + true + true $(NoWarn),1718,SYSLIB0013 true true $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser disable + + $(DefineConstants);FEATURE_GENERIC_MATH + @@ -81,7 +85,6 @@ - @@ -89,7 +92,6 @@ - @@ -247,6 +249,20 @@ + + + + + + + + + + + + + + diff --git a/src/libraries/System.Runtime/tests/System/ByteTests.GenericMath.cs b/src/libraries/System.Runtime/tests/System/ByteTests.GenericMath.cs new file mode 100644 index 000000000000..140279d54f2d --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/ByteTests.GenericMath.cs @@ -0,0 +1,1175 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Runtime.Versioning; +using Xunit; + +namespace System.Tests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [RequiresPreviewFeaturesAttribute] + public class ByteTests_GenericMath + { + [Fact] + public static void AdditiveIdentityTest() + { + Assert.Equal((byte)0x00, AdditiveIdentityHelper.AdditiveIdentity); + } + + [Fact] + public static void MinValueTest() + { + Assert.Equal((byte)0x00, MinMaxValueHelper.MinValue); + } + + [Fact] + public static void MaxValueTest() + { + Assert.Equal((byte)0xFF, MinMaxValueHelper.MaxValue); + } + + [Fact] + public static void MultiplicativeIdentityTest() + { + Assert.Equal((byte)0x01, MultiplicativeIdentityHelper.MultiplicativeIdentity); + } + + [Fact] + public static void OneTest() + { + Assert.Equal((byte)0x01, NumberHelper.One); + } + + [Fact] + public static void ZeroTest() + { + Assert.Equal((byte)0x00, NumberHelper.Zero); + } + + [Fact] + public static void op_AdditionTest() + { + Assert.Equal((byte)0x01, AdditionOperatorsHelper.op_Addition((byte)0x00, (byte)1)); + Assert.Equal((byte)0x02, AdditionOperatorsHelper.op_Addition((byte)0x01, (byte)1)); + Assert.Equal((byte)0x80, AdditionOperatorsHelper.op_Addition((byte)0x7F, (byte)1)); + Assert.Equal((byte)0x81, AdditionOperatorsHelper.op_Addition((byte)0x80, (byte)1)); + Assert.Equal((byte)0x00, AdditionOperatorsHelper.op_Addition((byte)0xFF, (byte)1)); + } + + [Fact] + public static void LeadingZeroCountTest() + { + Assert.Equal((byte)0x08, BinaryIntegerHelper.LeadingZeroCount((byte)0x00)); + Assert.Equal((byte)0x07, BinaryIntegerHelper.LeadingZeroCount((byte)0x01)); + Assert.Equal((byte)0x01, BinaryIntegerHelper.LeadingZeroCount((byte)0x7F)); + Assert.Equal((byte)0x00, BinaryIntegerHelper.LeadingZeroCount((byte)0x80)); + Assert.Equal((byte)0x00, BinaryIntegerHelper.LeadingZeroCount((byte)0xFF)); + } + + [Fact] + public static void PopCountTest() + { + Assert.Equal((byte)0x00, BinaryIntegerHelper.PopCount((byte)0x00)); + Assert.Equal((byte)0x01, BinaryIntegerHelper.PopCount((byte)0x01)); + Assert.Equal((byte)0x07, BinaryIntegerHelper.PopCount((byte)0x7F)); + Assert.Equal((byte)0x01, BinaryIntegerHelper.PopCount((byte)0x80)); + Assert.Equal((byte)0x08, BinaryIntegerHelper.PopCount((byte)0xFF)); + } + + [Fact] + public static void RotateLeftTest() + { + Assert.Equal((byte)0x00, BinaryIntegerHelper.RotateLeft((byte)0x00, 1)); + Assert.Equal((byte)0x02, BinaryIntegerHelper.RotateLeft((byte)0x01, 1)); + Assert.Equal((byte)0xFE, BinaryIntegerHelper.RotateLeft((byte)0x7F, 1)); + Assert.Equal((byte)0x01, BinaryIntegerHelper.RotateLeft((byte)0x80, 1)); + Assert.Equal((byte)0xFF, BinaryIntegerHelper.RotateLeft((byte)0xFF, 1)); + } + + [Fact] + public static void RotateRightTest() + { + Assert.Equal((byte)0x00, BinaryIntegerHelper.RotateRight((byte)0x00, 1)); + Assert.Equal((byte)0x80, BinaryIntegerHelper.RotateRight((byte)0x01, 1)); + Assert.Equal((byte)0xBF, BinaryIntegerHelper.RotateRight((byte)0x7F, 1)); + Assert.Equal((byte)0x40, BinaryIntegerHelper.RotateRight((byte)0x80, 1)); + Assert.Equal((byte)0xFF, BinaryIntegerHelper.RotateRight((byte)0xFF, 1)); + } + + [Fact] + public static void TrailingZeroCountTest() + { + Assert.Equal((byte)0x08, BinaryIntegerHelper.TrailingZeroCount((byte)0x00)); + Assert.Equal((byte)0x00, BinaryIntegerHelper.TrailingZeroCount((byte)0x01)); + Assert.Equal((byte)0x00, BinaryIntegerHelper.TrailingZeroCount((byte)0x7F)); + Assert.Equal((byte)0x07, BinaryIntegerHelper.TrailingZeroCount((byte)0x80)); + Assert.Equal((byte)0x00, BinaryIntegerHelper.TrailingZeroCount((byte)0xFF)); + } + + [Fact] + public static void IsPow2Test() + { + Assert.False(BinaryNumberHelper.IsPow2((byte)0x00)); + Assert.True(BinaryNumberHelper.IsPow2((byte)0x01)); + Assert.False(BinaryNumberHelper.IsPow2((byte)0x7F)); + Assert.True(BinaryNumberHelper.IsPow2((byte)0x80)); + Assert.False(BinaryNumberHelper.IsPow2((byte)0xFF)); + } + + [Fact] + public static void Log2Test() + { + Assert.Equal((byte)0x00, BinaryNumberHelper.Log2((byte)0x00)); + Assert.Equal((byte)0x00, BinaryNumberHelper.Log2((byte)0x01)); + Assert.Equal((byte)0x06, BinaryNumberHelper.Log2((byte)0x7F)); + Assert.Equal((byte)0x07, BinaryNumberHelper.Log2((byte)0x80)); + Assert.Equal((byte)0x07, BinaryNumberHelper.Log2((byte)0xFF)); + } + + [Fact] + public static void op_BitwiseAndTest() + { + Assert.Equal((byte)0x00, BitwiseOperatorsHelper.op_BitwiseAnd((byte)0x00, (byte)1)); + Assert.Equal((byte)0x01, BitwiseOperatorsHelper.op_BitwiseAnd((byte)0x01, (byte)1)); + Assert.Equal((byte)0x01, BitwiseOperatorsHelper.op_BitwiseAnd((byte)0x7F, (byte)1)); + Assert.Equal((byte)0x00, BitwiseOperatorsHelper.op_BitwiseAnd((byte)0x80, (byte)1)); + Assert.Equal((byte)0x01, BitwiseOperatorsHelper.op_BitwiseAnd((byte)0xFF, (byte)1)); + } + + [Fact] + public static void op_BitwiseOrTest() + { + Assert.Equal((byte)0x01, BitwiseOperatorsHelper.op_BitwiseOr((byte)0x00, (byte)1)); + Assert.Equal((byte)0x01, BitwiseOperatorsHelper.op_BitwiseOr((byte)0x01, (byte)1)); + Assert.Equal((byte)0x7F, BitwiseOperatorsHelper.op_BitwiseOr((byte)0x7F, (byte)1)); + Assert.Equal((byte)0x81, BitwiseOperatorsHelper.op_BitwiseOr((byte)0x80, (byte)1)); + Assert.Equal((byte)0xFF, BitwiseOperatorsHelper.op_BitwiseOr((byte)0xFF, (byte)1)); + } + + [Fact] + public static void op_ExclusiveOrTest() + { + Assert.Equal((byte)0x01, BitwiseOperatorsHelper.op_ExclusiveOr((byte)0x00, (byte)1)); + Assert.Equal((byte)0x00, BitwiseOperatorsHelper.op_ExclusiveOr((byte)0x01, (byte)1)); + Assert.Equal((byte)0x7E, BitwiseOperatorsHelper.op_ExclusiveOr((byte)0x7F, (byte)1)); + Assert.Equal((byte)0x81, BitwiseOperatorsHelper.op_ExclusiveOr((byte)0x80, (byte)1)); + Assert.Equal((byte)0xFE, BitwiseOperatorsHelper.op_ExclusiveOr((byte)0xFF, (byte)1)); + } + + [Fact] + public static void op_OnesComplementTest() + { + Assert.Equal((byte)0xFF, BitwiseOperatorsHelper.op_OnesComplement((byte)0x00)); + Assert.Equal((byte)0xFE, BitwiseOperatorsHelper.op_OnesComplement((byte)0x01)); + Assert.Equal((byte)0x80, BitwiseOperatorsHelper.op_OnesComplement((byte)0x7F)); + Assert.Equal((byte)0x7F, BitwiseOperatorsHelper.op_OnesComplement((byte)0x80)); + Assert.Equal((byte)0x00, BitwiseOperatorsHelper.op_OnesComplement((byte)0xFF)); + } + + [Fact] + public static void op_LessThanTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThan((byte)0x00, (byte)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((byte)0x01, (byte)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((byte)0x7F, (byte)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((byte)0x80, (byte)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((byte)0xFF, (byte)1)); + } + + [Fact] + public static void op_LessThanOrEqualTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((byte)0x00, (byte)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((byte)0x01, (byte)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((byte)0x7F, (byte)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((byte)0x80, (byte)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((byte)0xFF, (byte)1)); + } + + [Fact] + public static void op_GreaterThanTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((byte)0x00, (byte)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((byte)0x01, (byte)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((byte)0x7F, (byte)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((byte)0x80, (byte)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((byte)0xFF, (byte)1)); + } + + [Fact] + public static void op_GreaterThanOrEqualTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual((byte)0x00, (byte)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((byte)0x01, (byte)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((byte)0x7F, (byte)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((byte)0x80, (byte)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((byte)0xFF, (byte)1)); + } + + [Fact] + public static void op_DecrementTest() + { + Assert.Equal((byte)0xFF, DecrementOperatorsHelper.op_Decrement((byte)0x00)); + Assert.Equal((byte)0x00, DecrementOperatorsHelper.op_Decrement((byte)0x01)); + Assert.Equal((byte)0x7E, DecrementOperatorsHelper.op_Decrement((byte)0x7F)); + Assert.Equal((byte)0x7F, DecrementOperatorsHelper.op_Decrement((byte)0x80)); + Assert.Equal((byte)0xFE, DecrementOperatorsHelper.op_Decrement((byte)0xFF)); + } + + [Fact] + public static void op_DivisionTest() + { + Assert.Equal((byte)0x00, DivisionOperatorsHelper.op_Division((byte)0x00, (byte)2)); + Assert.Equal((byte)0x00, DivisionOperatorsHelper.op_Division((byte)0x01, (byte)2)); + Assert.Equal((byte)0x3F, DivisionOperatorsHelper.op_Division((byte)0x7F, (byte)2)); + Assert.Equal((byte)0x40, DivisionOperatorsHelper.op_Division((byte)0x80, (byte)2)); + Assert.Equal((byte)0x7F, DivisionOperatorsHelper.op_Division((byte)0xFF, (byte)2)); + } + + [Fact] + public static void op_EqualityTest() + { + Assert.False(EqualityOperatorsHelper.op_Equality((byte)0x00, (byte)1)); + Assert.True(EqualityOperatorsHelper.op_Equality((byte)0x01, (byte)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((byte)0x7F, (byte)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((byte)0x80, (byte)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((byte)0xFF, (byte)1)); + } + + [Fact] + public static void op_InequalityTest() + { + Assert.True(EqualityOperatorsHelper.op_Inequality((byte)0x00, (byte)1)); + Assert.False(EqualityOperatorsHelper.op_Inequality((byte)0x01, (byte)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((byte)0x7F, (byte)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((byte)0x80, (byte)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((byte)0xFF, (byte)1)); + } + + [Fact] + public static void op_IncrementTest() + { + Assert.Equal((byte)0x01, IncrementOperatorsHelper.op_Increment((byte)0x00)); + Assert.Equal((byte)0x02, IncrementOperatorsHelper.op_Increment((byte)0x01)); + Assert.Equal((byte)0x80, IncrementOperatorsHelper.op_Increment((byte)0x7F)); + Assert.Equal((byte)0x81, IncrementOperatorsHelper.op_Increment((byte)0x80)); + Assert.Equal((byte)0x00, IncrementOperatorsHelper.op_Increment((byte)0xFF)); + } + + [Fact] + public static void op_ModulusTest() + { + Assert.Equal((byte)0x00, ModulusOperatorsHelper.op_Modulus((byte)0x00, (byte)2)); + Assert.Equal((byte)0x01, ModulusOperatorsHelper.op_Modulus((byte)0x01, (byte)2)); + Assert.Equal((byte)0x01, ModulusOperatorsHelper.op_Modulus((byte)0x7F, (byte)2)); + Assert.Equal((byte)0x00, ModulusOperatorsHelper.op_Modulus((byte)0x80, (byte)2)); + Assert.Equal((byte)0x01, ModulusOperatorsHelper.op_Modulus((byte)0xFF, (byte)2)); + } + + [Fact] + public static void op_MultiplyTest() + { + Assert.Equal((byte)0x00, MultiplyOperatorsHelper.op_Multiply((byte)0x00, (byte)2)); + Assert.Equal((byte)0x02, MultiplyOperatorsHelper.op_Multiply((byte)0x01, (byte)2)); + Assert.Equal((byte)0xFE, MultiplyOperatorsHelper.op_Multiply((byte)0x7F, (byte)2)); + Assert.Equal((byte)0x00, MultiplyOperatorsHelper.op_Multiply((byte)0x80, (byte)2)); + Assert.Equal((byte)0xFE, MultiplyOperatorsHelper.op_Multiply((byte)0xFF, (byte)2)); + } + + [Fact] + public static void AbsTest() + { + Assert.Equal((byte)0x00, NumberHelper.Abs((byte)0x00)); + Assert.Equal((byte)0x01, NumberHelper.Abs((byte)0x01)); + Assert.Equal((byte)0x7F, NumberHelper.Abs((byte)0x7F)); + Assert.Equal((byte)0x80, NumberHelper.Abs((byte)0x80)); + Assert.Equal((byte)0xFF, NumberHelper.Abs((byte)0xFF)); + } + + [Fact] + public static void ClampTest() + { + Assert.Equal((byte)0x01, NumberHelper.Clamp((byte)0x00, (byte)0x01, (byte)0x3F)); + Assert.Equal((byte)0x01, NumberHelper.Clamp((byte)0x01, (byte)0x01, (byte)0x3F)); + Assert.Equal((byte)0x3F, NumberHelper.Clamp((byte)0x7F, (byte)0x01, (byte)0x3F)); + Assert.Equal((byte)0x3F, NumberHelper.Clamp((byte)0x80, (byte)0x01, (byte)0x3F)); + Assert.Equal((byte)0x3F, NumberHelper.Clamp((byte)0xFF, (byte)0x01, (byte)0x3F)); + } + + [Fact] + public static void CreateFromByteTest() + { + Assert.Equal((byte)0x00, NumberHelper.Create(0x00)); + Assert.Equal((byte)0x01, NumberHelper.Create(0x01)); + Assert.Equal((byte)0x7F, NumberHelper.Create(0x7F)); + Assert.Equal((byte)0x80, NumberHelper.Create(0x80)); + Assert.Equal((byte)0xFF, NumberHelper.Create(0xFF)); + } + + [Fact] + public static void CreateFromCharTest() + { + Assert.Equal((byte)0x00, NumberHelper.Create((char)0x0000)); + Assert.Equal((byte)0x01, NumberHelper.Create((char)0x0001)); + Assert.Throws(() => NumberHelper.Create((char)0x7FFF)); + Assert.Throws(() => NumberHelper.Create((char)0x8000)); + Assert.Throws(() => NumberHelper.Create((char)0xFFFF)); + } + + [Fact] + public static void CreateFromInt16Test() + { + Assert.Equal((byte)0x00, NumberHelper.Create(0x0000)); + Assert.Equal((byte)0x01, NumberHelper.Create(0x0001)); + Assert.Throws(() => NumberHelper.Create(0x7FFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((short)0x8000))); + Assert.Throws(() => NumberHelper.Create(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateFromInt32Test() + { + Assert.Equal((byte)0x00, NumberHelper.Create(0x00000000)); + Assert.Equal((byte)0x01, NumberHelper.Create(0x00000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((int)0x80000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateFromInt64Test() + { + Assert.Equal((byte)0x00, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((byte)0x01, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((byte)0x00, NumberHelper.Create(unchecked((nint)0x0000000000000000))); + Assert.Equal((byte)0x01, NumberHelper.Create(unchecked((nint)0x0000000000000001))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((byte)0x00, NumberHelper.Create((nint)0x00000000)); + Assert.Equal((byte)0x01, NumberHelper.Create((nint)0x00000001)); + Assert.Throws(() => NumberHelper.Create((nint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x80000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateFromSByteTest() + { + Assert.Equal((byte)0x00, NumberHelper.Create(0x00)); + Assert.Equal((byte)0x01, NumberHelper.Create(0x01)); + Assert.Equal((byte)0x7F, NumberHelper.Create(0x7F)); + Assert.Throws(() => NumberHelper.Create(unchecked((sbyte)0x80))); + Assert.Throws(() => NumberHelper.Create(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateFromUInt16Test() + { + Assert.Equal((byte)0x00, NumberHelper.Create(0x0000)); + Assert.Equal((byte)0x01, NumberHelper.Create(0x0001)); + Assert.Throws(() => NumberHelper.Create(0x7FFF)); + Assert.Throws(() => NumberHelper.Create(0x8000)); + Assert.Throws(() => NumberHelper.Create(0xFFFF)); + } + + [Fact] + public static void CreateFromUInt32Test() + { + Assert.Equal((byte)0x00, NumberHelper.Create(0x00000000)); + Assert.Equal((byte)0x01, NumberHelper.Create(0x00000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x80000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFF)); + } + + [Fact] + public static void CreateFromUInt64Test() + { + Assert.Equal((byte)0x00, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((byte)0x01, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x8000000000000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((byte)0x00, NumberHelper.Create(unchecked((nuint)0x0000000000000000))); + Assert.Equal((byte)0x01, NumberHelper.Create(unchecked((nuint)0x0000000000000001))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((byte)0x00, NumberHelper.Create((nuint)0x00000000)); + Assert.Equal((byte)0x01, NumberHelper.Create((nuint)0x00000001)); + Assert.Throws(() => NumberHelper.Create((nuint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create((nuint)0x80000000)); + Assert.Throws(() => NumberHelper.Create((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateSaturatingFromByteTest() + { + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((byte)0x01, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((byte)0x7F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((byte)0x80, NumberHelper.CreateSaturating(0x80)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(0xFF)); + } + + [Fact] + public static void CreateSaturatingFromCharTest() + { + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating((char)0x0000)); + Assert.Equal((byte)0x01, NumberHelper.CreateSaturating((char)0x0001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating((char)0x7FFF)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating((char)0x8000)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating((char)0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromInt16Test() + { + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((byte)0x01, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(unchecked((short)0x8000))); + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt32Test() + { + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((byte)0x01, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(unchecked((int)0x80000000))); + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt64Test() + { + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((byte)0x01, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(unchecked((long)0x8000000000000000))); + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000000))); + Assert.Equal((byte)0x01, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000001))); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(unchecked((nint)0x8000000000000000))); + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating((nint)0x00000000)); + Assert.Equal((byte)0x01, NumberHelper.CreateSaturating((nint)0x00000001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating((nint)0x7FFFFFFF)); + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(unchecked((nint)0x80000000))); + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateSaturatingFromSByteTest() + { + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((byte)0x01, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((byte)0x7F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(unchecked((sbyte)0x80))); + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateSaturatingFromUInt16Test() + { + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((byte)0x01, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(0x8000)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt32Test() + { + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((byte)0x01, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(0x80000000)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt64Test() + { + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((byte)0x01, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(0x8000000000000000)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((byte)0x01, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000001))); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(unchecked((nuint)0x8000000000000000))); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((byte)0x00, NumberHelper.CreateSaturating((nuint)0x00000000)); + Assert.Equal((byte)0x01, NumberHelper.CreateSaturating((nuint)0x00000001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating((nuint)0x7FFFFFFF)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating((nuint)0x80000000)); + Assert.Equal((byte)0xFF, NumberHelper.CreateSaturating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateTruncatingFromByteTest() + { + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((byte)0x01, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((byte)0x7F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((byte)0x80, NumberHelper.CreateTruncating(0x80)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(0xFF)); + } + + [Fact] + public static void CreateTruncatingFromCharTest() + { + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating((char)0x0000)); + Assert.Equal((byte)0x01, NumberHelper.CreateTruncating((char)0x0001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating((char)0x7FFF)); + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating((char)0x8000)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating((char)0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromInt16Test() + { + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((byte)0x01, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(unchecked((short)0x8000))); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt32Test() + { + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((byte)0x01, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(unchecked((int)0x80000000))); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt64Test() + { + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((byte)0x01, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(unchecked((long)0x8000000000000000))); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000000))); + Assert.Equal((byte)0x01, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000001))); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(unchecked((nint)0x8000000000000000))); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating((nint)0x00000000)); + Assert.Equal((byte)0x01, NumberHelper.CreateTruncating((nint)0x00000001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating((nint)0x7FFFFFFF)); + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(unchecked((nint)0x80000000))); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromSByteTest() + { + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((byte)0x01, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((byte)0x7F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((byte)0x80, NumberHelper.CreateTruncating(unchecked((sbyte)0x80))); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateTruncatingFromUInt16Test() + { + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((byte)0x01, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(0x8000)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt32Test() + { + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((byte)0x01, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(0x80000000)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt64Test() + { + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((byte)0x01, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(0x8000000000000000)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((byte)0x01, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000001))); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating(unchecked((nuint)0x8000000000000000))); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating((nuint)0x00000000)); + Assert.Equal((byte)0x01, NumberHelper.CreateTruncating((nuint)0x00000001)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating((nuint)0x7FFFFFFF)); + Assert.Equal((byte)0x00, NumberHelper.CreateTruncating((nuint)0x80000000)); + Assert.Equal((byte)0xFF, NumberHelper.CreateTruncating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void DivRemTest() + { + Assert.Equal(((byte)0x00, (byte)0x00), NumberHelper.DivRem((byte)0x00, (byte)2)); + Assert.Equal(((byte)0x00, (byte)0x01), NumberHelper.DivRem((byte)0x01, (byte)2)); + Assert.Equal(((byte)0x3F, (byte)0x01), NumberHelper.DivRem((byte)0x7F, (byte)2)); + Assert.Equal(((byte)0x40, (byte)0x00), NumberHelper.DivRem((byte)0x80, (byte)2)); + Assert.Equal(((byte)0x7F, (byte)0x01), NumberHelper.DivRem((byte)0xFF, (byte)2)); + } + + [Fact] + public static void MaxTest() + { + Assert.Equal((byte)0x01, NumberHelper.Max((byte)0x00, (byte)1)); + Assert.Equal((byte)0x01, NumberHelper.Max((byte)0x01, (byte)1)); + Assert.Equal((byte)0x7F, NumberHelper.Max((byte)0x7F, (byte)1)); + Assert.Equal((byte)0x80, NumberHelper.Max((byte)0x80, (byte)1)); + Assert.Equal((byte)0xFF, NumberHelper.Max((byte)0xFF, (byte)1)); + } + + [Fact] + public static void MinTest() + { + Assert.Equal((byte)0x00, NumberHelper.Min((byte)0x00, (byte)1)); + Assert.Equal((byte)0x01, NumberHelper.Min((byte)0x01, (byte)1)); + Assert.Equal((byte)0x01, NumberHelper.Min((byte)0x7F, (byte)1)); + Assert.Equal((byte)0x01, NumberHelper.Min((byte)0x80, (byte)1)); + Assert.Equal((byte)0x01, NumberHelper.Min((byte)0xFF, (byte)1)); + } + + [Fact] + public static void SignTest() + { + Assert.Equal((byte)0x00, NumberHelper.Sign((byte)0x00)); + Assert.Equal((byte)0x01, NumberHelper.Sign((byte)0x01)); + Assert.Equal((byte)0x01, NumberHelper.Sign((byte)0x7F)); + Assert.Equal((byte)0x01, NumberHelper.Sign((byte)0x80)); + Assert.Equal((byte)0x01, NumberHelper.Sign((byte)0xFF)); + } + + [Fact] + public static void TryCreateFromByteTest() + { + byte result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((byte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((byte)0x01, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((byte)0x7F, result); + + Assert.True(NumberHelper.TryCreate(0x80, out result)); + Assert.Equal((byte)0x80, result); + + Assert.True(NumberHelper.TryCreate(0xFF, out result)); + Assert.Equal((byte)0xFF, result); + } + + [Fact] + public static void TryCreateFromCharTest() + { + byte result; + + Assert.True(NumberHelper.TryCreate((char)0x0000, out result)); + Assert.Equal((byte)0x00, result); + + Assert.True(NumberHelper.TryCreate((char)0x0001, out result)); + Assert.Equal((byte)0x01, result); + + Assert.False(NumberHelper.TryCreate((char)0x7FFF, out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate((char)0x8000, out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate((char)0xFFFF, out result)); + Assert.Equal((byte)0x00, result); + } + + [Fact] + public static void TryCreateFromInt16Test() + { + byte result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((byte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((byte)0x01, result); + + Assert.False(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((short)0x8000), out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((short)0xFFFF), out result)); + Assert.Equal((byte)0x00, result); + } + + [Fact] + public static void TryCreateFromInt32Test() + { + byte result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((byte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((byte)0x01, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((int)0x80000000), out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((int)0xFFFFFFFF), out result)); + Assert.Equal((byte)0x00, result); + } + + [Fact] + public static void TryCreateFromInt64Test() + { + byte result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((byte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((byte)0x01, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0x8000000000000000), out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((byte)0x00, result); + } + + [Fact] + public static void TryCreateFromIntPtrTest() + { + byte result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000000), out result)); + Assert.Equal((byte)0x00, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000001), out result)); + Assert.Equal((byte)0x01, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x8000000000000000), out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((byte)0x00, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nint)0x00000000, out result)); + Assert.Equal((byte)0x00, result); + + Assert.True(NumberHelper.TryCreate((nint)0x00000001, out result)); + Assert.Equal((byte)0x01, result); + + Assert.False(NumberHelper.TryCreate((nint)0x7FFFFFFF, out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x80000000), out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFF), out result)); + Assert.Equal((byte)0x00, result); + } + } + + [Fact] + public static void TryCreateFromSByteTest() + { + byte result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((byte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((byte)0x01, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((byte)0x7F, result); + + Assert.False(NumberHelper.TryCreate(unchecked((sbyte)0x80), out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((sbyte)0xFF), out result)); + Assert.Equal((byte)0x00, result); + } + + [Fact] + public static void TryCreateFromUInt16Test() + { + byte result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((byte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((byte)0x01, result); + + Assert.False(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(0x8000, out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(0xFFFF, out result)); + Assert.Equal((byte)0x00, result); + } + + [Fact] + public static void TryCreateFromUInt32Test() + { + byte result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((byte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((byte)0x01, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(0x80000000, out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFF, out result)); + Assert.Equal((byte)0x00, result); + } + + [Fact] + public static void TryCreateFromUInt64Test() + { + byte result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((byte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((byte)0x01, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(0x8000000000000000, out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFFFFFFFFFF, out result)); + Assert.Equal((byte)0x00, result); + } + + [Fact] + public static void TryCreateFromUIntPtrTest() + { + byte result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000000), out result)); + Assert.Equal((byte)0x00, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000001), out result)); + Assert.Equal((byte)0x01, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x8000000000000000), out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((byte)0x00, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nuint)0x00000000, out result)); + Assert.Equal((byte)0x00, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x00000001, out result)); + Assert.Equal((byte)0x01, result); + + Assert.False(NumberHelper.TryCreate((nuint)0x7FFFFFFF, out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x80000000), out result)); + Assert.Equal((byte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFF), out result)); + Assert.Equal((byte)0x00, result); + } + } + + [Fact] + + public static void op_LeftShiftTest() + { + Assert.Equal((byte)0x00, ShiftOperatorsHelper.op_LeftShift((byte)0x00, 1)); + Assert.Equal((byte)0x02, ShiftOperatorsHelper.op_LeftShift((byte)0x01, 1)); + Assert.Equal((byte)0xFE, ShiftOperatorsHelper.op_LeftShift((byte)0x7F, 1)); + Assert.Equal((byte)0x00, ShiftOperatorsHelper.op_LeftShift((byte)0x80, 1)); + Assert.Equal((byte)0xFE, ShiftOperatorsHelper.op_LeftShift((byte)0xFF, 1)); + } + + [Fact] + public static void op_RightShiftTest() + { + Assert.Equal((byte)0x00, ShiftOperatorsHelper.op_RightShift((byte)0x00, 1)); + Assert.Equal((byte)0x00, ShiftOperatorsHelper.op_RightShift((byte)0x01, 1)); + Assert.Equal((byte)0x3F, ShiftOperatorsHelper.op_RightShift((byte)0x7F, 1)); + Assert.Equal((byte)0x40, ShiftOperatorsHelper.op_RightShift((byte)0x80, 1)); + Assert.Equal((byte)0x7F, ShiftOperatorsHelper.op_RightShift((byte)0xFF, 1)); + } + + [Fact] + public static void op_SubtractionTest() + { + Assert.Equal((byte)0xFF, SubtractionOperatorsHelper.op_Subtraction((byte)0x00, (byte)1)); + Assert.Equal((byte)0x00, SubtractionOperatorsHelper.op_Subtraction((byte)0x01, (byte)1)); + Assert.Equal((byte)0x7E, SubtractionOperatorsHelper.op_Subtraction((byte)0x7F, (byte)1)); + Assert.Equal((byte)0x7F, SubtractionOperatorsHelper.op_Subtraction((byte)0x80, (byte)1)); + Assert.Equal((byte)0xFE, SubtractionOperatorsHelper.op_Subtraction((byte)0xFF, (byte)1)); + } + + [Fact] + public static void op_UnaryNegationTest() + { + Assert.Equal((byte)0x00, UnaryNegationOperatorsHelper.op_UnaryNegation((byte)0x00)); + Assert.Equal((byte)0xFF, UnaryNegationOperatorsHelper.op_UnaryNegation((byte)0x01)); + Assert.Equal((byte)0x81, UnaryNegationOperatorsHelper.op_UnaryNegation((byte)0x7F)); + Assert.Equal((byte)0x80, UnaryNegationOperatorsHelper.op_UnaryNegation((byte)0x80)); + Assert.Equal((byte)0x01, UnaryNegationOperatorsHelper.op_UnaryNegation((byte)0xFF)); + } + + [Fact] + public static void op_UnaryPlusTest() + { + Assert.Equal((byte)0x00, UnaryPlusOperatorsHelper.op_UnaryPlus((byte)0x00)); + Assert.Equal((byte)0x01, UnaryPlusOperatorsHelper.op_UnaryPlus((byte)0x01)); + Assert.Equal((byte)0x7F, UnaryPlusOperatorsHelper.op_UnaryPlus((byte)0x7F)); + Assert.Equal((byte)0x80, UnaryPlusOperatorsHelper.op_UnaryPlus((byte)0x80)); + Assert.Equal((byte)0xFF, UnaryPlusOperatorsHelper.op_UnaryPlus((byte)0xFF)); + } + + [Theory] + [MemberData(nameof(ByteTests.Parse_Valid_TestData), MemberType = typeof(ByteTests))] + public static void ParseValidStringTest(string value, NumberStyles style, IFormatProvider provider, byte expected) + { + byte result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.True(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.True(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(ByteTests.Parse_Invalid_TestData), MemberType = typeof(ByteTests))] + public static void ParseInvalidStringTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + byte result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(default(byte), result); + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.False(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(default(byte), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.False(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(default(byte), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(ByteTests.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(ByteTests))] + public static void ParseValidSpanTest(string value, int offset, int count, NumberStyles style, IFormatProvider provider, byte expected) + { + byte result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(SpanParseableHelper.TryParse(value.AsSpan(offset, count), provider, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, NumberHelper.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(NumberHelper.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(ByteTests.Parse_Invalid_TestData), MemberType = typeof(ByteTests))] + public static void ParseInvalidSpanTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value is null) + { + return; + } + + byte result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(SpanParseableHelper.TryParse(value.AsSpan(), provider, out result)); + Assert.Equal(default(byte), result); + } + + Assert.Throws(exceptionType, () => NumberHelper.Parse(value.AsSpan(), style, provider)); + + Assert.False(NumberHelper.TryParse(value.AsSpan(), style, provider, out result)); + Assert.Equal(default(byte), result); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/CharTests.GenericMath.cs b/src/libraries/System.Runtime/tests/System/CharTests.GenericMath.cs new file mode 100644 index 000000000000..7dd512f9871e --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/CharTests.GenericMath.cs @@ -0,0 +1,1057 @@ +// 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.Versioning; +using Xunit; + +namespace System.Tests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [RequiresPreviewFeaturesAttribute] + public class CharTests_GenericMath + { + [Fact] + public static void AdditiveIdentityTest() + { + Assert.Equal((char)0x0000, AdditiveIdentityHelper.AdditiveIdentity); + } + + [Fact] + public static void MinValueTest() + { + Assert.Equal((char)0x0000, MinMaxValueHelper.MinValue); + } + + [Fact] + public static void MaxValueTest() + { + Assert.Equal((char)0xFFFF, MinMaxValueHelper.MaxValue); + } + + [Fact] + public static void MultiplicativeIdentityTest() + { + Assert.Equal((char)0x0001, MultiplicativeIdentityHelper.MultiplicativeIdentity); + } + + [Fact] + public static void OneTest() + { + Assert.Equal((char)0x0001, NumberHelper.One); + } + + [Fact] + public static void ZeroTest() + { + Assert.Equal((char)0x0000, NumberHelper.Zero); + } + + [Fact] + public static void op_AdditionTest() + { + Assert.Equal((char)0x0001, AdditionOperatorsHelper.op_Addition((char)0x0000, (char)1)); + Assert.Equal((char)0x0002, AdditionOperatorsHelper.op_Addition((char)0x0001, (char)1)); + Assert.Equal((char)0x8000, AdditionOperatorsHelper.op_Addition((char)0x7FFF, (char)1)); + Assert.Equal((char)0x8001, AdditionOperatorsHelper.op_Addition((char)0x8000, (char)1)); + Assert.Equal((char)0x0000, AdditionOperatorsHelper.op_Addition((char)0xFFFF, (char)1)); + } + + [Fact] + public static void LeadingZeroCountTest() + { + Assert.Equal((char)0x0010, BinaryIntegerHelper.LeadingZeroCount((char)0x0000)); + Assert.Equal((char)0x000F, BinaryIntegerHelper.LeadingZeroCount((char)0x0001)); + Assert.Equal((char)0x0001, BinaryIntegerHelper.LeadingZeroCount((char)0x7FFF)); + Assert.Equal((char)0x0000, BinaryIntegerHelper.LeadingZeroCount((char)0x8000)); + Assert.Equal((char)0x0000, BinaryIntegerHelper.LeadingZeroCount((char)0xFFFF)); + } + + [Fact] + public static void PopCountTest() + { + Assert.Equal((char)0x0000, BinaryIntegerHelper.PopCount((char)0x0000)); + Assert.Equal((char)0x0001, BinaryIntegerHelper.PopCount((char)0x0001)); + Assert.Equal((char)0x000F, BinaryIntegerHelper.PopCount((char)0x7FFF)); + Assert.Equal((char)0x0001, BinaryIntegerHelper.PopCount((char)0x8000)); + Assert.Equal((char)0x0010, BinaryIntegerHelper.PopCount((char)0xFFFF)); + } + + [Fact] + public static void RotateLeftTest() + { + Assert.Equal((char)0x0000, BinaryIntegerHelper.RotateLeft((char)0x0000, 1)); + Assert.Equal((char)0x0002, BinaryIntegerHelper.RotateLeft((char)0x0001, 1)); + Assert.Equal((char)0xFFFE, BinaryIntegerHelper.RotateLeft((char)0x7FFF, 1)); + Assert.Equal((char)0x0001, BinaryIntegerHelper.RotateLeft((char)0x8000, 1)); + Assert.Equal((char)0xFFFF, BinaryIntegerHelper.RotateLeft((char)0xFFFF, 1)); + } + + [Fact] + public static void RotateRightTest() + { + Assert.Equal((char)0x0000, BinaryIntegerHelper.RotateRight((char)0x0000, 1)); + Assert.Equal((char)0x8000, BinaryIntegerHelper.RotateRight((char)0x0001, 1)); + Assert.Equal((char)0xBFFF, BinaryIntegerHelper.RotateRight((char)0x7FFF, 1)); + Assert.Equal((char)0x4000, BinaryIntegerHelper.RotateRight((char)0x8000, 1)); + Assert.Equal((char)0xFFFF, BinaryIntegerHelper.RotateRight((char)0xFFFF, 1)); + } + + [Fact] + public static void TrailingZeroCountTest() + { + Assert.Equal((char)0x0010, BinaryIntegerHelper.TrailingZeroCount((char)0x0000)); + Assert.Equal((char)0x0000, BinaryIntegerHelper.TrailingZeroCount((char)0x0001)); + Assert.Equal((char)0x0000, BinaryIntegerHelper.TrailingZeroCount((char)0x7FFF)); + Assert.Equal((char)0x000F, BinaryIntegerHelper.TrailingZeroCount((char)0x8000)); + Assert.Equal((char)0x0000, BinaryIntegerHelper.TrailingZeroCount((char)0xFFFF)); + } + + [Fact] + public static void IsPow2Test() + { + Assert.False(BinaryNumberHelper.IsPow2((char)0x0000)); + Assert.True(BinaryNumberHelper.IsPow2((char)0x0001)); + Assert.False(BinaryNumberHelper.IsPow2((char)0x7FFF)); + Assert.True(BinaryNumberHelper.IsPow2((char)0x8000)); + Assert.False(BinaryNumberHelper.IsPow2((char)0xFFFF)); + } + + [Fact] + public static void Log2Test() + { + Assert.Equal((char)0x0000, BinaryNumberHelper.Log2((char)0x0000)); + Assert.Equal((char)0x0000, BinaryNumberHelper.Log2((char)0x0001)); + Assert.Equal((char)0x000E, BinaryNumberHelper.Log2((char)0x7FFF)); + Assert.Equal((char)0x000F, BinaryNumberHelper.Log2((char)0x8000)); + Assert.Equal((char)0x000F, BinaryNumberHelper.Log2((char)0xFFFF)); + } + + [Fact] + public static void op_BitwiseAndTest() + { + Assert.Equal((char)0x0000, BitwiseOperatorsHelper.op_BitwiseAnd((char)0x0000, (char)1)); + Assert.Equal((char)0x0001, BitwiseOperatorsHelper.op_BitwiseAnd((char)0x0001, (char)1)); + Assert.Equal((char)0x0001, BitwiseOperatorsHelper.op_BitwiseAnd((char)0x7FFF, (char)1)); + Assert.Equal((char)0x0000, BitwiseOperatorsHelper.op_BitwiseAnd((char)0x8000, (char)1)); + Assert.Equal((char)0x0001, BitwiseOperatorsHelper.op_BitwiseAnd((char)0xFFFF, (char)1)); + } + + [Fact] + public static void op_BitwiseOrTest() + { + Assert.Equal((char)0x0001, BitwiseOperatorsHelper.op_BitwiseOr((char)0x0000, (char)1)); + Assert.Equal((char)0x0001, BitwiseOperatorsHelper.op_BitwiseOr((char)0x0001, (char)1)); + Assert.Equal((char)0x7FFF, BitwiseOperatorsHelper.op_BitwiseOr((char)0x7FFF, (char)1)); + Assert.Equal((char)0x8001, BitwiseOperatorsHelper.op_BitwiseOr((char)0x8000, (char)1)); + Assert.Equal((char)0xFFFF, BitwiseOperatorsHelper.op_BitwiseOr((char)0xFFFF, (char)1)); + } + + [Fact] + public static void op_ExclusiveOrTest() + { + Assert.Equal((char)0x0001, BitwiseOperatorsHelper.op_ExclusiveOr((char)0x0000, (char)1)); + Assert.Equal((char)0x0000, BitwiseOperatorsHelper.op_ExclusiveOr((char)0x0001, (char)1)); + Assert.Equal((char)0x7FFE, BitwiseOperatorsHelper.op_ExclusiveOr((char)0x7FFF, (char)1)); + Assert.Equal((char)0x8001, BitwiseOperatorsHelper.op_ExclusiveOr((char)0x8000, (char)1)); + Assert.Equal((char)0xFFFE, BitwiseOperatorsHelper.op_ExclusiveOr((char)0xFFFF, (char)1)); + } + + [Fact] + public static void op_OnesComplementTest() + { + Assert.Equal((char)0xFFFF, BitwiseOperatorsHelper.op_OnesComplement((char)0x0000)); + Assert.Equal((char)0xFFFE, BitwiseOperatorsHelper.op_OnesComplement((char)0x0001)); + Assert.Equal((char)0x8000, BitwiseOperatorsHelper.op_OnesComplement((char)0x7FFF)); + Assert.Equal((char)0x7FFF, BitwiseOperatorsHelper.op_OnesComplement((char)0x8000)); + Assert.Equal((char)0x0000, BitwiseOperatorsHelper.op_OnesComplement((char)0xFFFF)); + } + + [Fact] + public static void op_LessThanTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThan((char)0x0000, (char)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((char)0x0001, (char)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((char)0x7FFF, (char)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((char)0x8000, (char)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((char)0xFFFF, (char)1)); + } + + [Fact] + public static void op_LessThanOrEqualTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((char)0x0000, (char)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((char)0x0001, (char)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((char)0x7FFF, (char)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((char)0x8000, (char)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((char)0xFFFF, (char)1)); + } + + [Fact] + public static void op_GreaterThanTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((char)0x0000, (char)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((char)0x0001, (char)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((char)0x7FFF, (char)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((char)0x8000, (char)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((char)0xFFFF, (char)1)); + } + + [Fact] + public static void op_GreaterThanOrEqualTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual((char)0x0000, (char)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((char)0x0001, (char)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((char)0x7FFF, (char)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((char)0x8000, (char)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((char)0xFFFF, (char)1)); + } + + [Fact] + public static void op_DecrementTest() + { + Assert.Equal((char)0xFFFF, DecrementOperatorsHelper.op_Decrement((char)0x0000)); + Assert.Equal((char)0x0000, DecrementOperatorsHelper.op_Decrement((char)0x0001)); + Assert.Equal((char)0x7FFE, DecrementOperatorsHelper.op_Decrement((char)0x7FFF)); + Assert.Equal((char)0x7FFF, DecrementOperatorsHelper.op_Decrement((char)0x8000)); + Assert.Equal((char)0xFFFE, DecrementOperatorsHelper.op_Decrement((char)0xFFFF)); + } + + [Fact] + public static void op_DivisionTest() + { + Assert.Equal((char)0x0000, DivisionOperatorsHelper.op_Division((char)0x0000, (char)2)); + Assert.Equal((char)0x0000, DivisionOperatorsHelper.op_Division((char)0x0001, (char)2)); + Assert.Equal((char)0x3FFF, DivisionOperatorsHelper.op_Division((char)0x7FFF, (char)2)); + Assert.Equal((char)0x4000, DivisionOperatorsHelper.op_Division((char)0x8000, (char)2)); + Assert.Equal((char)0x7FFF, DivisionOperatorsHelper.op_Division((char)0xFFFF, (char)2)); + } + + [Fact] + public static void op_EqualityTest() + { + Assert.False(EqualityOperatorsHelper.op_Equality((char)0x0000, (char)1)); + Assert.True(EqualityOperatorsHelper.op_Equality((char)0x0001, (char)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((char)0x7FFF, (char)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((char)0x8000, (char)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((char)0xFFFF, (char)1)); + } + + [Fact] + public static void op_InequalityTest() + { + Assert.True(EqualityOperatorsHelper.op_Inequality((char)0x0000, (char)1)); + Assert.False(EqualityOperatorsHelper.op_Inequality((char)0x0001, (char)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((char)0x7FFF, (char)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((char)0x8000, (char)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((char)0xFFFF, (char)1)); + } + + [Fact] + public static void op_IncrementTest() + { + Assert.Equal((char)0x0001, IncrementOperatorsHelper.op_Increment((char)0x0000)); + Assert.Equal((char)0x0002, IncrementOperatorsHelper.op_Increment((char)0x0001)); + Assert.Equal((char)0x8000, IncrementOperatorsHelper.op_Increment((char)0x7FFF)); + Assert.Equal((char)0x8001, IncrementOperatorsHelper.op_Increment((char)0x8000)); + Assert.Equal((char)0x0000, IncrementOperatorsHelper.op_Increment((char)0xFFFF)); + } + + [Fact] + public static void op_ModulusTest() + { + Assert.Equal((char)0x0000, ModulusOperatorsHelper.op_Modulus((char)0x0000, (char)2)); + Assert.Equal((char)0x0001, ModulusOperatorsHelper.op_Modulus((char)0x0001, (char)2)); + Assert.Equal((char)0x0001, ModulusOperatorsHelper.op_Modulus((char)0x7FFF, (char)2)); + Assert.Equal((char)0x0000, ModulusOperatorsHelper.op_Modulus((char)0x8000, (char)2)); + Assert.Equal((char)0x0001, ModulusOperatorsHelper.op_Modulus((char)0xFFFF, (char)2)); + } + + [Fact] + public static void op_MultiplyTest() + { + Assert.Equal((char)0x0000, MultiplyOperatorsHelper.op_Multiply((char)0x0000, (char)2)); + Assert.Equal((char)0x0002, MultiplyOperatorsHelper.op_Multiply((char)0x0001, (char)2)); + Assert.Equal((char)0xFFFE, MultiplyOperatorsHelper.op_Multiply((char)0x7FFF, (char)2)); + Assert.Equal((char)0x0000, MultiplyOperatorsHelper.op_Multiply((char)0x8000, (char)2)); + Assert.Equal((char)0xFFFE, MultiplyOperatorsHelper.op_Multiply((char)0xFFFF, (char)2)); + } + + [Fact] + public static void AbsTest() + { + Assert.Equal((char)0x0000, NumberHelper.Abs((char)0x0000)); + Assert.Equal((char)0x0001, NumberHelper.Abs((char)0x0001)); + Assert.Equal((char)0x7FFF, NumberHelper.Abs((char)0x7FFF)); + Assert.Equal((char)0x8000, NumberHelper.Abs((char)0x8000)); + Assert.Equal((char)0xFFFF, NumberHelper.Abs((char)0xFFFF)); + } + + [Fact] + public static void ClampTest() + { + Assert.Equal((char)0x0001, NumberHelper.Clamp((char)0x0000, (char)0x0001, (char)0x003F)); + Assert.Equal((char)0x0001, NumberHelper.Clamp((char)0x0001, (char)0x0001, (char)0x003F)); + Assert.Equal((char)0x003F, NumberHelper.Clamp((char)0x7FFF, (char)0x0001, (char)0x003F)); + Assert.Equal((char)0x003F, NumberHelper.Clamp((char)0x8000, (char)0x0001, (char)0x003F)); + Assert.Equal((char)0x003F, NumberHelper.Clamp((char)0xFFFF, (char)0x0001, (char)0x003F)); + } + + [Fact] + public static void CreateFromByteTest() + { + Assert.Equal((char)0x0000, NumberHelper.Create(0x00)); + Assert.Equal((char)0x0001, NumberHelper.Create(0x01)); + Assert.Equal((char)0x007F, NumberHelper.Create(0x7F)); + Assert.Equal((char)0x0080, NumberHelper.Create(0x80)); + Assert.Equal((char)0x00FF, NumberHelper.Create(0xFF)); + } + + [Fact] + public static void CreateFromCharTest() + { + Assert.Equal((char)0x0000, NumberHelper.Create((char)0x0000)); + Assert.Equal((char)0x0001, NumberHelper.Create((char)0x0001)); + Assert.Equal((char)0x7FFF, NumberHelper.Create((char)0x7FFF)); + Assert.Equal((char)0x8000, NumberHelper.Create((char)0x8000)); + Assert.Equal((char)0xFFFF, NumberHelper.Create((char)0xFFFF)); + } + + [Fact] + public static void CreateFromInt16Test() + { + Assert.Equal((char)0x0000, NumberHelper.Create(0x0000)); + Assert.Equal((char)0x0001, NumberHelper.Create(0x0001)); + Assert.Equal((char)0x7FFF, NumberHelper.Create(0x7FFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((short)0x8000))); + Assert.Throws(() => NumberHelper.Create(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateFromInt32Test() + { + Assert.Equal((char)0x0000, NumberHelper.Create(0x00000000)); + Assert.Equal((char)0x0001, NumberHelper.Create(0x00000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((int)0x80000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateFromInt64Test() + { + Assert.Equal((char)0x0000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((char)0x0001, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((char)0x0000, NumberHelper.Create(unchecked((nint)0x0000000000000000))); + Assert.Equal((char)0x0001, NumberHelper.Create(unchecked((nint)0x0000000000000001))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((char)0x0000, NumberHelper.Create((nint)0x00000000)); + Assert.Equal((char)0x0001, NumberHelper.Create((nint)0x00000001)); + Assert.Throws(() => NumberHelper.Create((nint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x80000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateFromSByteTest() + { + Assert.Equal((char)0x0000, NumberHelper.Create(0x00)); + Assert.Equal((char)0x0001, NumberHelper.Create(0x01)); + Assert.Equal((char)0x007F, NumberHelper.Create(0x7F)); + Assert.Throws(() => NumberHelper.Create(unchecked((sbyte)0x80))); + Assert.Throws(() => NumberHelper.Create(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateFromUInt16Test() + { + Assert.Equal((char)0x0000, NumberHelper.Create(0x0000)); + Assert.Equal((char)0x0001, NumberHelper.Create(0x0001)); + Assert.Equal((char)0x7FFF, NumberHelper.Create(0x7FFF)); + Assert.Equal((char)0x8000, NumberHelper.Create(0x8000)); + Assert.Equal((char)0xFFFF, NumberHelper.Create(0xFFFF)); + } + + [Fact] + public static void CreateFromUInt32Test() + { + Assert.Equal((char)0x0000, NumberHelper.Create(0x00000000)); + Assert.Equal((char)0x0001, NumberHelper.Create(0x00000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x80000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFF)); + } + + [Fact] + public static void CreateFromUInt64Test() + { + Assert.Equal((char)0x0000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((char)0x0001, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x8000000000000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((char)0x0000, NumberHelper.Create(unchecked((nuint)0x0000000000000000))); + Assert.Equal((char)0x0001, NumberHelper.Create(unchecked((nuint)0x0000000000000001))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((char)0x0000, NumberHelper.Create((nuint)0x00000000)); + Assert.Equal((char)0x0001, NumberHelper.Create((nuint)0x00000001)); + Assert.Throws(() => NumberHelper.Create((nuint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create((nuint)0x80000000)); + Assert.Throws(() => NumberHelper.Create((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateSaturatingFromByteTest() + { + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((char)0x0001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((char)0x007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((char)0x0080, NumberHelper.CreateSaturating(0x80)); + Assert.Equal((char)0x00FF, NumberHelper.CreateSaturating(0xFF)); + } + + [Fact] + public static void CreateSaturatingFromCharTest() + { + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating((char)0x0000)); + Assert.Equal((char)0x0001, NumberHelper.CreateSaturating((char)0x0001)); + Assert.Equal((char)0x7FFF, NumberHelper.CreateSaturating((char)0x7FFF)); + Assert.Equal((char)0x8000, NumberHelper.CreateSaturating((char)0x8000)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating((char)0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromInt16Test() + { + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((char)0x0001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((char)0x7FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(unchecked((short)0x8000))); + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt32Test() + { + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((char)0x0001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(unchecked((int)0x80000000))); + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt64Test() + { + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((char)0x0001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(unchecked((long)0x8000000000000000))); + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000000))); + Assert.Equal((char)0x0001, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000001))); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(unchecked((nint)0x8000000000000000))); + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating((nint)0x00000000)); + Assert.Equal((char)0x0001, NumberHelper.CreateSaturating((nint)0x00000001)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating((nint)0x7FFFFFFF)); + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(unchecked((nint)0x80000000))); + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateSaturatingFromSByteTest() + { + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((char)0x0001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((char)0x007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(unchecked((sbyte)0x80))); + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateSaturatingFromUInt16Test() + { + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((char)0x0001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((char)0x7FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((char)0x8000, NumberHelper.CreateSaturating(0x8000)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating(0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt32Test() + { + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((char)0x0001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating(0x80000000)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt64Test() + { + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((char)0x0001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating(0x8000000000000000)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((char)0x0001, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000001))); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0x8000000000000000))); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((char)0x0000, NumberHelper.CreateSaturating((nuint)0x00000000)); + Assert.Equal((char)0x0001, NumberHelper.CreateSaturating((nuint)0x00000001)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating((nuint)0x7FFFFFFF)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating((nuint)0x80000000)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateSaturating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateTruncatingFromByteTest() + { + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((char)0x0001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((char)0x007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((char)0x0080, NumberHelper.CreateTruncating(0x80)); + Assert.Equal((char)0x00FF, NumberHelper.CreateTruncating(0xFF)); + } + + [Fact] + public static void CreateTruncatingFromCharTest() + { + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating((char)0x0000)); + Assert.Equal((char)0x0001, NumberHelper.CreateTruncating((char)0x0001)); + Assert.Equal((char)0x7FFF, NumberHelper.CreateTruncating((char)0x7FFF)); + Assert.Equal((char)0x8000, NumberHelper.CreateTruncating((char)0x8000)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating((char)0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromInt16Test() + { + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((char)0x0001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((char)0x7FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((char)0x8000, NumberHelper.CreateTruncating(unchecked((short)0x8000))); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt32Test() + { + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((char)0x0001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(unchecked((int)0x80000000))); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt64Test() + { + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((char)0x0001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(unchecked((long)0x8000000000000000))); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000000))); + Assert.Equal((char)0x0001, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000001))); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(unchecked((nint)0x8000000000000000))); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating((nint)0x00000000)); + Assert.Equal((char)0x0001, NumberHelper.CreateTruncating((nint)0x00000001)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating((nint)0x7FFFFFFF)); + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(unchecked((nint)0x80000000))); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromSByteTest() + { + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((char)0x0001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((char)0x007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((char)0xFF80, NumberHelper.CreateTruncating(unchecked((sbyte)0x80))); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateTruncatingFromUInt16Test() + { + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((char)0x0001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((char)0x7FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((char)0x8000, NumberHelper.CreateTruncating(0x8000)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt32Test() + { + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((char)0x0001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(0x80000000)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt64Test() + { + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((char)0x0001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(0x8000000000000000)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((char)0x0001, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000001))); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating(unchecked((nuint)0x8000000000000000))); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating((nuint)0x00000000)); + Assert.Equal((char)0x0001, NumberHelper.CreateTruncating((nuint)0x00000001)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating((nuint)0x7FFFFFFF)); + Assert.Equal((char)0x0000, NumberHelper.CreateTruncating((nuint)0x80000000)); + Assert.Equal((char)0xFFFF, NumberHelper.CreateTruncating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void DivRemTest() + { + Assert.Equal(((char)0x0000, (char)0x0000), NumberHelper.DivRem((char)0x0000, (char)2)); + Assert.Equal(((char)0x0000, (char)0x0001), NumberHelper.DivRem((char)0x0001, (char)2)); + Assert.Equal(((char)0x3FFF, (char)0x0001), NumberHelper.DivRem((char)0x7FFF, (char)2)); + Assert.Equal(((char)0x4000, (char)0x0000), NumberHelper.DivRem((char)0x8000, (char)2)); + Assert.Equal(((char)0x7FFF, (char)0x0001), NumberHelper.DivRem((char)0xFFFF, (char)2)); + } + + [Fact] + public static void MaxTest() + { + Assert.Equal((char)0x0001, NumberHelper.Max((char)0x0000, (char)1)); + Assert.Equal((char)0x0001, NumberHelper.Max((char)0x0001, (char)1)); + Assert.Equal((char)0x7FFF, NumberHelper.Max((char)0x7FFF, (char)1)); + Assert.Equal((char)0x8000, NumberHelper.Max((char)0x8000, (char)1)); + Assert.Equal((char)0xFFFF, NumberHelper.Max((char)0xFFFF, (char)1)); + } + + [Fact] + public static void MinTest() + { + Assert.Equal((char)0x0000, NumberHelper.Min((char)0x0000, (char)1)); + Assert.Equal((char)0x0001, NumberHelper.Min((char)0x0001, (char)1)); + Assert.Equal((char)0x0001, NumberHelper.Min((char)0x7FFF, (char)1)); + Assert.Equal((char)0x0001, NumberHelper.Min((char)0x8000, (char)1)); + Assert.Equal((char)0x0001, NumberHelper.Min((char)0xFFFF, (char)1)); + } + + [Fact] + public static void SignTest() + { + Assert.Equal((char)0x0000, NumberHelper.Sign((char)0x0000)); + Assert.Equal((char)0x0001, NumberHelper.Sign((char)0x0001)); + Assert.Equal((char)0x0001, NumberHelper.Sign((char)0x7FFF)); + Assert.Equal((char)0x0001, NumberHelper.Sign((char)0x8000)); + Assert.Equal((char)0x0001, NumberHelper.Sign((char)0xFFFF)); + } + + [Fact] + public static void TryCreateFromByteTest() + { + char result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((char)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((char)0x0001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((char)0x007F, result); + + Assert.True(NumberHelper.TryCreate(0x80, out result)); + Assert.Equal((char)0x0080, result); + + Assert.True(NumberHelper.TryCreate(0xFF, out result)); + Assert.Equal((char)0x00FF, result); + } + + [Fact] + public static void TryCreateFromCharTest() + { + char result; + + Assert.True(NumberHelper.TryCreate((char)0x0000, out result)); + Assert.Equal((char)0x0000, result); + + Assert.True(NumberHelper.TryCreate((char)0x0001, out result)); + Assert.Equal((char)0x0001, result); + + Assert.True(NumberHelper.TryCreate((char)0x7FFF, out result)); + Assert.Equal((char)0x7FFF, result); + + Assert.True(NumberHelper.TryCreate((char)0x8000, out result)); + Assert.Equal((char)0x8000, result); + + Assert.True(NumberHelper.TryCreate((char)0xFFFF, out result)); + Assert.Equal((char)0xFFFF, result); + } + + [Fact] + public static void TryCreateFromInt16Test() + { + char result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((char)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((char)0x0001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((char)0x7FFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((short)0x8000), out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((short)0xFFFF), out result)); + Assert.Equal((char)0x0000, result); + } + + [Fact] + public static void TryCreateFromInt32Test() + { + char result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((char)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((char)0x0001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((int)0x80000000), out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((int)0xFFFFFFFF), out result)); + Assert.Equal((char)0x0000, result); + } + + [Fact] + public static void TryCreateFromInt64Test() + { + char result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((char)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((char)0x0001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0x8000000000000000), out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((char)0x0000, result); + } + + [Fact] + public static void TryCreateFromIntPtrTest() + { + char result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000000), out result)); + Assert.Equal((char)0x0000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000001), out result)); + Assert.Equal((char)0x0001, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x8000000000000000), out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((char)0x0000, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nint)0x00000000, out result)); + Assert.Equal((char)0x0000, result); + + Assert.True(NumberHelper.TryCreate((nint)0x00000001, out result)); + Assert.Equal((char)0x0001, result); + + Assert.False(NumberHelper.TryCreate((nint)0x7FFFFFFF, out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x80000000), out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFF), out result)); + Assert.Equal((char)0x0000, result); + } + } + + [Fact] + public static void TryCreateFromSByteTest() + { + char result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((char)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((char)0x0001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((char)0x007F, result); + + Assert.False(NumberHelper.TryCreate(unchecked((sbyte)0x80), out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((sbyte)0xFF), out result)); + Assert.Equal((char)0x0000, result); + } + + [Fact] + public static void TryCreateFromUInt16Test() + { + char result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((char)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((char)0x0001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((char)0x7FFF, result); + + Assert.True(NumberHelper.TryCreate(0x8000, out result)); + Assert.Equal((char)0x8000, result); + + Assert.True(NumberHelper.TryCreate(0xFFFF, out result)); + Assert.Equal((char)0xFFFF, result); + } + + [Fact] + public static void TryCreateFromUInt32Test() + { + char result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((char)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((char)0x0001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(0x80000000, out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFF, out result)); + Assert.Equal((char)0x0000, result); + } + + [Fact] + public static void TryCreateFromUInt64Test() + { + char result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((char)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((char)0x0001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(0x8000000000000000, out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFFFFFFFFFF, out result)); + Assert.Equal((char)0x0000, result); + } + + [Fact] + public static void TryCreateFromUIntPtrTest() + { + char result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000000), out result)); + Assert.Equal((char)0x0000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000001), out result)); + Assert.Equal((char)0x0001, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x8000000000000000), out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((char)0x0000, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nuint)0x00000000, out result)); + Assert.Equal((char)0x0000, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x00000001, out result)); + Assert.Equal((char)0x0001, result); + + Assert.False(NumberHelper.TryCreate((nuint)0x7FFFFFFF, out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x80000000), out result)); + Assert.Equal((char)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFF), out result)); + Assert.Equal((char)0x0000, result); + } + } + + [Fact] + + public static void op_LeftShiftTest() + { + Assert.Equal((char)0x0000, ShiftOperatorsHelper.op_LeftShift((char)0x0000, 1)); + Assert.Equal((char)0x0002, ShiftOperatorsHelper.op_LeftShift((char)0x0001, 1)); + Assert.Equal((char)0xFFFE, ShiftOperatorsHelper.op_LeftShift((char)0x7FFF, 1)); + Assert.Equal((char)0x0000, ShiftOperatorsHelper.op_LeftShift((char)0x8000, 1)); + Assert.Equal((char)0xFFFE, ShiftOperatorsHelper.op_LeftShift((char)0xFFFF, 1)); + } + + [Fact] + public static void op_RightShiftTest() + { + Assert.Equal((char)0x0000, ShiftOperatorsHelper.op_RightShift((char)0x0000, 1)); + Assert.Equal((char)0x0000, ShiftOperatorsHelper.op_RightShift((char)0x0001, 1)); + Assert.Equal((char)0x3FFF, ShiftOperatorsHelper.op_RightShift((char)0x7FFF, 1)); + Assert.Equal((char)0x4000, ShiftOperatorsHelper.op_RightShift((char)0x8000, 1)); + Assert.Equal((char)0x7FFF, ShiftOperatorsHelper.op_RightShift((char)0xFFFF, 1)); + } + + [Fact] + public static void op_SubtractionTest() + { + Assert.Equal((char)0xFFFF, SubtractionOperatorsHelper.op_Subtraction((char)0x0000, (char)1)); + Assert.Equal((char)0x0000, SubtractionOperatorsHelper.op_Subtraction((char)0x0001, (char)1)); + Assert.Equal((char)0x7FFE, SubtractionOperatorsHelper.op_Subtraction((char)0x7FFF, (char)1)); + Assert.Equal((char)0x7FFF, SubtractionOperatorsHelper.op_Subtraction((char)0x8000, (char)1)); + Assert.Equal((char)0xFFFE, SubtractionOperatorsHelper.op_Subtraction((char)0xFFFF, (char)1)); + } + + [Fact] + public static void op_UnaryNegationTest() + { + Assert.Equal((char)0x0000, UnaryNegationOperatorsHelper.op_UnaryNegation((char)0x0000)); + Assert.Equal((char)0xFFFF, UnaryNegationOperatorsHelper.op_UnaryNegation((char)0x0001)); + Assert.Equal((char)0x8001, UnaryNegationOperatorsHelper.op_UnaryNegation((char)0x7FFF)); + Assert.Equal((char)0x8000, UnaryNegationOperatorsHelper.op_UnaryNegation((char)0x8000)); + Assert.Equal((char)0x0001, UnaryNegationOperatorsHelper.op_UnaryNegation((char)0xFFFF)); + } + + [Fact] + public static void op_UnaryPlusTest() + { + Assert.Equal((char)0x0000, UnaryPlusOperatorsHelper.op_UnaryPlus((char)0x0000)); + Assert.Equal((char)0x0001, UnaryPlusOperatorsHelper.op_UnaryPlus((char)0x0001)); + Assert.Equal((char)0x7FFF, UnaryPlusOperatorsHelper.op_UnaryPlus((char)0x7FFF)); + Assert.Equal((char)0x8000, UnaryPlusOperatorsHelper.op_UnaryPlus((char)0x8000)); + Assert.Equal((char)0xFFFF, UnaryPlusOperatorsHelper.op_UnaryPlus((char)0xFFFF)); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/GenericMathHelpers.cs b/src/libraries/System.Runtime/tests/System/GenericMathHelpers.cs new file mode 100644 index 000000000000..2895dd884196 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/GenericMathHelpers.cs @@ -0,0 +1,228 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Runtime.Versioning; + +namespace System.Tests +{ + [RequiresPreviewFeatures] + public static class AdditionOperatorsHelper + where TSelf : IAdditionOperators + { + public static TResult op_Addition(TSelf left, TOther right) => left + right; + } + + [RequiresPreviewFeatures] + public static class AdditiveIdentityHelper + where TSelf : IAdditiveIdentity + { + public static TResult AdditiveIdentity => TSelf.AdditiveIdentity; + } + + [RequiresPreviewFeatures] + public static class BinaryIntegerHelper + where TSelf : IBinaryInteger + { + public static TSelf LeadingZeroCount(TSelf value) => TSelf.LeadingZeroCount(value); + + public static TSelf PopCount(TSelf value) => TSelf.PopCount(value); + + public static TSelf RotateLeft(TSelf value, int rotateAmount) => TSelf.RotateLeft(value, rotateAmount); + + public static TSelf RotateRight(TSelf value, int rotateAmount) => TSelf.RotateRight(value, rotateAmount); + + public static TSelf TrailingZeroCount(TSelf value) => TSelf.TrailingZeroCount(value); + } + + [RequiresPreviewFeatures] + public static class BinaryNumberHelper + where TSelf : IBinaryNumber + { + public static bool IsPow2(TSelf value) => TSelf.IsPow2(value); + + public static TSelf Log2(TSelf value) => TSelf.Log2(value); + } + + [RequiresPreviewFeatures] + public static class BitwiseOperatorsHelper + where TSelf : IBitwiseOperators + { + public static TResult op_BitwiseAnd(TSelf left, TOther right) => left & right; + + public static TResult op_BitwiseOr(TSelf left, TOther right) => left | right; + + public static TResult op_ExclusiveOr(TSelf left, TOther right) => left ^ right; + + public static TResult op_OnesComplement(TSelf value) => ~value; + } + + [RequiresPreviewFeatures] + public static class ComparisonOperatorsHelper + where TSelf : IComparisonOperators + { + public static bool op_GreaterThan(TSelf left, TOther right) => left > right; + + public static bool op_GreaterThanOrEqual(TSelf left, TOther right) => left >= right; + + public static bool op_LessThan(TSelf left, TOther right) => left < right; + + public static bool op_LessThanOrEqual(TSelf left, TOther right) => left <= right; + } + + [RequiresPreviewFeatures] + public static class DecrementOperatorsHelper + where TSelf : IDecrementOperators +{ + public static TSelf op_Decrement(TSelf value) => --value; + } + + [RequiresPreviewFeatures] + public static class DivisionOperatorsHelper + where TSelf : IDivisionOperators + { + public static TResult op_Division(TSelf left, TOther right) => left / right; + } + + [RequiresPreviewFeatures] + public static class EqualityOperatorsHelper + where TSelf : IEqualityOperators + { + public static bool op_Equality(TSelf left, TOther right) => left == right; + + public static bool op_Inequality(TSelf left, TOther right) => left != right; + } + + [RequiresPreviewFeatures] + public static class IncrementOperatorsHelper + where TSelf : IIncrementOperators + { + public static TSelf op_Increment(TSelf value) => ++value; + } + + [RequiresPreviewFeatures] + public static class ModulusOperatorsHelper + where TSelf : IModulusOperators + { + public static TResult op_Modulus(TSelf left, TOther right) => left % right; + } + + [RequiresPreviewFeatures] + public static class MultiplyOperatorsHelper + where TSelf : IMultiplyOperators + { + public static TResult op_Multiply(TSelf left, TOther right) => left * right; + } + + [RequiresPreviewFeatures] + public static class MinMaxValueHelper + where TSelf : IMinMaxValue + { + public static TSelf MaxValue => TSelf.MaxValue; + + public static TSelf MinValue => TSelf.MinValue; + } + + [RequiresPreviewFeatures] + public static class MultiplicativeIdentityHelper + where TSelf : IMultiplicativeIdentity + { + public static TResult MultiplicativeIdentity => TSelf.MultiplicativeIdentity; + } + + [RequiresPreviewFeatures] + public static class NumberHelper + where TSelf : INumber + { + public static TSelf One => TSelf.One; + + public static TSelf Zero => TSelf.Zero; + + public static TSelf Abs(TSelf value) => TSelf.Abs(value); + + public static TSelf Clamp(TSelf value, TSelf min, TSelf max) => TSelf.Clamp(value, min, max); + + public static TSelf Create(TOther value) + where TOther : INumber => TSelf.Create(value); + + public static TSelf CreateSaturating(TOther value) + where TOther : INumber => TSelf.CreateSaturating(value); + + public static TSelf CreateTruncating(TOther value) + where TOther : INumber => TSelf.CreateTruncating(value); + + public static (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right) => TSelf.DivRem(left, right); + + public static TSelf Max(TSelf x, TSelf y) => TSelf.Max(x, y); + + public static TSelf Min(TSelf x, TSelf y) => TSelf.Min(x, y); + + public static TSelf Parse(string s, NumberStyles style, IFormatProvider provider) => TSelf.Parse(s, style, provider); + + public static TSelf Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider) => TSelf.Parse(s, style, provider); + + public static TSelf Sign(TSelf value) => TSelf.Sign(value); + + public static bool TryCreate(TOther value, out TSelf result) + where TOther : INumber => TSelf.TryCreate(value, out result); + + public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out TSelf result) => TSelf.TryParse(s, style, provider, out result); + + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out TSelf result) => TSelf.TryParse(s, style, provider, out result); + } + + [RequiresPreviewFeatures] + public static class ParseableHelper + where TSelf : IParseable + { + public static TSelf Parse(string s, IFormatProvider provider) => TSelf.Parse(s, provider); + + public static bool TryParse(string s, IFormatProvider provider, out TSelf result) => TSelf.TryParse(s, provider, out result); + } + + [RequiresPreviewFeatures] + public static class ShiftOperatorsHelper + where TSelf : IShiftOperators + { + public static TResult op_LeftShift(TSelf value, int shiftAmount) => value << shiftAmount; + + public static TResult op_RightShift(TSelf value, int shiftAmount) => value >> shiftAmount; + } + + [RequiresPreviewFeatures] + public static class SignedNumberHelper + where TSelf : ISignedNumber + { + public static TSelf NegativeOne => TSelf.NegativeOne; + } + + [RequiresPreviewFeatures] + public static class SpanParseableHelper + where TSelf : ISpanParseable + { + public static TSelf Parse(ReadOnlySpan s, IFormatProvider provider) => TSelf.Parse(s, provider); + + public static bool TryParse(ReadOnlySpan s, IFormatProvider provider, out TSelf result) => TSelf.TryParse(s, provider, out result); + } + + [RequiresPreviewFeatures] + public static class SubtractionOperatorsHelper + where TSelf : ISubtractionOperators + { + public static TResult op_Subtraction(TSelf left, TOther right) => left - right; + } + + [RequiresPreviewFeatures] + public static class UnaryNegationOperatorsHelper + where TSelf : IUnaryNegationOperators + { + public static TResult op_UnaryNegation(TSelf value) => -value; + } + + [RequiresPreviewFeatures] + public static class UnaryPlusOperatorsHelper + where TSelf : IUnaryPlusOperators + { + public static TResult op_UnaryPlus(TSelf value) => +value; + } +} diff --git a/src/libraries/System.Runtime/tests/System/GenericMathTests.cs b/src/libraries/System.Runtime/tests/System/GenericMathTests.cs deleted file mode 100644 index ccd3b8a269fc..000000000000 --- a/src/libraries/System.Runtime/tests/System/GenericMathTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Linq; -using Xunit; - -namespace System.Tests -{ - public static class GenericMath - { - public static TResult Average(IEnumerable values) - where TSelf : INumber - where TResult : INumber - { - TResult sum = Sum(values); - return TResult.Create(sum) / TResult.Create(values.Count()); - } - - public static TResult StandardDeviation(IEnumerable values) - where TSelf : INumber - where TResult : IFloatingPoint - { - TResult standardDeviation = TResult.Zero; - - if (values.Any()) - { - TResult average = Average(values); - TResult sum = Sum(values.Select((value) => { - var deviation = TResult.Create(value) - average; - return deviation * deviation; - })); - standardDeviation = TResult.Sqrt(sum / TResult.Create(values.Count() - 1)); - } - - return standardDeviation; - } - - public static TResult Sum(IEnumerable values) - where TSelf : INumber - where TResult : INumber - { - TResult result = TResult.Zero; - - foreach (var value in values) - { - result += TResult.Create(value); - } - - return result; - } - } - - public abstract class GenericMathTests - where TSelf : INumber - { - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] - public abstract void AverageTest(); - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] - public abstract void StandardDeviationTest(); - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] - public abstract void SumTest(); - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] - public abstract void SumInt32Test(); - } -} diff --git a/src/libraries/System.Runtime/tests/System/Int16Tests.GenericMath.cs b/src/libraries/System.Runtime/tests/System/Int16Tests.GenericMath.cs new file mode 100644 index 000000000000..667a5f55ac22 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Int16Tests.GenericMath.cs @@ -0,0 +1,1181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Runtime.Versioning; +using Xunit; + +namespace System.Tests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [RequiresPreviewFeaturesAttribute] + public class Int16Tests_GenericMath + { + [Fact] + public static void AdditiveIdentityTest() + { + Assert.Equal((short)0x0000, AdditiveIdentityHelper.AdditiveIdentity); + } + + [Fact] + public static void MinValueTest() + { + Assert.Equal(unchecked((short)0x8000), MinMaxValueHelper.MinValue); + } + + [Fact] + public static void MaxValueTest() + { + Assert.Equal((short)0x7FFF, MinMaxValueHelper.MaxValue); + } + + [Fact] + public static void MultiplicativeIdentityTest() + { + Assert.Equal((short)0x0001, MultiplicativeIdentityHelper.MultiplicativeIdentity); + } + + [Fact] + public static void NegativeOneTest() + { + Assert.Equal(unchecked((short)0xFFFF), SignedNumberHelper.NegativeOne); + } + + [Fact] + public static void OneTest() + { + Assert.Equal((short)0x0001, NumberHelper.One); + } + + [Fact] + public static void ZeroTest() + { + Assert.Equal((short)0x0000, NumberHelper.Zero); + } + + [Fact] + public static void op_AdditionTest() + { + Assert.Equal((short)0x0001, AdditionOperatorsHelper.op_Addition((short)0x0000, (short)1)); + Assert.Equal((short)0x0002, AdditionOperatorsHelper.op_Addition((short)0x0001, (short)1)); + Assert.Equal(unchecked((short)0x8000), AdditionOperatorsHelper.op_Addition((short)0x7FFF, (short)1)); + Assert.Equal(unchecked((short)0x8001), AdditionOperatorsHelper.op_Addition(unchecked((short)0x8000), (short)1)); + Assert.Equal((short)0x0000, AdditionOperatorsHelper.op_Addition(unchecked((short)0xFFFF), (short)1)); + } + + [Fact] + public static void LeadingZeroCountTest() + { + Assert.Equal((short)0x0010, BinaryIntegerHelper.LeadingZeroCount((short)0x0000)); + Assert.Equal((short)0x000F, BinaryIntegerHelper.LeadingZeroCount((short)0x0001)); + Assert.Equal((short)0x0001, BinaryIntegerHelper.LeadingZeroCount((short)0x7FFF)); + Assert.Equal((short)0x0000, BinaryIntegerHelper.LeadingZeroCount(unchecked((short)0x8000))); + Assert.Equal((short)0x0000, BinaryIntegerHelper.LeadingZeroCount(unchecked((short)0xFFFF))); + } + + [Fact] + public static void PopCountTest() + { + Assert.Equal((short)0x0000, BinaryIntegerHelper.PopCount((short)0x0000)); + Assert.Equal((short)0x0001, BinaryIntegerHelper.PopCount((short)0x0001)); + Assert.Equal((short)0x000F, BinaryIntegerHelper.PopCount((short)0x7FFF)); + Assert.Equal((short)0x0001, BinaryIntegerHelper.PopCount(unchecked((short)0x8000))); + Assert.Equal((short)0x0010, BinaryIntegerHelper.PopCount(unchecked((short)0xFFFF))); + } + + [Fact] + public static void RotateLeftTest() + { + Assert.Equal((short)0x0000, BinaryIntegerHelper.RotateLeft((short)0x0000, 1)); + Assert.Equal((short)0x0002, BinaryIntegerHelper.RotateLeft((short)0x0001, 1)); + Assert.Equal(unchecked((short)0xFFFE), BinaryIntegerHelper.RotateLeft((short)0x7FFF, 1)); + Assert.Equal((short)0x0001, BinaryIntegerHelper.RotateLeft(unchecked((short)0x8000), 1)); + Assert.Equal(unchecked((short)0xFFFF), BinaryIntegerHelper.RotateLeft(unchecked((short)0xFFFF), 1)); + } + + [Fact] + public static void RotateRightTest() + { + Assert.Equal((short)0x0000, BinaryIntegerHelper.RotateRight((short)0x0000, 1)); + Assert.Equal(unchecked((short)0x8000), BinaryIntegerHelper.RotateRight((short)0x0001, 1)); + Assert.Equal(unchecked((short)0xBFFF), BinaryIntegerHelper.RotateRight((short)0x7FFF, 1)); + Assert.Equal((short)0x4000, BinaryIntegerHelper.RotateRight(unchecked((short)0x8000), 1)); + Assert.Equal(unchecked((short)0xFFFF), BinaryIntegerHelper.RotateRight(unchecked((short)0xFFFF), 1)); + } + + [Fact] + public static void TrailingZeroCountTest() + { + Assert.Equal((short)0x0010, BinaryIntegerHelper.TrailingZeroCount((short)0x0000)); + Assert.Equal((short)0x0000, BinaryIntegerHelper.TrailingZeroCount((short)0x0001)); + Assert.Equal((short)0x0000, BinaryIntegerHelper.TrailingZeroCount((short)0x7FFF)); + Assert.Equal((short)0x000F, BinaryIntegerHelper.TrailingZeroCount(unchecked((short)0x8000))); + Assert.Equal((short)0x0000, BinaryIntegerHelper.TrailingZeroCount(unchecked((short)0xFFFF))); + } + + [Fact] + public static void IsPow2Test() + { + Assert.False(BinaryNumberHelper.IsPow2((short)0x0000)); + Assert.True(BinaryNumberHelper.IsPow2((short)0x0001)); + Assert.False(BinaryNumberHelper.IsPow2((short)0x7FFF)); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((short)0x8000))); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((short)0xFFFF))); + } + + [Fact] + public static void Log2Test() + { + Assert.Equal((short)0x0000, BinaryNumberHelper.Log2((short)0x0000)); + Assert.Equal((short)0x0000, BinaryNumberHelper.Log2((short)0x0001)); + Assert.Equal((short)0x000E, BinaryNumberHelper.Log2((short)0x7FFF)); + Assert.Throws(() => BinaryNumberHelper.Log2(unchecked((short)0x8000))); + Assert.Throws(() => BinaryNumberHelper.Log2(unchecked((short)0xFFFF))); + } + + [Fact] + public static void op_BitwiseAndTest() + { + Assert.Equal((short)0x0000, BitwiseOperatorsHelper.op_BitwiseAnd((short)0x0000, (short)1)); + Assert.Equal((short)0x0001, BitwiseOperatorsHelper.op_BitwiseAnd((short)0x0001, (short)1)); + Assert.Equal((short)0x0001, BitwiseOperatorsHelper.op_BitwiseAnd((short)0x7FFF, (short)1)); + Assert.Equal((short)0x0000, BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((short)0x8000), (short)1)); + Assert.Equal((short)0x0001, BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((short)0xFFFF), (short)1)); + } + + [Fact] + public static void op_BitwiseOrTest() + { + Assert.Equal((short)0x0001, BitwiseOperatorsHelper.op_BitwiseOr((short)0x0000, (short)1)); + Assert.Equal((short)0x0001, BitwiseOperatorsHelper.op_BitwiseOr((short)0x0001, (short)1)); + Assert.Equal((short)0x7FFF, BitwiseOperatorsHelper.op_BitwiseOr((short)0x7FFF, (short)1)); + Assert.Equal(unchecked((short)0x8001), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((short)0x8000), (short)1)); + Assert.Equal(unchecked((short)0xFFFF), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((short)0xFFFF), (short)1)); + } + + [Fact] + public static void op_ExclusiveOrTest() + { + Assert.Equal((short)0x0001, BitwiseOperatorsHelper.op_ExclusiveOr((short)0x0000, (short)1)); + Assert.Equal((short)0x0000, BitwiseOperatorsHelper.op_ExclusiveOr((short)0x0001, (short)1)); + Assert.Equal((short)0x7FFE, BitwiseOperatorsHelper.op_ExclusiveOr((short)0x7FFF, (short)1)); + Assert.Equal(unchecked((short)0x8001), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((short)0x8000), (short)1)); + Assert.Equal(unchecked((short)0xFFFE), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((short)0xFFFF), (short)1)); + } + + [Fact] + public static void op_OnesComplementTest() + { + Assert.Equal(unchecked((short)0xFFFF), BitwiseOperatorsHelper.op_OnesComplement((short)0x0000)); + Assert.Equal(unchecked((short)0xFFFE), BitwiseOperatorsHelper.op_OnesComplement((short)0x0001)); + Assert.Equal(unchecked((short)0x8000), BitwiseOperatorsHelper.op_OnesComplement((short)0x7FFF)); + Assert.Equal((short)0x7FFF, BitwiseOperatorsHelper.op_OnesComplement(unchecked((short)0x8000))); + Assert.Equal((short)0x0000, BitwiseOperatorsHelper.op_OnesComplement(unchecked((short)0xFFFF))); + } + + [Fact] + public static void op_LessThanTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThan((short)0x0000, (short)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((short)0x0001, (short)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((short)0x7FFF, (short)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThan(unchecked((short)0x8000), (short)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThan(unchecked((short)0xFFFF), (short)1)); + } + + [Fact] + public static void op_LessThanOrEqualTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((short)0x0000, (short)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((short)0x0001, (short)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((short)0x7FFF, (short)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((short)0x8000), (short)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((short)0xFFFF), (short)1)); + } + + [Fact] + public static void op_GreaterThanTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((short)0x0000, (short)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((short)0x0001, (short)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((short)0x7FFF, (short)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((short)0x8000), (short)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((short)0xFFFF), (short)1)); + } + + [Fact] + public static void op_GreaterThanOrEqualTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual((short)0x0000, (short)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((short)0x0001, (short)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((short)0x7FFF, (short)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((short)0x8000), (short)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((short)0xFFFF), (short)1)); + } + + [Fact] + public static void op_DecrementTest() + { + Assert.Equal(unchecked((short)0xFFFF), DecrementOperatorsHelper.op_Decrement((short)0x0000)); + Assert.Equal((short)0x0000, DecrementOperatorsHelper.op_Decrement((short)0x0001)); + Assert.Equal((short)0x7FFE, DecrementOperatorsHelper.op_Decrement((short)0x7FFF)); + Assert.Equal((short)0x7FFF, DecrementOperatorsHelper.op_Decrement(unchecked((short)0x8000))); + Assert.Equal(unchecked((short)0xFFFE), DecrementOperatorsHelper.op_Decrement(unchecked((short)0xFFFF))); + } + + [Fact] + public static void op_DivisionTest() + { + Assert.Equal((short)0x0000, DivisionOperatorsHelper.op_Division((short)0x0000, (short)2)); + Assert.Equal((short)0x0000, DivisionOperatorsHelper.op_Division((short)0x0001, (short)2)); + Assert.Equal((short)0x3FFF, DivisionOperatorsHelper.op_Division((short)0x7FFF, (short)2)); + Assert.Equal(unchecked((short)0xC000), DivisionOperatorsHelper.op_Division(unchecked((short)0x8000), (short)2)); + Assert.Equal((short)0x0000, DivisionOperatorsHelper.op_Division(unchecked((short)0xFFFF), (short)2)); + } + + [Fact] + public static void op_EqualityTest() + { + Assert.False(EqualityOperatorsHelper.op_Equality((short)0x0000, (short)1)); + Assert.True(EqualityOperatorsHelper.op_Equality((short)0x0001, (short)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((short)0x7FFF, (short)1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((short)0x8000), (short)1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((short)0xFFFF), (short)1)); + } + + [Fact] + public static void op_InequalityTest() + { + Assert.True(EqualityOperatorsHelper.op_Inequality((short)0x0000, (short)1)); + Assert.False(EqualityOperatorsHelper.op_Inequality((short)0x0001, (short)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((short)0x7FFF, (short)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((short)0x8000), (short)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((short)0xFFFF), (short)1)); + } + + [Fact] + public static void op_IncrementTest() + { + Assert.Equal((short)0x0001, IncrementOperatorsHelper.op_Increment((short)0x0000)); + Assert.Equal((short)0x0002, IncrementOperatorsHelper.op_Increment((short)0x0001)); + Assert.Equal(unchecked((short)0x8000), IncrementOperatorsHelper.op_Increment((short)0x7FFF)); + Assert.Equal(unchecked((short)0x8001), IncrementOperatorsHelper.op_Increment(unchecked((short)0x8000))); + Assert.Equal((short)0x0000, IncrementOperatorsHelper.op_Increment(unchecked((short)0xFFFF))); + } + + [Fact] + public static void op_ModulusTest() + { + Assert.Equal((short)0x0000, ModulusOperatorsHelper.op_Modulus((short)0x0000, (short)2)); + Assert.Equal((short)0x0001, ModulusOperatorsHelper.op_Modulus((short)0x0001, (short)2)); + Assert.Equal((short)0x0001, ModulusOperatorsHelper.op_Modulus((short)0x7FFF, (short)2)); + Assert.Equal((short)0x0000, ModulusOperatorsHelper.op_Modulus(unchecked((short)0x8000), (short)2)); + Assert.Equal(unchecked((short)0xFFFF), ModulusOperatorsHelper.op_Modulus(unchecked((short)0xFFFF), (short)2)); + } + + [Fact] + public static void op_MultiplyTest() + { + Assert.Equal((short)0x0000, MultiplyOperatorsHelper.op_Multiply((short)0x0000, (short)2)); + Assert.Equal((short)0x0002, MultiplyOperatorsHelper.op_Multiply((short)0x0001, (short)2)); + Assert.Equal(unchecked((short)0xFFFE), MultiplyOperatorsHelper.op_Multiply((short)0x7FFF, (short)2)); + Assert.Equal((short)0x0000, MultiplyOperatorsHelper.op_Multiply(unchecked((short)0x8000), (short)2)); + Assert.Equal(unchecked((short)0xFFFE), MultiplyOperatorsHelper.op_Multiply(unchecked((short)0xFFFF), (short)2)); + } + + [Fact] + public static void AbsTest() + { + Assert.Equal((short)0x0000, NumberHelper.Abs((short)0x0000)); + Assert.Equal((short)0x0001, NumberHelper.Abs((short)0x0001)); + Assert.Equal((short)0x7FFF, NumberHelper.Abs((short)0x7FFF)); + Assert.Throws(() => NumberHelper.Abs(unchecked((short)0x8000))); + Assert.Equal((short)0x0001, NumberHelper.Abs(unchecked((short)0xFFFF))); + } + + [Fact] + public static void ClampTest() + { + Assert.Equal((short)0x0000, NumberHelper.Clamp((short)0x0000, unchecked((short)0xFFC0), (short)0x003F)); + Assert.Equal((short)0x0001, NumberHelper.Clamp((short)0x0001, unchecked((short)0xFFC0), (short)0x003F)); + Assert.Equal((short)0x003F, NumberHelper.Clamp((short)0x7FFF, unchecked((short)0xFFC0), (short)0x003F)); + Assert.Equal(unchecked((short)0xFFC0), NumberHelper.Clamp(unchecked((short)0x8000), unchecked((short)0xFFC0), (short)0x003F)); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.Clamp(unchecked((short)0xFFFF), unchecked((short)0xFFC0), (short)0x003F)); + } + + [Fact] + public static void CreateFromByteTest() + { + Assert.Equal((short)0x0000, NumberHelper.Create(0x00)); + Assert.Equal((short)0x0001, NumberHelper.Create(0x01)); + Assert.Equal((short)0x007F, NumberHelper.Create(0x7F)); + Assert.Equal((short)0x0080, NumberHelper.Create(0x80)); + Assert.Equal((short)0x00FF, NumberHelper.Create(0xFF)); + } + + [Fact] + public static void CreateFromCharTest() + { + Assert.Equal((short)0x0000, NumberHelper.Create((char)0x0000)); + Assert.Equal((short)0x0001, NumberHelper.Create((char)0x0001)); + Assert.Equal((short)0x7FFF, NumberHelper.Create((char)0x7FFF)); + Assert.Throws(() => NumberHelper.Create((char)0x8000)); + Assert.Throws(() => NumberHelper.Create((char)0xFFFF)); + } + + [Fact] + public static void CreateFromInt16Test() + { + Assert.Equal((short)0x0000, NumberHelper.Create(0x0000)); + Assert.Equal((short)0x0001, NumberHelper.Create(0x0001)); + Assert.Equal((short)0x7FFF, NumberHelper.Create(0x7FFF)); + Assert.Equal(unchecked((short)0x8000), NumberHelper.Create(unchecked((short)0x8000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.Create(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateFromInt32Test() + { + Assert.Equal((short)0x0000, NumberHelper.Create(0x00000000)); + Assert.Equal((short)0x0001, NumberHelper.Create(0x00000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((int)0x80000000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.Create(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateFromInt64Test() + { + Assert.Equal((short)0x0000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((short)0x0001, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.Create(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((short)0x0000, NumberHelper.Create(unchecked((nint)0x0000000000000000))); + Assert.Equal((short)0x0001, NumberHelper.Create(unchecked((nint)0x0000000000000001))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.Create(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((short)0x0000, NumberHelper.Create((nint)0x00000000)); + Assert.Equal((short)0x0001, NumberHelper.Create((nint)0x00000001)); + Assert.Throws(() => NumberHelper.Create((nint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.Create(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateFromSByteTest() + { + Assert.Equal((short)0x0000, NumberHelper.Create(0x00)); + Assert.Equal((short)0x0001, NumberHelper.Create(0x01)); + Assert.Equal((short)0x007F, NumberHelper.Create(0x7F)); + Assert.Equal(unchecked((short)0xFF80), NumberHelper.Create(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.Create(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateFromUInt16Test() + { + Assert.Equal((short)0x0000, NumberHelper.Create(0x0000)); + Assert.Equal((short)0x0001, NumberHelper.Create(0x0001)); + Assert.Equal((short)0x7FFF, NumberHelper.Create(0x7FFF)); + Assert.Throws(() => NumberHelper.Create(0x8000)); + Assert.Throws(() => NumberHelper.Create(0xFFFF)); + } + + [Fact] + public static void CreateFromUInt32Test() + { + Assert.Equal((short)0x0000, NumberHelper.Create(0x00000000)); + Assert.Equal((short)0x0001, NumberHelper.Create(0x00000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x80000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFF)); + } + + [Fact] + public static void CreateFromUInt64Test() + { + Assert.Equal((short)0x0000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((short)0x0001, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x8000000000000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((short)0x0000, NumberHelper.Create(unchecked((nuint)0x0000000000000000))); + Assert.Equal((short)0x0001, NumberHelper.Create(unchecked((nuint)0x0000000000000001))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((short)0x0000, NumberHelper.Create((nuint)0x00000000)); + Assert.Equal((short)0x0001, NumberHelper.Create((nuint)0x00000001)); + Assert.Throws(() => NumberHelper.Create((nuint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create((nuint)0x80000000)); + Assert.Throws(() => NumberHelper.Create((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateSaturatingFromByteTest() + { + Assert.Equal((short)0x0000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((short)0x0001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((short)0x007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((short)0x0080, NumberHelper.CreateSaturating(0x80)); + Assert.Equal((short)0x00FF, NumberHelper.CreateSaturating(0xFF)); + } + + [Fact] + public static void CreateSaturatingFromCharTest() + { + Assert.Equal((short)0x0000, NumberHelper.CreateSaturating((char)0x0000)); + Assert.Equal((short)0x0001, NumberHelper.CreateSaturating((char)0x0001)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating((char)0x7FFF)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating((char)0x8000)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating((char)0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromInt16Test() + { + Assert.Equal((short)0x0000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((short)0x0001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal(unchecked((short)0x8000), NumberHelper.CreateSaturating(unchecked((short)0x8000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateSaturating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt32Test() + { + Assert.Equal((short)0x0000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((short)0x0001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal(unchecked((short)0x8000), NumberHelper.CreateSaturating(unchecked((int)0x80000000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateSaturating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt64Test() + { + Assert.Equal((short)0x0000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((short)0x0001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((short)0x8000), NumberHelper.CreateSaturating(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateSaturating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((short)0x0000, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000000))); + Assert.Equal((short)0x0001, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000001))); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((short)0x8000), NumberHelper.CreateSaturating(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((short)0x0000, NumberHelper.CreateSaturating((nint)0x00000000)); + Assert.Equal((short)0x0001, NumberHelper.CreateSaturating((nint)0x00000001)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((short)0x8000), NumberHelper.CreateSaturating(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateSaturatingFromSByteTest() + { + Assert.Equal((short)0x0000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((short)0x0001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((short)0x007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal(unchecked((short)0xFF80), NumberHelper.CreateSaturating(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateSaturating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateSaturatingFromUInt16Test() + { + Assert.Equal((short)0x0000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((short)0x0001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(0x8000)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt32Test() + { + Assert.Equal((short)0x0000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((short)0x0001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(0x80000000)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt64Test() + { + Assert.Equal((short)0x0000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((short)0x0001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(0x8000000000000000)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((short)0x0000, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((short)0x0001, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000001))); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(unchecked((nuint)0x8000000000000000))); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((short)0x0000, NumberHelper.CreateSaturating((nuint)0x00000000)); + Assert.Equal((short)0x0001, NumberHelper.CreateSaturating((nuint)0x00000001)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating((nuint)0x7FFFFFFF)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating((nuint)0x80000000)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateSaturating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateTruncatingFromByteTest() + { + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((short)0x0001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((short)0x007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((short)0x0080, NumberHelper.CreateTruncating(0x80)); + Assert.Equal((short)0x00FF, NumberHelper.CreateTruncating(0xFF)); + } + + [Fact] + public static void CreateTruncatingFromCharTest() + { + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating((char)0x0000)); + Assert.Equal((short)0x0001, NumberHelper.CreateTruncating((char)0x0001)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateTruncating((char)0x7FFF)); + Assert.Equal(unchecked((short)0x8000), NumberHelper.CreateTruncating((char)0x8000)); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating((char)0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromInt16Test() + { + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((short)0x0001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal(unchecked((short)0x8000), NumberHelper.CreateTruncating(unchecked((short)0x8000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt32Test() + { + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((short)0x0001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(unchecked((int)0x80000000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt64Test() + { + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((short)0x0001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000000))); + Assert.Equal((short)0x0001, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating((nint)0x00000000)); + Assert.Equal((short)0x0001, NumberHelper.CreateTruncating((nint)0x00000001)); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating((nint)0x7FFFFFFF)); + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromSByteTest() + { + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((short)0x0001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((short)0x007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal(unchecked((short)0xFF80), NumberHelper.CreateTruncating(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateTruncatingFromUInt16Test() + { + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((short)0x0001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((short)0x7FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal(unchecked((short)0x8000), NumberHelper.CreateTruncating(0x8000)); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt32Test() + { + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((short)0x0001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(0x80000000)); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt64Test() + { + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((short)0x0001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(0x8000000000000000)); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((short)0x0001, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating((nuint)0x00000000)); + Assert.Equal((short)0x0001, NumberHelper.CreateTruncating((nuint)0x00000001)); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating((nuint)0x7FFFFFFF)); + Assert.Equal((short)0x0000, NumberHelper.CreateTruncating((nuint)0x80000000)); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.CreateTruncating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void DivRemTest() + { + Assert.Equal(((short)0x0000, (short)0x0000), NumberHelper.DivRem((short)0x0000, (short)2)); + Assert.Equal(((short)0x0000, (short)0x0001), NumberHelper.DivRem((short)0x0001, (short)2)); + Assert.Equal(((short)0x3FFF, (short)0x0001), NumberHelper.DivRem((short)0x7FFF, (short)2)); + Assert.Equal((unchecked((short)0xC000), (short)0x0000), NumberHelper.DivRem(unchecked((short)0x8000), (short)2)); + Assert.Equal(((short)0x0000, unchecked((short)0xFFFF)), NumberHelper.DivRem(unchecked((short)0xFFFF), (short)2)); + } + + [Fact] + public static void MaxTest() + { + Assert.Equal((short)0x0001, NumberHelper.Max((short)0x0000, (short)1)); + Assert.Equal((short)0x0001, NumberHelper.Max((short)0x0001, (short)1)); + Assert.Equal((short)0x7FFF, NumberHelper.Max((short)0x7FFF, (short)1)); + Assert.Equal((short)0x0001, NumberHelper.Max(unchecked((short)0x8000), (short)1)); + Assert.Equal((short)0x0001, NumberHelper.Max(unchecked((short)0xFFFF), (short)1)); + } + + [Fact] + public static void MinTest() + { + Assert.Equal((short)0x0000, NumberHelper.Min((short)0x0000, (short)1)); + Assert.Equal((short)0x0001, NumberHelper.Min((short)0x0001, (short)1)); + Assert.Equal((short)0x0001, NumberHelper.Min((short)0x7FFF, (short)1)); + Assert.Equal(unchecked((short)0x8000), NumberHelper.Min(unchecked((short)0x8000), (short)1)); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.Min(unchecked((short)0xFFFF), (short)1)); + } + + [Fact] + public static void SignTest() + { + Assert.Equal((short)0x0000, NumberHelper.Sign((short)0x0000)); + Assert.Equal((short)0x0001, NumberHelper.Sign((short)0x0001)); + Assert.Equal((short)0x0001, NumberHelper.Sign((short)0x7FFF)); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.Sign(unchecked((short)0x8000))); + Assert.Equal(unchecked((short)0xFFFF), NumberHelper.Sign(unchecked((short)0xFFFF))); + } + + [Fact] + public static void TryCreateFromByteTest() + { + short result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((short)0x0001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((short)0x007F, result); + + Assert.True(NumberHelper.TryCreate(0x80, out result)); + Assert.Equal((short)0x0080, result); + + Assert.True(NumberHelper.TryCreate(0xFF, out result)); + Assert.Equal((short)0x00FF, result); + } + + [Fact] + public static void TryCreateFromCharTest() + { + short result; + + Assert.True(NumberHelper.TryCreate((char)0x0000, out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate((char)0x0001, out result)); + Assert.Equal((short)0x0001, result); + + Assert.True(NumberHelper.TryCreate((char)0x7FFF, out result)); + Assert.Equal((short)0x7FFF, result); + + Assert.False(NumberHelper.TryCreate((char)0x8000, out result)); + Assert.Equal((short)0x0000, result); + + Assert.False(NumberHelper.TryCreate((char)0xFFFF, out result)); + Assert.Equal((short)0x0000, result); + } + + [Fact] + public static void TryCreateFromInt16Test() + { + short result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((short)0x0001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((short)0x7FFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked((short)0x8000), out result)); + Assert.Equal(unchecked((short)0x8000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((short)0xFFFF), out result)); + Assert.Equal(unchecked((short)0xFFFF), result); + } + + [Fact] + public static void TryCreateFromInt32Test() + { + short result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((short)0x0001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((short)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((int)0x80000000), out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((int)0xFFFFFFFF), out result)); + Assert.Equal(unchecked((short)0xFFFF), result); + } + + [Fact] + public static void TryCreateFromInt64Test() + { + short result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((short)0x0001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((short)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0x8000000000000000), out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((long)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((short)0xFFFF), result); + } + + [Fact] + public static void TryCreateFromIntPtrTest() + { + short result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000000), out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000001), out result)); + Assert.Equal((short)0x0001, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((short)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x8000000000000000), out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((short)0xFFFF), result); + } + else + { + Assert.True(NumberHelper.TryCreate((nint)0x00000000, out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate((nint)0x00000001, out result)); + Assert.Equal((short)0x0001, result); + + Assert.False(NumberHelper.TryCreate((nint)0x7FFFFFFF, out result)); + Assert.Equal((short)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x80000000), out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFF), out result)); + Assert.Equal(unchecked((short)0xFFFF), result); + } + } + + [Fact] + public static void TryCreateFromSByteTest() + { + short result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((short)0x0001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((short)0x007F, result); + + Assert.True(NumberHelper.TryCreate(unchecked((sbyte)0x80), out result)); + Assert.Equal(unchecked((short)0xFF80), result); + + Assert.True(NumberHelper.TryCreate(unchecked((sbyte)0xFF), out result)); + Assert.Equal(unchecked((short)0xFFFF), result); + } + + [Fact] + public static void TryCreateFromUInt16Test() + { + short result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((short)0x0001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((short)0x7FFF, result); + + Assert.False(NumberHelper.TryCreate(0x8000, out result)); + Assert.Equal((short)0x0000, result); + + Assert.False(NumberHelper.TryCreate(0xFFFF, out result)); + Assert.Equal((short)0x0000, result); + } + + [Fact] + public static void TryCreateFromUInt32Test() + { + short result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((short)0x0001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((short)0x0000, result); + + Assert.False(NumberHelper.TryCreate(0x80000000, out result)); + Assert.Equal((short)0x0000, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFF, out result)); + Assert.Equal((short)0x0000, result); + } + + [Fact] + public static void TryCreateFromUInt64Test() + { + short result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((short)0x0001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((short)0x0000, result); + + Assert.False(NumberHelper.TryCreate(0x8000000000000000, out result)); + Assert.Equal((short)0x0000, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFFFFFFFFFF, out result)); + Assert.Equal((short)0x0000, result); + } + + [Fact] + public static void TryCreateFromUIntPtrTest() + { + short result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000000), out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000001), out result)); + Assert.Equal((short)0x0001, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((short)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x8000000000000000), out result)); + Assert.Equal((short)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((short)0x0000, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nuint)0x00000000, out result)); + Assert.Equal((short)0x0000, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x00000001, out result)); + Assert.Equal((short)0x0001, result); + + Assert.False(NumberHelper.TryCreate((nuint)0x7FFFFFFF, out result)); + Assert.Equal((short)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x80000000), out result)); + Assert.Equal((short)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFF), out result)); + Assert.Equal((short)0x0000, result); + } + } + + [Fact] + + public static void op_LeftShiftTest() + { + Assert.Equal((short)0x0000, ShiftOperatorsHelper.op_LeftShift((short)0x0000, 1)); + Assert.Equal((short)0x0002, ShiftOperatorsHelper.op_LeftShift((short)0x0001, 1)); + Assert.Equal(unchecked((short)0xFFFE), ShiftOperatorsHelper.op_LeftShift((short)0x7FFF, 1)); + Assert.Equal((short)0x0000, ShiftOperatorsHelper.op_LeftShift(unchecked((short)0x8000), 1)); + Assert.Equal(unchecked((short)0xFFFE), ShiftOperatorsHelper.op_LeftShift(unchecked((short)0xFFFF), 1)); + } + + [Fact] + public static void op_RightShiftTest() + { + Assert.Equal((short)0x0000, ShiftOperatorsHelper.op_RightShift((short)0x0000, 1)); + Assert.Equal((short)0x0000, ShiftOperatorsHelper.op_RightShift((short)0x0001, 1)); + Assert.Equal((short)0x3FFF, ShiftOperatorsHelper.op_RightShift((short)0x7FFF, 1)); + Assert.Equal(unchecked((short)0xC000), ShiftOperatorsHelper.op_RightShift(unchecked((short)0x8000), 1)); + Assert.Equal(unchecked((short)0xFFFF), ShiftOperatorsHelper.op_RightShift(unchecked((short)0xFFFF), 1)); + } + + [Fact] + public static void op_SubtractionTest() + { + Assert.Equal(unchecked((short)0xFFFF), SubtractionOperatorsHelper.op_Subtraction((short)0x0000, (short)1)); + Assert.Equal((short)0x0000, SubtractionOperatorsHelper.op_Subtraction((short)0x0001, (short)1)); + Assert.Equal((short)0x7FFE, SubtractionOperatorsHelper.op_Subtraction((short)0x7FFF, (short)1)); + Assert.Equal((short)0x7FFF, SubtractionOperatorsHelper.op_Subtraction(unchecked((short)0x8000), (short)1)); + Assert.Equal(unchecked((short)0xFFFE), SubtractionOperatorsHelper.op_Subtraction(unchecked((short)0xFFFF), (short)1)); + } + + [Fact] + public static void op_UnaryNegationTest() + { + Assert.Equal((short)0x0000, UnaryNegationOperatorsHelper.op_UnaryNegation((short)0x0000)); + Assert.Equal(unchecked((short)0xFFFF), UnaryNegationOperatorsHelper.op_UnaryNegation((short)0x0001)); + Assert.Equal(unchecked((short)0x8001), UnaryNegationOperatorsHelper.op_UnaryNegation((short)0x7FFF)); + Assert.Equal(unchecked((short)0x8000), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((short)0x8000))); + Assert.Equal((short)0x0001, UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((short)0xFFFF))); + } + + [Fact] + public static void op_UnaryPlusTest() + { + Assert.Equal((short)0x0000, UnaryPlusOperatorsHelper.op_UnaryPlus((short)0x0000)); + Assert.Equal((short)0x0001, UnaryPlusOperatorsHelper.op_UnaryPlus((short)0x0001)); + Assert.Equal((short)0x7FFF, UnaryPlusOperatorsHelper.op_UnaryPlus((short)0x7FFF)); + Assert.Equal(unchecked((short)0x8000), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((short)0x8000))); + Assert.Equal(unchecked((short)0xFFFF), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((short)0xFFFF))); + } + + [Theory] + [MemberData(nameof(Int16Tests.Parse_Valid_TestData), MemberType = typeof(Int16Tests))] + public static void ParseValidStringTest(string value, NumberStyles style, IFormatProvider provider, short expected) + { + short result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.True(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.True(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(Int16Tests.Parse_Invalid_TestData), MemberType = typeof(Int16Tests))] + public static void ParseInvalidStringTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + short result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(default(short), result); + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.False(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(default(short), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.False(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(default(short), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(Int16Tests.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(Int16Tests))] + public static void ParseValidSpanTest(string value, int offset, int count, NumberStyles style, IFormatProvider provider, short expected) + { + short result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(SpanParseableHelper.TryParse(value.AsSpan(offset, count), provider, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, NumberHelper.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(NumberHelper.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Int16Tests.Parse_Invalid_TestData), MemberType = typeof(Int16Tests))] + public static void ParseInvalidSpanTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value is null) + { + return; + } + + short result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(SpanParseableHelper.TryParse(value.AsSpan(), provider, out result)); + Assert.Equal(default(short), result); + } + + Assert.Throws(exceptionType, () => NumberHelper.Parse(value.AsSpan(), style, provider)); + + Assert.False(NumberHelper.TryParse(value.AsSpan(), style, provider, out result)); + Assert.Equal(default(short), result); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/Int32GenericMathTests.cs b/src/libraries/System.Runtime/tests/System/Int32GenericMathTests.cs deleted file mode 100644 index c8f469d6eb3a..000000000000 --- a/src/libraries/System.Runtime/tests/System/Int32GenericMathTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Linq; -using Xunit; - -namespace System.Tests -{ - public sealed class Int32GenericMathTests : GenericMathTests - { - public override void AverageTest() - { - var values = Enumerable.Range(0, 32768); - Assert.Equal(expected: 16383.5, actual: GenericMath.Average(values)); - } - - public override void StandardDeviationTest() - { - var values = Enumerable.Range(0, 32768); - Assert.Equal(expected: 9459.451146868934, actual: GenericMath.StandardDeviation(values)); - } - - public override void SumTest() - { - var values = Enumerable.Range(0, 32768); - Assert.Equal(expected: 536854528, actual: GenericMath.Sum(values)); - } - - public override void SumInt32Test() - { - var values = Enumerable.Range(0, 32768); - Assert.Equal(expected: 536854528, actual: GenericMath.Sum(values)); - } - } -} diff --git a/src/libraries/System.Runtime/tests/System/Int32Tests.GenericMath.cs b/src/libraries/System.Runtime/tests/System/Int32Tests.GenericMath.cs new file mode 100644 index 000000000000..28f3c06c4039 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Int32Tests.GenericMath.cs @@ -0,0 +1,1181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Runtime.Versioning; +using Xunit; + +namespace System.Tests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [RequiresPreviewFeaturesAttribute] + public class Int32Tests_GenericMath + { + [Fact] + public static void AdditiveIdentityTest() + { + Assert.Equal((int)0x00000000, AdditiveIdentityHelper.AdditiveIdentity); + } + + [Fact] + public static void MinValueTest() + { + Assert.Equal(unchecked((int)0x80000000), MinMaxValueHelper.MinValue); + } + + [Fact] + public static void MaxValueTest() + { + Assert.Equal((int)0x7FFFFFFF, MinMaxValueHelper.MaxValue); + } + + [Fact] + public static void MultiplicativeIdentityTest() + { + Assert.Equal((int)0x00000001, MultiplicativeIdentityHelper.MultiplicativeIdentity); + } + + [Fact] + public static void NegativeOneTest() + { + Assert.Equal(unchecked((int)0xFFFFFFFF), SignedNumberHelper.NegativeOne); + } + + [Fact] + public static void OneTest() + { + Assert.Equal((int)0x00000001, NumberHelper.One); + } + + [Fact] + public static void ZeroTest() + { + Assert.Equal((int)0x00000000, NumberHelper.Zero); + } + + [Fact] + public static void op_AdditionTest() + { + Assert.Equal((int)0x00000001, AdditionOperatorsHelper.op_Addition((int)0x00000000, 1)); + Assert.Equal((int)0x00000002, AdditionOperatorsHelper.op_Addition((int)0x00000001, 1)); + Assert.Equal(unchecked((int)0x80000000), AdditionOperatorsHelper.op_Addition((int)0x7FFFFFFF, 1)); + Assert.Equal(unchecked((int)0x80000001), AdditionOperatorsHelper.op_Addition(unchecked((int)0x80000000), 1)); + Assert.Equal((int)0x00000000, AdditionOperatorsHelper.op_Addition(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void LeadingZeroCountTest() + { + Assert.Equal((int)0x00000020, BinaryIntegerHelper.LeadingZeroCount((int)0x00000000)); + Assert.Equal((int)0x0000001F, BinaryIntegerHelper.LeadingZeroCount((int)0x00000001)); + Assert.Equal((int)0x00000001, BinaryIntegerHelper.LeadingZeroCount((int)0x7FFFFFFF)); + Assert.Equal((int)0x00000000, BinaryIntegerHelper.LeadingZeroCount(unchecked((int)0x80000000))); + Assert.Equal((int)0x00000000, BinaryIntegerHelper.LeadingZeroCount(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void PopCountTest() + { + Assert.Equal((int)0x00000000, BinaryIntegerHelper.PopCount((int)0x00000000)); + Assert.Equal((int)0x00000001, BinaryIntegerHelper.PopCount((int)0x00000001)); + Assert.Equal((int)0x0000001F, BinaryIntegerHelper.PopCount((int)0x7FFFFFFF)); + Assert.Equal((int)0x00000001, BinaryIntegerHelper.PopCount(unchecked((int)0x80000000))); + Assert.Equal((int)0x00000020, BinaryIntegerHelper.PopCount(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void RotateLeftTest() + { + Assert.Equal((int)0x00000000, BinaryIntegerHelper.RotateLeft((int)0x00000000, 1)); + Assert.Equal((int)0x00000002, BinaryIntegerHelper.RotateLeft((int)0x00000001, 1)); + Assert.Equal(unchecked((int)0xFFFFFFFE), BinaryIntegerHelper.RotateLeft((int)0x7FFFFFFF, 1)); + Assert.Equal((int)0x00000001, BinaryIntegerHelper.RotateLeft(unchecked((int)0x80000000), 1)); + Assert.Equal(unchecked((int)0xFFFFFFFF), BinaryIntegerHelper.RotateLeft(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void RotateRightTest() + { + Assert.Equal((int)0x00000000, BinaryIntegerHelper.RotateRight((int)0x00000000, 1)); + Assert.Equal(unchecked((int)0x80000000), BinaryIntegerHelper.RotateRight((int)0x00000001, 1)); + Assert.Equal(unchecked((int)0xBFFFFFFF), BinaryIntegerHelper.RotateRight((int)0x7FFFFFFF, 1)); + Assert.Equal((int)0x40000000, BinaryIntegerHelper.RotateRight(unchecked((int)0x80000000), 1)); + Assert.Equal(unchecked((int)0xFFFFFFFF), BinaryIntegerHelper.RotateRight(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void TrailingZeroCountTest() + { + Assert.Equal((int)0x00000020, BinaryIntegerHelper.TrailingZeroCount((int)0x00000000)); + Assert.Equal((int)0x00000000, BinaryIntegerHelper.TrailingZeroCount((int)0x00000001)); + Assert.Equal((int)0x00000000, BinaryIntegerHelper.TrailingZeroCount((int)0x7FFFFFFF)); + Assert.Equal((int)0x0000001F, BinaryIntegerHelper.TrailingZeroCount(unchecked((int)0x80000000))); + Assert.Equal((int)0x00000000, BinaryIntegerHelper.TrailingZeroCount(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void IsPow2Test() + { + Assert.False(BinaryNumberHelper.IsPow2((int)0x00000000)); + Assert.True(BinaryNumberHelper.IsPow2((int)0x00000001)); + Assert.False(BinaryNumberHelper.IsPow2((int)0x7FFFFFFF)); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((int)0x80000000))); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void Log2Test() + { + Assert.Equal((int)0x00000000, BinaryNumberHelper.Log2((int)0x00000000)); + Assert.Equal((int)0x00000000, BinaryNumberHelper.Log2((int)0x00000001)); + Assert.Equal((int)0x0000001E, BinaryNumberHelper.Log2((int)0x7FFFFFFF)); + Assert.Throws(() => BinaryNumberHelper.Log2(unchecked((int)0x80000000))); + Assert.Throws(() => BinaryNumberHelper.Log2(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void op_BitwiseAndTest() + { + Assert.Equal((int)0x00000000, BitwiseOperatorsHelper.op_BitwiseAnd((int)0x00000000, 1)); + Assert.Equal((int)0x00000001, BitwiseOperatorsHelper.op_BitwiseAnd((int)0x00000001, 1)); + Assert.Equal((int)0x00000001, BitwiseOperatorsHelper.op_BitwiseAnd((int)0x7FFFFFFF, 1)); + Assert.Equal((int)0x00000000, BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((int)0x80000000), 1)); + Assert.Equal((int)0x00000001, BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void op_BitwiseOrTest() + { + Assert.Equal((int)0x00000001, BitwiseOperatorsHelper.op_BitwiseOr((int)0x00000000, 1)); + Assert.Equal((int)0x00000001, BitwiseOperatorsHelper.op_BitwiseOr((int)0x00000001, 1)); + Assert.Equal((int)0x7FFFFFFF, BitwiseOperatorsHelper.op_BitwiseOr((int)0x7FFFFFFF, 1)); + Assert.Equal(unchecked((int)0x80000001), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((int)0x80000000), 1)); + Assert.Equal(unchecked((int)0xFFFFFFFF), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void op_ExclusiveOrTest() + { + Assert.Equal((int)0x00000001, BitwiseOperatorsHelper.op_ExclusiveOr((int)0x00000000, 1)); + Assert.Equal((int)0x00000000, BitwiseOperatorsHelper.op_ExclusiveOr((int)0x00000001, 1)); + Assert.Equal((int)0x7FFFFFFE, BitwiseOperatorsHelper.op_ExclusiveOr((int)0x7FFFFFFF, 1)); + Assert.Equal(unchecked((int)0x80000001), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((int)0x80000000), 1)); + Assert.Equal(unchecked((int)0xFFFFFFFE), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void op_OnesComplementTest() + { + Assert.Equal(unchecked((int)0xFFFFFFFF), BitwiseOperatorsHelper.op_OnesComplement((int)0x00000000)); + Assert.Equal(unchecked((int)0xFFFFFFFE), BitwiseOperatorsHelper.op_OnesComplement((int)0x00000001)); + Assert.Equal(unchecked((int)0x80000000), BitwiseOperatorsHelper.op_OnesComplement((int)0x7FFFFFFF)); + Assert.Equal((int)0x7FFFFFFF, BitwiseOperatorsHelper.op_OnesComplement(unchecked((int)0x80000000))); + Assert.Equal((int)0x00000000, BitwiseOperatorsHelper.op_OnesComplement(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void op_LessThanTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThan((int)0x00000000, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((int)0x00000001, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((int)0x7FFFFFFF, 1)); + Assert.True(ComparisonOperatorsHelper.op_LessThan(unchecked((int)0x80000000), 1)); + Assert.True(ComparisonOperatorsHelper.op_LessThan(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void op_LessThanOrEqualTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((int)0x00000000, 1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((int)0x00000001, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((int)0x7FFFFFFF, 1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((int)0x80000000), 1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void op_GreaterThanTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((int)0x00000000, 1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((int)0x00000001, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((int)0x7FFFFFFF, 1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((int)0x80000000), 1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void op_GreaterThanOrEqualTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual((int)0x00000000, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((int)0x00000001, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((int)0x7FFFFFFF, 1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((int)0x80000000), 1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void op_DecrementTest() + { + Assert.Equal(unchecked((int)0xFFFFFFFF), DecrementOperatorsHelper.op_Decrement((int)0x00000000)); + Assert.Equal((int)0x00000000, DecrementOperatorsHelper.op_Decrement((int)0x00000001)); + Assert.Equal((int)0x7FFFFFFE, DecrementOperatorsHelper.op_Decrement((int)0x7FFFFFFF)); + Assert.Equal((int)0x7FFFFFFF, DecrementOperatorsHelper.op_Decrement(unchecked((int)0x80000000))); + Assert.Equal(unchecked((int)0xFFFFFFFE), DecrementOperatorsHelper.op_Decrement(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void op_DivisionTest() + { + Assert.Equal((int)0x00000000, DivisionOperatorsHelper.op_Division((int)0x00000000, 2)); + Assert.Equal((int)0x00000000, DivisionOperatorsHelper.op_Division((int)0x00000001, 2)); + Assert.Equal((int)0x3FFFFFFF, DivisionOperatorsHelper.op_Division((int)0x7FFFFFFF, 2)); + Assert.Equal(unchecked((int)0xC0000000), DivisionOperatorsHelper.op_Division(unchecked((int)0x80000000), 2)); + Assert.Equal((int)0x00000000, DivisionOperatorsHelper.op_Division(unchecked((int)0xFFFFFFFF), 2)); + } + + [Fact] + public static void op_EqualityTest() + { + Assert.False(EqualityOperatorsHelper.op_Equality((int)0x00000000, 1)); + Assert.True(EqualityOperatorsHelper.op_Equality((int)0x00000001, 1)); + Assert.False(EqualityOperatorsHelper.op_Equality((int)0x7FFFFFFF, 1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((int)0x80000000), 1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void op_InequalityTest() + { + Assert.True(EqualityOperatorsHelper.op_Inequality((int)0x00000000, 1)); + Assert.False(EqualityOperatorsHelper.op_Inequality((int)0x00000001, 1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((int)0x7FFFFFFF, 1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((int)0x80000000), 1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void op_IncrementTest() + { + Assert.Equal((int)0x00000001, IncrementOperatorsHelper.op_Increment((int)0x00000000)); + Assert.Equal((int)0x00000002, IncrementOperatorsHelper.op_Increment((int)0x00000001)); + Assert.Equal(unchecked((int)0x80000000), IncrementOperatorsHelper.op_Increment((int)0x7FFFFFFF)); + Assert.Equal(unchecked((int)0x80000001), IncrementOperatorsHelper.op_Increment(unchecked((int)0x80000000))); + Assert.Equal((int)0x00000000, IncrementOperatorsHelper.op_Increment(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void op_ModulusTest() + { + Assert.Equal((int)0x00000000, ModulusOperatorsHelper.op_Modulus((int)0x00000000, 2)); + Assert.Equal((int)0x00000001, ModulusOperatorsHelper.op_Modulus((int)0x00000001, 2)); + Assert.Equal((int)0x00000001, ModulusOperatorsHelper.op_Modulus((int)0x7FFFFFFF, 2)); + Assert.Equal((int)0x00000000, ModulusOperatorsHelper.op_Modulus(unchecked((int)0x80000000), 2)); + Assert.Equal(unchecked((int)0xFFFFFFFF), ModulusOperatorsHelper.op_Modulus(unchecked((int)0xFFFFFFFF), 2)); + } + + [Fact] + public static void op_MultiplyTest() + { + Assert.Equal((int)0x00000000, MultiplyOperatorsHelper.op_Multiply((int)0x00000000, 2)); + Assert.Equal((int)0x00000002, MultiplyOperatorsHelper.op_Multiply((int)0x00000001, 2)); + Assert.Equal(unchecked((int)0xFFFFFFFE), MultiplyOperatorsHelper.op_Multiply((int)0x7FFFFFFF, 2)); + Assert.Equal((int)0x00000000, MultiplyOperatorsHelper.op_Multiply(unchecked((int)0x80000000), 2)); + Assert.Equal(unchecked((int)0xFFFFFFFE), MultiplyOperatorsHelper.op_Multiply(unchecked((int)0xFFFFFFFF), 2)); + } + + [Fact] + public static void AbsTest() + { + Assert.Equal((int)0x00000000, NumberHelper.Abs((int)0x00000000)); + Assert.Equal((int)0x00000001, NumberHelper.Abs((int)0x00000001)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.Abs((int)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Abs(unchecked((int)0x80000000))); + Assert.Equal((int)0x00000001, NumberHelper.Abs(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void ClampTest() + { + Assert.Equal((int)0x00000000, NumberHelper.Clamp((int)0x00000000, unchecked((int)0xFFFFFFC0), 0x003F)); + Assert.Equal((int)0x00000001, NumberHelper.Clamp((int)0x00000001, unchecked((int)0xFFFFFFC0), 0x003F)); + Assert.Equal((int)0x0000003F, NumberHelper.Clamp((int)0x7FFFFFFF, unchecked((int)0xFFFFFFC0), 0x003F)); + Assert.Equal(unchecked((int)0xFFFFFFC0), NumberHelper.Clamp(unchecked((int)0x80000000), unchecked((int)0xFFFFFFC0), 0x003F)); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.Clamp(unchecked((int)0xFFFFFFFF), unchecked((int)0xFFFFFFC0), 0x003F)); + } + + [Fact] + public static void CreateFromByteTest() + { + Assert.Equal((int)0x00000000, NumberHelper.Create(0x00)); + Assert.Equal((int)0x00000001, NumberHelper.Create(0x01)); + Assert.Equal((int)0x0000007F, NumberHelper.Create(0x7F)); + Assert.Equal((int)0x00000080, NumberHelper.Create(0x80)); + Assert.Equal((int)0x000000FF, NumberHelper.Create(0xFF)); + } + + [Fact] + public static void CreateFromCharTest() + { + Assert.Equal((int)0x00000000, NumberHelper.Create((char)0x0000)); + Assert.Equal((int)0x00000001, NumberHelper.Create((char)0x0001)); + Assert.Equal((int)0x00007FFF, NumberHelper.Create((char)0x7FFF)); + Assert.Equal((int)0x00008000, NumberHelper.Create((char)0x8000)); + Assert.Equal((int)0x0000FFFF, NumberHelper.Create((char)0xFFFF)); + } + + [Fact] + public static void CreateFromInt16Test() + { + Assert.Equal((int)0x00000000, NumberHelper.Create(0x0000)); + Assert.Equal((int)0x00000001, NumberHelper.Create(0x0001)); + Assert.Equal((int)0x00007FFF, NumberHelper.Create(0x7FFF)); + Assert.Equal(unchecked((int)0xFFFF8000), NumberHelper.Create(unchecked((short)0x8000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.Create(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateFromInt32Test() + { + Assert.Equal((int)0x00000000, NumberHelper.Create(0x00000000)); + Assert.Equal((int)0x00000001, NumberHelper.Create(0x00000001)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.Create(0x7FFFFFFF)); + Assert.Equal(unchecked((int)0x80000000), NumberHelper.Create(unchecked(unchecked((int)0x80000000)))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.Create(unchecked(unchecked((int)0xFFFFFFFF)))); + } + + [Fact] + public static void CreateFromInt64Test() + { + Assert.Equal((int)0x00000000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((int)0x00000001, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.Create(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((int)0x00000000, NumberHelper.Create(unchecked((nint)0x0000000000000000))); + Assert.Equal((int)0x00000001, NumberHelper.Create(unchecked((nint)0x0000000000000001))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.Create(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((int)0x00000000, NumberHelper.Create((nint)0x00000000)); + Assert.Equal((int)0x00000001, NumberHelper.Create((nint)0x00000001)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.Create((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((int)0x80000000), NumberHelper.Create(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.Create(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateFromSByteTest() + { + Assert.Equal((int)0x00000000, NumberHelper.Create(0x00)); + Assert.Equal((int)0x00000001, NumberHelper.Create(0x01)); + Assert.Equal((int)0x0000007F, NumberHelper.Create(0x7F)); + Assert.Equal(unchecked((int)0xFFFFFF80), NumberHelper.Create(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.Create(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateFromUInt16Test() + { + Assert.Equal((int)0x00000000, NumberHelper.Create(0x0000)); + Assert.Equal((int)0x00000001, NumberHelper.Create(0x0001)); + Assert.Equal((int)0x00007FFF, NumberHelper.Create(0x7FFF)); + Assert.Equal((int)0x00008000, NumberHelper.Create(0x8000)); + Assert.Equal((int)0x0000FFFF, NumberHelper.Create(0xFFFF)); + } + + [Fact] + public static void CreateFromUInt32Test() + { + Assert.Equal((int)0x00000000, NumberHelper.Create(0x00000000)); + Assert.Equal((int)0x00000001, NumberHelper.Create(0x00000001)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x80000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFF)); + } + + [Fact] + public static void CreateFromUInt64Test() + { + Assert.Equal((int)0x00000000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((int)0x00000001, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x8000000000000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((int)0x00000000, NumberHelper.Create(unchecked((nuint)0x0000000000000000))); + Assert.Equal((int)0x00000001, NumberHelper.Create(unchecked((nuint)0x0000000000000001))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((int)0x00000000, NumberHelper.Create((nuint)0x00000000)); + Assert.Equal((int)0x00000001, NumberHelper.Create((nuint)0x00000001)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.Create((nuint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create((nuint)0x80000000)); + Assert.Throws(() => NumberHelper.Create((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateSaturatingFromByteTest() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((int)0x00000001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((int)0x0000007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((int)0x00000080, NumberHelper.CreateSaturating(0x80)); + Assert.Equal((int)0x000000FF, NumberHelper.CreateSaturating(0xFF)); + } + + [Fact] + public static void CreateSaturatingFromCharTest() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateSaturating((char)0x0000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateSaturating((char)0x0001)); + Assert.Equal((int)0x00007FFF, NumberHelper.CreateSaturating((char)0x7FFF)); + Assert.Equal((int)0x00008000, NumberHelper.CreateSaturating((char)0x8000)); + Assert.Equal((int)0x0000FFFF, NumberHelper.CreateSaturating((char)0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromInt16Test() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((int)0x00007FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal(unchecked((int)0xFFFF8000), NumberHelper.CreateSaturating(unchecked((short)0x8000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateSaturating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt32Test() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal(unchecked((int)0x80000000), NumberHelper.CreateSaturating(unchecked(unchecked((int)0x80000000)))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateSaturating(unchecked(unchecked((int)0xFFFFFFFF)))); + } + + [Fact] + public static void CreateSaturatingFromInt64Test() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal(unchecked((int)0x7FFFFFFF), NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((int)0x80000000), NumberHelper.CreateSaturating(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateSaturating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((int)0x00000000, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000000))); + Assert.Equal((int)0x00000001, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((int)0x7FFFFFFF), NumberHelper.CreateSaturating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((int)0x80000000), NumberHelper.CreateSaturating(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((int)0x00000000, NumberHelper.CreateSaturating((nint)0x00000000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateSaturating((nint)0x00000001)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateSaturating((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((int)0x80000000), NumberHelper.CreateSaturating(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateSaturatingFromSByteTest() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((int)0x00000001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((int)0x0000007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal(unchecked((int)0xFFFFFF80), NumberHelper.CreateSaturating(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateSaturating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateSaturatingFromUInt16Test() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((int)0x00007FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((int)0x00008000, NumberHelper.CreateSaturating(0x8000)); + Assert.Equal((int)0x0000FFFF, NumberHelper.CreateSaturating(0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt32Test() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateSaturating(0x80000000)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateSaturating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt64Test() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateSaturating(0x8000000000000000)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateSaturating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((int)0x00000000, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((int)0x00000001, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000001))); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0x8000000000000000))); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((int)0x00000000, NumberHelper.CreateSaturating((nuint)0x00000000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateSaturating((nuint)0x00000001)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateSaturating((nuint)0x7FFFFFFF)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateSaturating((nuint)0x80000000)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateSaturating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateTruncatingFromByteTest() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((int)0x00000001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((int)0x0000007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((int)0x00000080, NumberHelper.CreateTruncating(0x80)); + Assert.Equal((int)0x000000FF, NumberHelper.CreateTruncating(0xFF)); + } + + [Fact] + public static void CreateTruncatingFromCharTest() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating((char)0x0000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateTruncating((char)0x0001)); + Assert.Equal((int)0x00007FFF, NumberHelper.CreateTruncating((char)0x7FFF)); + Assert.Equal((int)0x00008000, NumberHelper.CreateTruncating((char)0x8000)); + Assert.Equal((int)0x0000FFFF, NumberHelper.CreateTruncating((char)0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromInt16Test() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((int)0x00007FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal(unchecked((int)0xFFFF8000), NumberHelper.CreateTruncating(unchecked((short)0x8000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt32Test() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal(unchecked((int)0x80000000), NumberHelper.CreateTruncating(unchecked(unchecked((int)0x80000000)))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked(unchecked((int)0xFFFFFFFF)))); + } + + [Fact] + public static void CreateTruncatingFromInt64Test() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000000))); + Assert.Equal((int)0x00000001, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating((nint)0x00000000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateTruncating((nint)0x00000001)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateTruncating((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((int)0x80000000), NumberHelper.CreateTruncating(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromSByteTest() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((int)0x00000001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((int)0x0000007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal(unchecked((int)0xFFFFFF80), NumberHelper.CreateTruncating(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateTruncatingFromUInt16Test() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((int)0x00007FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((int)0x00008000, NumberHelper.CreateTruncating(0x8000)); + Assert.Equal((int)0x0000FFFF, NumberHelper.CreateTruncating(0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt32Test() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal(unchecked((int)0x80000000), NumberHelper.CreateTruncating(0x80000000)); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateTruncating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt64Test() + { + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating(0x8000000000000000)); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateTruncating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((int)0x00000001, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((int)0x00000000, NumberHelper.CreateTruncating((nuint)0x00000000)); + Assert.Equal((int)0x00000001, NumberHelper.CreateTruncating((nuint)0x00000001)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.CreateTruncating((nuint)0x7FFFFFFF)); + Assert.Equal(unchecked((int)0x80000000), NumberHelper.CreateTruncating((nuint)0x80000000)); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.CreateTruncating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void DivRemTest() + { + Assert.Equal(((int)0x00000000, (int)0x00000000), NumberHelper.DivRem((int)0x00000000, 2)); + Assert.Equal(((int)0x00000000, (int)0x00000001), NumberHelper.DivRem((int)0x00000001, 2)); + Assert.Equal(((int)0x3FFFFFFF, (int)0x00000001), NumberHelper.DivRem((int)0x7FFFFFFF, 2)); + Assert.Equal((unchecked((int)0xC0000000), (int)0x00000000), NumberHelper.DivRem(unchecked((int)0x80000000), 2)); + Assert.Equal(((int)0x00000000, unchecked((int)0xFFFFFFFF)), NumberHelper.DivRem(unchecked((int)0xFFFFFFFF), 2)); + } + + [Fact] + public static void MaxTest() + { + Assert.Equal((int)0x00000001, NumberHelper.Max((int)0x00000000, 1)); + Assert.Equal((int)0x00000001, NumberHelper.Max((int)0x00000001, 1)); + Assert.Equal((int)0x7FFFFFFF, NumberHelper.Max((int)0x7FFFFFFF, 1)); + Assert.Equal((int)0x00000001, NumberHelper.Max(unchecked((int)0x80000000), 1)); + Assert.Equal((int)0x00000001, NumberHelper.Max(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void MinTest() + { + Assert.Equal((int)0x00000000, NumberHelper.Min((int)0x00000000, 1)); + Assert.Equal((int)0x00000001, NumberHelper.Min((int)0x00000001, 1)); + Assert.Equal((int)0x00000001, NumberHelper.Min((int)0x7FFFFFFF, 1)); + Assert.Equal(unchecked((int)0x80000000), NumberHelper.Min(unchecked((int)0x80000000), 1)); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.Min(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void SignTest() + { + Assert.Equal((int)0x00000000, NumberHelper.Sign((int)0x00000000)); + Assert.Equal((int)0x00000001, NumberHelper.Sign((int)0x00000001)); + Assert.Equal((int)0x00000001, NumberHelper.Sign((int)0x7FFFFFFF)); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.Sign(unchecked((int)0x80000000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), NumberHelper.Sign(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void TryCreateFromByteTest() + { + int result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((int)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((int)0x0000007F, result); + + Assert.True(NumberHelper.TryCreate(0x80, out result)); + Assert.Equal((int)0x00000080, result); + + Assert.True(NumberHelper.TryCreate(0xFF, out result)); + Assert.Equal((int)0x000000FF, result); + } + + [Fact] + public static void TryCreateFromCharTest() + { + int result; + + Assert.True(NumberHelper.TryCreate((char)0x0000, out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate((char)0x0001, out result)); + Assert.Equal((int)0x00000001, result); + + Assert.True(NumberHelper.TryCreate((char)0x7FFF, out result)); + Assert.Equal((int)0x00007FFF, result); + + Assert.True(NumberHelper.TryCreate((char)0x8000, out result)); + Assert.Equal((int)0x00008000, result); + + Assert.True(NumberHelper.TryCreate((char)0xFFFF, out result)); + Assert.Equal((int)0x0000FFFF, result); + } + + [Fact] + public static void TryCreateFromInt16Test() + { + int result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((int)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((int)0x00007FFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked((short)0x8000), out result)); + Assert.Equal(unchecked((int)0xFFFF8000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((short)0xFFFF), out result)); + Assert.Equal(unchecked((int)0xFFFFFFFF), result); + } + + [Fact] + public static void TryCreateFromInt32Test() + { + int result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((int)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((int)0x7FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked(unchecked((int)0x80000000)), out result)); + Assert.Equal(unchecked((int)0x80000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked(unchecked((int)0xFFFFFFFF)), out result)); + Assert.Equal(unchecked((int)0xFFFFFFFF), result); + } + + [Fact] + public static void TryCreateFromInt64Test() + { + int result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((int)0x00000001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((int)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0x8000000000000000), out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((long)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((int)0xFFFFFFFF), result); + } + + [Fact] + public static void TryCreateFromIntPtrTest() + { + int result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000000), out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000001), out result)); + Assert.Equal((int)0x00000001, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((int)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x8000000000000000), out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((int)0xFFFFFFFF), result); + } + else + { + Assert.True(NumberHelper.TryCreate((nint)0x00000000, out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate((nint)0x00000001, out result)); + Assert.Equal((int)0x00000001, result); + + Assert.True(NumberHelper.TryCreate((nint)0x7FFFFFFF, out result)); + Assert.Equal((int)0x7FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x80000000), out result)); + Assert.Equal(unchecked((int)0x80000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFF), out result)); + Assert.Equal(unchecked((int)0xFFFFFFFF), result); + } + } + + [Fact] + public static void TryCreateFromSByteTest() + { + int result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((int)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((int)0x0000007F, result); + + Assert.True(NumberHelper.TryCreate(unchecked((sbyte)0x80), out result)); + Assert.Equal(unchecked((int)0xFFFFFF80), result); + + Assert.True(NumberHelper.TryCreate(unchecked((sbyte)0xFF), out result)); + Assert.Equal(unchecked((int)0xFFFFFFFF), result); + } + + [Fact] + public static void TryCreateFromUInt16Test() + { + int result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((int)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((int)0x00007FFF, result); + + Assert.True(NumberHelper.TryCreate(0x8000, out result)); + Assert.Equal((int)0x00008000, result); + + Assert.True(NumberHelper.TryCreate(0xFFFF, out result)); + Assert.Equal((int)0x0000FFFF, result); + } + + [Fact] + public static void TryCreateFromUInt32Test() + { + int result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((int)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((int)0x7FFFFFFF, result); + + Assert.False(NumberHelper.TryCreate(0x80000000, out result)); + Assert.Equal(unchecked((int)0x00000000), result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFF, out result)); + Assert.Equal(unchecked((int)0x00000000), result); + } + + [Fact] + public static void TryCreateFromUInt64Test() + { + int result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((int)0x00000001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((int)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(0x8000000000000000, out result)); + Assert.Equal((int)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFFFFFFFFFF, out result)); + Assert.Equal((int)0x00000000, result); + } + + [Fact] + public static void TryCreateFromUIntPtrTest() + { + int result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000000), out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000001), out result)); + Assert.Equal((int)0x00000001, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((int)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x8000000000000000), out result)); + Assert.Equal((int)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((int)0x00000000, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nuint)0x00000000, out result)); + Assert.Equal((int)0x00000000, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x00000001, out result)); + Assert.Equal((int)0x00000001, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x7FFFFFFF, out result)); + Assert.Equal((int)0x7FFFFFFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x80000000), out result)); + Assert.Equal(unchecked((int)0x00000000), result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFF), out result)); + Assert.Equal(unchecked((int)0x00000000), result); + } + } + + [Fact] + + public static void op_LeftShiftTest() + { + Assert.Equal((int)0x00000000, ShiftOperatorsHelper.op_LeftShift((int)0x00000000, 1)); + Assert.Equal((int)0x00000002, ShiftOperatorsHelper.op_LeftShift((int)0x00000001, 1)); + Assert.Equal(unchecked((int)0xFFFFFFFE), ShiftOperatorsHelper.op_LeftShift((int)0x7FFFFFFF, 1)); + Assert.Equal((int)0x00000000, ShiftOperatorsHelper.op_LeftShift(unchecked((int)0x80000000), 1)); + Assert.Equal(unchecked((int)0xFFFFFFFE), ShiftOperatorsHelper.op_LeftShift(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void op_RightShiftTest() + { + Assert.Equal((int)0x00000000, ShiftOperatorsHelper.op_RightShift((int)0x00000000, 1)); + Assert.Equal((int)0x00000000, ShiftOperatorsHelper.op_RightShift((int)0x00000001, 1)); + Assert.Equal((int)0x3FFFFFFF, ShiftOperatorsHelper.op_RightShift((int)0x7FFFFFFF, 1)); + Assert.Equal(unchecked((int)0xC0000000), ShiftOperatorsHelper.op_RightShift(unchecked((int)0x80000000), 1)); + Assert.Equal(unchecked((int)0xFFFFFFFF), ShiftOperatorsHelper.op_RightShift(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void op_SubtractionTest() + { + Assert.Equal(unchecked((int)0xFFFFFFFF), SubtractionOperatorsHelper.op_Subtraction((int)0x00000000, 1)); + Assert.Equal((int)0x00000000, SubtractionOperatorsHelper.op_Subtraction((int)0x00000001, 1)); + Assert.Equal((int)0x7FFFFFFE, SubtractionOperatorsHelper.op_Subtraction((int)0x7FFFFFFF, 1)); + Assert.Equal((int)0x7FFFFFFF, SubtractionOperatorsHelper.op_Subtraction(unchecked((int)0x80000000), 1)); + Assert.Equal(unchecked((int)0xFFFFFFFE), SubtractionOperatorsHelper.op_Subtraction(unchecked((int)0xFFFFFFFF), 1)); + } + + [Fact] + public static void op_UnaryNegationTest() + { + Assert.Equal((int)0x00000000, UnaryNegationOperatorsHelper.op_UnaryNegation((int)0x00000000)); + Assert.Equal(unchecked((int)0xFFFFFFFF), UnaryNegationOperatorsHelper.op_UnaryNegation((int)0x00000001)); + Assert.Equal(unchecked((int)0x80000001), UnaryNegationOperatorsHelper.op_UnaryNegation((int)0x7FFFFFFF)); + Assert.Equal(unchecked((int)0x80000000), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((int)0x80000000))); + Assert.Equal((int)0x00000001, UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void op_UnaryPlusTest() + { + Assert.Equal((int)0x00000000, UnaryPlusOperatorsHelper.op_UnaryPlus((int)0x00000000)); + Assert.Equal((int)0x00000001, UnaryPlusOperatorsHelper.op_UnaryPlus((int)0x00000001)); + Assert.Equal((int)0x7FFFFFFF, UnaryPlusOperatorsHelper.op_UnaryPlus((int)0x7FFFFFFF)); + Assert.Equal(unchecked((int)0x80000000), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((int)0x80000000))); + Assert.Equal(unchecked((int)0xFFFFFFFF), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((int)0xFFFFFFFF))); + } + + [Theory] + [MemberData(nameof(Int32Tests.Parse_Valid_TestData), MemberType = typeof(Int32Tests))] + public static void ParseValidStringTest(string value, NumberStyles style, IFormatProvider provider, int expected) + { + int result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.True(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.True(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(Int32Tests.Parse_Invalid_TestData), MemberType = typeof(Int32Tests))] + public static void ParseInvalidStringTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + int result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(default(int), result); + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.False(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(default(int), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.False(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(default(int), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(Int32Tests.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(Int32Tests))] + public static void ParseValidSpanTest(string value, int offset, int count, NumberStyles style, IFormatProvider provider, int expected) + { + int result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(SpanParseableHelper.TryParse(value.AsSpan(offset, count), provider, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, NumberHelper.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(NumberHelper.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Int32Tests.Parse_Invalid_TestData), MemberType = typeof(Int32Tests))] + public static void ParseInvalidSpanTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value is null) + { + return; + } + + int result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(SpanParseableHelper.TryParse(value.AsSpan(), provider, out result)); + Assert.Equal(default(int), result); + } + + Assert.Throws(exceptionType, () => NumberHelper.Parse(value.AsSpan(), style, provider)); + + Assert.False(NumberHelper.TryParse(value.AsSpan(), style, provider, out result)); + Assert.Equal(default(int), result); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/Int64Tests.GenericMath.cs b/src/libraries/System.Runtime/tests/System/Int64Tests.GenericMath.cs new file mode 100644 index 000000000000..ddc380cc36e2 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Int64Tests.GenericMath.cs @@ -0,0 +1,1181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Runtime.Versioning; +using Xunit; + +namespace System.Tests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [RequiresPreviewFeaturesAttribute] + public class Int64Tests_GenericMath + { + [Fact] + public static void AdditiveIdentityTest() + { + Assert.Equal((long)0x0000000000000000, AdditiveIdentityHelper.AdditiveIdentity); + } + + [Fact] + public static void MinValueTest() + { + Assert.Equal(unchecked((long)0x8000000000000000), MinMaxValueHelper.MinValue); + } + + [Fact] + public static void MaxValueTest() + { + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, MinMaxValueHelper.MaxValue); + } + + [Fact] + public static void MultiplicativeIdentityTest() + { + Assert.Equal((long)0x0000000000000001, MultiplicativeIdentityHelper.MultiplicativeIdentity); + } + + [Fact] + public static void NegativeOneTest() + { + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), SignedNumberHelper.NegativeOne); + } + + [Fact] + public static void OneTest() + { + Assert.Equal((long)0x0000000000000001, NumberHelper.One); + } + + [Fact] + public static void ZeroTest() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Zero); + } + + [Fact] + public static void op_AdditionTest() + { + Assert.Equal((long)0x0000000000000001, AdditionOperatorsHelper.op_Addition((long)0x0000000000000000, 1)); + Assert.Equal((long)0x0000000000000002, AdditionOperatorsHelper.op_Addition((long)0x0000000000000001, 1)); + Assert.Equal(unchecked((long)0x8000000000000000), AdditionOperatorsHelper.op_Addition((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal(unchecked((long)0x8000000000000001), AdditionOperatorsHelper.op_Addition(unchecked((long)0x8000000000000000), 1)); + Assert.Equal((long)0x0000000000000000, AdditionOperatorsHelper.op_Addition(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void LeadingZeroCountTest() + { + Assert.Equal((long)0x0000000000000040, BinaryIntegerHelper.LeadingZeroCount((long)0x0000000000000000)); + Assert.Equal((long)0x000000000000003F, BinaryIntegerHelper.LeadingZeroCount((long)0x0000000000000001)); + Assert.Equal((long)0x0000000000000001, BinaryIntegerHelper.LeadingZeroCount((long)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((long)0x0000000000000000, BinaryIntegerHelper.LeadingZeroCount(unchecked((long)0x8000000000000000))); + Assert.Equal((long)0x0000000000000000, BinaryIntegerHelper.LeadingZeroCount(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void PopCountTest() + { + Assert.Equal((long)0x0000000000000000, BinaryIntegerHelper.PopCount((long)0x0000000000000000)); + Assert.Equal((long)0x0000000000000001, BinaryIntegerHelper.PopCount((long)0x0000000000000001)); + Assert.Equal((long)0x000000000000003F, BinaryIntegerHelper.PopCount((long)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((long)0x0000000000000001, BinaryIntegerHelper.PopCount(unchecked((long)0x8000000000000000))); + Assert.Equal((long)0x0000000000000040, BinaryIntegerHelper.PopCount(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void RotateLeftTest() + { + Assert.Equal((long)0x0000000000000000, BinaryIntegerHelper.RotateLeft((long)0x0000000000000000, 1)); + Assert.Equal((long)0x0000000000000002, BinaryIntegerHelper.RotateLeft((long)0x0000000000000001, 1)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFE), BinaryIntegerHelper.RotateLeft((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((long)0x0000000000000001, BinaryIntegerHelper.RotateLeft(unchecked((long)0x8000000000000000), 1)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), BinaryIntegerHelper.RotateLeft(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void RotateRightTest() + { + Assert.Equal((long)0x0000000000000000, BinaryIntegerHelper.RotateRight((long)0x0000000000000000, 1)); + Assert.Equal(unchecked((long)0x8000000000000000), BinaryIntegerHelper.RotateRight((long)0x0000000000000001, 1)); + Assert.Equal(unchecked((long)0xBFFFFFFFFFFFFFFF), BinaryIntegerHelper.RotateRight((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((long)0x4000000000000000, BinaryIntegerHelper.RotateRight(unchecked((long)0x8000000000000000), 1)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), BinaryIntegerHelper.RotateRight(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void TrailingZeroCountTest() + { + Assert.Equal((long)0x0000000000000040, BinaryIntegerHelper.TrailingZeroCount((long)0x0000000000000000)); + Assert.Equal((long)0x0000000000000000, BinaryIntegerHelper.TrailingZeroCount((long)0x0000000000000001)); + Assert.Equal((long)0x0000000000000000, BinaryIntegerHelper.TrailingZeroCount((long)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((long)0x000000000000003F, BinaryIntegerHelper.TrailingZeroCount(unchecked((long)0x8000000000000000))); + Assert.Equal((long)0x0000000000000000, BinaryIntegerHelper.TrailingZeroCount(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void IsPow2Test() + { + Assert.False(BinaryNumberHelper.IsPow2((long)0x0000000000000000)); + Assert.True(BinaryNumberHelper.IsPow2((long)0x0000000000000001)); + Assert.False(BinaryNumberHelper.IsPow2((long)0x7FFFFFFFFFFFFFFF)); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((long)0x8000000000000000))); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void Log2Test() + { + Assert.Equal((long)0x0000000000000000, BinaryNumberHelper.Log2((long)0x0000000000000000)); + Assert.Equal((long)0x0000000000000000, BinaryNumberHelper.Log2((long)0x0000000000000001)); + Assert.Equal((long)0x000000000000003E, BinaryNumberHelper.Log2((long)0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => BinaryNumberHelper.Log2(unchecked((long)0x8000000000000000))); + Assert.Throws(() => BinaryNumberHelper.Log2(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void op_BitwiseAndTest() + { + Assert.Equal((long)0x0000000000000000, BitwiseOperatorsHelper.op_BitwiseAnd((long)0x0000000000000000, 1)); + Assert.Equal((long)0x0000000000000001, BitwiseOperatorsHelper.op_BitwiseAnd((long)0x0000000000000001, 1)); + Assert.Equal((long)0x0000000000000001, BitwiseOperatorsHelper.op_BitwiseAnd((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((long)0x0000000000000000, BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((long)0x8000000000000000), 1)); + Assert.Equal((long)0x0000000000000001, BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void op_BitwiseOrTest() + { + Assert.Equal((long)0x0000000000000001, BitwiseOperatorsHelper.op_BitwiseOr((long)0x0000000000000000, 1)); + Assert.Equal((long)0x0000000000000001, BitwiseOperatorsHelper.op_BitwiseOr((long)0x0000000000000001, 1)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, BitwiseOperatorsHelper.op_BitwiseOr((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal(unchecked((long)0x8000000000000001), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((long)0x8000000000000000), 1)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void op_ExclusiveOrTest() + { + Assert.Equal((long)0x0000000000000001, BitwiseOperatorsHelper.op_ExclusiveOr((long)0x0000000000000000, 1)); + Assert.Equal((long)0x0000000000000000, BitwiseOperatorsHelper.op_ExclusiveOr((long)0x0000000000000001, 1)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFE, BitwiseOperatorsHelper.op_ExclusiveOr((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal(unchecked((long)0x8000000000000001), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((long)0x8000000000000000), 1)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFE), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void op_OnesComplementTest() + { + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), BitwiseOperatorsHelper.op_OnesComplement((long)0x0000000000000000)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFE), BitwiseOperatorsHelper.op_OnesComplement((long)0x0000000000000001)); + Assert.Equal(unchecked((long)0x8000000000000000), BitwiseOperatorsHelper.op_OnesComplement((long)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, BitwiseOperatorsHelper.op_OnesComplement(unchecked((long)0x8000000000000000))); + Assert.Equal((long)0x0000000000000000, BitwiseOperatorsHelper.op_OnesComplement(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void op_LessThanTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThan((long)0x0000000000000000, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((long)0x0000000000000001, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.True(ComparisonOperatorsHelper.op_LessThan(unchecked((long)0x8000000000000000), 1)); + Assert.True(ComparisonOperatorsHelper.op_LessThan(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void op_LessThanOrEqualTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((long)0x0000000000000000, 1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((long)0x0000000000000001, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((long)0x8000000000000000), 1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void op_GreaterThanTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((long)0x0000000000000000, 1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((long)0x0000000000000001, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((long)0x8000000000000000), 1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void op_GreaterThanOrEqualTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual((long)0x0000000000000000, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((long)0x0000000000000001, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((long)0x8000000000000000), 1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void op_DecrementTest() + { + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), DecrementOperatorsHelper.op_Decrement((long)0x0000000000000000)); + Assert.Equal((long)0x0000000000000000, DecrementOperatorsHelper.op_Decrement((long)0x0000000000000001)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFE, DecrementOperatorsHelper.op_Decrement((long)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, DecrementOperatorsHelper.op_Decrement(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFE), DecrementOperatorsHelper.op_Decrement(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void op_DivisionTest() + { + Assert.Equal((long)0x0000000000000000, DivisionOperatorsHelper.op_Division((long)0x0000000000000000, 2)); + Assert.Equal((long)0x0000000000000000, DivisionOperatorsHelper.op_Division((long)0x0000000000000001, 2)); + Assert.Equal((long)0x3FFFFFFFFFFFFFFF, DivisionOperatorsHelper.op_Division((long)0x7FFFFFFFFFFFFFFF, 2)); + Assert.Equal(unchecked((long)0xC000000000000000), DivisionOperatorsHelper.op_Division(unchecked((long)0x8000000000000000), 2)); + Assert.Equal((long)0x0000000000000000, DivisionOperatorsHelper.op_Division(unchecked((long)0xFFFFFFFFFFFFFFFF), 2)); + } + + [Fact] + public static void op_EqualityTest() + { + Assert.False(EqualityOperatorsHelper.op_Equality((long)0x0000000000000000, 1)); + Assert.True(EqualityOperatorsHelper.op_Equality((long)0x0000000000000001, 1)); + Assert.False(EqualityOperatorsHelper.op_Equality((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((long)0x8000000000000000), 1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void op_InequalityTest() + { + Assert.True(EqualityOperatorsHelper.op_Inequality((long)0x0000000000000000, 1)); + Assert.False(EqualityOperatorsHelper.op_Inequality((long)0x0000000000000001, 1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((long)0x8000000000000000), 1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void op_IncrementTest() + { + Assert.Equal((long)0x0000000000000001, IncrementOperatorsHelper.op_Increment((long)0x0000000000000000)); + Assert.Equal((long)0x0000000000000002, IncrementOperatorsHelper.op_Increment((long)0x0000000000000001)); + Assert.Equal(unchecked((long)0x8000000000000000), IncrementOperatorsHelper.op_Increment((long)0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((long)0x8000000000000001), IncrementOperatorsHelper.op_Increment(unchecked((long)0x8000000000000000))); + Assert.Equal((long)0x0000000000000000, IncrementOperatorsHelper.op_Increment(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void op_ModulusTest() + { + Assert.Equal((long)0x0000000000000000, ModulusOperatorsHelper.op_Modulus((long)0x0000000000000000, 2)); + Assert.Equal((long)0x0000000000000001, ModulusOperatorsHelper.op_Modulus((long)0x0000000000000001, 2)); + Assert.Equal((long)0x0000000000000001, ModulusOperatorsHelper.op_Modulus((long)0x7FFFFFFFFFFFFFFF, 2)); + Assert.Equal((long)0x0000000000000000, ModulusOperatorsHelper.op_Modulus(unchecked((long)0x8000000000000000), 2)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), ModulusOperatorsHelper.op_Modulus(unchecked((long)0xFFFFFFFFFFFFFFFF), 2)); + } + + [Fact] + public static void op_MultiplyTest() + { + Assert.Equal((long)0x0000000000000000, MultiplyOperatorsHelper.op_Multiply((long)0x0000000000000000, 2)); + Assert.Equal((long)0x0000000000000002, MultiplyOperatorsHelper.op_Multiply((long)0x0000000000000001, 2)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFE), MultiplyOperatorsHelper.op_Multiply((long)0x7FFFFFFFFFFFFFFF, 2)); + Assert.Equal((long)0x0000000000000000, MultiplyOperatorsHelper.op_Multiply(unchecked((long)0x8000000000000000), 2)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFE), MultiplyOperatorsHelper.op_Multiply(unchecked((long)0xFFFFFFFFFFFFFFFF), 2)); + } + + [Fact] + public static void AbsTest() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Abs((long)0x0000000000000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Abs((long)0x0000000000000001)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.Abs((long)0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Abs(unchecked((long)0x8000000000000000))); + Assert.Equal((long)0x0000000000000001, NumberHelper.Abs(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void ClampTest() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Clamp((long)0x0000000000000000, unchecked((long)0xFFFFFFFFFFFFFFC0), 0x003F)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Clamp((long)0x0000000000000001, unchecked((long)0xFFFFFFFFFFFFFFC0), 0x003F)); + Assert.Equal((long)0x000000000000003F, NumberHelper.Clamp((long)0x7FFFFFFFFFFFFFFF, unchecked((long)0xFFFFFFFFFFFFFFC0), 0x003F)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFC0), NumberHelper.Clamp(unchecked((long)0x8000000000000000), unchecked((long)0xFFFFFFFFFFFFFFC0), 0x003F)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.Clamp(unchecked((long)0xFFFFFFFFFFFFFFFF), unchecked((long)0xFFFFFFFFFFFFFFC0), 0x003F)); + } + + [Fact] + public static void CreateFromByteTest() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Create(0x00)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Create(0x01)); + Assert.Equal((long)0x000000000000007F, NumberHelper.Create(0x7F)); + Assert.Equal((long)0x0000000000000080, NumberHelper.Create(0x80)); + Assert.Equal((long)0x00000000000000FF, NumberHelper.Create(0xFF)); + } + + [Fact] + public static void CreateFromCharTest() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Create((char)0x0000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Create((char)0x0001)); + Assert.Equal((long)0x0000000000007FFF, NumberHelper.Create((char)0x7FFF)); + Assert.Equal((long)0x0000000000008000, NumberHelper.Create((char)0x8000)); + Assert.Equal((long)0x000000000000FFFF, NumberHelper.Create((char)0xFFFF)); + } + + [Fact] + public static void CreateFromInt16Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Create(0x0000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Create(0x0001)); + Assert.Equal((long)0x0000000000007FFF, NumberHelper.Create(0x7FFF)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFF8000), NumberHelper.Create(unchecked((short)0x8000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.Create(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateFromInt32Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Create(0x00000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Create(0x00000001)); + Assert.Equal((long)0x000000007FFFFFFF, NumberHelper.Create(0x7FFFFFFF)); + Assert.Equal(unchecked((long)0xFFFFFFFF80000000), NumberHelper.Create(unchecked((int)0x80000000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.Create(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateFromInt64Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Create(0x0000000000000001)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((long)0x8000000000000000), NumberHelper.Create(unchecked(unchecked((long)0x8000000000000000)))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.Create(unchecked(unchecked((long)0xFFFFFFFFFFFFFFFF)))); + } + + [Fact] + public static void CreateFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Create(unchecked((nint)0x0000000000000000))); + Assert.Equal((long)0x0000000000000001, NumberHelper.Create(unchecked((nint)0x0000000000000001))); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.Create(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((long)0x8000000000000000), NumberHelper.Create(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.Create(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Create((nint)0x00000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Create((nint)0x00000001)); + Assert.Equal((long)0x000000007FFFFFFF, NumberHelper.Create((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((long)0xFFFFFFFF80000000), NumberHelper.Create(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.Create(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateFromSByteTest() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Create(0x00)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Create(0x01)); + Assert.Equal((long)0x000000000000007F, NumberHelper.Create(0x7F)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFF80), NumberHelper.Create(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.Create(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateFromUInt16Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Create(0x0000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Create(0x0001)); + Assert.Equal((long)0x0000000000007FFF, NumberHelper.Create(0x7FFF)); + Assert.Equal((long)0x0000000000008000, NumberHelper.Create(0x8000)); + Assert.Equal((long)0x000000000000FFFF, NumberHelper.Create(0xFFFF)); + } + + [Fact] + public static void CreateFromUInt32Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Create(0x00000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Create(0x00000001)); + Assert.Equal((long)0x000000007FFFFFFF, NumberHelper.Create(0x7FFFFFFF)); + Assert.Equal((long)0x0000000080000000, NumberHelper.Create(0x80000000)); + Assert.Equal((long)0x00000000FFFFFFFF, NumberHelper.Create(0xFFFFFFFF)); + } + + [Fact] + public static void CreateFromUInt64Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Create(0x0000000000000001)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x8000000000000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Create(unchecked((nuint)0x0000000000000000))); + Assert.Equal((long)0x0000000000000001, NumberHelper.Create(unchecked((nuint)0x0000000000000001))); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.Create(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Create((nuint)0x00000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Create((nuint)0x00000001)); + Assert.Equal((long)0x000000007FFFFFFF, NumberHelper.Create((nuint)0x7FFFFFFF)); + Assert.Equal((long)0x0000000080000000, NumberHelper.Create((nuint)0x80000000)); + Assert.Equal((long)0x00000000FFFFFFFF, NumberHelper.Create((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateSaturatingFromByteTest() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((long)0x000000000000007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((long)0x0000000000000080, NumberHelper.CreateSaturating(0x80)); + Assert.Equal((long)0x00000000000000FF, NumberHelper.CreateSaturating(0xFF)); + } + + [Fact] + public static void CreateSaturatingFromCharTest() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateSaturating((char)0x0000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateSaturating((char)0x0001)); + Assert.Equal((long)0x0000000000007FFF, NumberHelper.CreateSaturating((char)0x7FFF)); + Assert.Equal((long)0x0000000000008000, NumberHelper.CreateSaturating((char)0x8000)); + Assert.Equal((long)0x000000000000FFFF, NumberHelper.CreateSaturating((char)0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromInt16Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((long)0x0000000000007FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFF8000), NumberHelper.CreateSaturating(unchecked((short)0x8000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt32Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((long)0x000000007FFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal(unchecked((long)0xFFFFFFFF80000000), NumberHelper.CreateSaturating(unchecked((int)0x80000000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt64Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((long)0x8000000000000000), NumberHelper.CreateSaturating(unchecked(unchecked((long)0x8000000000000000)))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked(unchecked((long)0xFFFFFFFFFFFFFFFF)))); + } + + [Fact] + public static void CreateSaturatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000000))); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000001))); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateSaturating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((long)0x8000000000000000), NumberHelper.CreateSaturating(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateSaturating((nint)0x00000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateSaturating((nint)0x00000001)); + Assert.Equal((long)0x000000007FFFFFFF, NumberHelper.CreateSaturating((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((long)0xFFFFFFFF80000000), NumberHelper.CreateSaturating(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateSaturatingFromSByteTest() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((long)0x000000000000007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFF80), NumberHelper.CreateSaturating(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateSaturatingFromUInt16Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((long)0x0000000000007FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((long)0x0000000000008000, NumberHelper.CreateSaturating(0x8000)); + Assert.Equal((long)0x000000000000FFFF, NumberHelper.CreateSaturating(0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt32Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((long)0x000000007FFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((long)0x0000000080000000, NumberHelper.CreateSaturating(0x80000000)); + Assert.Equal((long)0x00000000FFFFFFFF, NumberHelper.CreateSaturating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt64Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateSaturating(0x8000000000000000)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateSaturating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000001))); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0x8000000000000000))); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateSaturating((nuint)0x00000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateSaturating((nuint)0x00000001)); + Assert.Equal((long)0x000000007FFFFFFF, NumberHelper.CreateSaturating((nuint)0x7FFFFFFF)); + Assert.Equal((long)0x0000000080000000, NumberHelper.CreateSaturating((nuint)0x80000000)); + Assert.Equal((long)0x00000000FFFFFFFF, NumberHelper.CreateSaturating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateTruncatingFromByteTest() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((long)0x000000000000007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((long)0x0000000000000080, NumberHelper.CreateTruncating(0x80)); + Assert.Equal((long)0x00000000000000FF, NumberHelper.CreateTruncating(0xFF)); + } + + [Fact] + public static void CreateTruncatingFromCharTest() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateTruncating((char)0x0000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateTruncating((char)0x0001)); + Assert.Equal((long)0x0000000000007FFF, NumberHelper.CreateTruncating((char)0x7FFF)); + Assert.Equal((long)0x0000000000008000, NumberHelper.CreateTruncating((char)0x8000)); + Assert.Equal((long)0x000000000000FFFF, NumberHelper.CreateTruncating((char)0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromInt16Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((long)0x0000000000007FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFF8000), NumberHelper.CreateTruncating(unchecked((short)0x8000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt32Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((long)0x000000007FFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal(unchecked((long)0xFFFFFFFF80000000), NumberHelper.CreateTruncating(unchecked((int)0x80000000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt64Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((long)0x8000000000000000), NumberHelper.CreateTruncating(unchecked(unchecked((long)0x8000000000000000)))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked(unchecked((long)0xFFFFFFFFFFFFFFFF)))); + } + + [Fact] + public static void CreateTruncatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000000))); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000001))); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((long)0x8000000000000000), NumberHelper.CreateTruncating(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateTruncating((nint)0x00000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateTruncating((nint)0x00000001)); + Assert.Equal((long)0x000000007FFFFFFF, NumberHelper.CreateTruncating((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((long)0xFFFFFFFF80000000), NumberHelper.CreateTruncating(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromSByteTest() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((long)0x000000000000007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFF80), NumberHelper.CreateTruncating(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateTruncatingFromUInt16Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((long)0x0000000000007FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((long)0x0000000000008000, NumberHelper.CreateTruncating(0x8000)); + Assert.Equal((long)0x000000000000FFFF, NumberHelper.CreateTruncating(0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt32Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((long)0x000000007FFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((long)0x0000000080000000, NumberHelper.CreateTruncating(0x80000000)); + Assert.Equal((long)0x00000000FFFFFFFF, NumberHelper.CreateTruncating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt64Test() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((long)0x8000000000000000), NumberHelper.CreateTruncating(0x8000000000000000)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000001))); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((long)0x8000000000000000), NumberHelper.CreateTruncating(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((long)0x0000000000000000, NumberHelper.CreateTruncating((nuint)0x00000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.CreateTruncating((nuint)0x00000001)); + Assert.Equal((long)0x000000007FFFFFFF, NumberHelper.CreateTruncating((nuint)0x7FFFFFFF)); + Assert.Equal((long)0x0000000080000000, NumberHelper.CreateTruncating((nuint)0x80000000)); + Assert.Equal((long)0x00000000FFFFFFFF, NumberHelper.CreateTruncating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void DivRemTest() + { + Assert.Equal(((long)0x0000000000000000, (long)0x0000000000000000), NumberHelper.DivRem((long)0x0000000000000000, 2)); + Assert.Equal(((long)0x0000000000000000, (long)0x0000000000000001), NumberHelper.DivRem((long)0x0000000000000001, 2)); + Assert.Equal(((long)0x3FFFFFFFFFFFFFFF, (long)0x0000000000000001), NumberHelper.DivRem((long)0x7FFFFFFFFFFFFFFF, 2)); + Assert.Equal((unchecked((long)0xC000000000000000), (long)0x0000000000000000), NumberHelper.DivRem(unchecked((long)0x8000000000000000), 2)); + Assert.Equal(((long)0x0000000000000000, unchecked((long)0xFFFFFFFFFFFFFFFF)), NumberHelper.DivRem(unchecked((long)0xFFFFFFFFFFFFFFFF), 2)); + } + + [Fact] + public static void MaxTest() + { + Assert.Equal((long)0x0000000000000001, NumberHelper.Max((long)0x0000000000000000, 1)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Max((long)0x0000000000000001, 1)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, NumberHelper.Max((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Max(unchecked((long)0x8000000000000000), 1)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Max(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void MinTest() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Min((long)0x0000000000000000, 1)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Min((long)0x0000000000000001, 1)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Min((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal(unchecked((long)0x8000000000000000), NumberHelper.Min(unchecked((long)0x8000000000000000), 1)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.Min(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void SignTest() + { + Assert.Equal((long)0x0000000000000000, NumberHelper.Sign((long)0x0000000000000000)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Sign((long)0x0000000000000001)); + Assert.Equal((long)0x0000000000000001, NumberHelper.Sign((long)0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.Sign(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), NumberHelper.Sign(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void TryCreateFromByteTest() + { + long result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((long)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((long)0x000000000000007F, result); + + Assert.True(NumberHelper.TryCreate(0x80, out result)); + Assert.Equal((long)0x0000000000000080, result); + + Assert.True(NumberHelper.TryCreate(0xFF, out result)); + Assert.Equal((long)0x00000000000000FF, result); + } + + [Fact] + public static void TryCreateFromCharTest() + { + long result; + + Assert.True(NumberHelper.TryCreate((char)0x0000, out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate((char)0x0001, out result)); + Assert.Equal((long)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate((char)0x7FFF, out result)); + Assert.Equal((long)0x0000000000007FFF, result); + + Assert.True(NumberHelper.TryCreate((char)0x8000, out result)); + Assert.Equal((long)0x0000000000008000, result); + + Assert.True(NumberHelper.TryCreate((char)0xFFFF, out result)); + Assert.Equal((long)0x000000000000FFFF, result); + } + + [Fact] + public static void TryCreateFromInt16Test() + { + long result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((long)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((long)0x0000000000007FFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked((short)0x8000), out result)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFF8000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((short)0xFFFF), out result)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), result); + } + + [Fact] + public static void TryCreateFromInt32Test() + { + long result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((long)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((long)0x000000007FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked((int)0x80000000), out result)); + Assert.Equal(unchecked((long)0xFFFFFFFF80000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((int)0xFFFFFFFF), out result)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), result); + } + + [Fact] + public static void TryCreateFromInt64Test() + { + long result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((long)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked(unchecked((long)0x8000000000000000)), out result)); + Assert.Equal(unchecked((long)0x8000000000000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked(unchecked((long)0xFFFFFFFFFFFFFFFF)), out result)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), result); + } + + [Fact] + public static void TryCreateFromIntPtrTest() + { + long result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000000), out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000001), out result)); + Assert.Equal((long)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x8000000000000000), out result)); + Assert.Equal(unchecked((long)0x8000000000000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), result); + } + else + { + Assert.True(NumberHelper.TryCreate((nint)0x00000000, out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate((nint)0x00000001, out result)); + Assert.Equal((long)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate((nint)0x7FFFFFFF, out result)); + Assert.Equal((long)0x000000007FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x80000000), out result)); + Assert.Equal(unchecked((long)0xFFFFFFFF80000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFF), out result)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), result); + } + } + + [Fact] + public static void TryCreateFromSByteTest() + { + long result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((long)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((long)0x000000000000007F, result); + + Assert.True(NumberHelper.TryCreate(unchecked((sbyte)0x80), out result)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFF80), result); + + Assert.True(NumberHelper.TryCreate(unchecked((sbyte)0xFF), out result)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), result); + } + + [Fact] + public static void TryCreateFromUInt16Test() + { + long result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((long)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((long)0x0000000000007FFF, result); + + Assert.True(NumberHelper.TryCreate(0x8000, out result)); + Assert.Equal((long)0x0000000000008000, result); + + Assert.True(NumberHelper.TryCreate(0xFFFF, out result)); + Assert.Equal((long)0x000000000000FFFF, result); + } + + [Fact] + public static void TryCreateFromUInt32Test() + { + long result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((long)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((long)0x000000007FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(0x80000000, out result)); + Assert.Equal((long)0x0000000080000000, result); + + Assert.True(NumberHelper.TryCreate(0xFFFFFFFF, out result)); + Assert.Equal((long)0x00000000FFFFFFFF, result); + } + + [Fact] + public static void TryCreateFromUInt64Test() + { + long result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((long)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, result); + + Assert.False(NumberHelper.TryCreate(0x8000000000000000, out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFFFFFFFFFF, out result)); + Assert.Equal((long)0x0000000000000000, result); + } + + [Fact] + public static void TryCreateFromUIntPtrTest() + { + long result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000000), out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000001), out result)); + Assert.Equal((long)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x8000000000000000), out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((long)0x0000000000000000, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nuint)0x00000000, out result)); + Assert.Equal((long)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x00000001, out result)); + Assert.Equal((long)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x7FFFFFFF, out result)); + Assert.Equal((long)0x000000007FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x80000000), out result)); + Assert.Equal((long)0x0000000080000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFF), out result)); + Assert.Equal((long)0x00000000FFFFFFFF, result); + } + } + + [Fact] + + public static void op_LeftShiftTest() + { + Assert.Equal((long)0x0000000000000000, ShiftOperatorsHelper.op_LeftShift((long)0x0000000000000000, 1)); + Assert.Equal((long)0x0000000000000002, ShiftOperatorsHelper.op_LeftShift((long)0x0000000000000001, 1)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFE), ShiftOperatorsHelper.op_LeftShift((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((long)0x0000000000000000, ShiftOperatorsHelper.op_LeftShift(unchecked((long)0x8000000000000000), 1)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFE), ShiftOperatorsHelper.op_LeftShift(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void op_RightShiftTest() + { + Assert.Equal((long)0x0000000000000000, ShiftOperatorsHelper.op_RightShift((long)0x0000000000000000, 1)); + Assert.Equal((long)0x0000000000000000, ShiftOperatorsHelper.op_RightShift((long)0x0000000000000001, 1)); + Assert.Equal((long)0x3FFFFFFFFFFFFFFF, ShiftOperatorsHelper.op_RightShift((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal(unchecked((long)0xC000000000000000), ShiftOperatorsHelper.op_RightShift(unchecked((long)0x8000000000000000), 1)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), ShiftOperatorsHelper.op_RightShift(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void op_SubtractionTest() + { + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), SubtractionOperatorsHelper.op_Subtraction((long)0x0000000000000000, 1)); + Assert.Equal((long)0x0000000000000000, SubtractionOperatorsHelper.op_Subtraction((long)0x0000000000000001, 1)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFE, SubtractionOperatorsHelper.op_Subtraction((long)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, SubtractionOperatorsHelper.op_Subtraction(unchecked((long)0x8000000000000000), 1)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFE), SubtractionOperatorsHelper.op_Subtraction(unchecked((long)0xFFFFFFFFFFFFFFFF), 1)); + } + + [Fact] + public static void op_UnaryNegationTest() + { + Assert.Equal((long)0x0000000000000000, UnaryNegationOperatorsHelper.op_UnaryNegation((long)0x0000000000000000)); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), UnaryNegationOperatorsHelper.op_UnaryNegation((long)0x0000000000000001)); + Assert.Equal(unchecked((long)0x8000000000000001), UnaryNegationOperatorsHelper.op_UnaryNegation((long)0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((long)0x8000000000000000), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((long)0x8000000000000000))); + Assert.Equal((long)0x0000000000000001, UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void op_UnaryPlusTest() + { + Assert.Equal((long)0x0000000000000000, UnaryPlusOperatorsHelper.op_UnaryPlus((long)0x0000000000000000)); + Assert.Equal((long)0x0000000000000001, UnaryPlusOperatorsHelper.op_UnaryPlus((long)0x0000000000000001)); + Assert.Equal((long)0x7FFFFFFFFFFFFFFF, UnaryPlusOperatorsHelper.op_UnaryPlus((long)0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((long)0x8000000000000000), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((long)0xFFFFFFFFFFFFFFFF), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Theory] + [MemberData(nameof(Int64Tests.Parse_Valid_TestData), MemberType = typeof(Int64Tests))] + public static void ParseValidStringTest(string value, NumberStyles style, IFormatProvider provider, long expected) + { + long result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.True(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.True(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(Int64Tests.Parse_Invalid_TestData), MemberType = typeof(Int64Tests))] + public static void ParseInvalidStringTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + long result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(default(long), result); + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.False(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(default(long), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.False(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(default(long), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(Int64Tests.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(Int64Tests))] + public static void ParseValidSpanTest(string value, int offset, int count, NumberStyles style, IFormatProvider provider, long expected) + { + long result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(SpanParseableHelper.TryParse(value.AsSpan(offset, count), provider, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, NumberHelper.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(NumberHelper.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Int64Tests.Parse_Invalid_TestData), MemberType = typeof(Int64Tests))] + public static void ParseInvalidSpanTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value is null) + { + return; + } + + long result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(SpanParseableHelper.TryParse(value.AsSpan(), provider, out result)); + Assert.Equal(default(long), result); + } + + Assert.Throws(exceptionType, () => NumberHelper.Parse(value.AsSpan(), style, provider)); + + Assert.False(NumberHelper.TryParse(value.AsSpan(), style, provider, out result)); + Assert.Equal(default(long), result); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/IntPtrTests.GenericMath.cs b/src/libraries/System.Runtime/tests/System/IntPtrTests.GenericMath.cs new file mode 100644 index 000000000000..1f5b4144f94c --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/IntPtrTests.GenericMath.cs @@ -0,0 +1,1746 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Runtime.Versioning; +using Xunit; + +namespace System.Tests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [RequiresPreviewFeaturesAttribute] + public class IntPtrTests_GenericMath + { + [Fact] + public static void AdditiveIdentityTest() + { + Assert.Equal((nint)0x00000000, AdditiveIdentityHelper.AdditiveIdentity); + } + + [Fact] + public static void MinValueTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x8000000000000000), MinMaxValueHelper.MinValue); + } + else + { + Assert.Equal(unchecked((nint)0x80000000), MinMaxValueHelper.MinValue); + } + } + + [Fact] + public static void MaxValueTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), MinMaxValueHelper.MaxValue); + } + else + { + Assert.Equal((nint)0x7FFFFFFF, MinMaxValueHelper.MaxValue); + } + } + + [Fact] + public static void MultiplicativeIdentityTest() + { + Assert.Equal((nint)0x00000001, MultiplicativeIdentityHelper.MultiplicativeIdentity); + } + + [Fact] + public static void NegativeOneTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), SignedNumberHelper.NegativeOne); + } + else + { + Assert.Equal(unchecked((nint)0xFFFFFFFF), SignedNumberHelper.NegativeOne); + } + } + + [Fact] + public static void OneTest() + { + Assert.Equal((nint)0x00000001, NumberHelper.One); + } + + [Fact] + public static void ZeroTest() + { + Assert.Equal((nint)0x00000000, NumberHelper.Zero); + } + + [Fact] + public static void op_AdditionTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000001), AdditionOperatorsHelper.op_Addition(unchecked((nint)0x0000000000000000), (nint)1)); + Assert.Equal(unchecked((nint)0x0000000000000002), AdditionOperatorsHelper.op_Addition(unchecked((nint)0x0000000000000001), (nint)1)); + Assert.Equal(unchecked((nint)0x8000000000000000), AdditionOperatorsHelper.op_Addition(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)1)); + Assert.Equal(unchecked((nint)0x8000000000000001), AdditionOperatorsHelper.op_Addition(unchecked((nint)0x8000000000000000), (nint)1)); + Assert.Equal(unchecked((nint)0x0000000000000000), AdditionOperatorsHelper.op_Addition(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)1)); + } + else + { + Assert.Equal((nint)0x00000001, AdditionOperatorsHelper.op_Addition((nint)0x00000000, (nint)1)); + Assert.Equal((nint)0x00000002, AdditionOperatorsHelper.op_Addition((nint)0x00000001, (nint)1)); + Assert.Equal(unchecked((nint)0x80000000), AdditionOperatorsHelper.op_Addition((nint)0x7FFFFFFF, (nint)1)); + Assert.Equal(unchecked((nint)0x80000001), AdditionOperatorsHelper.op_Addition(unchecked((nint)0x80000000), (nint)1)); + Assert.Equal((nint)0x00000000, AdditionOperatorsHelper.op_Addition(unchecked((nint)0xFFFFFFFF), (nint)1)); + } + } + + [Fact] + public static void LeadingZeroCountTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000040), BinaryIntegerHelper.LeadingZeroCount(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x000000000000003F), BinaryIntegerHelper.LeadingZeroCount(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x0000000000000001), BinaryIntegerHelper.LeadingZeroCount(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nint)0x0000000000000000), BinaryIntegerHelper.LeadingZeroCount(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000000), BinaryIntegerHelper.LeadingZeroCount(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x0000000000000020, BinaryIntegerHelper.LeadingZeroCount((nint)0x00000000)); + Assert.Equal((nint)0x000000000000001F, BinaryIntegerHelper.LeadingZeroCount((nint)0x00000001)); + Assert.Equal((nint)0x0000000000000001, BinaryIntegerHelper.LeadingZeroCount((nint)0x7FFFFFFF)); + Assert.Equal((nint)0x0000000000000000, BinaryIntegerHelper.LeadingZeroCount(unchecked((nint)0x80000000))); + Assert.Equal((nint)0x0000000000000000, BinaryIntegerHelper.LeadingZeroCount(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void PopCountTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), BinaryIntegerHelper.PopCount(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000001), BinaryIntegerHelper.PopCount(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x000000000000003F), BinaryIntegerHelper.PopCount(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nint)0x0000000000000001), BinaryIntegerHelper.PopCount(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000040), BinaryIntegerHelper.PopCount(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, BinaryIntegerHelper.PopCount((nint)0x00000000)); + Assert.Equal((nint)0x00000001, BinaryIntegerHelper.PopCount((nint)0x00000001)); + Assert.Equal((nint)0x0000001F, BinaryIntegerHelper.PopCount((nint)0x7FFFFFFF)); + Assert.Equal((nint)0x00000001, BinaryIntegerHelper.PopCount(unchecked((nint)0x80000000))); + Assert.Equal((nint)0x00000020, BinaryIntegerHelper.PopCount(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void RotateLeftTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), BinaryIntegerHelper.RotateLeft(unchecked((nint)0x0000000000000000), 1)); + Assert.Equal(unchecked((nint)0x0000000000000002), BinaryIntegerHelper.RotateLeft(unchecked((nint)0x0000000000000001), 1)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFE), BinaryIntegerHelper.RotateLeft(unchecked((nint)0x7FFFFFFFFFFFFFFF), 1)); + Assert.Equal(unchecked((nint)0x0000000000000001), BinaryIntegerHelper.RotateLeft(unchecked((nint)0x8000000000000000), 1)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), BinaryIntegerHelper.RotateLeft(unchecked((nint)0xFFFFFFFFFFFFFFFF), 1)); + } + else + { + Assert.Equal((nint)0x00000000, BinaryIntegerHelper.RotateLeft((nint)0x00000000, 1)); + Assert.Equal((nint)0x00000002, BinaryIntegerHelper.RotateLeft((nint)0x00000001, 1)); + Assert.Equal(unchecked((nint)0xFFFFFFFE), BinaryIntegerHelper.RotateLeft((nint)0x7FFFFFFF, 1)); + Assert.Equal((nint)0x00000001, BinaryIntegerHelper.RotateLeft(unchecked((nint)0x80000000), 1)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), BinaryIntegerHelper.RotateLeft(unchecked((nint)0xFFFFFFFF), 1)); + } + } + + [Fact] + public static void RotateRightTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), BinaryIntegerHelper.RotateRight(unchecked((nint)0x0000000000000000), 1)); + Assert.Equal(unchecked((nint)0x8000000000000000), BinaryIntegerHelper.RotateRight(unchecked((nint)0x0000000000000001), 1)); + Assert.Equal(unchecked((nint)0xBFFFFFFFFFFFFFFF), BinaryIntegerHelper.RotateRight(unchecked((nint)0x7FFFFFFFFFFFFFFF), 1)); + Assert.Equal(unchecked((nint)0x4000000000000000), BinaryIntegerHelper.RotateRight(unchecked((nint)0x8000000000000000), 1)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), BinaryIntegerHelper.RotateRight(unchecked((nint)0xFFFFFFFFFFFFFFFF), 1)); + } + else + { + Assert.Equal((nint)0x00000000, BinaryIntegerHelper.RotateRight((nint)0x00000000, 1)); + Assert.Equal(unchecked((nint)0x80000000), BinaryIntegerHelper.RotateRight((nint)0x00000001, 1)); + Assert.Equal(unchecked((nint)0xBFFFFFFF), BinaryIntegerHelper.RotateRight((nint)0x7FFFFFFF, 1)); + Assert.Equal((nint)0x40000000, BinaryIntegerHelper.RotateRight(unchecked((nint)0x80000000), 1)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), BinaryIntegerHelper.RotateRight(unchecked((nint)0xFFFFFFFF), 1)); + } + } + + [Fact] + public static void TrailingZeroCountTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000040), BinaryIntegerHelper.TrailingZeroCount(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000000), BinaryIntegerHelper.TrailingZeroCount(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x0000000000000000), BinaryIntegerHelper.TrailingZeroCount(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nint)0x000000000000003F), BinaryIntegerHelper.TrailingZeroCount(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000000), BinaryIntegerHelper.TrailingZeroCount(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000020, BinaryIntegerHelper.TrailingZeroCount((nint)0x00000000)); + Assert.Equal((nint)0x00000000, BinaryIntegerHelper.TrailingZeroCount((nint)0x00000001)); + Assert.Equal((nint)0x00000000, BinaryIntegerHelper.TrailingZeroCount((nint)0x7FFFFFFF)); + Assert.Equal((nint)0x0000001F, BinaryIntegerHelper.TrailingZeroCount(unchecked((nint)0x80000000))); + Assert.Equal((nint)0x00000000, BinaryIntegerHelper.TrailingZeroCount(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void IsPow2Test() + { + if (Environment.Is64BitProcess) + { + Assert.False(BinaryNumberHelper.IsPow2(unchecked((nint)0x0000000000000000))); + Assert.True(BinaryNumberHelper.IsPow2(unchecked((nint)0x0000000000000001))); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((nint)0x8000000000000000))); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.False(BinaryNumberHelper.IsPow2((nint)0x00000000)); + Assert.True(BinaryNumberHelper.IsPow2((nint)0x00000001)); + Assert.False(BinaryNumberHelper.IsPow2((nint)0x7FFFFFFF)); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((nint)0x80000000))); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void Log2Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), BinaryNumberHelper.Log2(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000000), BinaryNumberHelper.Log2(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x000000000000003E), BinaryNumberHelper.Log2(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => BinaryNumberHelper.Log2(unchecked((nint)0x8000000000000000))); + Assert.Throws(() => BinaryNumberHelper.Log2(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, BinaryNumberHelper.Log2((nint)0x00000000)); + Assert.Equal((nint)0x00000000, BinaryNumberHelper.Log2((nint)0x00000001)); + Assert.Equal((nint)0x0000001E, BinaryNumberHelper.Log2((nint)0x7FFFFFFF)); + Assert.Throws(() => BinaryNumberHelper.Log2(unchecked((nint)0x80000000))); + Assert.Throws(() => BinaryNumberHelper.Log2(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void op_BitwiseAndTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((nint)0x0000000000000000), (nint)1)); + Assert.Equal(unchecked((nint)0x0000000000000001), BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((nint)0x0000000000000001), (nint)1)); + Assert.Equal(unchecked((nint)0x0000000000000001), BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)1)); + Assert.Equal(unchecked((nint)0x0000000000000000), BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((nint)0x8000000000000000), (nint)1)); + Assert.Equal(unchecked((nint)0x0000000000000001), BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)1)); + } + else + { + Assert.Equal((nint)0x00000000, BitwiseOperatorsHelper.op_BitwiseAnd((nint)0x00000000, (nint)1)); + Assert.Equal((nint)0x00000001, BitwiseOperatorsHelper.op_BitwiseAnd((nint)0x00000001, (nint)1)); + Assert.Equal((nint)0x00000001, BitwiseOperatorsHelper.op_BitwiseAnd((nint)0x7FFFFFFF, (nint)1)); + Assert.Equal((nint)0x00000000, BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((nint)0x80000000), (nint)1)); + Assert.Equal((nint)0x00000001, BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((nint)0xFFFFFFFF), (nint)1)); + } + } + + [Fact] + public static void op_BitwiseOrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000001), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((nint)0x0000000000000000), (nint)1)); + Assert.Equal(unchecked((nint)0x0000000000000001), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((nint)0x0000000000000001), (nint)1)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)1)); + Assert.Equal(unchecked((nint)0x8000000000000001), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((nint)0x8000000000000000), (nint)1)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)1)); + } + else + { + Assert.Equal((nint)0x00000001, BitwiseOperatorsHelper.op_BitwiseOr((nint)0x00000000, (nint)1)); + Assert.Equal((nint)0x00000001, BitwiseOperatorsHelper.op_BitwiseOr((nint)0x00000001, (nint)1)); + Assert.Equal((nint)0x7FFFFFFF, BitwiseOperatorsHelper.op_BitwiseOr((nint)0x7FFFFFFF, (nint)1)); + Assert.Equal(unchecked((nint)0x80000001), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((nint)0x80000000), (nint)1)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((nint)0xFFFFFFFF), (nint)1)); + } + } + + [Fact] + public static void op_ExclusiveOrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000001), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((nint)0x0000000000000000), (nint)1)); + Assert.Equal(unchecked((nint)0x0000000000000000), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((nint)0x0000000000000001), (nint)1)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFE), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)1)); + Assert.Equal(unchecked((nint)0x8000000000000001), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((nint)0x8000000000000000), (nint)1)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFE), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)1)); + } + else + { + Assert.Equal((nint)0x00000001, BitwiseOperatorsHelper.op_ExclusiveOr((nint)0x00000000, (nint)1)); + Assert.Equal((nint)0x00000000, BitwiseOperatorsHelper.op_ExclusiveOr((nint)0x00000001, (nint)1)); + Assert.Equal((nint)0x7FFFFFFE, BitwiseOperatorsHelper.op_ExclusiveOr((nint)0x7FFFFFFF, (nint)1)); + Assert.Equal(unchecked((nint)0x80000001), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((nint)0x80000000), (nint)1)); + Assert.Equal(unchecked((nint)0xFFFFFFFE), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((nint)0xFFFFFFFF), (nint)1)); + } + } + + [Fact] + public static void op_OnesComplementTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), BitwiseOperatorsHelper.op_OnesComplement(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFE), BitwiseOperatorsHelper.op_OnesComplement(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x8000000000000000), BitwiseOperatorsHelper.op_OnesComplement(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), BitwiseOperatorsHelper.op_OnesComplement(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000000), BitwiseOperatorsHelper.op_OnesComplement(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal(unchecked((nint)0xFFFFFFFF), BitwiseOperatorsHelper.op_OnesComplement((nint)0x00000000)); + Assert.Equal(unchecked((nint)0xFFFFFFFE), BitwiseOperatorsHelper.op_OnesComplement((nint)0x00000001)); + Assert.Equal(unchecked((nint)0x80000000), BitwiseOperatorsHelper.op_OnesComplement((nint)0x7FFFFFFF)); + Assert.Equal((nint)0x7FFFFFFF, BitwiseOperatorsHelper.op_OnesComplement(unchecked((nint)0x80000000))); + Assert.Equal((nint)0x00000000, BitwiseOperatorsHelper.op_OnesComplement(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void op_LessThanTest() + { + if (Environment.Is64BitProcess) + { + Assert.True(ComparisonOperatorsHelper.op_LessThan(unchecked((nint)0x0000000000000000), (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan(unchecked((nint)0x0000000000000001), (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThan(unchecked((nint)0x8000000000000000), (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThan(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)1)); + } + else + { + Assert.True(ComparisonOperatorsHelper.op_LessThan((nint)0x00000000, (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((nint)0x00000001, (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((nint)0x7FFFFFFF, (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThan(unchecked((nint)0x80000000), (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThan(unchecked((nint)0xFFFFFFFF), (nint)1)); + } + } + + [Fact] + public static void op_LessThanOrEqualTest() + { + if (Environment.Is64BitProcess) + { + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((nint)0x0000000000000000), (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((nint)0x0000000000000001), (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((nint)0x8000000000000000), (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)1)); + } + else + { + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((nint)0x00000000, (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((nint)0x00000001, (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((nint)0x7FFFFFFF, (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((nint)0x80000000), (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((nint)0xFFFFFFFF), (nint)1)); + } + } + + [Fact] + public static void op_GreaterThanTest() + { + if (Environment.Is64BitProcess) + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((nint)0x0000000000000000), (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((nint)0x0000000000000001), (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((nint)0x8000000000000000), (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)1)); + } + else + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((nint)0x00000000, (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((nint)0x00000001, (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((nint)0x7FFFFFFF, (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((nint)0x80000000), (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((nint)0xFFFFFFFF), (nint)1)); + } + } + + [Fact] + public static void op_GreaterThanOrEqualTest() + { + if (Environment.Is64BitProcess) + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((nint)0x0000000000000000), (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((nint)0x0000000000000001), (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((nint)0x8000000000000000), (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)1)); + } + else + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual((nint)0x00000000, (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((nint)0x00000001, (nint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((nint)0x7FFFFFFF, (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((nint)0x80000000), (nint)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((nint)0xFFFFFFFF), (nint)1)); + } + } + + [Fact] + public static void op_DecrementTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), DecrementOperatorsHelper.op_Decrement(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000000), DecrementOperatorsHelper.op_Decrement(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFE), DecrementOperatorsHelper.op_Decrement(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), DecrementOperatorsHelper.op_Decrement(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFE), DecrementOperatorsHelper.op_Decrement(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal(unchecked((nint)0xFFFFFFFF), DecrementOperatorsHelper.op_Decrement((nint)0x00000000)); + Assert.Equal((nint)0x00000000, DecrementOperatorsHelper.op_Decrement((nint)0x00000001)); + Assert.Equal((nint)0x7FFFFFFE, DecrementOperatorsHelper.op_Decrement((nint)0x7FFFFFFF)); + Assert.Equal((nint)0x7FFFFFFF, DecrementOperatorsHelper.op_Decrement(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFE), DecrementOperatorsHelper.op_Decrement(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void op_DivisionTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), DivisionOperatorsHelper.op_Division(unchecked((nint)0x0000000000000000), (nint)2)); + Assert.Equal(unchecked((nint)0x0000000000000000), DivisionOperatorsHelper.op_Division(unchecked((nint)0x0000000000000001), (nint)2)); + Assert.Equal(unchecked((nint)0x3FFFFFFFFFFFFFFF), DivisionOperatorsHelper.op_Division(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)2)); + Assert.Equal(unchecked((nint)0xC000000000000000), DivisionOperatorsHelper.op_Division(unchecked((nint)0x8000000000000000), (nint)2)); + Assert.Equal(unchecked((nint)0x0000000000000000), DivisionOperatorsHelper.op_Division(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)2)); + } + else + { + Assert.Equal((nint)0x00000000, DivisionOperatorsHelper.op_Division((nint)0x00000000, (nint)2)); + Assert.Equal((nint)0x00000000, DivisionOperatorsHelper.op_Division((nint)0x00000001, (nint)2)); + Assert.Equal((nint)0x3FFFFFFF, DivisionOperatorsHelper.op_Division((nint)0x7FFFFFFF, (nint)2)); + Assert.Equal(unchecked((nint)0xC0000000), DivisionOperatorsHelper.op_Division(unchecked((nint)0x80000000), (nint)2)); + Assert.Equal((nint)0x00000000, DivisionOperatorsHelper.op_Division(unchecked((nint)0xFFFFFFFF), (nint)2)); + } + } + + [Fact] + public static void op_EqualityTest() + { + if (Environment.Is64BitProcess) + { + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((nint)0x0000000000000000), (nint)1)); + Assert.True(EqualityOperatorsHelper.op_Equality(unchecked((nint)0x0000000000000001), (nint)1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((nint)0x8000000000000000), (nint)1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)1)); + } + else + { + Assert.False(EqualityOperatorsHelper.op_Equality((nint)0x00000000, (nint)1)); + Assert.True(EqualityOperatorsHelper.op_Equality((nint)0x00000001, (nint)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((nint)0x7FFFFFFF, (nint)1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((nint)0x80000000), (nint)1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((nint)0xFFFFFFFF), (nint)1)); + } + } + + [Fact] + public static void op_InequalityTest() + { + if (Environment.Is64BitProcess) + { + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((nint)0x0000000000000000), (nint)1)); + Assert.False(EqualityOperatorsHelper.op_Inequality(unchecked((nint)0x0000000000000001), (nint)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((nint)0x8000000000000000), (nint)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)1)); + } + else + { + Assert.True(EqualityOperatorsHelper.op_Inequality((nint)0x00000000, (nint)1)); + Assert.False(EqualityOperatorsHelper.op_Inequality((nint)0x00000001, (nint)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((nint)0x7FFFFFFF, (nint)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((nint)0x80000000), (nint)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((nint)0xFFFFFFFF), (nint)1)); + } + } + + [Fact] + public static void op_IncrementTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000001), IncrementOperatorsHelper.op_Increment(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000002), IncrementOperatorsHelper.op_Increment(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x8000000000000000), IncrementOperatorsHelper.op_Increment(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nint)0x8000000000000001), IncrementOperatorsHelper.op_Increment(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000000), IncrementOperatorsHelper.op_Increment(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000001, IncrementOperatorsHelper.op_Increment((nint)0x00000000)); + Assert.Equal((nint)0x00000002, IncrementOperatorsHelper.op_Increment((nint)0x00000001)); + Assert.Equal(unchecked((nint)0x80000000), IncrementOperatorsHelper.op_Increment((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((nint)0x80000001), IncrementOperatorsHelper.op_Increment(unchecked((nint)0x80000000))); + Assert.Equal((nint)0x00000000, IncrementOperatorsHelper.op_Increment(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void op_ModulusTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), ModulusOperatorsHelper.op_Modulus(unchecked((nint)0x0000000000000000), (nint)2)); + Assert.Equal(unchecked((nint)0x0000000000000001), ModulusOperatorsHelper.op_Modulus(unchecked((nint)0x0000000000000001), (nint)2)); + Assert.Equal(unchecked((nint)0x0000000000000001), ModulusOperatorsHelper.op_Modulus(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)2)); + Assert.Equal(unchecked((nint)0x0000000000000000), ModulusOperatorsHelper.op_Modulus(unchecked((nint)0x8000000000000000), (nint)2)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), ModulusOperatorsHelper.op_Modulus(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)2)); + } + else + { + Assert.Equal((nint)0x00000000, ModulusOperatorsHelper.op_Modulus((nint)0x00000000, (nint)2)); + Assert.Equal((nint)0x00000001, ModulusOperatorsHelper.op_Modulus((nint)0x00000001, (nint)2)); + Assert.Equal((nint)0x00000001, ModulusOperatorsHelper.op_Modulus((nint)0x7FFFFFFF, (nint)2)); + Assert.Equal((nint)0x00000000, ModulusOperatorsHelper.op_Modulus(unchecked((nint)0x80000000), (nint)2)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), ModulusOperatorsHelper.op_Modulus(unchecked((nint)0xFFFFFFFF), (nint)2)); + } + } + + [Fact] + public static void op_MultiplyTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), MultiplyOperatorsHelper.op_Multiply(unchecked((nint)0x0000000000000000), (nint)2)); + Assert.Equal(unchecked((nint)0x0000000000000002), MultiplyOperatorsHelper.op_Multiply(unchecked((nint)0x0000000000000001), (nint)2)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFE), MultiplyOperatorsHelper.op_Multiply(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)2)); + Assert.Equal(unchecked((nint)0x0000000000000000), MultiplyOperatorsHelper.op_Multiply(unchecked((nint)0x8000000000000000), (nint)2)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFE), MultiplyOperatorsHelper.op_Multiply(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)2)); + } + else + { + Assert.Equal((nint)0x00000000, MultiplyOperatorsHelper.op_Multiply((nint)0x00000000, (nint)2)); + Assert.Equal((nint)0x00000002, MultiplyOperatorsHelper.op_Multiply((nint)0x00000001, (nint)2)); + Assert.Equal(unchecked((nint)0xFFFFFFFE), MultiplyOperatorsHelper.op_Multiply((nint)0x7FFFFFFF, (nint)2)); + Assert.Equal((nint)0x00000000, MultiplyOperatorsHelper.op_Multiply(unchecked((nint)0x80000000), (nint)2)); + Assert.Equal(unchecked((nint)0xFFFFFFFE), MultiplyOperatorsHelper.op_Multiply(unchecked((nint)0xFFFFFFFF), (nint)2)); + } + } + + [Fact] + public static void AbsTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.Abs(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Abs(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.Abs(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Abs(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Abs(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.Abs((nint)0x00000000)); + Assert.Equal((nint)0x00000001, NumberHelper.Abs((nint)0x00000001)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.Abs((nint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Abs(unchecked((nint)0x80000000))); + Assert.Equal((nint)0x00000001, NumberHelper.Abs(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void ClampTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.Clamp(unchecked((nint)0x0000000000000000), unchecked((nint)0xFFFFFFFFFFFFFFC0), unchecked((nint)0x000000000000003F))); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Clamp(unchecked((nint)0x0000000000000001), unchecked((nint)0xFFFFFFFFFFFFFFC0), unchecked((nint)0x000000000000003F))); + Assert.Equal(unchecked((nint)0x000000000000003F), NumberHelper.Clamp(unchecked((nint)0x7FFFFFFFFFFFFFFF), unchecked((nint)0xFFFFFFFFFFFFFFC0), unchecked((nint)0x000000000000003F))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFC0), NumberHelper.Clamp(unchecked((nint)0x8000000000000000), unchecked((nint)0xFFFFFFFFFFFFFFC0), unchecked((nint)0x000000000000003F))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), NumberHelper.Clamp(unchecked((nint)0xFFFFFFFFFFFFFFFF), unchecked((nint)0xFFFFFFFFFFFFFFC0), unchecked((nint)0x000000000000003F))); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.Clamp((nint)0x00000000, unchecked((nint)0xFFFFFFC0), (nint)0x0000003F)); + Assert.Equal((nint)0x00000001, NumberHelper.Clamp((nint)0x00000001, unchecked((nint)0xFFFFFFC0), (nint)0x0000003F)); + Assert.Equal((nint)0x0000003F, NumberHelper.Clamp((nint)0x7FFFFFFF, unchecked((nint)0xFFFFFFC0), (nint)0x0000003F)); + Assert.Equal(unchecked((nint)0xFFFFFFC0), NumberHelper.Clamp(unchecked((nint)0x80000000), unchecked((nint)0xFFFFFFC0), (nint)0x0000003F)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.Clamp(unchecked((nint)0xFFFFFFFF), unchecked((nint)0xFFFFFFC0), (nint)0x0000003F)); + } + } + + [Fact] + public static void CreateFromByteTest() + { + Assert.Equal((nint)0x00000000, NumberHelper.Create(0x00)); + Assert.Equal((nint)0x00000001, NumberHelper.Create(0x01)); + Assert.Equal((nint)0x0000007F, NumberHelper.Create(0x7F)); + Assert.Equal((nint)0x00000080, NumberHelper.Create(0x80)); + Assert.Equal((nint)0x000000FF, NumberHelper.Create(0xFF)); + } + + [Fact] + public static void CreateFromCharTest() + { + Assert.Equal((nint)0x00000000, NumberHelper.Create((char)0x0000)); + Assert.Equal((nint)0x00000001, NumberHelper.Create((char)0x0001)); + Assert.Equal((nint)0x00007FFF, NumberHelper.Create((char)0x7FFF)); + Assert.Equal((nint)0x00008000, NumberHelper.Create((char)0x8000)); + Assert.Equal((nint)0x0000FFFF, NumberHelper.Create((char)0xFFFF)); + } + + [Fact] + public static void CreateFromInt16Test() + { + Assert.Equal((nint)0x00000000, NumberHelper.Create(0x0000)); + Assert.Equal((nint)0x00000001, NumberHelper.Create(0x0001)); + Assert.Equal((nint)0x00007FFF, NumberHelper.Create(0x7FFF)); + Assert.Equal(unchecked((nint)(int)0xFFFF8000), NumberHelper.Create(unchecked((short)0x8000))); + Assert.Equal(unchecked((nint)(int)0xFFFFFFFF), NumberHelper.Create(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateFromInt32Test() + { + Assert.Equal((nint)0x00000000, NumberHelper.Create(0x00000000)); + Assert.Equal((nint)0x00000001, NumberHelper.Create(0x00000001)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.Create(0x7FFFFFFF)); + Assert.Equal(unchecked((nint)(int)0x80000000), NumberHelper.Create(unchecked((int)0x80000000))); + Assert.Equal(unchecked((nint)(int)0xFFFFFFFF), NumberHelper.Create(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateFromInt64Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.Create(0x0000000000000000)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Create(0x0000000000000001)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((nint)0x8000000000000000), NumberHelper.Create(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), NumberHelper.Create(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((nint)0x00000001, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.Create(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + } + + [Fact] + public static void CreateFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.Create(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Create(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.Create(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nint)0x8000000000000000), NumberHelper.Create(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), NumberHelper.Create(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.Create((nint)0x00000000)); + Assert.Equal((nint)0x00000001, NumberHelper.Create((nint)0x00000001)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.Create((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((nint)0x80000000), NumberHelper.Create(unchecked(unchecked((nint)0x80000000)))); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.Create(unchecked(unchecked((nint)0xFFFFFFFF)))); + } + } + + [Fact] + public static void CreateFromSByteTest() + { + Assert.Equal((nint)0x00000000, NumberHelper.Create(0x00)); + Assert.Equal((nint)0x00000001, NumberHelper.Create(0x01)); + Assert.Equal((nint)0x0000007F, NumberHelper.Create(0x7F)); + Assert.Equal(unchecked((nint)(int)0xFFFFFF80), NumberHelper.Create(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((nint)(int)0xFFFFFFFF), NumberHelper.Create(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateFromUInt16Test() + { + Assert.Equal((nint)0x00000000, NumberHelper.Create(0x0000)); + Assert.Equal((nint)0x00000001, NumberHelper.Create(0x0001)); + Assert.Equal((nint)0x00007FFF, NumberHelper.Create(0x7FFF)); + Assert.Equal((nint)0x00008000, NumberHelper.Create(0x8000)); + Assert.Equal((nint)0x0000FFFF, NumberHelper.Create(0xFFFF)); + } + + [Fact] + public static void CreateFromUInt32Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.Create(0x00000000)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Create(0x00000001)); + Assert.Equal(unchecked((nint)0x000000007FFFFFFF), NumberHelper.Create(0x7FFFFFFF)); + Assert.Equal(unchecked((nint)0x0000000080000000), NumberHelper.Create(0x80000000)); + Assert.Equal(unchecked((nint)0x00000000FFFFFFFF), NumberHelper.Create(0xFFFFFFFF)); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.Create(0x00000000)); + Assert.Equal((nint)0x00000001, NumberHelper.Create(0x00000001)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x80000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateFromUInt64Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.Create(0x0000000000000000)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Create(0x0000000000000001)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x8000000000000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFFFFFFFFFF)); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((nint)0x00000001, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x8000000000000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFFFFFFFFFF)); + } + } + + [Fact] + public static void CreateFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.Create(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Create(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.Create(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.Create((nuint)0x00000000)); + Assert.Equal((nint)0x00000001, NumberHelper.Create((nuint)0x00000001)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.Create((nuint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x80000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateSaturatingFromByteTest() + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((nint)0x0000007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((nint)0x00000080, NumberHelper.CreateSaturating(0x80)); + Assert.Equal((nint)0x000000FF, NumberHelper.CreateSaturating(0xFF)); + } + + [Fact] + public static void CreateSaturatingFromCharTest() + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateSaturating((char)0x0000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateSaturating((char)0x0001)); + Assert.Equal((nint)0x00007FFF, NumberHelper.CreateSaturating((char)0x7FFF)); + Assert.Equal((nint)0x00008000, NumberHelper.CreateSaturating((char)0x8000)); + Assert.Equal((nint)0x0000FFFF, NumberHelper.CreateSaturating((char)0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromInt16Test() + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((nint)0x00007FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal(unchecked((nint)(int)0xFFFF8000), NumberHelper.CreateSaturating(unchecked((short)0x8000))); + Assert.Equal(unchecked((nint)(int)0xFFFFFFFF), NumberHelper.CreateSaturating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt32Test() + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal(unchecked((nint)(int)0x80000000), NumberHelper.CreateSaturating(unchecked((int)0x80000000))); + Assert.Equal(unchecked((nint)(int)0xFFFFFFFF), NumberHelper.CreateSaturating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt64Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((nint)0x8000000000000000), NumberHelper.CreateSaturating(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal(unchecked((nint)0x7FFFFFFF), NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((nint)0x80000000), NumberHelper.CreateSaturating(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.CreateSaturating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + } + + [Fact] + public static void CreateSaturatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nint)0x8000000000000000), NumberHelper.CreateSaturating(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateSaturating((nint)0x00000000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateSaturating((nint)0x00000001)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateSaturating((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((nint)0x80000000), NumberHelper.CreateSaturating(unchecked(unchecked((nint)0x80000000)))); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.CreateSaturating(unchecked(unchecked((nint)0xFFFFFFFF)))); + } + } + + [Fact] + public static void CreateSaturatingFromSByteTest() + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((nint)0x0000007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal(unchecked((nint)(int)0xFFFFFF80), NumberHelper.CreateSaturating(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((nint)(int)0xFFFFFFFF), NumberHelper.CreateSaturating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateSaturatingFromUInt16Test() + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((nint)0x00007FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((nint)0x00008000, NumberHelper.CreateSaturating(0x8000)); + Assert.Equal((nint)0x0000FFFF, NumberHelper.CreateSaturating(0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt32Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal(unchecked((nint)0x000000007FFFFFFF), NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal(unchecked((nint)0x0000000080000000), NumberHelper.CreateSaturating(0x80000000)); + Assert.Equal(unchecked((nint)0x00000000FFFFFFFF), NumberHelper.CreateSaturating(0xFFFFFFFF)); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateSaturating(0x80000000)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateSaturating(0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateSaturatingFromUInt64Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(0x8000000000000000)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(0xFFFFFFFFFFFFFFFF)); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateSaturating(0x8000000000000000)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateSaturating(0xFFFFFFFFFFFFFFFF)); + } + } + + [Fact] + public static void CreateSaturatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateSaturating((nuint)0x00000000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateSaturating((nuint)0x00000001)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateSaturating((nuint)0x7FFFFFFF)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0x80000000))); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromByteTest() + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((nint)0x0000007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((nint)0x00000080, NumberHelper.CreateTruncating(0x80)); + Assert.Equal((nint)0x000000FF, NumberHelper.CreateTruncating(0xFF)); + } + + [Fact] + public static void CreateTruncatingFromCharTest() + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateTruncating((char)0x0000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateTruncating((char)0x0001)); + Assert.Equal((nint)0x00007FFF, NumberHelper.CreateTruncating((char)0x7FFF)); + Assert.Equal((nint)0x00008000, NumberHelper.CreateTruncating((char)0x8000)); + Assert.Equal((nint)0x0000FFFF, NumberHelper.CreateTruncating((char)0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromInt16Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.CreateTruncating(0x0000)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.CreateTruncating(0x0001)); + Assert.Equal(unchecked((nint)0x0000000000007FFF), NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFF8000), NumberHelper.CreateTruncating(unchecked((short)0x8000))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((short)0xFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((nint)0x00007FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal(unchecked((nint)0xFFFF8000), NumberHelper.CreateTruncating(unchecked((short)0x8000))); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked((short)0xFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromInt32Test() + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal(unchecked((nint)(int)0x80000000), NumberHelper.CreateTruncating(unchecked((int)0x80000000))); + Assert.Equal(unchecked((nint)(int)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt64Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((nint)0x8000000000000000), NumberHelper.CreateTruncating(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((nint)0x00000000, NumberHelper.CreateTruncating(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nint)0x8000000000000000), NumberHelper.CreateTruncating(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateTruncating((nint)0x00000000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateTruncating((nint)0x00000001)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateTruncating((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((nint)0x80000000), NumberHelper.CreateTruncating(unchecked(unchecked((nint)0x80000000)))); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked(unchecked((nint)0xFFFFFFFF)))); + } + } + + [Fact] + public static void CreateTruncatingFromSByteTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.CreateTruncating(0x00)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.CreateTruncating(0x01)); + Assert.Equal(unchecked((nint)0x000000000000007F), NumberHelper.CreateTruncating(0x7F)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFF80), NumberHelper.CreateTruncating(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((sbyte)0xFF))); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((nint)0x0000007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal(unchecked((nint)0xFFFFFF80), NumberHelper.CreateTruncating(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked((sbyte)0xFF))); + } + } + + [Fact] + public static void CreateTruncatingFromUInt16Test() + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((nint)0x00007FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((nint)0x00008000, NumberHelper.CreateTruncating(0x8000)); + Assert.Equal((nint)0x0000FFFF, NumberHelper.CreateTruncating(0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt32Test() + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal(unchecked((nint)0x80000000), NumberHelper.CreateTruncating(0x80000000)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.CreateTruncating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt64Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((nint)0x8000000000000000), NumberHelper.CreateTruncating(0x8000000000000000)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(0xFFFFFFFFFFFFFFFF)); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((nint)0x00000000, NumberHelper.CreateTruncating(0x8000000000000000)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.CreateTruncating(0xFFFFFFFFFFFFFFFF)); + } + } + + [Fact] + public static void CreateTruncatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nint)0x8000000000000000), NumberHelper.CreateTruncating(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.CreateTruncating((nuint)0x00000000)); + Assert.Equal((nint)0x00000001, NumberHelper.CreateTruncating((nuint)0x00000001)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.CreateTruncating((nuint)0x7FFFFFFF)); + Assert.Equal(unchecked((nint)0x80000000), NumberHelper.CreateTruncating(unchecked((nuint)0x80000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nuint)0xFFFFFFFF))); + } + } + + [Fact] + public static void DivRemTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((unchecked((nint)0x0000000000000000), unchecked((nint)0x0000000000000000)), NumberHelper.DivRem(unchecked((nint)0x0000000000000000), (nint)2)); + Assert.Equal((unchecked((nint)0x0000000000000000), unchecked((nint)0x0000000000000001)), NumberHelper.DivRem(unchecked((nint)0x0000000000000001), (nint)2)); + Assert.Equal((unchecked((nint)0x3FFFFFFFFFFFFFFF), unchecked((nint)0x0000000000000001)), NumberHelper.DivRem(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)2)); + Assert.Equal((unchecked((nint)0xC000000000000000), unchecked((nint)0x0000000000000000)), NumberHelper.DivRem(unchecked((nint)0x8000000000000000), (nint)2)); + Assert.Equal((unchecked((nint)0x0000000000000000), unchecked((nint)0xFFFFFFFFFFFFFFFF)), NumberHelper.DivRem(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)2)); + } + else + { + Assert.Equal(((nint)0x00000000, (nint)0x00000000), NumberHelper.DivRem((nint)0x00000000, (nint)2)); + Assert.Equal(((nint)0x00000000, (nint)0x00000001), NumberHelper.DivRem((nint)0x00000001, (nint)2)); + Assert.Equal(((nint)0x3FFFFFFF, (nint)0x00000001), NumberHelper.DivRem((nint)0x7FFFFFFF, (nint)2)); + Assert.Equal((unchecked((nint)0xC0000000), (nint)0x00000000), NumberHelper.DivRem(unchecked((nint)0x80000000), (nint)2)); + Assert.Equal(((nint)0x00000000, unchecked((nint)0xFFFFFFFF)), NumberHelper.DivRem(unchecked((nint)0xFFFFFFFF), (nint)2)); + } + } + + [Fact] + public static void MaxTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Max(unchecked((nint)0x0000000000000000), (nint)1)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Max(unchecked((nint)0x0000000000000001), (nint)1)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), NumberHelper.Max(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)1)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Max(unchecked((nint)0x8000000000000000), (nint)1)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Max(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)1)); + } + else + { + Assert.Equal((nint)0x00000001, NumberHelper.Max((nint)0x00000000, (nint)1)); + Assert.Equal((nint)0x00000001, NumberHelper.Max((nint)0x00000001, (nint)1)); + Assert.Equal((nint)0x7FFFFFFF, NumberHelper.Max((nint)0x7FFFFFFF, (nint)1)); + Assert.Equal((nint)0x00000001, NumberHelper.Max(unchecked((nint)0x80000000), (nint)1)); + Assert.Equal((nint)0x00000001, NumberHelper.Max(unchecked((nint)0xFFFFFFFF), (nint)1)); + } + } + + [Fact] + public static void MinTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.Min(unchecked((nint)0x0000000000000000), (nint)1)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Min(unchecked((nint)0x0000000000000001), (nint)1)); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Min(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)1)); + Assert.Equal(unchecked((nint)0x8000000000000000), NumberHelper.Min(unchecked((nint)0x8000000000000000), (nint)1)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), NumberHelper.Min(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)1)); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.Min((nint)0x00000000, (nint)1)); + Assert.Equal((nint)0x00000001, NumberHelper.Min((nint)0x00000001, (nint)1)); + Assert.Equal((nint)0x00000001, NumberHelper.Min((nint)0x7FFFFFFF, (nint)1)); + Assert.Equal(unchecked((nint)0x80000000), NumberHelper.Min(unchecked((nint)0x80000000), (nint)1)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.Min(unchecked((nint)0xFFFFFFFF), (nint)1)); + } + } + + [Fact] + public static void SignTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), NumberHelper.Sign(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Sign(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x0000000000000001), NumberHelper.Sign(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), NumberHelper.Sign(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), NumberHelper.Sign(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, NumberHelper.Sign((nint)0x00000000)); + Assert.Equal((nint)0x00000001, NumberHelper.Sign((nint)0x00000001)); + Assert.Equal((nint)0x00000001, NumberHelper.Sign((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.Sign(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFF), NumberHelper.Sign(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void TryCreateFromByteTest() + { + nint result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((nint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((nint)0x0000007F, result); + + Assert.True(NumberHelper.TryCreate(0x80, out result)); + Assert.Equal((nint)0x00000080, result); + + Assert.True(NumberHelper.TryCreate(0xFF, out result)); + Assert.Equal((nint)0x000000FF, result); + } + + [Fact] + public static void TryCreateFromCharTest() + { + nint result; + + Assert.True(NumberHelper.TryCreate((char)0x0000, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate((char)0x0001, out result)); + Assert.Equal((nint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate((char)0x7FFF, out result)); + Assert.Equal((nint)0x00007FFF, result); + + Assert.True(NumberHelper.TryCreate((char)0x8000, out result)); + Assert.Equal((nint)0x00008000, result); + + Assert.True(NumberHelper.TryCreate((char)0xFFFF, out result)); + Assert.Equal((nint)0x0000FFFF, result); + } + + [Fact] + public static void TryCreateFromInt16Test() + { + nint result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((nint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((nint)0x00007FFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked((short)0x8000), out result)); + Assert.Equal(unchecked((nint)(int)0xFFFF8000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((short)0xFFFF), out result)); + Assert.Equal(unchecked((nint)(int)0xFFFFFFFF), result); + } + + [Fact] + public static void TryCreateFromInt32Test() + { + nint result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((nint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((nint)0x7FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked((int)0x80000000), out result)); + Assert.Equal(unchecked((nint)(int)0x80000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((int)0xFFFFFFFF), out result)); + Assert.Equal(unchecked((nint)(int)0xFFFFFFFF), result); + } + + [Fact] + public static void TryCreateFromInt64Test() + { + nint result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal(unchecked((nint)0x0000000000000000), result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal(unchecked((nint)0x0000000000000001), result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), result); + + Assert.True(NumberHelper.TryCreate(unchecked((long)0x8000000000000000), out result)); + Assert.Equal(unchecked((nint)0x8000000000000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((long)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), result); + } + else + { + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((nint)0x00000001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0x8000000000000000), out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((long)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), result); + } + } + + [Fact] + public static void TryCreateFromIntPtrTest() + { + nint result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000000), out result)); + Assert.Equal(unchecked((nint)0x0000000000000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000001), out result)); + Assert.Equal(unchecked((nint)0x0000000000000001), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x8000000000000000), out result)); + Assert.Equal(unchecked((nint)0x8000000000000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), result); + } + else + { + Assert.True(NumberHelper.TryCreate((nint)0x00000000, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate((nint)0x00000001, out result)); + Assert.Equal((nint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate((nint)0x7FFFFFFF, out result)); + Assert.Equal((nint)0x7FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked(unchecked((nint)0x80000000)), out result)); + Assert.Equal(unchecked((nint)0x80000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked(unchecked((nint)0xFFFFFFFF)), out result)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), result); + } + } + + [Fact] + public static void TryCreateFromSByteTest() + { + nint result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((nint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((nint)0x0000007F, result); + + Assert.True(NumberHelper.TryCreate(unchecked((sbyte)0x80), out result)); + Assert.Equal(unchecked((nint)(int)0xFFFFFF80), result); + + Assert.True(NumberHelper.TryCreate(unchecked((sbyte)0xFF), out result)); + Assert.Equal(unchecked((nint)(int)0xFFFFFFFF), result); + } + + [Fact] + public static void TryCreateFromUInt16Test() + { + nint result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((nint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((nint)0x00007FFF, result); + + Assert.True(NumberHelper.TryCreate(0x8000, out result)); + Assert.Equal((nint)0x00008000, result); + + Assert.True(NumberHelper.TryCreate(0xFFFF, out result)); + Assert.Equal((nint)0x0000FFFF, result); + } + + [Fact] + public static void TryCreateFromUInt32Test() + { + nint result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((nint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((nint)0x7FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(0x80000000, out result)); + Assert.Equal(unchecked((nint)0x0000000080000000), result); + + Assert.True(NumberHelper.TryCreate(0xFFFFFFFF, out result)); + Assert.Equal(unchecked((nint)0x00000000FFFFFFFF), result); + } + else + { + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((nint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((nint)0x7FFFFFFF, result); + + Assert.False(NumberHelper.TryCreate(0x80000000, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFF, out result)); + Assert.Equal((nint)0x00000000, result); + } + } + + [Fact] + public static void TryCreateFromUInt64Test() + { + nint result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal(unchecked((nint)0x0000000000000000), result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal(unchecked((nint)0x00000000000000001), result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), result); + + Assert.False(NumberHelper.TryCreate(0x8000000000000000, out result)); + Assert.Equal((nint)0x0000000000000000, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFFFFFFFFFF, out result)); + Assert.Equal((nint)0x0000000000000000, result); + } + else + { + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((nint)0x00000001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(0x8000000000000000, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFFFFFFFFFF, out result)); + Assert.Equal((nint)0x00000000, result); + } + } + + [Fact] + public static void TryCreateFromUIntPtrTest() + { + nint result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000000), out result)); + Assert.Equal(unchecked((nint)0x0000000000000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000001), out result)); + Assert.Equal(unchecked((nint)0x0000000000000001), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x8000000000000000), out result)); + Assert.Equal((nint)0x0000000000000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((nint)0x0000000000000000, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nuint)0x00000000, out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x00000001, out result)); + Assert.Equal((nint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x7FFFFFFF, out result)); + Assert.Equal((nint)0x7FFFFFFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked(unchecked((nuint)0x80000000)), out result)); + Assert.Equal((nint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked(unchecked((nuint)0xFFFFFFFF)), out result)); + Assert.Equal((nint)0x00000000, result); + } + } + + [Fact] + + public static void op_LeftShiftTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), ShiftOperatorsHelper.op_LeftShift(unchecked((nint)0x0000000000000000), 1)); + Assert.Equal(unchecked((nint)0x0000000000000002), ShiftOperatorsHelper.op_LeftShift(unchecked((nint)0x0000000000000001), 1)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFE), ShiftOperatorsHelper.op_LeftShift(unchecked((nint)0x7FFFFFFFFFFFFFFF), 1)); + Assert.Equal(unchecked((nint)0x0000000000000000), ShiftOperatorsHelper.op_LeftShift(unchecked((nint)0x8000000000000000), 1)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFE), ShiftOperatorsHelper.op_LeftShift(unchecked((nint)0xFFFFFFFFFFFFFFFF), 1)); + } + else + { + Assert.Equal((nint)0x00000000, ShiftOperatorsHelper.op_LeftShift((nint)0x00000000, 1)); + Assert.Equal((nint)0x00000002, ShiftOperatorsHelper.op_LeftShift((nint)0x00000001, 1)); + Assert.Equal(unchecked((nint)0xFFFFFFFE), ShiftOperatorsHelper.op_LeftShift((nint)0x7FFFFFFF, 1)); + Assert.Equal((nint)0x00000000, ShiftOperatorsHelper.op_LeftShift(unchecked((nint)0x80000000), 1)); + Assert.Equal(unchecked((nint)0xFFFFFFFE), ShiftOperatorsHelper.op_LeftShift(unchecked((nint)0xFFFFFFFF), 1)); + } + } + + [Fact] + public static void op_RightShiftTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), ShiftOperatorsHelper.op_RightShift(unchecked((nint)0x0000000000000000), 1)); + Assert.Equal(unchecked((nint)0x0000000000000000), ShiftOperatorsHelper.op_RightShift(unchecked((nint)0x0000000000000001), 1)); + Assert.Equal(unchecked((nint)0x3FFFFFFFFFFFFFFF), ShiftOperatorsHelper.op_RightShift(unchecked((nint)0x7FFFFFFFFFFFFFFF), 1)); + Assert.Equal(unchecked((nint)0xC000000000000000), ShiftOperatorsHelper.op_RightShift(unchecked((nint)0x8000000000000000), 1)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), ShiftOperatorsHelper.op_RightShift(unchecked((nint)0xFFFFFFFFFFFFFFFF), 1)); + } + else + { + Assert.Equal((nint)0x00000000, ShiftOperatorsHelper.op_RightShift((nint)0x00000000, 1)); + Assert.Equal((nint)0x00000000, ShiftOperatorsHelper.op_RightShift((nint)0x00000001, 1)); + Assert.Equal((nint)0x3FFFFFFF, ShiftOperatorsHelper.op_RightShift((nint)0x7FFFFFFF, 1)); + Assert.Equal(unchecked((nint)0xC0000000), ShiftOperatorsHelper.op_RightShift(unchecked((nint)0x80000000), 1)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), ShiftOperatorsHelper.op_RightShift(unchecked((nint)0xFFFFFFFF), 1)); + } + } + + [Fact] + public static void op_SubtractionTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), SubtractionOperatorsHelper.op_Subtraction(unchecked((nint)0x0000000000000000), (nint)1)); + Assert.Equal(unchecked((nint)0x0000000000000000), SubtractionOperatorsHelper.op_Subtraction(unchecked((nint)0x0000000000000001), (nint)1)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFE), SubtractionOperatorsHelper.op_Subtraction(unchecked((nint)0x7FFFFFFFFFFFFFFF), (nint)1)); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), SubtractionOperatorsHelper.op_Subtraction(unchecked((nint)0x8000000000000000), (nint)1)); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFE), SubtractionOperatorsHelper.op_Subtraction(unchecked((nint)0xFFFFFFFFFFFFFFFF), (nint)1)); + } + else + { + Assert.Equal(unchecked((nint)0xFFFFFFFF), SubtractionOperatorsHelper.op_Subtraction((nint)0x00000000, (nint)1)); + Assert.Equal((nint)0x00000000, SubtractionOperatorsHelper.op_Subtraction((nint)0x00000001, (nint)1)); + Assert.Equal((nint)0x7FFFFFFE, SubtractionOperatorsHelper.op_Subtraction((nint)0x7FFFFFFF, (nint)1)); + Assert.Equal((nint)0x7FFFFFFF, SubtractionOperatorsHelper.op_Subtraction(unchecked((nint)0x80000000), (nint)1)); + Assert.Equal(unchecked((nint)0xFFFFFFFE), SubtractionOperatorsHelper.op_Subtraction(unchecked((nint)0xFFFFFFFF), (nint)1)); + } + } + + [Fact] + public static void op_UnaryNegationTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x8000000000000001), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nint)0x8000000000000000), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000001), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, UnaryNegationOperatorsHelper.op_UnaryNegation((nint)0x00000000)); + Assert.Equal(unchecked((nint)0xFFFFFFFF), UnaryNegationOperatorsHelper.op_UnaryNegation((nint)0x00000001)); + Assert.Equal(unchecked((nint)0x80000001), UnaryNegationOperatorsHelper.op_UnaryNegation((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((nint)0x80000000), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((nint)0x80000000))); + Assert.Equal((nint)0x00000001, UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void op_UnaryPlusTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nint)0x0000000000000000), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nint)0x0000000000000001), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nint)0x7FFFFFFFFFFFFFFF), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nint)0x8000000000000000), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFFFFFFFFFF), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nint)0x00000000, UnaryPlusOperatorsHelper.op_UnaryPlus((nint)0x00000000)); + Assert.Equal((nint)0x00000001, UnaryPlusOperatorsHelper.op_UnaryPlus((nint)0x00000001)); + Assert.Equal((nint)0x7FFFFFFF, UnaryPlusOperatorsHelper.op_UnaryPlus((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((nint)0x80000000), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((nint)0xFFFFFFFF), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((nint)0xFFFFFFFF))); + } + } + + [Theory] + [MemberData(nameof(IntPtrTests.Parse_Valid_TestData), MemberType = typeof(IntPtrTests))] + public static void ParseValidStringTest(string value, NumberStyles style, IFormatProvider provider, nint expected) + { + nint result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.True(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.True(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(IntPtrTests.Parse_Invalid_TestData), MemberType = typeof(IntPtrTests))] + public static void ParseInvalidStringTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + nint result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(default(nint), result); + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.False(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(default(nint), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.False(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(default(nint), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(IntPtrTests.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(IntPtrTests))] + public static void ParseValidSpanTest(string value, int offset, int count, NumberStyles style, IFormatProvider provider, nint expected) + { + nint result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(SpanParseableHelper.TryParse(value.AsSpan(offset, count), provider, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, NumberHelper.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(NumberHelper.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(IntPtrTests.Parse_Invalid_TestData), MemberType = typeof(IntPtrTests))] + public static void ParseInvalidSpanTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value is null) + { + return; + } + + nint result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(SpanParseableHelper.TryParse(value.AsSpan(), provider, out result)); + Assert.Equal(default(nint), result); + } + + Assert.Throws(exceptionType, () => NumberHelper.Parse(value.AsSpan(), style, provider)); + + Assert.False(NumberHelper.TryParse(value.AsSpan(), style, provider, out result)); + Assert.Equal(default(nint), result); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/SByteTests.GenericMath.cs b/src/libraries/System.Runtime/tests/System/SByteTests.GenericMath.cs new file mode 100644 index 000000000000..82877a1e2d60 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/SByteTests.GenericMath.cs @@ -0,0 +1,1181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Runtime.Versioning; +using Xunit; + +namespace System.Tests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [RequiresPreviewFeaturesAttribute] + public class SByteTests_GenericMath + { + [Fact] + public static void AdditiveIdentityTest() + { + Assert.Equal((sbyte)0x00, AdditiveIdentityHelper.AdditiveIdentity); + } + + [Fact] + public static void MinValueTest() + { + Assert.Equal(unchecked((sbyte)0x80), MinMaxValueHelper.MinValue); + } + + [Fact] + public static void MaxValueTest() + { + Assert.Equal((sbyte)0x7F, MinMaxValueHelper.MaxValue); + } + + [Fact] + public static void MultiplicativeIdentityTest() + { + Assert.Equal((sbyte)0x01, MultiplicativeIdentityHelper.MultiplicativeIdentity); + } + + [Fact] + public static void NegativeOneTest() + { + Assert.Equal(unchecked((sbyte)0xFF), SignedNumberHelper.NegativeOne); + } + + [Fact] + public static void OneTest() + { + Assert.Equal((sbyte)0x01, NumberHelper.One); + } + + [Fact] + public static void ZeroTest() + { + Assert.Equal((sbyte)0x00, NumberHelper.Zero); + } + + [Fact] + public static void op_AdditionTest() + { + Assert.Equal((sbyte)0x01, AdditionOperatorsHelper.op_Addition((sbyte)0x00, (sbyte)1)); + Assert.Equal((sbyte)0x02, AdditionOperatorsHelper.op_Addition((sbyte)0x01, (sbyte)1)); + Assert.Equal(unchecked((sbyte)0x80), AdditionOperatorsHelper.op_Addition((sbyte)0x7F, (sbyte)1)); + Assert.Equal(unchecked((sbyte)0x81), AdditionOperatorsHelper.op_Addition(unchecked((sbyte)0x80), (sbyte)1)); + Assert.Equal((sbyte)0x00, AdditionOperatorsHelper.op_Addition(unchecked((sbyte)0xFF), (sbyte)1)); + } + + [Fact] + public static void LeadingZeroCountTest() + { + Assert.Equal((sbyte)0x08, BinaryIntegerHelper.LeadingZeroCount((sbyte)0x00)); + Assert.Equal((sbyte)0x07, BinaryIntegerHelper.LeadingZeroCount((sbyte)0x01)); + Assert.Equal((sbyte)0x01, BinaryIntegerHelper.LeadingZeroCount((sbyte)0x7F)); + Assert.Equal((sbyte)0x00, BinaryIntegerHelper.LeadingZeroCount(unchecked((sbyte)0x80))); + Assert.Equal((sbyte)0x00, BinaryIntegerHelper.LeadingZeroCount(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void PopCountTest() + { + Assert.Equal((sbyte)0x00, BinaryIntegerHelper.PopCount((sbyte)0x00)); + Assert.Equal((sbyte)0x01, BinaryIntegerHelper.PopCount((sbyte)0x01)); + Assert.Equal((sbyte)0x07, BinaryIntegerHelper.PopCount((sbyte)0x7F)); + Assert.Equal((sbyte)0x01, BinaryIntegerHelper.PopCount(unchecked((sbyte)0x80))); + Assert.Equal((sbyte)0x08, BinaryIntegerHelper.PopCount(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void RotateLeftTest() + { + Assert.Equal((sbyte)0x00, BinaryIntegerHelper.RotateLeft((sbyte)0x00, 1)); + Assert.Equal((sbyte)0x02, BinaryIntegerHelper.RotateLeft((sbyte)0x01, 1)); + Assert.Equal(unchecked((sbyte)0xFE), BinaryIntegerHelper.RotateLeft((sbyte)0x7F, 1)); + Assert.Equal((sbyte)0x01, BinaryIntegerHelper.RotateLeft(unchecked((sbyte)0x80), 1)); + Assert.Equal(unchecked((sbyte)0xFF), BinaryIntegerHelper.RotateLeft(unchecked((sbyte)0xFF), 1)); + } + + [Fact] + public static void RotateRightTest() + { + Assert.Equal((sbyte)0x00, BinaryIntegerHelper.RotateRight((sbyte)0x00, 1)); + Assert.Equal(unchecked((sbyte)0x80), BinaryIntegerHelper.RotateRight((sbyte)0x01, 1)); + Assert.Equal(unchecked((sbyte)0xBF), BinaryIntegerHelper.RotateRight((sbyte)0x7F, 1)); + Assert.Equal((sbyte)0x40, BinaryIntegerHelper.RotateRight(unchecked((sbyte)0x80), 1)); + Assert.Equal(unchecked((sbyte)0xFF), BinaryIntegerHelper.RotateRight(unchecked((sbyte)0xFF), 1)); + } + + [Fact] + public static void TrailingZeroCountTest() + { + Assert.Equal((sbyte)0x08, BinaryIntegerHelper.TrailingZeroCount((sbyte)0x00)); + Assert.Equal((sbyte)0x00, BinaryIntegerHelper.TrailingZeroCount((sbyte)0x01)); + Assert.Equal((sbyte)0x00, BinaryIntegerHelper.TrailingZeroCount((sbyte)0x7F)); + Assert.Equal((sbyte)0x07, BinaryIntegerHelper.TrailingZeroCount(unchecked((sbyte)0x80))); + Assert.Equal((sbyte)0x00, BinaryIntegerHelper.TrailingZeroCount(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void IsPow2Test() + { + Assert.False(BinaryNumberHelper.IsPow2((sbyte)0x00)); + Assert.True(BinaryNumberHelper.IsPow2((sbyte)0x01)); + Assert.False(BinaryNumberHelper.IsPow2((sbyte)0x7F)); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((sbyte)0x80))); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void Log2Test() + { + Assert.Equal((sbyte)0x00, BinaryNumberHelper.Log2((sbyte)0x00)); + Assert.Equal((sbyte)0x00, BinaryNumberHelper.Log2((sbyte)0x01)); + Assert.Equal((sbyte)0x06, BinaryNumberHelper.Log2((sbyte)0x7F)); + Assert.Throws(() => BinaryNumberHelper.Log2(unchecked((sbyte)0x80))); + Assert.Throws(() => BinaryNumberHelper.Log2(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void op_BitwiseAndTest() + { + Assert.Equal((sbyte)0x00, BitwiseOperatorsHelper.op_BitwiseAnd((sbyte)0x00, (sbyte)1)); + Assert.Equal((sbyte)0x01, BitwiseOperatorsHelper.op_BitwiseAnd((sbyte)0x01, (sbyte)1)); + Assert.Equal((sbyte)0x01, BitwiseOperatorsHelper.op_BitwiseAnd((sbyte)0x7F, (sbyte)1)); + Assert.Equal((sbyte)0x00, BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((sbyte)0x80), (sbyte)1)); + Assert.Equal((sbyte)0x01, BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((sbyte)0xFF), (sbyte)1)); + } + + [Fact] + public static void op_BitwiseOrTest() + { + Assert.Equal((sbyte)0x01, BitwiseOperatorsHelper.op_BitwiseOr((sbyte)0x00, (sbyte)1)); + Assert.Equal((sbyte)0x01, BitwiseOperatorsHelper.op_BitwiseOr((sbyte)0x01, (sbyte)1)); + Assert.Equal((sbyte)0x7F, BitwiseOperatorsHelper.op_BitwiseOr((sbyte)0x7F, (sbyte)1)); + Assert.Equal(unchecked((sbyte)0x81), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((sbyte)0x80), (sbyte)1)); + Assert.Equal(unchecked((sbyte)0xFF), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((sbyte)0xFF), (sbyte)1)); + } + + [Fact] + public static void op_ExclusiveOrTest() + { + Assert.Equal((sbyte)0x01, BitwiseOperatorsHelper.op_ExclusiveOr((sbyte)0x00, (sbyte)1)); + Assert.Equal((sbyte)0x00, BitwiseOperatorsHelper.op_ExclusiveOr((sbyte)0x01, (sbyte)1)); + Assert.Equal((sbyte)0x7E, BitwiseOperatorsHelper.op_ExclusiveOr((sbyte)0x7F, (sbyte)1)); + Assert.Equal(unchecked((sbyte)0x81), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((sbyte)0x80), (sbyte)1)); + Assert.Equal(unchecked((sbyte)0xFE), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((sbyte)0xFF), (sbyte)1)); + } + + [Fact] + public static void op_OnesComplementTest() + { + Assert.Equal(unchecked((sbyte)0xFF), BitwiseOperatorsHelper.op_OnesComplement((sbyte)0x00)); + Assert.Equal(unchecked((sbyte)0xFE), BitwiseOperatorsHelper.op_OnesComplement((sbyte)0x01)); + Assert.Equal(unchecked((sbyte)0x80), BitwiseOperatorsHelper.op_OnesComplement((sbyte)0x7F)); + Assert.Equal((sbyte)0x7F, BitwiseOperatorsHelper.op_OnesComplement(unchecked((sbyte)0x80))); + Assert.Equal((sbyte)0x00, BitwiseOperatorsHelper.op_OnesComplement(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void op_LessThanTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThan((sbyte)0x00, (sbyte)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((sbyte)0x01, (sbyte)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((sbyte)0x7F, (sbyte)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThan(unchecked((sbyte)0x80), (sbyte)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThan(unchecked((sbyte)0xFF), (sbyte)1)); + } + + [Fact] + public static void op_LessThanOrEqualTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((sbyte)0x00, (sbyte)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((sbyte)0x01, (sbyte)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((sbyte)0x7F, (sbyte)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((sbyte)0x80), (sbyte)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((sbyte)0xFF), (sbyte)1)); + } + + [Fact] + public static void op_GreaterThanTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((sbyte)0x00, (sbyte)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((sbyte)0x01, (sbyte)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((sbyte)0x7F, (sbyte)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((sbyte)0x80), (sbyte)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((sbyte)0xFF), (sbyte)1)); + } + + [Fact] + public static void op_GreaterThanOrEqualTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual((sbyte)0x00, (sbyte)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((sbyte)0x01, (sbyte)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((sbyte)0x7F, (sbyte)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((sbyte)0x80), (sbyte)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((sbyte)0xFF), (sbyte)1)); + } + + [Fact] + public static void op_DecrementTest() + { + Assert.Equal(unchecked((sbyte)0xFF), DecrementOperatorsHelper.op_Decrement((sbyte)0x00)); + Assert.Equal((sbyte)0x00, DecrementOperatorsHelper.op_Decrement((sbyte)0x01)); + Assert.Equal((sbyte)0x7E, DecrementOperatorsHelper.op_Decrement((sbyte)0x7F)); + Assert.Equal((sbyte)0x7F, DecrementOperatorsHelper.op_Decrement(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((sbyte)0xFE), DecrementOperatorsHelper.op_Decrement(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void op_DivisionTest() + { + Assert.Equal((sbyte)0x00, DivisionOperatorsHelper.op_Division((sbyte)0x00, (sbyte)2)); + Assert.Equal((sbyte)0x00, DivisionOperatorsHelper.op_Division((sbyte)0x01, (sbyte)2)); + Assert.Equal((sbyte)0x3F, DivisionOperatorsHelper.op_Division((sbyte)0x7F, (sbyte)2)); + Assert.Equal(unchecked((sbyte)0xC0), DivisionOperatorsHelper.op_Division(unchecked((sbyte)0x80), (sbyte)2)); + Assert.Equal((sbyte)0x00, DivisionOperatorsHelper.op_Division(unchecked((sbyte)0xFF), (sbyte)2)); + } + + [Fact] + public static void op_EqualityTest() + { + Assert.False(EqualityOperatorsHelper.op_Equality((sbyte)0x00, (sbyte)1)); + Assert.True(EqualityOperatorsHelper.op_Equality((sbyte)0x01, (sbyte)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((sbyte)0x7F, (sbyte)1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((sbyte)0x80), (sbyte)1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((sbyte)0xFF), (sbyte)1)); + } + + [Fact] + public static void op_InequalityTest() + { + Assert.True(EqualityOperatorsHelper.op_Inequality((sbyte)0x00, (sbyte)1)); + Assert.False(EqualityOperatorsHelper.op_Inequality((sbyte)0x01, (sbyte)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((sbyte)0x7F, (sbyte)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((sbyte)0x80), (sbyte)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((sbyte)0xFF), (sbyte)1)); + } + + [Fact] + public static void op_IncrementTest() + { + Assert.Equal((sbyte)0x01, IncrementOperatorsHelper.op_Increment((sbyte)0x00)); + Assert.Equal((sbyte)0x02, IncrementOperatorsHelper.op_Increment((sbyte)0x01)); + Assert.Equal(unchecked((sbyte)0x80), IncrementOperatorsHelper.op_Increment((sbyte)0x7F)); + Assert.Equal(unchecked((sbyte)0x81), IncrementOperatorsHelper.op_Increment(unchecked((sbyte)0x80))); + Assert.Equal((sbyte)0x00, IncrementOperatorsHelper.op_Increment(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void op_ModulusTest() + { + Assert.Equal((sbyte)0x00, ModulusOperatorsHelper.op_Modulus((sbyte)0x00, (sbyte)2)); + Assert.Equal((sbyte)0x01, ModulusOperatorsHelper.op_Modulus((sbyte)0x01, (sbyte)2)); + Assert.Equal((sbyte)0x01, ModulusOperatorsHelper.op_Modulus((sbyte)0x7F, (sbyte)2)); + Assert.Equal((sbyte)0x00, ModulusOperatorsHelper.op_Modulus(unchecked((sbyte)0x80), (sbyte)2)); + Assert.Equal(unchecked((sbyte)0xFF), ModulusOperatorsHelper.op_Modulus(unchecked((sbyte)0xFF), (sbyte)2)); + } + + [Fact] + public static void op_MultiplyTest() + { + Assert.Equal((sbyte)0x00, MultiplyOperatorsHelper.op_Multiply((sbyte)0x00, (sbyte)2)); + Assert.Equal((sbyte)0x02, MultiplyOperatorsHelper.op_Multiply((sbyte)0x01, (sbyte)2)); + Assert.Equal(unchecked((sbyte)0xFE), MultiplyOperatorsHelper.op_Multiply((sbyte)0x7F, (sbyte)2)); + Assert.Equal((sbyte)0x00, MultiplyOperatorsHelper.op_Multiply(unchecked((sbyte)0x80), (sbyte)2)); + Assert.Equal(unchecked((sbyte)0xFE), MultiplyOperatorsHelper.op_Multiply(unchecked((sbyte)0xFF), (sbyte)2)); + } + + [Fact] + public static void AbsTest() + { + Assert.Equal((sbyte)0x00, NumberHelper.Abs((sbyte)0x00)); + Assert.Equal((sbyte)0x01, NumberHelper.Abs((sbyte)0x01)); + Assert.Equal((sbyte)0x7F, NumberHelper.Abs((sbyte)0x7F)); + Assert.Throws(() => NumberHelper.Abs(unchecked((sbyte)0x80))); + Assert.Equal((sbyte)0x01, NumberHelper.Abs(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void ClampTest() + { + Assert.Equal((sbyte)0x00, NumberHelper.Clamp((sbyte)0x00, unchecked((sbyte)0xC0), (sbyte)0x3F)); + Assert.Equal((sbyte)0x01, NumberHelper.Clamp((sbyte)0x01, unchecked((sbyte)0xC0), (sbyte)0x3F)); + Assert.Equal((sbyte)0x3F, NumberHelper.Clamp((sbyte)0x7F, unchecked((sbyte)0xC0), (sbyte)0x3F)); + Assert.Equal(unchecked((sbyte)0xC0), NumberHelper.Clamp(unchecked((sbyte)0x80), unchecked((sbyte)0xC0), (sbyte)0x3F)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.Clamp(unchecked((sbyte)0xFF), unchecked((sbyte)0xC0), (sbyte)0x3F)); + } + + [Fact] + public static void CreateFromByteTest() + { + Assert.Equal((sbyte)0x00, NumberHelper.Create(0x00)); + Assert.Equal((sbyte)0x01, NumberHelper.Create(0x01)); + Assert.Equal((sbyte)0x7F, NumberHelper.Create(0x7F)); + Assert.Throws(() => NumberHelper.Create(0x80)); + Assert.Throws(() => NumberHelper.Create(0xFF)); + } + + [Fact] + public static void CreateFromCharTest() + { + Assert.Equal((sbyte)0x00, NumberHelper.Create((char)0x0000)); + Assert.Equal((sbyte)0x01, NumberHelper.Create((char)0x0001)); + Assert.Throws(() => NumberHelper.Create((char)0x7FFF)); + Assert.Throws(() => NumberHelper.Create((char)0x8000)); + Assert.Throws(() => NumberHelper.Create((char)0xFFFF)); + } + + [Fact] + public static void CreateFromInt16Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.Create(0x0000)); + Assert.Equal((sbyte)0x01, NumberHelper.Create(0x0001)); + Assert.Throws(() => NumberHelper.Create(0x7FFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((short)0x8000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.Create(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateFromInt32Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.Create(0x00000000)); + Assert.Equal((sbyte)0x01, NumberHelper.Create(0x00000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((int)0x80000000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.Create(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateFromInt64Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((sbyte)0x01, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.Create(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((sbyte)0x00, NumberHelper.Create(unchecked((nint)0x0000000000000000))); + Assert.Equal((sbyte)0x01, NumberHelper.Create(unchecked((nint)0x0000000000000001))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.Create(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((sbyte)0x00, NumberHelper.Create((nint)0x00000000)); + Assert.Equal((sbyte)0x01, NumberHelper.Create((nint)0x00000001)); + Assert.Throws(() => NumberHelper.Create((nint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.Create(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateFromSByteTest() + { + Assert.Equal((sbyte)0x00, NumberHelper.Create(0x00)); + Assert.Equal((sbyte)0x01, NumberHelper.Create(0x01)); + Assert.Equal((sbyte)0x7F, NumberHelper.Create(0x7F)); + Assert.Equal(unchecked((sbyte)0x80), NumberHelper.Create(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.Create(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateFromUInt16Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.Create(0x0000)); + Assert.Equal((sbyte)0x01, NumberHelper.Create(0x0001)); + Assert.Throws(() => NumberHelper.Create(0x7FFF)); + Assert.Throws(() => NumberHelper.Create(0x8000)); + Assert.Throws(() => NumberHelper.Create(0xFFFF)); + } + + [Fact] + public static void CreateFromUInt32Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.Create(0x00000000)); + Assert.Equal((sbyte)0x01, NumberHelper.Create(0x00000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x80000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFF)); + } + + [Fact] + public static void CreateFromUInt64Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((sbyte)0x01, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x8000000000000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((sbyte)0x00, NumberHelper.Create(unchecked((nuint)0x0000000000000000))); + Assert.Equal((sbyte)0x01, NumberHelper.Create(unchecked((nuint)0x0000000000000001))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((sbyte)0x00, NumberHelper.Create((nuint)0x00000000)); + Assert.Equal((sbyte)0x01, NumberHelper.Create((nuint)0x00000001)); + Assert.Throws(() => NumberHelper.Create((nuint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create((nuint)0x80000000)); + Assert.Throws(() => NumberHelper.Create((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateSaturatingFromByteTest() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((sbyte)0x7F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((sbyte)0x7F, NumberHelper.CreateSaturating(0x80)); + Assert.Equal((sbyte)0x7F, NumberHelper.CreateSaturating(0xFF)); + } + + [Fact] + public static void CreateSaturatingFromCharTest() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateSaturating((char)0x0000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateSaturating((char)0x0001)); + Assert.Equal((sbyte)0x7F, NumberHelper.CreateSaturating((char)0x7FFF)); + Assert.Equal((sbyte)0x7F, NumberHelper.CreateSaturating((char)0x8000)); + Assert.Equal((sbyte)0x7F, NumberHelper.CreateSaturating((char)0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromInt16Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal(unchecked((sbyte)0x80), NumberHelper.CreateSaturating(unchecked((short)0x8000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateSaturating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt32Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal(unchecked((sbyte)0x80), NumberHelper.CreateSaturating(unchecked((int)0x80000000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateSaturating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt64Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((sbyte)0x80), NumberHelper.CreateSaturating(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateSaturating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000000))); + Assert.Equal((sbyte)0x01, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((sbyte)0x80), NumberHelper.CreateSaturating(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateSaturating((nint)0x00000000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateSaturating((nint)0x00000001)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating((nint)0x7FFFFFFF)); + Assert.Equal(unchecked((sbyte)0x80), NumberHelper.CreateSaturating(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateSaturatingFromSByteTest() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((sbyte)0x7F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal(unchecked((sbyte)0x80), NumberHelper.CreateSaturating(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateSaturating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateSaturatingFromUInt16Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(0x8000)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt32Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(0x80000000)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt64Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(0x8000000000000000)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((sbyte)0x01, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateSaturating((nuint)0x00000000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateSaturating((nuint)0x00000001)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating((nuint)0x7FFFFFFF)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating((nuint)0x80000000)); + Assert.Equal(unchecked((sbyte)0x7F), NumberHelper.CreateSaturating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateTruncatingFromByteTest() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((sbyte)0x7F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal(unchecked((sbyte)0x80), NumberHelper.CreateTruncating(0x80)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(0xFF)); + } + + [Fact] + public static void CreateTruncatingFromCharTest() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating((char)0x0000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateTruncating((char)0x0001)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating((char)0x7FFF)); + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating((char)0x8000)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating((char)0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromInt16Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(unchecked((short)0x8000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt32Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(unchecked((int)0x80000000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt64Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000000))); + Assert.Equal((sbyte)0x01, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating((nint)0x00000000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateTruncating((nint)0x00000001)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating((nint)0x7FFFFFFF)); + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(unchecked((nint)0x80000000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromSByteTest() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((sbyte)0x7F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal(unchecked((sbyte)0x80), NumberHelper.CreateTruncating(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateTruncatingFromUInt16Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(0x8000)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt32Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(0x80000000)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt64Test() + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(0x8000000000000000)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((sbyte)0x01, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating((nuint)0x00000000)); + Assert.Equal((sbyte)0x01, NumberHelper.CreateTruncating((nuint)0x00000001)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating((nuint)0x7FFFFFFF)); + Assert.Equal((sbyte)0x00, NumberHelper.CreateTruncating((nuint)0x80000000)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.CreateTruncating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void DivRemTest() + { + Assert.Equal(((sbyte)0x00, (sbyte)0x00), NumberHelper.DivRem((sbyte)0x00, (sbyte)2)); + Assert.Equal(((sbyte)0x00, (sbyte)0x01), NumberHelper.DivRem((sbyte)0x01, (sbyte)2)); + Assert.Equal(((sbyte)0x3F, (sbyte)0x01), NumberHelper.DivRem((sbyte)0x7F, (sbyte)2)); + Assert.Equal((unchecked((sbyte)0xC0), (sbyte)0x00), NumberHelper.DivRem(unchecked((sbyte)0x80), (sbyte)2)); + Assert.Equal(((sbyte)0x00, unchecked((sbyte)0xFF)), NumberHelper.DivRem(unchecked((sbyte)0xFF), (sbyte)2)); + } + + [Fact] + public static void MaxTest() + { + Assert.Equal((sbyte)0x01, NumberHelper.Max((sbyte)0x00, (sbyte)1)); + Assert.Equal((sbyte)0x01, NumberHelper.Max((sbyte)0x01, (sbyte)1)); + Assert.Equal((sbyte)0x7F, NumberHelper.Max((sbyte)0x7F, (sbyte)1)); + Assert.Equal((sbyte)0x01, NumberHelper.Max(unchecked((sbyte)0x80), (sbyte)1)); + Assert.Equal((sbyte)0x01, NumberHelper.Max(unchecked((sbyte)0xFF), (sbyte)1)); + } + + [Fact] + public static void MinTest() + { + Assert.Equal((sbyte)0x00, NumberHelper.Min((sbyte)0x00, (sbyte)1)); + Assert.Equal((sbyte)0x01, NumberHelper.Min((sbyte)0x01, (sbyte)1)); + Assert.Equal((sbyte)0x01, NumberHelper.Min((sbyte)0x7F, (sbyte)1)); + Assert.Equal(unchecked((sbyte)0x80), NumberHelper.Min(unchecked((sbyte)0x80), (sbyte)1)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.Min(unchecked((sbyte)0xFF), (sbyte)1)); + } + + [Fact] + public static void SignTest() + { + Assert.Equal((sbyte)0x00, NumberHelper.Sign((sbyte)0x00)); + Assert.Equal((sbyte)0x01, NumberHelper.Sign((sbyte)0x01)); + Assert.Equal((sbyte)0x01, NumberHelper.Sign((sbyte)0x7F)); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.Sign(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((sbyte)0xFF), NumberHelper.Sign(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void TryCreateFromByteTest() + { + sbyte result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((sbyte)0x01, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((sbyte)0x7F, result); + + Assert.False(NumberHelper.TryCreate(0x80, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(0xFF, out result)); + Assert.Equal((sbyte)0x00, result); + } + + [Fact] + public static void TryCreateFromCharTest() + { + sbyte result; + + Assert.True(NumberHelper.TryCreate((char)0x0000, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate((char)0x0001, out result)); + Assert.Equal((sbyte)0x01, result); + + Assert.False(NumberHelper.TryCreate((char)0x7FFF, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate((char)0x8000, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate((char)0xFFFF, out result)); + Assert.Equal((sbyte)0x00, result); + } + + [Fact] + public static void TryCreateFromInt16Test() + { + sbyte result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((sbyte)0x01, result); + + Assert.False(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((short)0x8000), out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(unchecked((short)0xFFFF), out result)); + Assert.Equal(unchecked((sbyte)0xFF), result); + } + + [Fact] + public static void TryCreateFromInt32Test() + { + sbyte result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((sbyte)0x01, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((int)0x80000000), out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(unchecked((int)0xFFFFFFFF), out result)); + Assert.Equal(unchecked((sbyte)0xFF), result); + } + + [Fact] + public static void TryCreateFromInt64Test() + { + sbyte result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((sbyte)0x01, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0x8000000000000000), out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(unchecked((long)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((sbyte)0xFF), result); + } + + [Fact] + public static void TryCreateFromIntPtrTest() + { + sbyte result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000000), out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000001), out result)); + Assert.Equal((sbyte)0x01, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x8000000000000000), out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((sbyte)0xFF), result); + } + else + { + Assert.True(NumberHelper.TryCreate((nint)0x00000000, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate((nint)0x00000001, out result)); + Assert.Equal((sbyte)0x01, result); + + Assert.False(NumberHelper.TryCreate((nint)0x7FFFFFFF, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x80000000), out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFF), out result)); + Assert.Equal(unchecked((sbyte)0xFF), result); + } + } + + [Fact] + public static void TryCreateFromSByteTest() + { + sbyte result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((sbyte)0x01, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((sbyte)0x7F, result); + + Assert.True(NumberHelper.TryCreate(unchecked((sbyte)0x80), out result)); + Assert.Equal(unchecked((sbyte)0x80), result); + + Assert.True(NumberHelper.TryCreate(unchecked((sbyte)0xFF), out result)); + Assert.Equal(unchecked((sbyte)0xFF), result); + } + + [Fact] + public static void TryCreateFromUInt16Test() + { + sbyte result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((sbyte)0x01, result); + + Assert.False(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(0x8000, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(0xFFFF, out result)); + Assert.Equal((sbyte)0x00, result); + } + + [Fact] + public static void TryCreateFromUInt32Test() + { + sbyte result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((sbyte)0x01, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(0x80000000, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFF, out result)); + Assert.Equal((sbyte)0x00, result); + } + + [Fact] + public static void TryCreateFromUInt64Test() + { + sbyte result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((sbyte)0x01, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(0x8000000000000000, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFFFFFFFFFF, out result)); + Assert.Equal((sbyte)0x00, result); + } + + [Fact] + public static void TryCreateFromUIntPtrTest() + { + sbyte result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000000), out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000001), out result)); + Assert.Equal((sbyte)0x01, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x8000000000000000), out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((sbyte)0x00, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nuint)0x00000000, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x00000001, out result)); + Assert.Equal((sbyte)0x01, result); + + Assert.False(NumberHelper.TryCreate((nuint)0x7FFFFFFF, out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x80000000), out result)); + Assert.Equal((sbyte)0x00, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFF), out result)); + Assert.Equal((sbyte)0x00, result); + } + } + + [Fact] + + public static void op_LeftShiftTest() + { + Assert.Equal((sbyte)0x00, ShiftOperatorsHelper.op_LeftShift((sbyte)0x00, 1)); + Assert.Equal((sbyte)0x02, ShiftOperatorsHelper.op_LeftShift((sbyte)0x01, 1)); + Assert.Equal(unchecked((sbyte)0xFE), ShiftOperatorsHelper.op_LeftShift((sbyte)0x7F, 1)); + Assert.Equal((sbyte)0x00, ShiftOperatorsHelper.op_LeftShift(unchecked((sbyte)0x80), 1)); + Assert.Equal(unchecked((sbyte)0xFE), ShiftOperatorsHelper.op_LeftShift(unchecked((sbyte)0xFF), 1)); + } + + [Fact] + public static void op_RightShiftTest() + { + Assert.Equal((sbyte)0x00, ShiftOperatorsHelper.op_RightShift((sbyte)0x00, 1)); + Assert.Equal((sbyte)0x00, ShiftOperatorsHelper.op_RightShift((sbyte)0x01, 1)); + Assert.Equal((sbyte)0x3F, ShiftOperatorsHelper.op_RightShift((sbyte)0x7F, 1)); + Assert.Equal(unchecked((sbyte)0xC0), ShiftOperatorsHelper.op_RightShift(unchecked((sbyte)0x80), 1)); + Assert.Equal(unchecked((sbyte)0xFF), ShiftOperatorsHelper.op_RightShift(unchecked((sbyte)0xFF), 1)); + } + + [Fact] + public static void op_SubtractionTest() + { + Assert.Equal(unchecked((sbyte)0xFF), SubtractionOperatorsHelper.op_Subtraction((sbyte)0x00, (sbyte)1)); + Assert.Equal((sbyte)0x00, SubtractionOperatorsHelper.op_Subtraction((sbyte)0x01, (sbyte)1)); + Assert.Equal((sbyte)0x7E, SubtractionOperatorsHelper.op_Subtraction((sbyte)0x7F, (sbyte)1)); + Assert.Equal((sbyte)0x7F, SubtractionOperatorsHelper.op_Subtraction(unchecked((sbyte)0x80), (sbyte)1)); + Assert.Equal(unchecked((sbyte)0xFE), SubtractionOperatorsHelper.op_Subtraction(unchecked((sbyte)0xFF), (sbyte)1)); + } + + [Fact] + public static void op_UnaryNegationTest() + { + Assert.Equal((sbyte)0x00, UnaryNegationOperatorsHelper.op_UnaryNegation((sbyte)0x00)); + Assert.Equal(unchecked((sbyte)0xFF), UnaryNegationOperatorsHelper.op_UnaryNegation((sbyte)0x01)); + Assert.Equal(unchecked((sbyte)0x81), UnaryNegationOperatorsHelper.op_UnaryNegation((sbyte)0x7F)); + Assert.Equal(unchecked((sbyte)0x80), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((sbyte)0x80))); + Assert.Equal((sbyte)0x01, UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void op_UnaryPlusTest() + { + Assert.Equal((sbyte)0x00, UnaryPlusOperatorsHelper.op_UnaryPlus((sbyte)0x00)); + Assert.Equal((sbyte)0x01, UnaryPlusOperatorsHelper.op_UnaryPlus((sbyte)0x01)); + Assert.Equal((sbyte)0x7F, UnaryPlusOperatorsHelper.op_UnaryPlus((sbyte)0x7F)); + Assert.Equal(unchecked((sbyte)0x80), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((sbyte)0xFF), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((sbyte)0xFF))); + } + + [Theory] + [MemberData(nameof(SByteTests.Parse_Valid_TestData), MemberType = typeof(SByteTests))] + public static void ParseValidStringTest(string value, NumberStyles style, IFormatProvider provider, sbyte expected) + { + sbyte result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.True(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.True(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(SByteTests.Parse_Invalid_TestData), MemberType = typeof(SByteTests))] + public static void ParseInvalidStringTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + sbyte result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(default(sbyte), result); + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.False(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(default(sbyte), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.False(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(default(sbyte), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(SByteTests.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(SByteTests))] + public static void ParseValidSpanTest(string value, int offset, int count, NumberStyles style, IFormatProvider provider, sbyte expected) + { + sbyte result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(SpanParseableHelper.TryParse(value.AsSpan(offset, count), provider, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, NumberHelper.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(NumberHelper.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(SByteTests.Parse_Invalid_TestData), MemberType = typeof(SByteTests))] + public static void ParseInvalidSpanTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value is null) + { + return; + } + + sbyte result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(SpanParseableHelper.TryParse(value.AsSpan(), provider, out result)); + Assert.Equal(default(sbyte), result); + } + + Assert.Throws(exceptionType, () => NumberHelper.Parse(value.AsSpan(), style, provider)); + + Assert.False(NumberHelper.TryParse(value.AsSpan(), style, provider, out result)); + Assert.Equal(default(sbyte), result); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/UInt16Tests.GenericMath.cs b/src/libraries/System.Runtime/tests/System/UInt16Tests.GenericMath.cs new file mode 100644 index 000000000000..982740495bb9 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/UInt16Tests.GenericMath.cs @@ -0,0 +1,1175 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Runtime.Versioning; +using Xunit; + +namespace System.Tests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [RequiresPreviewFeaturesAttribute] + public class UInt16Tests_GenericMath + { + [Fact] + public static void AdditiveIdentityTest() + { + Assert.Equal((ushort)0x0000, AdditiveIdentityHelper.AdditiveIdentity); + } + + [Fact] + public static void MinValueTest() + { + Assert.Equal((ushort)0x0000, MinMaxValueHelper.MinValue); + } + + [Fact] + public static void MaxValueTest() + { + Assert.Equal((ushort)0xFFFF, MinMaxValueHelper.MaxValue); + } + + [Fact] + public static void MultiplicativeIdentityTest() + { + Assert.Equal((ushort)0x0001, MultiplicativeIdentityHelper.MultiplicativeIdentity); + } + + [Fact] + public static void OneTest() + { + Assert.Equal((ushort)0x0001, NumberHelper.One); + } + + [Fact] + public static void ZeroTest() + { + Assert.Equal((ushort)0x0000, NumberHelper.Zero); + } + + [Fact] + public static void op_AdditionTest() + { + Assert.Equal((ushort)0x0001, AdditionOperatorsHelper.op_Addition((ushort)0x0000, (ushort)1)); + Assert.Equal((ushort)0x0002, AdditionOperatorsHelper.op_Addition((ushort)0x0001, (ushort)1)); + Assert.Equal((ushort)0x8000, AdditionOperatorsHelper.op_Addition((ushort)0x7FFF, (ushort)1)); + Assert.Equal((ushort)0x8001, AdditionOperatorsHelper.op_Addition((ushort)0x8000, (ushort)1)); + Assert.Equal((ushort)0x0000, AdditionOperatorsHelper.op_Addition((ushort)0xFFFF, (ushort)1)); + } + + [Fact] + public static void LeadingZeroCountTest() + { + Assert.Equal((ushort)0x0010, BinaryIntegerHelper.LeadingZeroCount((ushort)0x0000)); + Assert.Equal((ushort)0x000F, BinaryIntegerHelper.LeadingZeroCount((ushort)0x0001)); + Assert.Equal((ushort)0x0001, BinaryIntegerHelper.LeadingZeroCount((ushort)0x7FFF)); + Assert.Equal((ushort)0x0000, BinaryIntegerHelper.LeadingZeroCount((ushort)0x8000)); + Assert.Equal((ushort)0x0000, BinaryIntegerHelper.LeadingZeroCount((ushort)0xFFFF)); + } + + [Fact] + public static void PopCountTest() + { + Assert.Equal((ushort)0x0000, BinaryIntegerHelper.PopCount((ushort)0x0000)); + Assert.Equal((ushort)0x0001, BinaryIntegerHelper.PopCount((ushort)0x0001)); + Assert.Equal((ushort)0x000F, BinaryIntegerHelper.PopCount((ushort)0x7FFF)); + Assert.Equal((ushort)0x0001, BinaryIntegerHelper.PopCount((ushort)0x8000)); + Assert.Equal((ushort)0x0010, BinaryIntegerHelper.PopCount((ushort)0xFFFF)); + } + + [Fact] + public static void RotateLeftTest() + { + Assert.Equal((ushort)0x0000, BinaryIntegerHelper.RotateLeft((ushort)0x0000, 1)); + Assert.Equal((ushort)0x0002, BinaryIntegerHelper.RotateLeft((ushort)0x0001, 1)); + Assert.Equal((ushort)0xFFFE, BinaryIntegerHelper.RotateLeft((ushort)0x7FFF, 1)); + Assert.Equal((ushort)0x0001, BinaryIntegerHelper.RotateLeft((ushort)0x8000, 1)); + Assert.Equal((ushort)0xFFFF, BinaryIntegerHelper.RotateLeft((ushort)0xFFFF, 1)); + } + + [Fact] + public static void RotateRightTest() + { + Assert.Equal((ushort)0x0000, BinaryIntegerHelper.RotateRight((ushort)0x0000, 1)); + Assert.Equal((ushort)0x8000, BinaryIntegerHelper.RotateRight((ushort)0x0001, 1)); + Assert.Equal((ushort)0xBFFF, BinaryIntegerHelper.RotateRight((ushort)0x7FFF, 1)); + Assert.Equal((ushort)0x4000, BinaryIntegerHelper.RotateRight((ushort)0x8000, 1)); + Assert.Equal((ushort)0xFFFF, BinaryIntegerHelper.RotateRight((ushort)0xFFFF, 1)); + } + + [Fact] + public static void TrailingZeroCountTest() + { + Assert.Equal((ushort)0x0010, BinaryIntegerHelper.TrailingZeroCount((ushort)0x0000)); + Assert.Equal((ushort)0x0000, BinaryIntegerHelper.TrailingZeroCount((ushort)0x0001)); + Assert.Equal((ushort)0x0000, BinaryIntegerHelper.TrailingZeroCount((ushort)0x7FFF)); + Assert.Equal((ushort)0x000F, BinaryIntegerHelper.TrailingZeroCount((ushort)0x8000)); + Assert.Equal((ushort)0x0000, BinaryIntegerHelper.TrailingZeroCount((ushort)0xFFFF)); + } + + [Fact] + public static void IsPow2Test() + { + Assert.False(BinaryNumberHelper.IsPow2((ushort)0x0000)); + Assert.True(BinaryNumberHelper.IsPow2((ushort)0x0001)); + Assert.False(BinaryNumberHelper.IsPow2((ushort)0x7FFF)); + Assert.True(BinaryNumberHelper.IsPow2((ushort)0x8000)); + Assert.False(BinaryNumberHelper.IsPow2((ushort)0xFFFF)); + } + + [Fact] + public static void Log2Test() + { + Assert.Equal((ushort)0x0000, BinaryNumberHelper.Log2((ushort)0x0000)); + Assert.Equal((ushort)0x0000, BinaryNumberHelper.Log2((ushort)0x0001)); + Assert.Equal((ushort)0x000E, BinaryNumberHelper.Log2((ushort)0x7FFF)); + Assert.Equal((ushort)0x000F, BinaryNumberHelper.Log2((ushort)0x8000)); + Assert.Equal((ushort)0x000F, BinaryNumberHelper.Log2((ushort)0xFFFF)); + } + + [Fact] + public static void op_BitwiseAndTest() + { + Assert.Equal((ushort)0x0000, BitwiseOperatorsHelper.op_BitwiseAnd((ushort)0x0000, (ushort)1)); + Assert.Equal((ushort)0x0001, BitwiseOperatorsHelper.op_BitwiseAnd((ushort)0x0001, (ushort)1)); + Assert.Equal((ushort)0x0001, BitwiseOperatorsHelper.op_BitwiseAnd((ushort)0x7FFF, (ushort)1)); + Assert.Equal((ushort)0x0000, BitwiseOperatorsHelper.op_BitwiseAnd((ushort)0x8000, (ushort)1)); + Assert.Equal((ushort)0x0001, BitwiseOperatorsHelper.op_BitwiseAnd((ushort)0xFFFF, (ushort)1)); + } + + [Fact] + public static void op_BitwiseOrTest() + { + Assert.Equal((ushort)0x0001, BitwiseOperatorsHelper.op_BitwiseOr((ushort)0x0000, (ushort)1)); + Assert.Equal((ushort)0x0001, BitwiseOperatorsHelper.op_BitwiseOr((ushort)0x0001, (ushort)1)); + Assert.Equal((ushort)0x7FFF, BitwiseOperatorsHelper.op_BitwiseOr((ushort)0x7FFF, (ushort)1)); + Assert.Equal((ushort)0x8001, BitwiseOperatorsHelper.op_BitwiseOr((ushort)0x8000, (ushort)1)); + Assert.Equal((ushort)0xFFFF, BitwiseOperatorsHelper.op_BitwiseOr((ushort)0xFFFF, (ushort)1)); + } + + [Fact] + public static void op_ExclusiveOrTest() + { + Assert.Equal((ushort)0x0001, BitwiseOperatorsHelper.op_ExclusiveOr((ushort)0x0000, (ushort)1)); + Assert.Equal((ushort)0x0000, BitwiseOperatorsHelper.op_ExclusiveOr((ushort)0x0001, (ushort)1)); + Assert.Equal((ushort)0x7FFE, BitwiseOperatorsHelper.op_ExclusiveOr((ushort)0x7FFF, (ushort)1)); + Assert.Equal((ushort)0x8001, BitwiseOperatorsHelper.op_ExclusiveOr((ushort)0x8000, (ushort)1)); + Assert.Equal((ushort)0xFFFE, BitwiseOperatorsHelper.op_ExclusiveOr((ushort)0xFFFF, (ushort)1)); + } + + [Fact] + public static void op_OnesComplementTest() + { + Assert.Equal((ushort)0xFFFF, BitwiseOperatorsHelper.op_OnesComplement((ushort)0x0000)); + Assert.Equal((ushort)0xFFFE, BitwiseOperatorsHelper.op_OnesComplement((ushort)0x0001)); + Assert.Equal((ushort)0x8000, BitwiseOperatorsHelper.op_OnesComplement((ushort)0x7FFF)); + Assert.Equal((ushort)0x7FFF, BitwiseOperatorsHelper.op_OnesComplement((ushort)0x8000)); + Assert.Equal((ushort)0x0000, BitwiseOperatorsHelper.op_OnesComplement((ushort)0xFFFF)); + } + + [Fact] + public static void op_LessThanTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThan((ushort)0x0000, (ushort)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((ushort)0x0001, (ushort)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((ushort)0x7FFF, (ushort)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((ushort)0x8000, (ushort)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((ushort)0xFFFF, (ushort)1)); + } + + [Fact] + public static void op_LessThanOrEqualTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((ushort)0x0000, (ushort)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((ushort)0x0001, (ushort)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((ushort)0x7FFF, (ushort)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((ushort)0x8000, (ushort)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((ushort)0xFFFF, (ushort)1)); + } + + [Fact] + public static void op_GreaterThanTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((ushort)0x0000, (ushort)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((ushort)0x0001, (ushort)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((ushort)0x7FFF, (ushort)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((ushort)0x8000, (ushort)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((ushort)0xFFFF, (ushort)1)); + } + + [Fact] + public static void op_GreaterThanOrEqualTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual((ushort)0x0000, (ushort)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((ushort)0x0001, (ushort)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((ushort)0x7FFF, (ushort)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((ushort)0x8000, (ushort)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((ushort)0xFFFF, (ushort)1)); + } + + [Fact] + public static void op_DecrementTest() + { + Assert.Equal((ushort)0xFFFF, DecrementOperatorsHelper.op_Decrement((ushort)0x0000)); + Assert.Equal((ushort)0x0000, DecrementOperatorsHelper.op_Decrement((ushort)0x0001)); + Assert.Equal((ushort)0x7FFE, DecrementOperatorsHelper.op_Decrement((ushort)0x7FFF)); + Assert.Equal((ushort)0x7FFF, DecrementOperatorsHelper.op_Decrement((ushort)0x8000)); + Assert.Equal((ushort)0xFFFE, DecrementOperatorsHelper.op_Decrement((ushort)0xFFFF)); + } + + [Fact] + public static void op_DivisionTest() + { + Assert.Equal((ushort)0x0000, DivisionOperatorsHelper.op_Division((ushort)0x0000, (ushort)2)); + Assert.Equal((ushort)0x0000, DivisionOperatorsHelper.op_Division((ushort)0x0001, (ushort)2)); + Assert.Equal((ushort)0x3FFF, DivisionOperatorsHelper.op_Division((ushort)0x7FFF, (ushort)2)); + Assert.Equal((ushort)0x4000, DivisionOperatorsHelper.op_Division((ushort)0x8000, (ushort)2)); + Assert.Equal((ushort)0x7FFF, DivisionOperatorsHelper.op_Division((ushort)0xFFFF, (ushort)2)); + } + + [Fact] + public static void op_EqualityTest() + { + Assert.False(EqualityOperatorsHelper.op_Equality((ushort)0x0000, (ushort)1)); + Assert.True(EqualityOperatorsHelper.op_Equality((ushort)0x0001, (ushort)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((ushort)0x7FFF, (ushort)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((ushort)0x8000, (ushort)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((ushort)0xFFFF, (ushort)1)); + } + + [Fact] + public static void op_InequalityTest() + { + Assert.True(EqualityOperatorsHelper.op_Inequality((ushort)0x0000, (ushort)1)); + Assert.False(EqualityOperatorsHelper.op_Inequality((ushort)0x0001, (ushort)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((ushort)0x7FFF, (ushort)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((ushort)0x8000, (ushort)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((ushort)0xFFFF, (ushort)1)); + } + + [Fact] + public static void op_IncrementTest() + { + Assert.Equal((ushort)0x0001, IncrementOperatorsHelper.op_Increment((ushort)0x0000)); + Assert.Equal((ushort)0x0002, IncrementOperatorsHelper.op_Increment((ushort)0x0001)); + Assert.Equal((ushort)0x8000, IncrementOperatorsHelper.op_Increment((ushort)0x7FFF)); + Assert.Equal((ushort)0x8001, IncrementOperatorsHelper.op_Increment((ushort)0x8000)); + Assert.Equal((ushort)0x0000, IncrementOperatorsHelper.op_Increment((ushort)0xFFFF)); + } + + [Fact] + public static void op_ModulusTest() + { + Assert.Equal((ushort)0x0000, ModulusOperatorsHelper.op_Modulus((ushort)0x0000, (ushort)2)); + Assert.Equal((ushort)0x0001, ModulusOperatorsHelper.op_Modulus((ushort)0x0001, (ushort)2)); + Assert.Equal((ushort)0x0001, ModulusOperatorsHelper.op_Modulus((ushort)0x7FFF, (ushort)2)); + Assert.Equal((ushort)0x0000, ModulusOperatorsHelper.op_Modulus((ushort)0x8000, (ushort)2)); + Assert.Equal((ushort)0x0001, ModulusOperatorsHelper.op_Modulus((ushort)0xFFFF, (ushort)2)); + } + + [Fact] + public static void op_MultiplyTest() + { + Assert.Equal((ushort)0x0000, MultiplyOperatorsHelper.op_Multiply((ushort)0x0000, (ushort)2)); + Assert.Equal((ushort)0x0002, MultiplyOperatorsHelper.op_Multiply((ushort)0x0001, (ushort)2)); + Assert.Equal((ushort)0xFFFE, MultiplyOperatorsHelper.op_Multiply((ushort)0x7FFF, (ushort)2)); + Assert.Equal((ushort)0x0000, MultiplyOperatorsHelper.op_Multiply((ushort)0x8000, (ushort)2)); + Assert.Equal((ushort)0xFFFE, MultiplyOperatorsHelper.op_Multiply((ushort)0xFFFF, (ushort)2)); + } + + [Fact] + public static void AbsTest() + { + Assert.Equal((ushort)0x0000, NumberHelper.Abs((ushort)0x0000)); + Assert.Equal((ushort)0x0001, NumberHelper.Abs((ushort)0x0001)); + Assert.Equal((ushort)0x7FFF, NumberHelper.Abs((ushort)0x7FFF)); + Assert.Equal((ushort)0x8000, NumberHelper.Abs((ushort)0x8000)); + Assert.Equal((ushort)0xFFFF, NumberHelper.Abs((ushort)0xFFFF)); + } + + [Fact] + public static void ClampTest() + { + Assert.Equal((ushort)0x0001, NumberHelper.Clamp((ushort)0x0000, (ushort)0x0001, (ushort)0x003F)); + Assert.Equal((ushort)0x0001, NumberHelper.Clamp((ushort)0x0001, (ushort)0x0001, (ushort)0x003F)); + Assert.Equal((ushort)0x003F, NumberHelper.Clamp((ushort)0x7FFF, (ushort)0x0001, (ushort)0x003F)); + Assert.Equal((ushort)0x003F, NumberHelper.Clamp((ushort)0x8000, (ushort)0x0001, (ushort)0x003F)); + Assert.Equal((ushort)0x003F, NumberHelper.Clamp((ushort)0xFFFF, (ushort)0x0001, (ushort)0x003F)); + } + + [Fact] + public static void CreateFromByteTest() + { + Assert.Equal((ushort)0x0000, NumberHelper.Create(0x00)); + Assert.Equal((ushort)0x0001, NumberHelper.Create(0x01)); + Assert.Equal((ushort)0x007F, NumberHelper.Create(0x7F)); + Assert.Equal((ushort)0x0080, NumberHelper.Create(0x80)); + Assert.Equal((ushort)0x00FF, NumberHelper.Create(0xFF)); + } + + [Fact] + public static void CreateFromCharTest() + { + Assert.Equal((ushort)0x0000, NumberHelper.Create((char)0x0000)); + Assert.Equal((ushort)0x0001, NumberHelper.Create((char)0x0001)); + Assert.Equal((ushort)0x7FFF, NumberHelper.Create((char)0x7FFF)); + Assert.Equal((ushort)0x8000, NumberHelper.Create((char)0x8000)); + Assert.Equal((ushort)0xFFFF, NumberHelper.Create((char)0xFFFF)); + } + + [Fact] + public static void CreateFromInt16Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.Create(0x0000)); + Assert.Equal((ushort)0x0001, NumberHelper.Create(0x0001)); + Assert.Equal((ushort)0x7FFF, NumberHelper.Create(0x7FFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((short)0x8000))); + Assert.Throws(() => NumberHelper.Create(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateFromInt32Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.Create(0x00000000)); + Assert.Equal((ushort)0x0001, NumberHelper.Create(0x00000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((int)0x80000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateFromInt64Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((ushort)0x0001, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((ushort)0x0000, NumberHelper.Create(unchecked((nint)0x0000000000000000))); + Assert.Equal((ushort)0x0001, NumberHelper.Create(unchecked((nint)0x0000000000000001))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((ushort)0x0000, NumberHelper.Create((nint)0x00000000)); + Assert.Equal((ushort)0x0001, NumberHelper.Create((nint)0x00000001)); + Assert.Throws(() => NumberHelper.Create((nint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x80000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateFromSByteTest() + { + Assert.Equal((ushort)0x0000, NumberHelper.Create(0x00)); + Assert.Equal((ushort)0x0001, NumberHelper.Create(0x01)); + Assert.Equal((ushort)0x007F, NumberHelper.Create(0x7F)); + Assert.Throws(() => NumberHelper.Create(unchecked((sbyte)0x80))); + Assert.Throws(() => NumberHelper.Create(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateFromUInt16Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.Create(0x0000)); + Assert.Equal((ushort)0x0001, NumberHelper.Create(0x0001)); + Assert.Equal((ushort)0x7FFF, NumberHelper.Create(0x7FFF)); + Assert.Equal((ushort)0x8000, NumberHelper.Create(0x8000)); + Assert.Equal((ushort)0xFFFF, NumberHelper.Create(0xFFFF)); + } + + [Fact] + public static void CreateFromUInt32Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.Create(0x00000000)); + Assert.Equal((ushort)0x0001, NumberHelper.Create(0x00000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x80000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFF)); + } + + [Fact] + public static void CreateFromUInt64Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((ushort)0x0001, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x8000000000000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((ushort)0x0000, NumberHelper.Create(unchecked((nuint)0x0000000000000000))); + Assert.Equal((ushort)0x0001, NumberHelper.Create(unchecked((nuint)0x0000000000000001))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((ushort)0x0000, NumberHelper.Create((nuint)0x00000000)); + Assert.Equal((ushort)0x0001, NumberHelper.Create((nuint)0x00000001)); + Assert.Throws(() => NumberHelper.Create((nuint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create((nuint)0x80000000)); + Assert.Throws(() => NumberHelper.Create((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateSaturatingFromByteTest() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((ushort)0x007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((ushort)0x0080, NumberHelper.CreateSaturating(0x80)); + Assert.Equal((ushort)0x00FF, NumberHelper.CreateSaturating(0xFF)); + } + + [Fact] + public static void CreateSaturatingFromCharTest() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating((char)0x0000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateSaturating((char)0x0001)); + Assert.Equal((ushort)0x7FFF, NumberHelper.CreateSaturating((char)0x7FFF)); + Assert.Equal((ushort)0x8000, NumberHelper.CreateSaturating((char)0x8000)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating((char)0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromInt16Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((ushort)0x7FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(unchecked((short)0x8000))); + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt32Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(unchecked((int)0x80000000))); + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt64Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(unchecked((long)0x8000000000000000))); + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000000))); + Assert.Equal((ushort)0x0001, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000001))); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(unchecked((nint)0x8000000000000000))); + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating((nint)0x00000000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateSaturating((nint)0x00000001)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating((nint)0x7FFFFFFF)); + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(unchecked((nint)0x80000000))); + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateSaturatingFromSByteTest() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((ushort)0x007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(unchecked((sbyte)0x80))); + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateSaturatingFromUInt16Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((ushort)0x7FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((ushort)0x8000, NumberHelper.CreateSaturating(0x8000)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating(0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt32Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating(0x80000000)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt64Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating(0x8000000000000000)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((ushort)0x0001, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000001))); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0x8000000000000000))); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateSaturating((nuint)0x00000000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateSaturating((nuint)0x00000001)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating((nuint)0x7FFFFFFF)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating((nuint)0x80000000)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateSaturating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateTruncatingFromByteTest() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((ushort)0x007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((ushort)0x0080, NumberHelper.CreateTruncating(0x80)); + Assert.Equal((ushort)0x00FF, NumberHelper.CreateTruncating(0xFF)); + } + + [Fact] + public static void CreateTruncatingFromCharTest() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating((char)0x0000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateTruncating((char)0x0001)); + Assert.Equal((ushort)0x7FFF, NumberHelper.CreateTruncating((char)0x7FFF)); + Assert.Equal((ushort)0x8000, NumberHelper.CreateTruncating((char)0x8000)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating((char)0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromInt16Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((ushort)0x7FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((ushort)0x8000, NumberHelper.CreateTruncating(unchecked((short)0x8000))); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt32Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(unchecked((int)0x80000000))); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt64Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(unchecked((long)0x8000000000000000))); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000000))); + Assert.Equal((ushort)0x0001, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000001))); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(unchecked((nint)0x8000000000000000))); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating((nint)0x00000000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateTruncating((nint)0x00000001)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating((nint)0x7FFFFFFF)); + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(unchecked((nint)0x80000000))); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromSByteTest() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((ushort)0x007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((ushort)0xFF80, NumberHelper.CreateTruncating(unchecked((sbyte)0x80))); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateTruncatingFromUInt16Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((ushort)0x7FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((ushort)0x8000, NumberHelper.CreateTruncating(0x8000)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt32Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(0x80000000)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt64Test() + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(0x8000000000000000)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((ushort)0x0001, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000001))); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating(unchecked((nuint)0x8000000000000000))); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating((nuint)0x00000000)); + Assert.Equal((ushort)0x0001, NumberHelper.CreateTruncating((nuint)0x00000001)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating((nuint)0x7FFFFFFF)); + Assert.Equal((ushort)0x0000, NumberHelper.CreateTruncating((nuint)0x80000000)); + Assert.Equal((ushort)0xFFFF, NumberHelper.CreateTruncating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void DivRemTest() + { + Assert.Equal(((ushort)0x0000, (ushort)0x0000), NumberHelper.DivRem((ushort)0x0000, (ushort)2)); + Assert.Equal(((ushort)0x0000, (ushort)0x0001), NumberHelper.DivRem((ushort)0x0001, (ushort)2)); + Assert.Equal(((ushort)0x3FFF, (ushort)0x0001), NumberHelper.DivRem((ushort)0x7FFF, (ushort)2)); + Assert.Equal(((ushort)0x4000, (ushort)0x0000), NumberHelper.DivRem((ushort)0x8000, (ushort)2)); + Assert.Equal(((ushort)0x7FFF, (ushort)0x0001), NumberHelper.DivRem((ushort)0xFFFF, (ushort)2)); + } + + [Fact] + public static void MaxTest() + { + Assert.Equal((ushort)0x0001, NumberHelper.Max((ushort)0x0000, (ushort)1)); + Assert.Equal((ushort)0x0001, NumberHelper.Max((ushort)0x0001, (ushort)1)); + Assert.Equal((ushort)0x7FFF, NumberHelper.Max((ushort)0x7FFF, (ushort)1)); + Assert.Equal((ushort)0x8000, NumberHelper.Max((ushort)0x8000, (ushort)1)); + Assert.Equal((ushort)0xFFFF, NumberHelper.Max((ushort)0xFFFF, (ushort)1)); + } + + [Fact] + public static void MinTest() + { + Assert.Equal((ushort)0x0000, NumberHelper.Min((ushort)0x0000, (ushort)1)); + Assert.Equal((ushort)0x0001, NumberHelper.Min((ushort)0x0001, (ushort)1)); + Assert.Equal((ushort)0x0001, NumberHelper.Min((ushort)0x7FFF, (ushort)1)); + Assert.Equal((ushort)0x0001, NumberHelper.Min((ushort)0x8000, (ushort)1)); + Assert.Equal((ushort)0x0001, NumberHelper.Min((ushort)0xFFFF, (ushort)1)); + } + + [Fact] + public static void SignTest() + { + Assert.Equal((ushort)0x0000, NumberHelper.Sign((ushort)0x0000)); + Assert.Equal((ushort)0x0001, NumberHelper.Sign((ushort)0x0001)); + Assert.Equal((ushort)0x0001, NumberHelper.Sign((ushort)0x7FFF)); + Assert.Equal((ushort)0x0001, NumberHelper.Sign((ushort)0x8000)); + Assert.Equal((ushort)0x0001, NumberHelper.Sign((ushort)0xFFFF)); + } + + [Fact] + public static void TryCreateFromByteTest() + { + ushort result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((ushort)0x0001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((ushort)0x007F, result); + + Assert.True(NumberHelper.TryCreate(0x80, out result)); + Assert.Equal((ushort)0x0080, result); + + Assert.True(NumberHelper.TryCreate(0xFF, out result)); + Assert.Equal((ushort)0x00FF, result); + } + + [Fact] + public static void TryCreateFromCharTest() + { + ushort result; + + Assert.True(NumberHelper.TryCreate((char)0x0000, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.True(NumberHelper.TryCreate((char)0x0001, out result)); + Assert.Equal((ushort)0x0001, result); + + Assert.True(NumberHelper.TryCreate((char)0x7FFF, out result)); + Assert.Equal((ushort)0x7FFF, result); + + Assert.True(NumberHelper.TryCreate((char)0x8000, out result)); + Assert.Equal((ushort)0x8000, result); + + Assert.True(NumberHelper.TryCreate((char)0xFFFF, out result)); + Assert.Equal((ushort)0xFFFF, result); + } + + [Fact] + public static void TryCreateFromInt16Test() + { + ushort result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((ushort)0x0001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((ushort)0x7FFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((short)0x8000), out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((short)0xFFFF), out result)); + Assert.Equal((ushort)0x0000, result); + } + + [Fact] + public static void TryCreateFromInt32Test() + { + ushort result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((ushort)0x0001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((int)0x80000000), out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((int)0xFFFFFFFF), out result)); + Assert.Equal((ushort)0x0000, result); + } + + [Fact] + public static void TryCreateFromInt64Test() + { + ushort result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((ushort)0x0001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0x8000000000000000), out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((ushort)0x0000, result); + } + + [Fact] + public static void TryCreateFromIntPtrTest() + { + ushort result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000000), out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000001), out result)); + Assert.Equal((ushort)0x0001, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x8000000000000000), out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((ushort)0x0000, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nint)0x00000000, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.True(NumberHelper.TryCreate((nint)0x00000001, out result)); + Assert.Equal((ushort)0x0001, result); + + Assert.False(NumberHelper.TryCreate((nint)0x7FFFFFFF, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x80000000), out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFF), out result)); + Assert.Equal((ushort)0x0000, result); + } + } + + [Fact] + public static void TryCreateFromSByteTest() + { + ushort result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((ushort)0x0001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((ushort)0x007F, result); + + Assert.False(NumberHelper.TryCreate(unchecked((sbyte)0x80), out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((sbyte)0xFF), out result)); + Assert.Equal((ushort)0x0000, result); + } + + [Fact] + public static void TryCreateFromUInt16Test() + { + ushort result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((ushort)0x0001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((ushort)0x7FFF, result); + + Assert.True(NumberHelper.TryCreate(0x8000, out result)); + Assert.Equal((ushort)0x8000, result); + + Assert.True(NumberHelper.TryCreate(0xFFFF, out result)); + Assert.Equal((ushort)0xFFFF, result); + } + + [Fact] + public static void TryCreateFromUInt32Test() + { + ushort result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((ushort)0x0001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(0x80000000, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFF, out result)); + Assert.Equal((ushort)0x0000, result); + } + + [Fact] + public static void TryCreateFromUInt64Test() + { + ushort result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((ushort)0x0001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(0x8000000000000000, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFFFFFFFFFF, out result)); + Assert.Equal((ushort)0x0000, result); + } + + [Fact] + public static void TryCreateFromUIntPtrTest() + { + ushort result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000000), out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000001), out result)); + Assert.Equal((ushort)0x0001, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x8000000000000000), out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((ushort)0x0000, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nuint)0x00000000, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x00000001, out result)); + Assert.Equal((ushort)0x0001, result); + + Assert.False(NumberHelper.TryCreate((nuint)0x7FFFFFFF, out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x80000000), out result)); + Assert.Equal((ushort)0x0000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFF), out result)); + Assert.Equal((ushort)0x0000, result); + } + } + + [Fact] + + public static void op_LeftShiftTest() + { + Assert.Equal((ushort)0x0000, ShiftOperatorsHelper.op_LeftShift((ushort)0x0000, 1)); + Assert.Equal((ushort)0x0002, ShiftOperatorsHelper.op_LeftShift((ushort)0x0001, 1)); + Assert.Equal((ushort)0xFFFE, ShiftOperatorsHelper.op_LeftShift((ushort)0x7FFF, 1)); + Assert.Equal((ushort)0x0000, ShiftOperatorsHelper.op_LeftShift((ushort)0x8000, 1)); + Assert.Equal((ushort)0xFFFE, ShiftOperatorsHelper.op_LeftShift((ushort)0xFFFF, 1)); + } + + [Fact] + public static void op_RightShiftTest() + { + Assert.Equal((ushort)0x0000, ShiftOperatorsHelper.op_RightShift((ushort)0x0000, 1)); + Assert.Equal((ushort)0x0000, ShiftOperatorsHelper.op_RightShift((ushort)0x0001, 1)); + Assert.Equal((ushort)0x3FFF, ShiftOperatorsHelper.op_RightShift((ushort)0x7FFF, 1)); + Assert.Equal((ushort)0x4000, ShiftOperatorsHelper.op_RightShift((ushort)0x8000, 1)); + Assert.Equal((ushort)0x7FFF, ShiftOperatorsHelper.op_RightShift((ushort)0xFFFF, 1)); + } + + [Fact] + public static void op_SubtractionTest() + { + Assert.Equal((ushort)0xFFFF, SubtractionOperatorsHelper.op_Subtraction((ushort)0x0000, (ushort)1)); + Assert.Equal((ushort)0x0000, SubtractionOperatorsHelper.op_Subtraction((ushort)0x0001, (ushort)1)); + Assert.Equal((ushort)0x7FFE, SubtractionOperatorsHelper.op_Subtraction((ushort)0x7FFF, (ushort)1)); + Assert.Equal((ushort)0x7FFF, SubtractionOperatorsHelper.op_Subtraction((ushort)0x8000, (ushort)1)); + Assert.Equal((ushort)0xFFFE, SubtractionOperatorsHelper.op_Subtraction((ushort)0xFFFF, (ushort)1)); + } + + [Fact] + public static void op_UnaryNegationTest() + { + Assert.Equal((ushort)0x0000, UnaryNegationOperatorsHelper.op_UnaryNegation((ushort)0x0000)); + Assert.Equal((ushort)0xFFFF, UnaryNegationOperatorsHelper.op_UnaryNegation((ushort)0x0001)); + Assert.Equal((ushort)0x8001, UnaryNegationOperatorsHelper.op_UnaryNegation((ushort)0x7FFF)); + Assert.Equal((ushort)0x8000, UnaryNegationOperatorsHelper.op_UnaryNegation((ushort)0x8000)); + Assert.Equal((ushort)0x0001, UnaryNegationOperatorsHelper.op_UnaryNegation((ushort)0xFFFF)); + } + + [Fact] + public static void op_UnaryPlusTest() + { + Assert.Equal((ushort)0x0000, UnaryPlusOperatorsHelper.op_UnaryPlus((ushort)0x0000)); + Assert.Equal((ushort)0x0001, UnaryPlusOperatorsHelper.op_UnaryPlus((ushort)0x0001)); + Assert.Equal((ushort)0x7FFF, UnaryPlusOperatorsHelper.op_UnaryPlus((ushort)0x7FFF)); + Assert.Equal((ushort)0x8000, UnaryPlusOperatorsHelper.op_UnaryPlus((ushort)0x8000)); + Assert.Equal((ushort)0xFFFF, UnaryPlusOperatorsHelper.op_UnaryPlus((ushort)0xFFFF)); + } + + [Theory] + [MemberData(nameof(UInt16Tests.Parse_Valid_TestData), MemberType = typeof(UInt16Tests))] + public static void ParseValidStringTest(string value, NumberStyles style, IFormatProvider provider, ushort expected) + { + ushort result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.True(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.True(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(UInt16Tests.Parse_Invalid_TestData), MemberType = typeof(UInt16Tests))] + public static void ParseInvalidStringTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + ushort result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(default(ushort), result); + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.False(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(default(ushort), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.False(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(default(ushort), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(UInt16Tests.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(UInt16Tests))] + public static void ParseValidSpanTest(string value, int offset, int count, NumberStyles style, IFormatProvider provider, ushort expected) + { + ushort result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(SpanParseableHelper.TryParse(value.AsSpan(offset, count), provider, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, NumberHelper.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(NumberHelper.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(UInt16Tests.Parse_Invalid_TestData), MemberType = typeof(UInt16Tests))] + public static void ParseInvalidSpanTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value is null) + { + return; + } + + ushort result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(SpanParseableHelper.TryParse(value.AsSpan(), provider, out result)); + Assert.Equal(default(ushort), result); + } + + Assert.Throws(exceptionType, () => NumberHelper.Parse(value.AsSpan(), style, provider)); + + Assert.False(NumberHelper.TryParse(value.AsSpan(), style, provider, out result)); + Assert.Equal(default(ushort), result); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/UInt32Tests.GenericMath.cs b/src/libraries/System.Runtime/tests/System/UInt32Tests.GenericMath.cs new file mode 100644 index 000000000000..ab6a6e02471e --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/UInt32Tests.GenericMath.cs @@ -0,0 +1,1175 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Runtime.Versioning; +using Xunit; + +namespace System.Tests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [RequiresPreviewFeaturesAttribute] + public class UInt32Tests_GenericMath + { + [Fact] + public static void AdditiveIdentityTest() + { + Assert.Equal((uint)0x00000000, AdditiveIdentityHelper.AdditiveIdentity); + } + + [Fact] + public static void MinValueTest() + { + Assert.Equal((uint)0x00000000, MinMaxValueHelper.MinValue); + } + + [Fact] + public static void MaxValueTest() + { + Assert.Equal((uint)0xFFFFFFFF, MinMaxValueHelper.MaxValue); + } + + [Fact] + public static void MultiplicativeIdentityTest() + { + Assert.Equal((uint)0x00000001, MultiplicativeIdentityHelper.MultiplicativeIdentity); + } + + [Fact] + public static void OneTest() + { + Assert.Equal((uint)0x00000001, NumberHelper.One); + } + + [Fact] + public static void ZeroTest() + { + Assert.Equal((uint)0x00000000, NumberHelper.Zero); + } + + [Fact] + public static void op_AdditionTest() + { + Assert.Equal((uint)0x00000001, AdditionOperatorsHelper.op_Addition((uint)0x00000000, 1)); + Assert.Equal((uint)0x00000002, AdditionOperatorsHelper.op_Addition((uint)0x00000001, 1)); + Assert.Equal((uint)0x80000000, AdditionOperatorsHelper.op_Addition((uint)0x7FFFFFFF, 1)); + Assert.Equal((uint)0x80000001, AdditionOperatorsHelper.op_Addition((uint)0x80000000, 1)); + Assert.Equal((uint)0x00000000, AdditionOperatorsHelper.op_Addition((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void LeadingZeroCountTest() + { + Assert.Equal((uint)0x00000020, BinaryIntegerHelper.LeadingZeroCount((uint)0x00000000)); + Assert.Equal((uint)0x0000001F, BinaryIntegerHelper.LeadingZeroCount((uint)0x00000001)); + Assert.Equal((uint)0x00000001, BinaryIntegerHelper.LeadingZeroCount((uint)0x7FFFFFFF)); + Assert.Equal((uint)0x00000000, BinaryIntegerHelper.LeadingZeroCount((uint)0x80000000)); + Assert.Equal((uint)0x00000000, BinaryIntegerHelper.LeadingZeroCount((uint)0xFFFFFFFF)); + } + + [Fact] + public static void PopCountTest() + { + Assert.Equal((uint)0x00000000, BinaryIntegerHelper.PopCount((uint)0x00000000)); + Assert.Equal((uint)0x00000001, BinaryIntegerHelper.PopCount((uint)0x00000001)); + Assert.Equal((uint)0x0000001F, BinaryIntegerHelper.PopCount((uint)0x7FFFFFFF)); + Assert.Equal((uint)0x00000001, BinaryIntegerHelper.PopCount((uint)0x80000000)); + Assert.Equal((uint)0x00000020, BinaryIntegerHelper.PopCount((uint)0xFFFFFFFF)); + } + + [Fact] + public static void RotateLeftTest() + { + Assert.Equal((uint)0x00000000, BinaryIntegerHelper.RotateLeft((uint)0x00000000, 1)); + Assert.Equal((uint)0x00000002, BinaryIntegerHelper.RotateLeft((uint)0x00000001, 1)); + Assert.Equal((uint)0xFFFFFFFE, BinaryIntegerHelper.RotateLeft((uint)0x7FFFFFFF, 1)); + Assert.Equal((uint)0x00000001, BinaryIntegerHelper.RotateLeft((uint)0x80000000, 1)); + Assert.Equal((uint)0xFFFFFFFF, BinaryIntegerHelper.RotateLeft((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void RotateRightTest() + { + Assert.Equal((uint)0x00000000, BinaryIntegerHelper.RotateRight((uint)0x00000000, 1)); + Assert.Equal((uint)0x80000000, BinaryIntegerHelper.RotateRight((uint)0x00000001, 1)); + Assert.Equal((uint)0xBFFFFFFF, BinaryIntegerHelper.RotateRight((uint)0x7FFFFFFF, 1)); + Assert.Equal((uint)0x40000000, BinaryIntegerHelper.RotateRight((uint)0x80000000, 1)); + Assert.Equal((uint)0xFFFFFFFF, BinaryIntegerHelper.RotateRight((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void TrailingZeroCountTest() + { + Assert.Equal((uint)0x00000020, BinaryIntegerHelper.TrailingZeroCount((uint)0x00000000)); + Assert.Equal((uint)0x00000000, BinaryIntegerHelper.TrailingZeroCount((uint)0x00000001)); + Assert.Equal((uint)0x00000000, BinaryIntegerHelper.TrailingZeroCount((uint)0x7FFFFFFF)); + Assert.Equal((uint)0x0000001F, BinaryIntegerHelper.TrailingZeroCount((uint)0x80000000)); + Assert.Equal((uint)0x00000000, BinaryIntegerHelper.TrailingZeroCount((uint)0xFFFFFFFF)); + } + + [Fact] + public static void IsPow2Test() + { + Assert.False(BinaryNumberHelper.IsPow2((uint)0x00000000)); + Assert.True(BinaryNumberHelper.IsPow2((uint)0x00000001)); + Assert.False(BinaryNumberHelper.IsPow2((uint)0x7FFFFFFF)); + Assert.True(BinaryNumberHelper.IsPow2((uint)0x80000000)); + Assert.False(BinaryNumberHelper.IsPow2((uint)0xFFFFFFFF)); + } + + [Fact] + public static void Log2Test() + { + Assert.Equal((uint)0x00000000, BinaryNumberHelper.Log2((uint)0x00000000)); + Assert.Equal((uint)0x00000000, BinaryNumberHelper.Log2((uint)0x00000001)); + Assert.Equal((uint)0x0000001E, BinaryNumberHelper.Log2((uint)0x7FFFFFFF)); + Assert.Equal((uint)0x0000001F, BinaryNumberHelper.Log2((uint)0x80000000)); + Assert.Equal((uint)0x0000001F, BinaryNumberHelper.Log2((uint)0xFFFFFFFF)); + } + + [Fact] + public static void op_BitwiseAndTest() + { + Assert.Equal((uint)0x00000000, BitwiseOperatorsHelper.op_BitwiseAnd((uint)0x00000000, 1)); + Assert.Equal((uint)0x00000001, BitwiseOperatorsHelper.op_BitwiseAnd((uint)0x00000001, 1)); + Assert.Equal((uint)0x00000001, BitwiseOperatorsHelper.op_BitwiseAnd((uint)0x7FFFFFFF, 1)); + Assert.Equal((uint)0x00000000, BitwiseOperatorsHelper.op_BitwiseAnd((uint)0x80000000, 1)); + Assert.Equal((uint)0x00000001, BitwiseOperatorsHelper.op_BitwiseAnd((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void op_BitwiseOrTest() + { + Assert.Equal((uint)0x00000001, BitwiseOperatorsHelper.op_BitwiseOr((uint)0x00000000, 1)); + Assert.Equal((uint)0x00000001, BitwiseOperatorsHelper.op_BitwiseOr((uint)0x00000001, 1)); + Assert.Equal((uint)0x7FFFFFFF, BitwiseOperatorsHelper.op_BitwiseOr((uint)0x7FFFFFFF, 1)); + Assert.Equal((uint)0x80000001, BitwiseOperatorsHelper.op_BitwiseOr((uint)0x80000000, 1)); + Assert.Equal((uint)0xFFFFFFFF, BitwiseOperatorsHelper.op_BitwiseOr((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void op_ExclusiveOrTest() + { + Assert.Equal((uint)0x00000001, BitwiseOperatorsHelper.op_ExclusiveOr((uint)0x00000000, 1)); + Assert.Equal((uint)0x00000000, BitwiseOperatorsHelper.op_ExclusiveOr((uint)0x00000001, 1)); + Assert.Equal((uint)0x7FFFFFFE, BitwiseOperatorsHelper.op_ExclusiveOr((uint)0x7FFFFFFF, 1)); + Assert.Equal((uint)0x80000001, BitwiseOperatorsHelper.op_ExclusiveOr((uint)0x80000000, 1)); + Assert.Equal((uint)0xFFFFFFFE, BitwiseOperatorsHelper.op_ExclusiveOr((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void op_OnesComplementTest() + { + Assert.Equal((uint)0xFFFFFFFF, BitwiseOperatorsHelper.op_OnesComplement((uint)0x00000000)); + Assert.Equal((uint)0xFFFFFFFE, BitwiseOperatorsHelper.op_OnesComplement((uint)0x00000001)); + Assert.Equal((uint)0x80000000, BitwiseOperatorsHelper.op_OnesComplement((uint)0x7FFFFFFF)); + Assert.Equal((uint)0x7FFFFFFF, BitwiseOperatorsHelper.op_OnesComplement((uint)0x80000000)); + Assert.Equal((uint)0x00000000, BitwiseOperatorsHelper.op_OnesComplement((uint)0xFFFFFFFF)); + } + + [Fact] + public static void op_LessThanTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThan((uint)0x00000000, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((uint)0x00000001, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((uint)0x7FFFFFFF, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((uint)0x80000000, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void op_LessThanOrEqualTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((uint)0x00000000, 1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((uint)0x00000001, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((uint)0x7FFFFFFF, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((uint)0x80000000, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void op_GreaterThanTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((uint)0x00000000, 1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((uint)0x00000001, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((uint)0x7FFFFFFF, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((uint)0x80000000, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void op_GreaterThanOrEqualTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual((uint)0x00000000, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((uint)0x00000001, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((uint)0x7FFFFFFF, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((uint)0x80000000, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void op_DecrementTest() + { + Assert.Equal((uint)0xFFFFFFFF, DecrementOperatorsHelper.op_Decrement((uint)0x00000000)); + Assert.Equal((uint)0x00000000, DecrementOperatorsHelper.op_Decrement((uint)0x00000001)); + Assert.Equal((uint)0x7FFFFFFE, DecrementOperatorsHelper.op_Decrement((uint)0x7FFFFFFF)); + Assert.Equal((uint)0x7FFFFFFF, DecrementOperatorsHelper.op_Decrement((uint)0x80000000)); + Assert.Equal((uint)0xFFFFFFFE, DecrementOperatorsHelper.op_Decrement((uint)0xFFFFFFFF)); + } + + [Fact] + public static void op_DivisionTest() + { + Assert.Equal((uint)0x00000000, DivisionOperatorsHelper.op_Division((uint)0x00000000, 2)); + Assert.Equal((uint)0x00000000, DivisionOperatorsHelper.op_Division((uint)0x00000001, 2)); + Assert.Equal((uint)0x3FFFFFFF, DivisionOperatorsHelper.op_Division((uint)0x7FFFFFFF, 2)); + Assert.Equal((uint)0x40000000, DivisionOperatorsHelper.op_Division((uint)0x80000000, 2)); + Assert.Equal((uint)0x7FFFFFFF, DivisionOperatorsHelper.op_Division((uint)0xFFFFFFFF, 2)); + } + + [Fact] + public static void op_EqualityTest() + { + Assert.False(EqualityOperatorsHelper.op_Equality((uint)0x00000000, 1)); + Assert.True(EqualityOperatorsHelper.op_Equality((uint)0x00000001, 1)); + Assert.False(EqualityOperatorsHelper.op_Equality((uint)0x7FFFFFFF, 1)); + Assert.False(EqualityOperatorsHelper.op_Equality((uint)0x80000000, 1)); + Assert.False(EqualityOperatorsHelper.op_Equality((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void op_InequalityTest() + { + Assert.True(EqualityOperatorsHelper.op_Inequality((uint)0x00000000, 1)); + Assert.False(EqualityOperatorsHelper.op_Inequality((uint)0x00000001, 1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((uint)0x7FFFFFFF, 1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((uint)0x80000000, 1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void op_IncrementTest() + { + Assert.Equal((uint)0x00000001, IncrementOperatorsHelper.op_Increment((uint)0x00000000)); + Assert.Equal((uint)0x00000002, IncrementOperatorsHelper.op_Increment((uint)0x00000001)); + Assert.Equal((uint)0x80000000, IncrementOperatorsHelper.op_Increment((uint)0x7FFFFFFF)); + Assert.Equal((uint)0x80000001, IncrementOperatorsHelper.op_Increment((uint)0x80000000)); + Assert.Equal((uint)0x00000000, IncrementOperatorsHelper.op_Increment((uint)0xFFFFFFFF)); + } + + [Fact] + public static void op_ModulusTest() + { + Assert.Equal((uint)0x00000000, ModulusOperatorsHelper.op_Modulus((uint)0x00000000, 2)); + Assert.Equal((uint)0x00000001, ModulusOperatorsHelper.op_Modulus((uint)0x00000001, 2)); + Assert.Equal((uint)0x00000001, ModulusOperatorsHelper.op_Modulus((uint)0x7FFFFFFF, 2)); + Assert.Equal((uint)0x00000000, ModulusOperatorsHelper.op_Modulus((uint)0x80000000, 2)); + Assert.Equal((uint)0x00000001, ModulusOperatorsHelper.op_Modulus((uint)0xFFFFFFFF, 2)); + } + + [Fact] + public static void op_MultiplyTest() + { + Assert.Equal((uint)0x00000000, MultiplyOperatorsHelper.op_Multiply((uint)0x00000000, 2)); + Assert.Equal((uint)0x00000002, MultiplyOperatorsHelper.op_Multiply((uint)0x00000001, 2)); + Assert.Equal((uint)0xFFFFFFFE, MultiplyOperatorsHelper.op_Multiply((uint)0x7FFFFFFF, 2)); + Assert.Equal((uint)0x00000000, MultiplyOperatorsHelper.op_Multiply((uint)0x80000000, 2)); + Assert.Equal((uint)0xFFFFFFFE, MultiplyOperatorsHelper.op_Multiply((uint)0xFFFFFFFF, 2)); + } + + [Fact] + public static void AbsTest() + { + Assert.Equal((uint)0x00000000, NumberHelper.Abs((uint)0x00000000)); + Assert.Equal((uint)0x00000001, NumberHelper.Abs((uint)0x00000001)); + Assert.Equal((uint)0x7FFFFFFF, NumberHelper.Abs((uint)0x7FFFFFFF)); + Assert.Equal((uint)0x80000000, NumberHelper.Abs((uint)0x80000000)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.Abs((uint)0xFFFFFFFF)); + } + + [Fact] + public static void ClampTest() + { + Assert.Equal((uint)0x00000001, NumberHelper.Clamp((uint)0x00000000, 0x0001, 0x003F)); + Assert.Equal((uint)0x00000001, NumberHelper.Clamp((uint)0x00000001, 0x0001, 0x003F)); + Assert.Equal((uint)0x0000003F, NumberHelper.Clamp((uint)0x7FFFFFFF, 0x0001, 0x003F)); + Assert.Equal((uint)0x0000003F, NumberHelper.Clamp((uint)0x80000000, 0x0001, 0x003F)); + Assert.Equal((uint)0x0000003F, NumberHelper.Clamp((uint)0xFFFFFFFF, 0x0001, 0x003F)); + } + + [Fact] + public static void CreateFromByteTest() + { + Assert.Equal((uint)0x00000000, NumberHelper.Create(0x00)); + Assert.Equal((uint)0x00000001, NumberHelper.Create(0x01)); + Assert.Equal((uint)0x0000007F, NumberHelper.Create(0x7F)); + Assert.Equal((uint)0x00000080, NumberHelper.Create(0x80)); + Assert.Equal((uint)0x000000FF, NumberHelper.Create(0xFF)); + } + + [Fact] + public static void CreateFromCharTest() + { + Assert.Equal((uint)0x00000000, NumberHelper.Create((char)0x0000)); + Assert.Equal((uint)0x00000001, NumberHelper.Create((char)0x0001)); + Assert.Equal((uint)0x00007FFF, NumberHelper.Create((char)0x7FFF)); + Assert.Equal((uint)0x00008000, NumberHelper.Create((char)0x8000)); + Assert.Equal((uint)0x0000FFFF, NumberHelper.Create((char)0xFFFF)); + } + + [Fact] + public static void CreateFromInt16Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.Create(0x0000)); + Assert.Equal((uint)0x00000001, NumberHelper.Create(0x0001)); + Assert.Equal((uint)0x00007FFF, NumberHelper.Create(0x7FFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((short)0x8000))); + Assert.Throws(() => NumberHelper.Create(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateFromInt32Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.Create(0x00000000)); + Assert.Equal((uint)0x00000001, NumberHelper.Create(0x00000001)); + Assert.Equal((uint)0x7FFFFFFF, NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((int)0x80000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateFromInt64Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((uint)0x00000001, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((uint)0x00000000, NumberHelper.Create(unchecked((nint)0x0000000000000000))); + Assert.Equal((uint)0x00000001, NumberHelper.Create(unchecked((nint)0x0000000000000001))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((uint)0x00000000, NumberHelper.Create((nint)0x00000000)); + Assert.Equal((uint)0x00000001, NumberHelper.Create((nint)0x00000001)); + Assert.Equal((uint)0x7FFFFFFF, NumberHelper.Create((nint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x80000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateFromSByteTest() + { + Assert.Equal((uint)0x00000000, NumberHelper.Create(0x00)); + Assert.Equal((uint)0x00000001, NumberHelper.Create(0x01)); + Assert.Equal((uint)0x0000007F, NumberHelper.Create(0x7F)); + Assert.Throws(() => NumberHelper.Create(unchecked((sbyte)0x80))); + Assert.Throws(() => NumberHelper.Create(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateFromUInt16Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.Create(0x0000)); + Assert.Equal((uint)0x00000001, NumberHelper.Create(0x0001)); + Assert.Equal((uint)0x00007FFF, NumberHelper.Create(0x7FFF)); + Assert.Equal((uint)0x00008000, NumberHelper.Create(0x8000)); + Assert.Equal((uint)0x0000FFFF, NumberHelper.Create(0xFFFF)); + } + + [Fact] + public static void CreateFromUInt32Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.Create(0x00000000)); + Assert.Equal((uint)0x00000001, NumberHelper.Create(0x00000001)); + Assert.Equal((uint)0x7FFFFFFF, NumberHelper.Create(0x7FFFFFFF)); + Assert.Equal((uint)0x80000000, NumberHelper.Create(0x80000000)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.Create(0xFFFFFFFF)); + } + + [Fact] + public static void CreateFromUInt64Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((uint)0x00000001, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x8000000000000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((uint)0x00000000, NumberHelper.Create(unchecked((nuint)0x0000000000000000))); + Assert.Equal((uint)0x00000001, NumberHelper.Create(unchecked((nuint)0x0000000000000001))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((uint)0x00000000, NumberHelper.Create((nuint)0x00000000)); + Assert.Equal((uint)0x00000001, NumberHelper.Create((nuint)0x00000001)); + Assert.Equal((uint)0x7FFFFFFF, NumberHelper.Create((nuint)0x7FFFFFFF)); + Assert.Equal((uint)0x80000000, NumberHelper.Create((nuint)0x80000000)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.Create((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateSaturatingFromByteTest() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((uint)0x0000007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((uint)0x00000080, NumberHelper.CreateSaturating(0x80)); + Assert.Equal((uint)0x000000FF, NumberHelper.CreateSaturating(0xFF)); + } + + [Fact] + public static void CreateSaturatingFromCharTest() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating((char)0x0000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateSaturating((char)0x0001)); + Assert.Equal((uint)0x00007FFF, NumberHelper.CreateSaturating((char)0x7FFF)); + Assert.Equal((uint)0x00008000, NumberHelper.CreateSaturating((char)0x8000)); + Assert.Equal((uint)0x0000FFFF, NumberHelper.CreateSaturating((char)0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromInt16Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((uint)0x00007FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(unchecked((short)0x8000))); + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt32Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((uint)0x7FFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(unchecked((int)0x80000000))); + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt64Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(unchecked((long)0x8000000000000000))); + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000000))); + Assert.Equal((uint)0x00000001, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000001))); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateSaturating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(unchecked((nint)0x8000000000000000))); + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating((nint)0x00000000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateSaturating((nint)0x00000001)); + Assert.Equal((uint)0x7FFFFFFF, NumberHelper.CreateSaturating((nint)0x7FFFFFFF)); + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(unchecked((nint)0x80000000))); + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateSaturatingFromSByteTest() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((uint)0x0000007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(unchecked((sbyte)0x80))); + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateSaturatingFromUInt16Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((uint)0x00007FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((uint)0x00008000, NumberHelper.CreateSaturating(0x8000)); + Assert.Equal((uint)0x0000FFFF, NumberHelper.CreateSaturating(0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt32Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((uint)0x7FFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((uint)0x80000000, NumberHelper.CreateSaturating(0x80000000)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateSaturating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt64Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateSaturating(0x8000000000000000)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateSaturating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((uint)0x00000001, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000001))); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0x8000000000000000))); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateSaturating((nuint)0x00000000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateSaturating((nuint)0x00000001)); + Assert.Equal((uint)0x7FFFFFFF, NumberHelper.CreateSaturating((nuint)0x7FFFFFFF)); + Assert.Equal((uint)0x80000000, NumberHelper.CreateSaturating((nuint)0x80000000)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateSaturating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateTruncatingFromByteTest() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((uint)0x0000007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((uint)0x00000080, NumberHelper.CreateTruncating(0x80)); + Assert.Equal((uint)0x000000FF, NumberHelper.CreateTruncating(0xFF)); + } + + [Fact] + public static void CreateTruncatingFromCharTest() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating((char)0x0000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateTruncating((char)0x0001)); + Assert.Equal((uint)0x00007FFF, NumberHelper.CreateTruncating((char)0x7FFF)); + Assert.Equal((uint)0x00008000, NumberHelper.CreateTruncating((char)0x8000)); + Assert.Equal((uint)0x0000FFFF, NumberHelper.CreateTruncating((char)0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromInt16Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((uint)0x00007FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((uint)0xFFFF8000, NumberHelper.CreateTruncating(unchecked((short)0x8000))); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateTruncating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt32Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((uint)0x7FFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((uint)0x80000000, NumberHelper.CreateTruncating(unchecked((int)0x80000000))); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateTruncating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt64Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating(unchecked((long)0x8000000000000000))); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateTruncating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000000))); + Assert.Equal((uint)0x00000001, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000001))); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateTruncating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating(unchecked((nint)0x8000000000000000))); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating((nint)0x00000000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateTruncating((nint)0x00000001)); + Assert.Equal((uint)0x7FFFFFFF, NumberHelper.CreateTruncating((nint)0x7FFFFFFF)); + Assert.Equal((uint)0x80000000, NumberHelper.CreateTruncating(unchecked((nint)0x80000000))); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromSByteTest() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((uint)0x0000007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((uint)0xFFFFFF80, NumberHelper.CreateTruncating(unchecked((sbyte)0x80))); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateTruncating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateTruncatingFromUInt16Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((uint)0x00007FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((uint)0x00008000, NumberHelper.CreateTruncating(0x8000)); + Assert.Equal((uint)0x0000FFFF, NumberHelper.CreateTruncating(0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt32Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((uint)0x7FFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((uint)0x80000000, NumberHelper.CreateTruncating(0x80000000)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateTruncating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt64Test() + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating(0x8000000000000000)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateTruncating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((uint)0x00000001, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000001))); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateTruncating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating(unchecked((nuint)0x8000000000000000))); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateTruncating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((uint)0x00000000, NumberHelper.CreateTruncating((nuint)0x00000000)); + Assert.Equal((uint)0x00000001, NumberHelper.CreateTruncating((nuint)0x00000001)); + Assert.Equal((uint)0x7FFFFFFF, NumberHelper.CreateTruncating((nuint)0x7FFFFFFF)); + Assert.Equal((uint)0x80000000, NumberHelper.CreateTruncating((nuint)0x80000000)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.CreateTruncating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void DivRemTest() + { + Assert.Equal(((uint)0x00000000, (uint)0x00000000), NumberHelper.DivRem((uint)0x00000000, 2)); + Assert.Equal(((uint)0x00000000, (uint)0x00000001), NumberHelper.DivRem((uint)0x00000001, 2)); + Assert.Equal(((uint)0x3FFFFFFF, (uint)0x00000001), NumberHelper.DivRem((uint)0x7FFFFFFF, 2)); + Assert.Equal(((uint)0x40000000, (uint)0x00000000), NumberHelper.DivRem((uint)0x80000000, 2)); + Assert.Equal(((uint)0x7FFFFFFF, (uint)0x00000001), NumberHelper.DivRem((uint)0xFFFFFFFF, 2)); + } + + [Fact] + public static void MaxTest() + { + Assert.Equal((uint)0x00000001, NumberHelper.Max((uint)0x00000000, 1)); + Assert.Equal((uint)0x00000001, NumberHelper.Max((uint)0x00000001, 1)); + Assert.Equal((uint)0x7FFFFFFF, NumberHelper.Max((uint)0x7FFFFFFF, 1)); + Assert.Equal((uint)0x80000000, NumberHelper.Max((uint)0x80000000, 1)); + Assert.Equal((uint)0xFFFFFFFF, NumberHelper.Max((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void MinTest() + { + Assert.Equal((uint)0x00000000, NumberHelper.Min((uint)0x00000000, 1)); + Assert.Equal((uint)0x00000001, NumberHelper.Min((uint)0x00000001, 1)); + Assert.Equal((uint)0x00000001, NumberHelper.Min((uint)0x7FFFFFFF, 1)); + Assert.Equal((uint)0x00000001, NumberHelper.Min((uint)0x80000000, 1)); + Assert.Equal((uint)0x00000001, NumberHelper.Min((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void SignTest() + { + Assert.Equal((uint)0x00000000, NumberHelper.Sign((uint)0x00000000)); + Assert.Equal((uint)0x00000001, NumberHelper.Sign((uint)0x00000001)); + Assert.Equal((uint)0x00000001, NumberHelper.Sign((uint)0x7FFFFFFF)); + Assert.Equal((uint)0x00000001, NumberHelper.Sign((uint)0x80000000)); + Assert.Equal((uint)0x00000001, NumberHelper.Sign((uint)0xFFFFFFFF)); + } + + [Fact] + public static void TryCreateFromByteTest() + { + uint result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((uint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((uint)0x0000007F, result); + + Assert.True(NumberHelper.TryCreate(0x80, out result)); + Assert.Equal((uint)0x00000080, result); + + Assert.True(NumberHelper.TryCreate(0xFF, out result)); + Assert.Equal((uint)0x000000FF, result); + } + + [Fact] + public static void TryCreateFromCharTest() + { + uint result; + + Assert.True(NumberHelper.TryCreate((char)0x0000, out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate((char)0x0001, out result)); + Assert.Equal((uint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate((char)0x7FFF, out result)); + Assert.Equal((uint)0x00007FFF, result); + + Assert.True(NumberHelper.TryCreate((char)0x8000, out result)); + Assert.Equal((uint)0x00008000, result); + + Assert.True(NumberHelper.TryCreate((char)0xFFFF, out result)); + Assert.Equal((uint)0x0000FFFF, result); + } + + [Fact] + public static void TryCreateFromInt16Test() + { + uint result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((uint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((uint)0x00007FFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((short)0x8000), out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((short)0xFFFF), out result)); + Assert.Equal((uint)0x00000000, result); + } + + [Fact] + public static void TryCreateFromInt32Test() + { + uint result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((uint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((uint)0x7FFFFFFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((int)0x80000000), out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((int)0xFFFFFFFF), out result)); + Assert.Equal((uint)0x00000000, result); + } + + [Fact] + public static void TryCreateFromInt64Test() + { + uint result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((uint)0x00000001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0x8000000000000000), out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((uint)0x00000000, result); + } + + [Fact] + public static void TryCreateFromIntPtrTest() + { + uint result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000000), out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000001), out result)); + Assert.Equal((uint)0x00000001, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x8000000000000000), out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((uint)0x00000000, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nint)0x00000000, out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate((nint)0x00000001, out result)); + Assert.Equal((uint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate((nint)0x7FFFFFFF, out result)); + Assert.Equal((uint)0x7FFFFFFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x80000000), out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFF), out result)); + Assert.Equal((uint)0x00000000, result); + } + } + + [Fact] + public static void TryCreateFromSByteTest() + { + uint result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((uint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((uint)0x0000007F, result); + + Assert.False(NumberHelper.TryCreate(unchecked((sbyte)0x80), out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((sbyte)0xFF), out result)); + Assert.Equal((uint)0x00000000, result); + } + + [Fact] + public static void TryCreateFromUInt16Test() + { + uint result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((uint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((uint)0x00007FFF, result); + + Assert.True(NumberHelper.TryCreate(0x8000, out result)); + Assert.Equal((uint)0x00008000, result); + + Assert.True(NumberHelper.TryCreate(0xFFFF, out result)); + Assert.Equal((uint)0x0000FFFF, result); + } + + [Fact] + public static void TryCreateFromUInt32Test() + { + uint result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((uint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((uint)0x7FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(0x80000000, out result)); + Assert.Equal((uint)0x80000000, result); + + Assert.True(NumberHelper.TryCreate(0xFFFFFFFF, out result)); + Assert.Equal((uint)0xFFFFFFFF, result); + } + + [Fact] + public static void TryCreateFromUInt64Test() + { + uint result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((uint)0x00000001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(0x8000000000000000, out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFFFFFFFFFF, out result)); + Assert.Equal((uint)0x00000000, result); + } + + [Fact] + public static void TryCreateFromUIntPtrTest() + { + uint result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000000), out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000001), out result)); + Assert.Equal((uint)0x00000001, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0x8000000000000000), out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((uint)0x00000000, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nuint)0x00000000, out result)); + Assert.Equal((uint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x00000001, out result)); + Assert.Equal((uint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x7FFFFFFF, out result)); + Assert.Equal((uint)0x7FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x80000000), out result)); + Assert.Equal((uint)0x80000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFF), out result)); + Assert.Equal((uint)0xFFFFFFFF, result); + } + } + + [Fact] + + public static void op_LeftShiftTest() + { + Assert.Equal((uint)0x00000000, ShiftOperatorsHelper.op_LeftShift((uint)0x00000000, 1)); + Assert.Equal((uint)0x00000002, ShiftOperatorsHelper.op_LeftShift((uint)0x00000001, 1)); + Assert.Equal((uint)0xFFFFFFFE, ShiftOperatorsHelper.op_LeftShift((uint)0x7FFFFFFF, 1)); + Assert.Equal((uint)0x00000000, ShiftOperatorsHelper.op_LeftShift((uint)0x80000000, 1)); + Assert.Equal((uint)0xFFFFFFFE, ShiftOperatorsHelper.op_LeftShift((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void op_RightShiftTest() + { + Assert.Equal((uint)0x00000000, ShiftOperatorsHelper.op_RightShift((uint)0x00000000, 1)); + Assert.Equal((uint)0x00000000, ShiftOperatorsHelper.op_RightShift((uint)0x00000001, 1)); + Assert.Equal((uint)0x3FFFFFFF, ShiftOperatorsHelper.op_RightShift((uint)0x7FFFFFFF, 1)); + Assert.Equal((uint)0x40000000, ShiftOperatorsHelper.op_RightShift((uint)0x80000000, 1)); + Assert.Equal((uint)0x7FFFFFFF, ShiftOperatorsHelper.op_RightShift((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void op_SubtractionTest() + { + Assert.Equal((uint)0xFFFFFFFF, SubtractionOperatorsHelper.op_Subtraction((uint)0x00000000, 1)); + Assert.Equal((uint)0x00000000, SubtractionOperatorsHelper.op_Subtraction((uint)0x00000001, 1)); + Assert.Equal((uint)0x7FFFFFFE, SubtractionOperatorsHelper.op_Subtraction((uint)0x7FFFFFFF, 1)); + Assert.Equal((uint)0x7FFFFFFF, SubtractionOperatorsHelper.op_Subtraction((uint)0x80000000, 1)); + Assert.Equal((uint)0xFFFFFFFE, SubtractionOperatorsHelper.op_Subtraction((uint)0xFFFFFFFF, 1)); + } + + [Fact] + public static void op_UnaryNegationTest() + { + Assert.Equal((uint)0x00000000, UnaryNegationOperatorsHelper.op_UnaryNegation((uint)0x00000000)); + Assert.Equal((uint)0xFFFFFFFF, UnaryNegationOperatorsHelper.op_UnaryNegation((uint)0x00000001)); + Assert.Equal((uint)0x80000001, UnaryNegationOperatorsHelper.op_UnaryNegation((uint)0x7FFFFFFF)); + Assert.Equal((uint)0x80000000, UnaryNegationOperatorsHelper.op_UnaryNegation((uint)0x80000000)); + Assert.Equal((uint)0x00000001, UnaryNegationOperatorsHelper.op_UnaryNegation((uint)0xFFFFFFFF)); + } + + [Fact] + public static void op_UnaryPlusTest() + { + Assert.Equal((uint)0x00000000, UnaryPlusOperatorsHelper.op_UnaryPlus((uint)0x00000000)); + Assert.Equal((uint)0x00000001, UnaryPlusOperatorsHelper.op_UnaryPlus((uint)0x00000001)); + Assert.Equal((uint)0x7FFFFFFF, UnaryPlusOperatorsHelper.op_UnaryPlus((uint)0x7FFFFFFF)); + Assert.Equal((uint)0x80000000, UnaryPlusOperatorsHelper.op_UnaryPlus((uint)0x80000000)); + Assert.Equal((uint)0xFFFFFFFF, UnaryPlusOperatorsHelper.op_UnaryPlus((uint)0xFFFFFFFF)); + } + + [Theory] + [MemberData(nameof(UInt32Tests.Parse_Valid_TestData), MemberType = typeof(UInt32Tests))] + public static void ParseValidStringTest(string value, NumberStyles style, IFormatProvider provider, uint expected) + { + uint result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.True(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.True(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(UInt32Tests.Parse_Invalid_TestData), MemberType = typeof(UInt32Tests))] + public static void ParseInvalidStringTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + uint result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(default(uint), result); + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.False(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(default(uint), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.False(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(default(uint), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(UInt32Tests.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(UInt32Tests))] + public static void ParseValidSpanTest(string value, int offset, int count, NumberStyles style, IFormatProvider provider, uint expected) + { + uint result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(SpanParseableHelper.TryParse(value.AsSpan(offset, count), provider, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, NumberHelper.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(NumberHelper.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(UInt32Tests.Parse_Invalid_TestData), MemberType = typeof(UInt32Tests))] + public static void ParseInvalidSpanTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value is null) + { + return; + } + + uint result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(SpanParseableHelper.TryParse(value.AsSpan(), provider, out result)); + Assert.Equal(default(uint), result); + } + + Assert.Throws(exceptionType, () => NumberHelper.Parse(value.AsSpan(), style, provider)); + + Assert.False(NumberHelper.TryParse(value.AsSpan(), style, provider, out result)); + Assert.Equal(default(uint), result); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/UInt64Tests.GenericMath.cs b/src/libraries/System.Runtime/tests/System/UInt64Tests.GenericMath.cs new file mode 100644 index 000000000000..1b7ee209a9f4 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/UInt64Tests.GenericMath.cs @@ -0,0 +1,1175 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Runtime.Versioning; +using Xunit; + +namespace System.Tests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [RequiresPreviewFeaturesAttribute] + public class UInt64Tests_GenericMath + { + [Fact] + public static void AdditiveIdentityTest() + { + Assert.Equal((ulong)0x0000000000000000, AdditiveIdentityHelper.AdditiveIdentity); + } + + [Fact] + public static void MinValueTest() + { + Assert.Equal((ulong)0x0000000000000000, MinMaxValueHelper.MinValue); + } + + [Fact] + public static void MaxValueTest() + { + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, MinMaxValueHelper.MaxValue); + } + + [Fact] + public static void MultiplicativeIdentityTest() + { + Assert.Equal((ulong)0x0000000000000001, MultiplicativeIdentityHelper.MultiplicativeIdentity); + } + + [Fact] + public static void OneTest() + { + Assert.Equal((ulong)0x0000000000000001, NumberHelper.One); + } + + [Fact] + public static void ZeroTest() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Zero); + } + + [Fact] + public static void op_AdditionTest() + { + Assert.Equal((ulong)0x0000000000000001, AdditionOperatorsHelper.op_Addition((ulong)0x0000000000000000, 1)); + Assert.Equal((ulong)0x0000000000000002, AdditionOperatorsHelper.op_Addition((ulong)0x0000000000000001, 1)); + Assert.Equal((ulong)0x8000000000000000, AdditionOperatorsHelper.op_Addition((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((ulong)0x8000000000000001, AdditionOperatorsHelper.op_Addition((ulong)0x8000000000000000, 1)); + Assert.Equal((ulong)0x0000000000000000, AdditionOperatorsHelper.op_Addition((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void LeadingZeroCountTest() + { + Assert.Equal((ulong)0x0000000000000040, BinaryIntegerHelper.LeadingZeroCount((ulong)0x0000000000000000)); + Assert.Equal((ulong)0x000000000000003F, BinaryIntegerHelper.LeadingZeroCount((ulong)0x0000000000000001)); + Assert.Equal((ulong)0x0000000000000001, BinaryIntegerHelper.LeadingZeroCount((ulong)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x0000000000000000, BinaryIntegerHelper.LeadingZeroCount((ulong)0x8000000000000000)); + Assert.Equal((ulong)0x0000000000000000, BinaryIntegerHelper.LeadingZeroCount((ulong)0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void PopCountTest() + { + Assert.Equal((ulong)0x0000000000000000, BinaryIntegerHelper.PopCount((ulong)0x0000000000000000)); + Assert.Equal((ulong)0x0000000000000001, BinaryIntegerHelper.PopCount((ulong)0x0000000000000001)); + Assert.Equal((ulong)0x000000000000003F, BinaryIntegerHelper.PopCount((ulong)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x0000000000000001, BinaryIntegerHelper.PopCount((ulong)0x8000000000000000)); + Assert.Equal((ulong)0x0000000000000040, BinaryIntegerHelper.PopCount((ulong)0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void RotateLeftTest() + { + Assert.Equal((ulong)0x0000000000000000, BinaryIntegerHelper.RotateLeft((ulong)0x0000000000000000, 1)); + Assert.Equal((ulong)0x0000000000000002, BinaryIntegerHelper.RotateLeft((ulong)0x0000000000000001, 1)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFE, BinaryIntegerHelper.RotateLeft((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((ulong)0x0000000000000001, BinaryIntegerHelper.RotateLeft((ulong)0x8000000000000000, 1)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, BinaryIntegerHelper.RotateLeft((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void RotateRightTest() + { + Assert.Equal((ulong)0x0000000000000000, BinaryIntegerHelper.RotateRight((ulong)0x0000000000000000, 1)); + Assert.Equal((ulong)0x8000000000000000, BinaryIntegerHelper.RotateRight((ulong)0x0000000000000001, 1)); + Assert.Equal((ulong)0xBFFFFFFFFFFFFFFF, BinaryIntegerHelper.RotateRight((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((ulong)0x4000000000000000, BinaryIntegerHelper.RotateRight((ulong)0x8000000000000000, 1)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, BinaryIntegerHelper.RotateRight((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void TrailingZeroCountTest() + { + Assert.Equal((ulong)0x0000000000000040, BinaryIntegerHelper.TrailingZeroCount((ulong)0x0000000000000000)); + Assert.Equal((ulong)0x0000000000000000, BinaryIntegerHelper.TrailingZeroCount((ulong)0x0000000000000001)); + Assert.Equal((ulong)0x0000000000000000, BinaryIntegerHelper.TrailingZeroCount((ulong)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x000000000000003F, BinaryIntegerHelper.TrailingZeroCount((ulong)0x8000000000000000)); + Assert.Equal((ulong)0x0000000000000000, BinaryIntegerHelper.TrailingZeroCount((ulong)0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void IsPow2Test() + { + Assert.False(BinaryNumberHelper.IsPow2((ulong)0x0000000000000000)); + Assert.True(BinaryNumberHelper.IsPow2((ulong)0x0000000000000001)); + Assert.False(BinaryNumberHelper.IsPow2((ulong)0x7FFFFFFFFFFFFFFF)); + Assert.True(BinaryNumberHelper.IsPow2((ulong)0x8000000000000000)); + Assert.False(BinaryNumberHelper.IsPow2((ulong)0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void Log2Test() + { + Assert.Equal((ulong)0x0000000000000000, BinaryNumberHelper.Log2((ulong)0x0000000000000000)); + Assert.Equal((ulong)0x0000000000000000, BinaryNumberHelper.Log2((ulong)0x0000000000000001)); + Assert.Equal((ulong)0x000000000000003E, BinaryNumberHelper.Log2((ulong)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x000000000000003F, BinaryNumberHelper.Log2((ulong)0x8000000000000000)); + Assert.Equal((ulong)0x000000000000003F, BinaryNumberHelper.Log2((ulong)0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void op_BitwiseAndTest() + { + Assert.Equal((ulong)0x0000000000000000, BitwiseOperatorsHelper.op_BitwiseAnd((ulong)0x0000000000000000, 1)); + Assert.Equal((ulong)0x0000000000000001, BitwiseOperatorsHelper.op_BitwiseAnd((ulong)0x0000000000000001, 1)); + Assert.Equal((ulong)0x0000000000000001, BitwiseOperatorsHelper.op_BitwiseAnd((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((ulong)0x0000000000000000, BitwiseOperatorsHelper.op_BitwiseAnd((ulong)0x8000000000000000, 1)); + Assert.Equal((ulong)0x0000000000000001, BitwiseOperatorsHelper.op_BitwiseAnd((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void op_BitwiseOrTest() + { + Assert.Equal((ulong)0x0000000000000001, BitwiseOperatorsHelper.op_BitwiseOr((ulong)0x0000000000000000, 1)); + Assert.Equal((ulong)0x0000000000000001, BitwiseOperatorsHelper.op_BitwiseOr((ulong)0x0000000000000001, 1)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, BitwiseOperatorsHelper.op_BitwiseOr((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((ulong)0x8000000000000001, BitwiseOperatorsHelper.op_BitwiseOr((ulong)0x8000000000000000, 1)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, BitwiseOperatorsHelper.op_BitwiseOr((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void op_ExclusiveOrTest() + { + Assert.Equal((ulong)0x0000000000000001, BitwiseOperatorsHelper.op_ExclusiveOr((ulong)0x0000000000000000, 1)); + Assert.Equal((ulong)0x0000000000000000, BitwiseOperatorsHelper.op_ExclusiveOr((ulong)0x0000000000000001, 1)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFE, BitwiseOperatorsHelper.op_ExclusiveOr((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((ulong)0x8000000000000001, BitwiseOperatorsHelper.op_ExclusiveOr((ulong)0x8000000000000000, 1)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFE, BitwiseOperatorsHelper.op_ExclusiveOr((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void op_OnesComplementTest() + { + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, BitwiseOperatorsHelper.op_OnesComplement((ulong)0x0000000000000000)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFE, BitwiseOperatorsHelper.op_OnesComplement((ulong)0x0000000000000001)); + Assert.Equal((ulong)0x8000000000000000, BitwiseOperatorsHelper.op_OnesComplement((ulong)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, BitwiseOperatorsHelper.op_OnesComplement((ulong)0x8000000000000000)); + Assert.Equal((ulong)0x0000000000000000, BitwiseOperatorsHelper.op_OnesComplement((ulong)0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void op_LessThanTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThan((ulong)0x0000000000000000, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((ulong)0x0000000000000001, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((ulong)0x8000000000000000, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void op_LessThanOrEqualTest() + { + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((ulong)0x0000000000000000, 1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((ulong)0x0000000000000001, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((ulong)0x8000000000000000, 1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void op_GreaterThanTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((ulong)0x0000000000000000, 1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((ulong)0x0000000000000001, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((ulong)0x8000000000000000, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void op_GreaterThanOrEqualTest() + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual((ulong)0x0000000000000000, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((ulong)0x0000000000000001, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((ulong)0x8000000000000000, 1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void op_DecrementTest() + { + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, DecrementOperatorsHelper.op_Decrement((ulong)0x0000000000000000)); + Assert.Equal((ulong)0x0000000000000000, DecrementOperatorsHelper.op_Decrement((ulong)0x0000000000000001)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFE, DecrementOperatorsHelper.op_Decrement((ulong)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, DecrementOperatorsHelper.op_Decrement((ulong)0x8000000000000000)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFE, DecrementOperatorsHelper.op_Decrement((ulong)0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void op_DivisionTest() + { + Assert.Equal((ulong)0x0000000000000000, DivisionOperatorsHelper.op_Division((ulong)0x0000000000000000, 2)); + Assert.Equal((ulong)0x0000000000000000, DivisionOperatorsHelper.op_Division((ulong)0x0000000000000001, 2)); + Assert.Equal((ulong)0x3FFFFFFFFFFFFFFF, DivisionOperatorsHelper.op_Division((ulong)0x7FFFFFFFFFFFFFFF, 2)); + Assert.Equal((ulong)0x4000000000000000, DivisionOperatorsHelper.op_Division((ulong)0x8000000000000000, 2)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, DivisionOperatorsHelper.op_Division((ulong)0xFFFFFFFFFFFFFFFF, 2)); + } + + [Fact] + public static void op_EqualityTest() + { + Assert.False(EqualityOperatorsHelper.op_Equality((ulong)0x0000000000000000, 1)); + Assert.True(EqualityOperatorsHelper.op_Equality((ulong)0x0000000000000001, 1)); + Assert.False(EqualityOperatorsHelper.op_Equality((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.False(EqualityOperatorsHelper.op_Equality((ulong)0x8000000000000000, 1)); + Assert.False(EqualityOperatorsHelper.op_Equality((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void op_InequalityTest() + { + Assert.True(EqualityOperatorsHelper.op_Inequality((ulong)0x0000000000000000, 1)); + Assert.False(EqualityOperatorsHelper.op_Inequality((ulong)0x0000000000000001, 1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((ulong)0x8000000000000000, 1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void op_IncrementTest() + { + Assert.Equal((ulong)0x0000000000000001, IncrementOperatorsHelper.op_Increment((ulong)0x0000000000000000)); + Assert.Equal((ulong)0x0000000000000002, IncrementOperatorsHelper.op_Increment((ulong)0x0000000000000001)); + Assert.Equal((ulong)0x8000000000000000, IncrementOperatorsHelper.op_Increment((ulong)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x8000000000000001, IncrementOperatorsHelper.op_Increment((ulong)0x8000000000000000)); + Assert.Equal((ulong)0x0000000000000000, IncrementOperatorsHelper.op_Increment((ulong)0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void op_ModulusTest() + { + Assert.Equal((ulong)0x0000000000000000, ModulusOperatorsHelper.op_Modulus((ulong)0x0000000000000000, 2)); + Assert.Equal((ulong)0x0000000000000001, ModulusOperatorsHelper.op_Modulus((ulong)0x0000000000000001, 2)); + Assert.Equal((ulong)0x0000000000000001, ModulusOperatorsHelper.op_Modulus((ulong)0x7FFFFFFFFFFFFFFF, 2)); + Assert.Equal((ulong)0x0000000000000000, ModulusOperatorsHelper.op_Modulus((ulong)0x8000000000000000, 2)); + Assert.Equal((ulong)0x0000000000000001, ModulusOperatorsHelper.op_Modulus((ulong)0xFFFFFFFFFFFFFFFF, 2)); + } + + [Fact] + public static void op_MultiplyTest() + { + Assert.Equal((ulong)0x0000000000000000, MultiplyOperatorsHelper.op_Multiply((ulong)0x0000000000000000, 2)); + Assert.Equal((ulong)0x0000000000000002, MultiplyOperatorsHelper.op_Multiply((ulong)0x0000000000000001, 2)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFE, MultiplyOperatorsHelper.op_Multiply((ulong)0x7FFFFFFFFFFFFFFF, 2)); + Assert.Equal((ulong)0x0000000000000000, MultiplyOperatorsHelper.op_Multiply((ulong)0x8000000000000000, 2)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFE, MultiplyOperatorsHelper.op_Multiply((ulong)0xFFFFFFFFFFFFFFFF, 2)); + } + + [Fact] + public static void AbsTest() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Abs((ulong)0x0000000000000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Abs((ulong)0x0000000000000001)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, NumberHelper.Abs((ulong)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x8000000000000000, NumberHelper.Abs((ulong)0x8000000000000000)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, NumberHelper.Abs((ulong)0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void ClampTest() + { + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Clamp((ulong)0x0000000000000000, 0x0001, 0x003F)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Clamp((ulong)0x0000000000000001, 0x0001, 0x003F)); + Assert.Equal((ulong)0x000000000000003F, NumberHelper.Clamp((ulong)0x7FFFFFFFFFFFFFFF, 0x0001, 0x003F)); + Assert.Equal((ulong)0x000000000000003F, NumberHelper.Clamp((ulong)0x8000000000000000, 0x0001, 0x003F)); + Assert.Equal((ulong)0x000000000000003F, NumberHelper.Clamp((ulong)0xFFFFFFFFFFFFFFFF, 0x0001, 0x003F)); + } + + [Fact] + public static void CreateFromByteTest() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Create(0x00)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Create(0x01)); + Assert.Equal((ulong)0x000000000000007F, NumberHelper.Create(0x7F)); + Assert.Equal((ulong)0x0000000000000080, NumberHelper.Create(0x80)); + Assert.Equal((ulong)0x00000000000000FF, NumberHelper.Create(0xFF)); + } + + [Fact] + public static void CreateFromCharTest() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Create((char)0x0000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Create((char)0x0001)); + Assert.Equal((ulong)0x0000000000007FFF, NumberHelper.Create((char)0x7FFF)); + Assert.Equal((ulong)0x0000000000008000, NumberHelper.Create((char)0x8000)); + Assert.Equal((ulong)0x000000000000FFFF, NumberHelper.Create((char)0xFFFF)); + } + + [Fact] + public static void CreateFromInt16Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Create(0x0000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Create(0x0001)); + Assert.Equal((ulong)0x0000000000007FFF, NumberHelper.Create(0x7FFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((short)0x8000))); + Assert.Throws(() => NumberHelper.Create(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateFromInt32Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Create(0x00000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Create(0x00000001)); + Assert.Equal((ulong)0x000000007FFFFFFF, NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((int)0x80000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateFromInt64Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Create(0x0000000000000001)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Create(unchecked((nint)0x0000000000000000))); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Create(unchecked((nint)0x0000000000000001))); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, NumberHelper.Create(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Create((nint)0x00000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Create((nint)0x00000001)); + Assert.Equal((ulong)0x000000007FFFFFFF, NumberHelper.Create((nint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x80000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateFromSByteTest() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Create(0x00)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Create(0x01)); + Assert.Equal((ulong)0x000000000000007F, NumberHelper.Create(0x7F)); + Assert.Throws(() => NumberHelper.Create(unchecked((sbyte)0x80))); + Assert.Throws(() => NumberHelper.Create(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateFromUInt16Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Create(0x0000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Create(0x0001)); + Assert.Equal((ulong)0x0000000000007FFF, NumberHelper.Create(0x7FFF)); + Assert.Equal((ulong)0x0000000000008000, NumberHelper.Create(0x8000)); + Assert.Equal((ulong)0x000000000000FFFF, NumberHelper.Create(0xFFFF)); + } + + [Fact] + public static void CreateFromUInt32Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Create(0x00000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Create(0x00000001)); + Assert.Equal((ulong)0x000000007FFFFFFF, NumberHelper.Create(0x7FFFFFFF)); + Assert.Equal((ulong)0x0000000080000000, NumberHelper.Create(0x80000000)); + Assert.Equal((ulong)0x00000000FFFFFFFF, NumberHelper.Create(0xFFFFFFFF)); + } + + [Fact] + public static void CreateFromUInt64Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Create(0x0000000000000001)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x8000000000000000, NumberHelper.Create(0x8000000000000000)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, NumberHelper.Create(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Create(unchecked((nuint)0x0000000000000000))); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Create(unchecked((nuint)0x0000000000000001))); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, NumberHelper.Create(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((ulong)0x8000000000000000, NumberHelper.Create(unchecked((nuint)0x8000000000000000))); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, NumberHelper.Create(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Create((nuint)0x00000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Create((nuint)0x00000001)); + Assert.Equal((ulong)0x000000007FFFFFFF, NumberHelper.Create((nuint)0x7FFFFFFF)); + Assert.Equal((ulong)0x0000000080000000, NumberHelper.Create((nuint)0x80000000)); + Assert.Equal((ulong)0x00000000FFFFFFFF, NumberHelper.Create((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateSaturatingFromByteTest() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((ulong)0x000000000000007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((ulong)0x0000000000000080, NumberHelper.CreateSaturating(0x80)); + Assert.Equal((ulong)0x00000000000000FF, NumberHelper.CreateSaturating(0xFF)); + } + + [Fact] + public static void CreateSaturatingFromCharTest() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating((char)0x0000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateSaturating((char)0x0001)); + Assert.Equal((ulong)0x0000000000007FFF, NumberHelper.CreateSaturating((char)0x7FFF)); + Assert.Equal((ulong)0x0000000000008000, NumberHelper.CreateSaturating((char)0x8000)); + Assert.Equal((ulong)0x000000000000FFFF, NumberHelper.CreateSaturating((char)0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromInt16Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((ulong)0x0000000000007FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((short)0x8000))); + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt32Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((ulong)0x000000007FFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((int)0x80000000))); + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt64Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((long)0x8000000000000000))); + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000000))); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000001))); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateSaturating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((nint)0x8000000000000000))); + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating((nint)0x00000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateSaturating((nint)0x00000001)); + Assert.Equal((ulong)0x000000007FFFFFFF, NumberHelper.CreateSaturating((nint)0x7FFFFFFF)); + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((nint)0x80000000))); + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateSaturatingFromSByteTest() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((ulong)0x000000000000007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((sbyte)0x80))); + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateSaturatingFromUInt16Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((ulong)0x0000000000007FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((ulong)0x0000000000008000, NumberHelper.CreateSaturating(0x8000)); + Assert.Equal((ulong)0x000000000000FFFF, NumberHelper.CreateSaturating(0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt32Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((ulong)0x000000007FFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((ulong)0x0000000080000000, NumberHelper.CreateSaturating(0x80000000)); + Assert.Equal((ulong)0x00000000FFFFFFFF, NumberHelper.CreateSaturating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt64Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x8000000000000000, NumberHelper.CreateSaturating(0x8000000000000000)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, NumberHelper.CreateSaturating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000001))); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((ulong)0x8000000000000000, NumberHelper.CreateSaturating(unchecked((nuint)0x8000000000000000))); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, NumberHelper.CreateSaturating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateSaturating((nuint)0x00000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateSaturating((nuint)0x00000001)); + Assert.Equal((ulong)0x000000007FFFFFFF, NumberHelper.CreateSaturating((nuint)0x7FFFFFFF)); + Assert.Equal((ulong)0x0000000080000000, NumberHelper.CreateSaturating((nuint)0x80000000)); + Assert.Equal((ulong)0x00000000FFFFFFFF, NumberHelper.CreateSaturating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateTruncatingFromByteTest() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((ulong)0x000000000000007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((ulong)0x0000000000000080, NumberHelper.CreateTruncating(0x80)); + Assert.Equal((ulong)0x00000000000000FF, NumberHelper.CreateTruncating(0xFF)); + } + + [Fact] + public static void CreateTruncatingFromCharTest() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateTruncating((char)0x0000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateTruncating((char)0x0001)); + Assert.Equal((ulong)0x0000000000007FFF, NumberHelper.CreateTruncating((char)0x7FFF)); + Assert.Equal((ulong)0x0000000000008000, NumberHelper.CreateTruncating((char)0x8000)); + Assert.Equal((ulong)0x000000000000FFFF, NumberHelper.CreateTruncating((char)0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromInt16Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((ulong)0x0000000000007FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((ulong)0xFFFFFFFFFFFF8000, NumberHelper.CreateTruncating(unchecked((short)0x8000))); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt32Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((ulong)0x000000007FFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((ulong)0xFFFFFFFF80000000, NumberHelper.CreateTruncating(unchecked((int)0x80000000))); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromInt64Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x8000000000000000, NumberHelper.CreateTruncating(unchecked((long)0x8000000000000000))); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + + [Fact] + public static void CreateTruncatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000000))); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000001))); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((ulong)0x8000000000000000, NumberHelper.CreateTruncating(unchecked((nint)0x8000000000000000))); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateTruncating((nint)0x00000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateTruncating((nint)0x00000001)); + Assert.Equal((ulong)0x000000007FFFFFFF, NumberHelper.CreateTruncating((nint)0x7FFFFFFF)); + Assert.Equal((ulong)0xFFFFFFFF80000000, NumberHelper.CreateTruncating(unchecked((nint)0x80000000))); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromSByteTest() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((ulong)0x000000000000007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFF80, NumberHelper.CreateTruncating(unchecked((sbyte)0x80))); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateTruncatingFromUInt16Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((ulong)0x0000000000007FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((ulong)0x0000000000008000, NumberHelper.CreateTruncating(0x8000)); + Assert.Equal((ulong)0x000000000000FFFF, NumberHelper.CreateTruncating(0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt32Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((ulong)0x000000007FFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((ulong)0x0000000080000000, NumberHelper.CreateTruncating(0x80000000)); + Assert.Equal((ulong)0x00000000FFFFFFFF, NumberHelper.CreateTruncating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt64Test() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x8000000000000000, NumberHelper.CreateTruncating(0x8000000000000000)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000000))); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000001))); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal((ulong)0x8000000000000000, NumberHelper.CreateTruncating(unchecked((nuint)0x8000000000000000))); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, NumberHelper.CreateTruncating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.CreateTruncating((nuint)0x00000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.CreateTruncating((nuint)0x00000001)); + Assert.Equal((ulong)0x000000007FFFFFFF, NumberHelper.CreateTruncating((nuint)0x7FFFFFFF)); + Assert.Equal((ulong)0x0000000080000000, NumberHelper.CreateTruncating((nuint)0x80000000)); + Assert.Equal((ulong)0x00000000FFFFFFFF, NumberHelper.CreateTruncating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void DivRemTest() + { + Assert.Equal(((ulong)0x0000000000000000, (ulong)0x0000000000000000), NumberHelper.DivRem((ulong)0x0000000000000000, 2)); + Assert.Equal(((ulong)0x0000000000000000, (ulong)0x0000000000000001), NumberHelper.DivRem((ulong)0x0000000000000001, 2)); + Assert.Equal(((ulong)0x3FFFFFFFFFFFFFFF, (ulong)0x0000000000000001), NumberHelper.DivRem((ulong)0x7FFFFFFFFFFFFFFF, 2)); + Assert.Equal(((ulong)0x4000000000000000, (ulong)0x0000000000000000), NumberHelper.DivRem((ulong)0x8000000000000000, 2)); + Assert.Equal(((ulong)0x7FFFFFFFFFFFFFFF, (ulong)0x0000000000000001), NumberHelper.DivRem((ulong)0xFFFFFFFFFFFFFFFF, 2)); + } + + [Fact] + public static void MaxTest() + { + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Max((ulong)0x0000000000000000, 1)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Max((ulong)0x0000000000000001, 1)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, NumberHelper.Max((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((ulong)0x8000000000000000, NumberHelper.Max((ulong)0x8000000000000000, 1)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, NumberHelper.Max((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void MinTest() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Min((ulong)0x0000000000000000, 1)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Min((ulong)0x0000000000000001, 1)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Min((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Min((ulong)0x8000000000000000, 1)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Min((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void SignTest() + { + Assert.Equal((ulong)0x0000000000000000, NumberHelper.Sign((ulong)0x0000000000000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Sign((ulong)0x0000000000000001)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Sign((ulong)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Sign((ulong)0x8000000000000000)); + Assert.Equal((ulong)0x0000000000000001, NumberHelper.Sign((ulong)0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void TryCreateFromByteTest() + { + ulong result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((ulong)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((ulong)0x000000000000007F, result); + + Assert.True(NumberHelper.TryCreate(0x80, out result)); + Assert.Equal((ulong)0x0000000000000080, result); + + Assert.True(NumberHelper.TryCreate(0xFF, out result)); + Assert.Equal((ulong)0x00000000000000FF, result); + } + + [Fact] + public static void TryCreateFromCharTest() + { + ulong result; + + Assert.True(NumberHelper.TryCreate((char)0x0000, out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate((char)0x0001, out result)); + Assert.Equal((ulong)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate((char)0x7FFF, out result)); + Assert.Equal((ulong)0x0000000000007FFF, result); + + Assert.True(NumberHelper.TryCreate((char)0x8000, out result)); + Assert.Equal((ulong)0x0000000000008000, result); + + Assert.True(NumberHelper.TryCreate((char)0xFFFF, out result)); + Assert.Equal((ulong)0x000000000000FFFF, result); + } + + [Fact] + public static void TryCreateFromInt16Test() + { + ulong result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((ulong)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((ulong)0x0000000000007FFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((short)0x8000), out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((short)0xFFFF), out result)); + Assert.Equal((ulong)0x0000000000000000, result); + } + + [Fact] + public static void TryCreateFromInt32Test() + { + ulong result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((ulong)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((ulong)0x000000007FFFFFFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((int)0x80000000), out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((int)0xFFFFFFFF), out result)); + Assert.Equal((ulong)0x0000000000000000, result); + } + + [Fact] + public static void TryCreateFromInt64Test() + { + ulong result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((ulong)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0x8000000000000000), out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((ulong)0x0000000000000000, result); + } + + [Fact] + public static void TryCreateFromIntPtrTest() + { + ulong result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000000), out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000001), out result)); + Assert.Equal((ulong)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x8000000000000000), out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((ulong)0x0000000000000000, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nint)0x00000000, out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate((nint)0x00000001, out result)); + Assert.Equal((ulong)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate((nint)0x7FFFFFFF, out result)); + Assert.Equal((ulong)0x000000007FFFFFFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x80000000), out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFF), out result)); + Assert.Equal((ulong)0x0000000000000000, result); + } + } + + [Fact] + public static void TryCreateFromSByteTest() + { + ulong result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((ulong)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((ulong)0x000000000000007F, result); + + Assert.False(NumberHelper.TryCreate(unchecked((sbyte)0x80), out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((sbyte)0xFF), out result)); + Assert.Equal((ulong)0x0000000000000000, result); + } + + [Fact] + public static void TryCreateFromUInt16Test() + { + ulong result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((ulong)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((ulong)0x0000000000007FFF, result); + + Assert.True(NumberHelper.TryCreate(0x8000, out result)); + Assert.Equal((ulong)0x0000000000008000, result); + + Assert.True(NumberHelper.TryCreate(0xFFFF, out result)); + Assert.Equal((ulong)0x000000000000FFFF, result); + } + + [Fact] + public static void TryCreateFromUInt32Test() + { + ulong result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((ulong)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((ulong)0x000000007FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(0x80000000, out result)); + Assert.Equal((ulong)0x0000000080000000, result); + + Assert.True(NumberHelper.TryCreate(0xFFFFFFFF, out result)); + Assert.Equal((ulong)0x00000000FFFFFFFF, result); + } + + [Fact] + public static void TryCreateFromUInt64Test() + { + ulong result; + + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((ulong)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(0x8000000000000000, out result)); + Assert.Equal((ulong)0x8000000000000000, result); + + Assert.True(NumberHelper.TryCreate(0xFFFFFFFFFFFFFFFF, out result)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, result); + } + + [Fact] + public static void TryCreateFromUIntPtrTest() + { + ulong result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000000), out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000001), out result)); + Assert.Equal((ulong)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x8000000000000000), out result)); + Assert.Equal((ulong)0x8000000000000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, result); + } + else + { + Assert.True(NumberHelper.TryCreate((nuint)0x00000000, out result)); + Assert.Equal((ulong)0x0000000000000000, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x00000001, out result)); + Assert.Equal((ulong)0x0000000000000001, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x7FFFFFFF, out result)); + Assert.Equal((ulong)0x000000007FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x80000000), out result)); + Assert.Equal((ulong)0x0000000080000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFF), out result)); + Assert.Equal((ulong)0x00000000FFFFFFFF, result); + } + } + + [Fact] + + public static void op_LeftShiftTest() + { + Assert.Equal((ulong)0x0000000000000000, ShiftOperatorsHelper.op_LeftShift((ulong)0x0000000000000000, 1)); + Assert.Equal((ulong)0x0000000000000002, ShiftOperatorsHelper.op_LeftShift((ulong)0x0000000000000001, 1)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFE, ShiftOperatorsHelper.op_LeftShift((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((ulong)0x0000000000000000, ShiftOperatorsHelper.op_LeftShift((ulong)0x8000000000000000, 1)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFE, ShiftOperatorsHelper.op_LeftShift((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void op_RightShiftTest() + { + Assert.Equal((ulong)0x0000000000000000, ShiftOperatorsHelper.op_RightShift((ulong)0x0000000000000000, 1)); + Assert.Equal((ulong)0x0000000000000000, ShiftOperatorsHelper.op_RightShift((ulong)0x0000000000000001, 1)); + Assert.Equal((ulong)0x3FFFFFFFFFFFFFFF, ShiftOperatorsHelper.op_RightShift((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((ulong)0x4000000000000000, ShiftOperatorsHelper.op_RightShift((ulong)0x8000000000000000, 1)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, ShiftOperatorsHelper.op_RightShift((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void op_SubtractionTest() + { + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, SubtractionOperatorsHelper.op_Subtraction((ulong)0x0000000000000000, 1)); + Assert.Equal((ulong)0x0000000000000000, SubtractionOperatorsHelper.op_Subtraction((ulong)0x0000000000000001, 1)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFE, SubtractionOperatorsHelper.op_Subtraction((ulong)0x7FFFFFFFFFFFFFFF, 1)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, SubtractionOperatorsHelper.op_Subtraction((ulong)0x8000000000000000, 1)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFE, SubtractionOperatorsHelper.op_Subtraction((ulong)0xFFFFFFFFFFFFFFFF, 1)); + } + + [Fact] + public static void op_UnaryNegationTest() + { + Assert.Equal((ulong)0x0000000000000000, UnaryNegationOperatorsHelper.op_UnaryNegation((ulong)0x0000000000000000)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, UnaryNegationOperatorsHelper.op_UnaryNegation((ulong)0x0000000000000001)); + Assert.Equal((ulong)0x8000000000000001, UnaryNegationOperatorsHelper.op_UnaryNegation((ulong)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x8000000000000000, UnaryNegationOperatorsHelper.op_UnaryNegation((ulong)0x8000000000000000)); + Assert.Equal((ulong)0x0000000000000001, UnaryNegationOperatorsHelper.op_UnaryNegation((ulong)0xFFFFFFFFFFFFFFFF)); + } + + [Fact] + public static void op_UnaryPlusTest() + { + Assert.Equal((ulong)0x0000000000000000, UnaryPlusOperatorsHelper.op_UnaryPlus((ulong)0x0000000000000000)); + Assert.Equal((ulong)0x0000000000000001, UnaryPlusOperatorsHelper.op_UnaryPlus((ulong)0x0000000000000001)); + Assert.Equal((ulong)0x7FFFFFFFFFFFFFFF, UnaryPlusOperatorsHelper.op_UnaryPlus((ulong)0x7FFFFFFFFFFFFFFF)); + Assert.Equal((ulong)0x8000000000000000, UnaryPlusOperatorsHelper.op_UnaryPlus((ulong)0x8000000000000000)); + Assert.Equal((ulong)0xFFFFFFFFFFFFFFFF, UnaryPlusOperatorsHelper.op_UnaryPlus((ulong)0xFFFFFFFFFFFFFFFF)); + } + + [Theory] + [MemberData(nameof(UInt64Tests.Parse_Valid_TestData), MemberType = typeof(UInt64Tests))] + public static void ParseValidStringTest(string value, NumberStyles style, IFormatProvider provider, ulong expected) + { + ulong result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.True(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.True(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(UInt64Tests.Parse_Invalid_TestData), MemberType = typeof(UInt64Tests))] + public static void ParseInvalidStringTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + ulong result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(default(ulong), result); + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.False(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(default(ulong), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.False(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(default(ulong), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(UInt64Tests.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(UInt64Tests))] + public static void ParseValidSpanTest(string value, int offset, int count, NumberStyles style, IFormatProvider provider, ulong expected) + { + ulong result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(SpanParseableHelper.TryParse(value.AsSpan(offset, count), provider, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, NumberHelper.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(NumberHelper.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(UInt64Tests.Parse_Invalid_TestData), MemberType = typeof(UInt64Tests))] + public static void ParseInvalidSpanTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value is null) + { + return; + } + + ulong result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(SpanParseableHelper.TryParse(value.AsSpan(), provider, out result)); + Assert.Equal(default(ulong), result); + } + + Assert.Throws(exceptionType, () => NumberHelper.Parse(value.AsSpan(), style, provider)); + + Assert.False(NumberHelper.TryParse(value.AsSpan(), style, provider, out result)); + Assert.Equal(default(ulong), result); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/UIntPtrTests.GenericMath.cs b/src/libraries/System.Runtime/tests/System/UIntPtrTests.GenericMath.cs new file mode 100644 index 000000000000..8e0c92c2a82c --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/UIntPtrTests.GenericMath.cs @@ -0,0 +1,1695 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Runtime.Versioning; +using Xunit; + +namespace System.Tests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [RequiresPreviewFeaturesAttribute] + public class UIntPtrTests_GenericMath + { + [Fact] + public static void AdditiveIdentityTest() + { + Assert.Equal((nuint)0x00000000, AdditiveIdentityHelper.AdditiveIdentity); + } + + [Fact] + public static void MinValueTest() + { + Assert.Equal((nuint)0x00000000, MinMaxValueHelper.MinValue); + } + + [Fact] + public static void MaxValueTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), MinMaxValueHelper.MaxValue); + } + else + { + Assert.Equal((nuint)0xFFFFFFFF, MinMaxValueHelper.MaxValue); + } + } + + [Fact] + public static void MultiplicativeIdentityTest() + { + Assert.Equal((nuint)0x00000001, MultiplicativeIdentityHelper.MultiplicativeIdentity); + } + + [Fact] + public static void OneTest() + { + Assert.Equal((nuint)0x00000001, NumberHelper.One); + } + + [Fact] + public static void ZeroTest() + { + Assert.Equal((nuint)0x00000000, NumberHelper.Zero); + } + + [Fact] + public static void op_AdditionTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000001), AdditionOperatorsHelper.op_Addition(unchecked((nuint)0x0000000000000000), (nuint)1)); + Assert.Equal(unchecked((nuint)0x0000000000000002), AdditionOperatorsHelper.op_Addition(unchecked((nuint)0x0000000000000001), (nuint)1)); + Assert.Equal(unchecked((nuint)0x8000000000000000), AdditionOperatorsHelper.op_Addition(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)1)); + Assert.Equal(unchecked((nuint)0x8000000000000001), AdditionOperatorsHelper.op_Addition(unchecked((nuint)0x8000000000000000), (nuint)1)); + Assert.Equal(unchecked((nuint)0x0000000000000000), AdditionOperatorsHelper.op_Addition(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)1)); + } + else + { + Assert.Equal((nuint)0x00000001, AdditionOperatorsHelper.op_Addition((nuint)0x00000000, (nuint)1)); + Assert.Equal((nuint)0x00000002, AdditionOperatorsHelper.op_Addition((nuint)0x00000001, (nuint)1)); + Assert.Equal((nuint)0x80000000, AdditionOperatorsHelper.op_Addition((nuint)0x7FFFFFFF, (nuint)1)); + Assert.Equal((nuint)0x80000001, AdditionOperatorsHelper.op_Addition((nuint)0x80000000, (nuint)1)); + Assert.Equal((nuint)0x00000000, AdditionOperatorsHelper.op_Addition((nuint)0xFFFFFFFF, (nuint)1)); + } + } + + [Fact] + public static void LeadingZeroCountTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000040), BinaryIntegerHelper.LeadingZeroCount(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x000000000000003F), BinaryIntegerHelper.LeadingZeroCount(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x0000000000000001), BinaryIntegerHelper.LeadingZeroCount(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x0000000000000000), BinaryIntegerHelper.LeadingZeroCount(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000000), BinaryIntegerHelper.LeadingZeroCount(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x0000000000000020, BinaryIntegerHelper.LeadingZeroCount((nuint)0x00000000)); + Assert.Equal((nuint)0x000000000000001F, BinaryIntegerHelper.LeadingZeroCount((nuint)0x00000001)); + Assert.Equal((nuint)0x0000000000000001, BinaryIntegerHelper.LeadingZeroCount((nuint)0x7FFFFFFF)); + Assert.Equal((nuint)0x0000000000000000, BinaryIntegerHelper.LeadingZeroCount((nuint)0x80000000)); + Assert.Equal((nuint)0x0000000000000000, BinaryIntegerHelper.LeadingZeroCount((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void PopCountTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), BinaryIntegerHelper.PopCount(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000001), BinaryIntegerHelper.PopCount(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x000000000000003F), BinaryIntegerHelper.PopCount(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x0000000000000001), BinaryIntegerHelper.PopCount(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000040), BinaryIntegerHelper.PopCount(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, BinaryIntegerHelper.PopCount((nuint)0x00000000)); + Assert.Equal((nuint)0x00000001, BinaryIntegerHelper.PopCount((nuint)0x00000001)); + Assert.Equal((nuint)0x0000001F, BinaryIntegerHelper.PopCount((nuint)0x7FFFFFFF)); + Assert.Equal((nuint)0x00000001, BinaryIntegerHelper.PopCount((nuint)0x80000000)); + Assert.Equal((nuint)0x00000020, BinaryIntegerHelper.PopCount((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void RotateLeftTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), BinaryIntegerHelper.RotateLeft(unchecked((nuint)0x0000000000000000), 1)); + Assert.Equal(unchecked((nuint)0x0000000000000002), BinaryIntegerHelper.RotateLeft(unchecked((nuint)0x0000000000000001), 1)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFE), BinaryIntegerHelper.RotateLeft(unchecked((nuint)0x7FFFFFFFFFFFFFFF), 1)); + Assert.Equal(unchecked((nuint)0x0000000000000001), BinaryIntegerHelper.RotateLeft(unchecked((nuint)0x8000000000000000), 1)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), BinaryIntegerHelper.RotateLeft(unchecked((nuint)0xFFFFFFFFFFFFFFFF), 1)); + } + else + { + Assert.Equal((nuint)0x00000000, BinaryIntegerHelper.RotateLeft((nuint)0x00000000, 1)); + Assert.Equal((nuint)0x00000002, BinaryIntegerHelper.RotateLeft((nuint)0x00000001, 1)); + Assert.Equal((nuint)0xFFFFFFFE, BinaryIntegerHelper.RotateLeft((nuint)0x7FFFFFFF, 1)); + Assert.Equal((nuint)0x00000001, BinaryIntegerHelper.RotateLeft((nuint)0x80000000, 1)); + Assert.Equal((nuint)0xFFFFFFFF, BinaryIntegerHelper.RotateLeft((nuint)0xFFFFFFFF, 1)); + } + } + + [Fact] + public static void RotateRightTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), BinaryIntegerHelper.RotateRight(unchecked((nuint)0x0000000000000000), 1)); + Assert.Equal(unchecked((nuint)0x8000000000000000), BinaryIntegerHelper.RotateRight(unchecked((nuint)0x0000000000000001), 1)); + Assert.Equal(unchecked((nuint)0xBFFFFFFFFFFFFFFF), BinaryIntegerHelper.RotateRight(unchecked((nuint)0x7FFFFFFFFFFFFFFF), 1)); + Assert.Equal(unchecked((nuint)0x4000000000000000), BinaryIntegerHelper.RotateRight(unchecked((nuint)0x8000000000000000), 1)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), BinaryIntegerHelper.RotateRight(unchecked((nuint)0xFFFFFFFFFFFFFFFF), 1)); + } + else + { + Assert.Equal((nuint)0x00000000, BinaryIntegerHelper.RotateRight((nuint)0x00000000, 1)); + Assert.Equal((nuint)0x80000000, BinaryIntegerHelper.RotateRight((nuint)0x00000001, 1)); + Assert.Equal((nuint)0xBFFFFFFF, BinaryIntegerHelper.RotateRight((nuint)0x7FFFFFFF, 1)); + Assert.Equal((nuint)0x40000000, BinaryIntegerHelper.RotateRight((nuint)0x80000000, 1)); + Assert.Equal((nuint)0xFFFFFFFF, BinaryIntegerHelper.RotateRight((nuint)0xFFFFFFFF, 1)); + } + } + + [Fact] + public static void TrailingZeroCountTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000040), BinaryIntegerHelper.TrailingZeroCount(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000000), BinaryIntegerHelper.TrailingZeroCount(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x0000000000000000), BinaryIntegerHelper.TrailingZeroCount(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x000000000000003F), BinaryIntegerHelper.TrailingZeroCount(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000000), BinaryIntegerHelper.TrailingZeroCount(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000020, BinaryIntegerHelper.TrailingZeroCount((nuint)0x00000000)); + Assert.Equal((nuint)0x00000000, BinaryIntegerHelper.TrailingZeroCount((nuint)0x00000001)); + Assert.Equal((nuint)0x00000000, BinaryIntegerHelper.TrailingZeroCount((nuint)0x7FFFFFFF)); + Assert.Equal((nuint)0x0000001F, BinaryIntegerHelper.TrailingZeroCount((nuint)0x80000000)); + Assert.Equal((nuint)0x00000000, BinaryIntegerHelper.TrailingZeroCount((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void IsPow2Test() + { + if (Environment.Is64BitProcess) + { + Assert.False(BinaryNumberHelper.IsPow2(unchecked((nuint)0x0000000000000000))); + Assert.True(BinaryNumberHelper.IsPow2(unchecked((nuint)0x0000000000000001))); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.True(BinaryNumberHelper.IsPow2(unchecked((nuint)0x8000000000000000))); + Assert.False(BinaryNumberHelper.IsPow2(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.False(BinaryNumberHelper.IsPow2((nuint)0x00000000)); + Assert.True(BinaryNumberHelper.IsPow2((nuint)0x00000001)); + Assert.False(BinaryNumberHelper.IsPow2((nuint)0x7FFFFFFF)); + Assert.True(BinaryNumberHelper.IsPow2((nuint)0x80000000)); + Assert.False(BinaryNumberHelper.IsPow2((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void Log2Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), BinaryNumberHelper.Log2(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000000), BinaryNumberHelper.Log2(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x000000000000003E), BinaryNumberHelper.Log2(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x000000000000003F), BinaryNumberHelper.Log2(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0x000000000000003F), BinaryNumberHelper.Log2(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, BinaryNumberHelper.Log2((nuint)0x00000000)); + Assert.Equal((nuint)0x00000000, BinaryNumberHelper.Log2((nuint)0x00000001)); + Assert.Equal((nuint)0x0000001E, BinaryNumberHelper.Log2((nuint)0x7FFFFFFF)); + Assert.Equal((nuint)0x0000001F, BinaryNumberHelper.Log2((nuint)0x80000000)); + Assert.Equal((nuint)0x0000001F, BinaryNumberHelper.Log2((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void op_BitwiseAndTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((nuint)0x0000000000000000), (nuint)1)); + Assert.Equal(unchecked((nuint)0x0000000000000001), BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((nuint)0x0000000000000001), (nuint)1)); + Assert.Equal(unchecked((nuint)0x0000000000000001), BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)1)); + Assert.Equal(unchecked((nuint)0x0000000000000000), BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((nuint)0x8000000000000000), (nuint)1)); + Assert.Equal(unchecked((nuint)0x0000000000000001), BitwiseOperatorsHelper.op_BitwiseAnd(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)1)); + } + else + { + Assert.Equal((nuint)0x00000000, BitwiseOperatorsHelper.op_BitwiseAnd((nuint)0x00000000, (nuint)1)); + Assert.Equal((nuint)0x00000001, BitwiseOperatorsHelper.op_BitwiseAnd((nuint)0x00000001, (nuint)1)); + Assert.Equal((nuint)0x00000001, BitwiseOperatorsHelper.op_BitwiseAnd((nuint)0x7FFFFFFF, (nuint)1)); + Assert.Equal((nuint)0x00000000, BitwiseOperatorsHelper.op_BitwiseAnd((nuint)0x80000000, (nuint)1)); + Assert.Equal((nuint)0x00000001, BitwiseOperatorsHelper.op_BitwiseAnd((nuint)0xFFFFFFFF, (nuint)1)); + } + } + + [Fact] + public static void op_BitwiseOrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000001), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((nuint)0x0000000000000000), (nuint)1)); + Assert.Equal(unchecked((nuint)0x0000000000000001), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((nuint)0x0000000000000001), (nuint)1)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)1)); + Assert.Equal(unchecked((nuint)0x8000000000000001), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((nuint)0x8000000000000000), (nuint)1)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), BitwiseOperatorsHelper.op_BitwiseOr(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)1)); + } + else + { + Assert.Equal((nuint)0x00000001, BitwiseOperatorsHelper.op_BitwiseOr((nuint)0x00000000, (nuint)1)); + Assert.Equal((nuint)0x00000001, BitwiseOperatorsHelper.op_BitwiseOr((nuint)0x00000001, (nuint)1)); + Assert.Equal((nuint)0x7FFFFFFF, BitwiseOperatorsHelper.op_BitwiseOr((nuint)0x7FFFFFFF, (nuint)1)); + Assert.Equal((nuint)0x80000001, BitwiseOperatorsHelper.op_BitwiseOr((nuint)0x80000000, (nuint)1)); + Assert.Equal((nuint)0xFFFFFFFF, BitwiseOperatorsHelper.op_BitwiseOr((nuint)0xFFFFFFFF, (nuint)1)); + } + } + + [Fact] + public static void op_ExclusiveOrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000001), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((nuint)0x0000000000000000), (nuint)1)); + Assert.Equal(unchecked((nuint)0x0000000000000000), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((nuint)0x0000000000000001), (nuint)1)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFE), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)1)); + Assert.Equal(unchecked((nuint)0x8000000000000001), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((nuint)0x8000000000000000), (nuint)1)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFE), BitwiseOperatorsHelper.op_ExclusiveOr(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)1)); + } + else + { + Assert.Equal((nuint)0x00000001, BitwiseOperatorsHelper.op_ExclusiveOr((nuint)0x00000000, (nuint)1)); + Assert.Equal((nuint)0x00000000, BitwiseOperatorsHelper.op_ExclusiveOr((nuint)0x00000001, (nuint)1)); + Assert.Equal((nuint)0x7FFFFFFE, BitwiseOperatorsHelper.op_ExclusiveOr((nuint)0x7FFFFFFF, (nuint)1)); + Assert.Equal((nuint)0x80000001, BitwiseOperatorsHelper.op_ExclusiveOr((nuint)0x80000000, (nuint)1)); + Assert.Equal((nuint)0xFFFFFFFE, BitwiseOperatorsHelper.op_ExclusiveOr((nuint)0xFFFFFFFF, (nuint)1)); + } + } + + [Fact] + public static void op_OnesComplementTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), BitwiseOperatorsHelper.op_OnesComplement(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFE), BitwiseOperatorsHelper.op_OnesComplement(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x8000000000000000), BitwiseOperatorsHelper.op_OnesComplement(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), BitwiseOperatorsHelper.op_OnesComplement(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000000), BitwiseOperatorsHelper.op_OnesComplement(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0xFFFFFFFF, BitwiseOperatorsHelper.op_OnesComplement((nuint)0x00000000)); + Assert.Equal((nuint)0xFFFFFFFE, BitwiseOperatorsHelper.op_OnesComplement((nuint)0x00000001)); + Assert.Equal((nuint)0x80000000, BitwiseOperatorsHelper.op_OnesComplement((nuint)0x7FFFFFFF)); + Assert.Equal((nuint)0x7FFFFFFF, BitwiseOperatorsHelper.op_OnesComplement((nuint)0x80000000)); + Assert.Equal((nuint)0x00000000, BitwiseOperatorsHelper.op_OnesComplement((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void op_LessThanTest() + { + if (Environment.Is64BitProcess) + { + Assert.True(ComparisonOperatorsHelper.op_LessThan(unchecked((nuint)0x0000000000000000), (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan(unchecked((nuint)0x0000000000000001), (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan(unchecked((nuint)0x8000000000000000), (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)1)); + } + else + { + Assert.True(ComparisonOperatorsHelper.op_LessThan((nuint)0x00000000, (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((nuint)0x00000001, (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((nuint)0x7FFFFFFF, (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((nuint)0x80000000, (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThan((nuint)0xFFFFFFFF, (nuint)1)); + } + } + + [Fact] + public static void op_LessThanOrEqualTest() + { + if (Environment.Is64BitProcess) + { + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((nuint)0x0000000000000000), (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((nuint)0x0000000000000001), (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((nuint)0x8000000000000000), (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)1)); + } + else + { + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((nuint)0x00000000, (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_LessThanOrEqual((nuint)0x00000001, (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((nuint)0x7FFFFFFF, (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((nuint)0x80000000, (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_LessThanOrEqual((nuint)0xFFFFFFFF, (nuint)1)); + } + } + + [Fact] + public static void op_GreaterThanTest() + { + if (Environment.Is64BitProcess) + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((nuint)0x0000000000000000), (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan(unchecked((nuint)0x0000000000000001), (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan(unchecked((nuint)0x8000000000000000), (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)1)); + } + else + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((nuint)0x00000000, (nuint)1)); + Assert.False(ComparisonOperatorsHelper.op_GreaterThan((nuint)0x00000001, (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((nuint)0x7FFFFFFF, (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((nuint)0x80000000, (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThan((nuint)0xFFFFFFFF, (nuint)1)); + } + } + + [Fact] + public static void op_GreaterThanOrEqualTest() + { + if (Environment.Is64BitProcess) + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((nuint)0x0000000000000000), (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((nuint)0x0000000000000001), (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((nuint)0x8000000000000000), (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)1)); + } + else + { + Assert.False(ComparisonOperatorsHelper.op_GreaterThanOrEqual((nuint)0x00000000, (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((nuint)0x00000001, (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((nuint)0x7FFFFFFF, (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((nuint)0x80000000, (nuint)1)); + Assert.True(ComparisonOperatorsHelper.op_GreaterThanOrEqual((nuint)0xFFFFFFFF, (nuint)1)); + } + } + + [Fact] + public static void op_DecrementTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), DecrementOperatorsHelper.op_Decrement(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000000), DecrementOperatorsHelper.op_Decrement(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFE), DecrementOperatorsHelper.op_Decrement(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), DecrementOperatorsHelper.op_Decrement(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFE), DecrementOperatorsHelper.op_Decrement(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0xFFFFFFFF, DecrementOperatorsHelper.op_Decrement((nuint)0x00000000)); + Assert.Equal((nuint)0x00000000, DecrementOperatorsHelper.op_Decrement((nuint)0x00000001)); + Assert.Equal((nuint)0x7FFFFFFE, DecrementOperatorsHelper.op_Decrement((nuint)0x7FFFFFFF)); + Assert.Equal((nuint)0x7FFFFFFF, DecrementOperatorsHelper.op_Decrement((nuint)0x80000000)); + Assert.Equal((nuint)0xFFFFFFFE, DecrementOperatorsHelper.op_Decrement((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void op_DivisionTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), DivisionOperatorsHelper.op_Division(unchecked((nuint)0x0000000000000000), (nuint)2)); + Assert.Equal(unchecked((nuint)0x0000000000000000), DivisionOperatorsHelper.op_Division(unchecked((nuint)0x0000000000000001), (nuint)2)); + Assert.Equal(unchecked((nuint)0x3FFFFFFFFFFFFFFF), DivisionOperatorsHelper.op_Division(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)2)); + Assert.Equal(unchecked((nuint)0x4000000000000000), DivisionOperatorsHelper.op_Division(unchecked((nuint)0x8000000000000000), (nuint)2)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), DivisionOperatorsHelper.op_Division(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)2)); + } + else + { + Assert.Equal((nuint)0x00000000, DivisionOperatorsHelper.op_Division((nuint)0x00000000, (nuint)2)); + Assert.Equal((nuint)0x00000000, DivisionOperatorsHelper.op_Division((nuint)0x00000001, (nuint)2)); + Assert.Equal((nuint)0x3FFFFFFF, DivisionOperatorsHelper.op_Division((nuint)0x7FFFFFFF, (nuint)2)); + Assert.Equal((nuint)0x40000000, DivisionOperatorsHelper.op_Division((nuint)0x80000000, (nuint)2)); + Assert.Equal((nuint)0x7FFFFFFF, DivisionOperatorsHelper.op_Division((nuint)0xFFFFFFFF, (nuint)2)); + } + } + + [Fact] + public static void op_EqualityTest() + { + if (Environment.Is64BitProcess) + { + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((nuint)0x0000000000000000), (nuint)1)); + Assert.True(EqualityOperatorsHelper.op_Equality(unchecked((nuint)0x0000000000000001), (nuint)1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((nuint)0x8000000000000000), (nuint)1)); + Assert.False(EqualityOperatorsHelper.op_Equality(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)1)); + } + else + { + Assert.False(EqualityOperatorsHelper.op_Equality((nuint)0x00000000, (nuint)1)); + Assert.True(EqualityOperatorsHelper.op_Equality((nuint)0x00000001, (nuint)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((nuint)0x7FFFFFFF, (nuint)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((nuint)0x80000000, (nuint)1)); + Assert.False(EqualityOperatorsHelper.op_Equality((nuint)0xFFFFFFFF, (nuint)1)); + } + } + + [Fact] + public static void op_InequalityTest() + { + if (Environment.Is64BitProcess) + { + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((nuint)0x0000000000000000), (nuint)1)); + Assert.False(EqualityOperatorsHelper.op_Inequality(unchecked((nuint)0x0000000000000001), (nuint)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((nuint)0x8000000000000000), (nuint)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)1)); + } + else + { + Assert.True(EqualityOperatorsHelper.op_Inequality((nuint)0x00000000, (nuint)1)); + Assert.False(EqualityOperatorsHelper.op_Inequality((nuint)0x00000001, (nuint)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((nuint)0x7FFFFFFF, (nuint)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((nuint)0x80000000, (nuint)1)); + Assert.True(EqualityOperatorsHelper.op_Inequality((nuint)0xFFFFFFFF, (nuint)1)); + } + } + + [Fact] + public static void op_IncrementTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000001), IncrementOperatorsHelper.op_Increment(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000002), IncrementOperatorsHelper.op_Increment(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x8000000000000000), IncrementOperatorsHelper.op_Increment(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x8000000000000001), IncrementOperatorsHelper.op_Increment(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000000), IncrementOperatorsHelper.op_Increment(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000001, IncrementOperatorsHelper.op_Increment((nuint)0x00000000)); + Assert.Equal((nuint)0x00000002, IncrementOperatorsHelper.op_Increment((nuint)0x00000001)); + Assert.Equal((nuint)0x80000000, IncrementOperatorsHelper.op_Increment((nuint)0x7FFFFFFF)); + Assert.Equal((nuint)0x80000001, IncrementOperatorsHelper.op_Increment((nuint)0x80000000)); + Assert.Equal((nuint)0x00000000, IncrementOperatorsHelper.op_Increment((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void op_ModulusTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), ModulusOperatorsHelper.op_Modulus(unchecked((nuint)0x0000000000000000), (nuint)2)); + Assert.Equal(unchecked((nuint)0x0000000000000001), ModulusOperatorsHelper.op_Modulus(unchecked((nuint)0x0000000000000001), (nuint)2)); + Assert.Equal(unchecked((nuint)0x0000000000000001), ModulusOperatorsHelper.op_Modulus(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)2)); + Assert.Equal(unchecked((nuint)0x0000000000000000), ModulusOperatorsHelper.op_Modulus(unchecked((nuint)0x8000000000000000), (nuint)2)); + Assert.Equal(unchecked((nuint)0x0000000000000001), ModulusOperatorsHelper.op_Modulus(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)2)); + } + else + { + Assert.Equal((nuint)0x00000000, ModulusOperatorsHelper.op_Modulus((nuint)0x00000000, (nuint)2)); + Assert.Equal((nuint)0x00000001, ModulusOperatorsHelper.op_Modulus((nuint)0x00000001, (nuint)2)); + Assert.Equal((nuint)0x00000001, ModulusOperatorsHelper.op_Modulus((nuint)0x7FFFFFFF, (nuint)2)); + Assert.Equal((nuint)0x00000000, ModulusOperatorsHelper.op_Modulus((nuint)0x80000000, (nuint)2)); + Assert.Equal((nuint)0x00000001, ModulusOperatorsHelper.op_Modulus((nuint)0xFFFFFFFF, (nuint)2)); + } + } + + [Fact] + public static void op_MultiplyTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), MultiplyOperatorsHelper.op_Multiply(unchecked((nuint)0x0000000000000000), (nuint)2)); + Assert.Equal(unchecked((nuint)0x0000000000000002), MultiplyOperatorsHelper.op_Multiply(unchecked((nuint)0x0000000000000001), (nuint)2)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFE), MultiplyOperatorsHelper.op_Multiply(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)2)); + Assert.Equal(unchecked((nuint)0x0000000000000000), MultiplyOperatorsHelper.op_Multiply(unchecked((nuint)0x8000000000000000), (nuint)2)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFE), MultiplyOperatorsHelper.op_Multiply(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)2)); + } + else + { + Assert.Equal((nuint)0x00000000, MultiplyOperatorsHelper.op_Multiply((nuint)0x00000000, (nuint)2)); + Assert.Equal((nuint)0x00000002, MultiplyOperatorsHelper.op_Multiply((nuint)0x00000001, (nuint)2)); + Assert.Equal((nuint)0xFFFFFFFE, MultiplyOperatorsHelper.op_Multiply((nuint)0x7FFFFFFF, (nuint)2)); + Assert.Equal((nuint)0x00000000, MultiplyOperatorsHelper.op_Multiply((nuint)0x80000000, (nuint)2)); + Assert.Equal((nuint)0xFFFFFFFE, MultiplyOperatorsHelper.op_Multiply((nuint)0xFFFFFFFF, (nuint)2)); + } + } + + [Fact] + public static void AbsTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.Abs(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Abs(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), NumberHelper.Abs(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x8000000000000000), NumberHelper.Abs(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), NumberHelper.Abs(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.Abs((nuint)0x00000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.Abs((nuint)0x00000001)); + Assert.Equal((nuint)0x7FFFFFFF, NumberHelper.Abs((nuint)0x7FFFFFFF)); + Assert.Equal((nuint)0x80000000, NumberHelper.Abs((nuint)0x80000000)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.Abs((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void ClampTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Clamp(unchecked((nuint)0x0000000000000000), unchecked((nuint)0x0000000000000001), unchecked((nuint)0x000000000000003F))); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Clamp(unchecked((nuint)0x0000000000000001), unchecked((nuint)0x0000000000000001), unchecked((nuint)0x000000000000003F))); + Assert.Equal(unchecked((nuint)0x000000000000003F), NumberHelper.Clamp(unchecked((nuint)0x7FFFFFFFFFFFFFFF), unchecked((nuint)0x0000000000000001), unchecked((nuint)0x000000000000003F))); + Assert.Equal(unchecked((nuint)0x000000000000003F), NumberHelper.Clamp(unchecked((nuint)0x8000000000000000), unchecked((nuint)0x0000000000000001), unchecked((nuint)0x000000000000003F))); + Assert.Equal(unchecked((nuint)0x000000000000003F), NumberHelper.Clamp(unchecked((nuint)0xFFFFFFFFFFFFFFFF), unchecked((nuint)0x0000000000000001), unchecked((nuint)0x000000000000003F))); + } + else + { + Assert.Equal((nuint)0x00000001, NumberHelper.Clamp((nuint)0x00000000, (nuint)0x00000001, (nuint)0x0000003F)); + Assert.Equal((nuint)0x00000001, NumberHelper.Clamp((nuint)0x00000001, (nuint)0x00000001, (nuint)0x0000003F)); + Assert.Equal((nuint)0x0000003F, NumberHelper.Clamp((nuint)0x7FFFFFFF, (nuint)0x00000001, (nuint)0x0000003F)); + Assert.Equal((nuint)0x0000003F, NumberHelper.Clamp((nuint)0x80000000, (nuint)0x00000001, (nuint)0x0000003F)); + Assert.Equal((nuint)0x0000003F, NumberHelper.Clamp((nuint)0xFFFFFFFF, (nuint)0x00000001, (nuint)0x0000003F)); + } + } + + [Fact] + public static void CreateFromByteTest() + { + Assert.Equal((nuint)0x00000000, NumberHelper.Create(0x00)); + Assert.Equal((nuint)0x00000001, NumberHelper.Create(0x01)); + Assert.Equal((nuint)0x0000007F, NumberHelper.Create(0x7F)); + Assert.Equal((nuint)0x00000080, NumberHelper.Create(0x80)); + Assert.Equal((nuint)0x000000FF, NumberHelper.Create(0xFF)); + } + + [Fact] + public static void CreateFromCharTest() + { + Assert.Equal((nuint)0x00000000, NumberHelper.Create((char)0x0000)); + Assert.Equal((nuint)0x00000001, NumberHelper.Create((char)0x0001)); + Assert.Equal((nuint)0x00007FFF, NumberHelper.Create((char)0x7FFF)); + Assert.Equal((nuint)0x00008000, NumberHelper.Create((char)0x8000)); + Assert.Equal((nuint)0x0000FFFF, NumberHelper.Create((char)0xFFFF)); + } + + [Fact] + public static void CreateFromInt16Test() + { + Assert.Equal((nuint)0x00000000, NumberHelper.Create(0x0000)); + Assert.Equal((nuint)0x00000001, NumberHelper.Create(0x0001)); + Assert.Equal((nuint)0x00007FFF, NumberHelper.Create(0x7FFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((short)0x8000))); + Assert.Throws(() => NumberHelper.Create(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateFromInt32Test() + { + Assert.Equal((nuint)0x00000000, NumberHelper.Create(0x00000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.Create(0x00000001)); + Assert.Equal((nuint)0x7FFFFFFF, NumberHelper.Create(0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((int)0x80000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateFromInt64Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.Create(0x0000000000000000)); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Create(0x0000000000000001)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + } + + [Fact] + public static void CreateFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.Create(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Create(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), NumberHelper.Create(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x8000000000000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.Create((nint)0x00000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.Create((nint)0x00000001)); + Assert.Equal((nuint)0x7FFFFFFF, NumberHelper.Create((nint)0x7FFFFFFF)); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0x80000000))); + Assert.Throws(() => NumberHelper.Create(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateFromSByteTest() + { + Assert.Equal((nuint)0x00000000, NumberHelper.Create(0x00)); + Assert.Equal((nuint)0x00000001, NumberHelper.Create(0x01)); + Assert.Equal((nuint)0x0000007F, NumberHelper.Create(0x7F)); + Assert.Throws(() => NumberHelper.Create(unchecked((sbyte)0x80))); + Assert.Throws(() => NumberHelper.Create(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateFromUInt16Test() + { + Assert.Equal((nuint)0x00000000, NumberHelper.Create(0x0000)); + Assert.Equal((nuint)0x00000001, NumberHelper.Create(0x0001)); + Assert.Equal((nuint)0x00007FFF, NumberHelper.Create(0x7FFF)); + Assert.Equal((nuint)0x00008000, NumberHelper.Create(0x8000)); + Assert.Equal((nuint)0x0000FFFF, NumberHelper.Create(0xFFFF)); + } + + [Fact] + public static void CreateFromUInt32Test() + { + Assert.Equal((nuint)0x00000000, NumberHelper.Create(0x00000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.Create(0x00000001)); + Assert.Equal((nuint)0x7FFFFFFF, NumberHelper.Create(0x7FFFFFFF)); + Assert.Equal((nuint)0x80000000, NumberHelper.Create(0x80000000)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.Create(0xFFFFFFFF)); + } + + [Fact] + public static void CreateFromUInt64Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.Create(0x0000000000000000)); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Create(0x0000000000000001)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((nuint)0x8000000000000000), NumberHelper.Create(0x8000000000000000)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), NumberHelper.Create(0xFFFFFFFFFFFFFFFF)); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.Create(0x0000000000000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.Create(0x0000000000000001)); + Assert.Throws(() => NumberHelper.Create(0x7FFFFFFFFFFFFFFF)); + Assert.Throws(() => NumberHelper.Create(0x8000000000000000)); + Assert.Throws(() => NumberHelper.Create(0xFFFFFFFFFFFFFFFF)); + } + } + + [Fact] + public static void CreateFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.Create(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Create(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), NumberHelper.Create(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x8000000000000000), NumberHelper.Create(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), NumberHelper.Create(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.Create((nuint)0x00000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.Create((nuint)0x00000001)); + Assert.Equal((nuint)0x7FFFFFFF, NumberHelper.Create((nuint)0x7FFFFFFF)); + Assert.Equal((nuint)0x80000000, NumberHelper.Create((nuint)0x80000000)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.Create((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateSaturatingFromByteTest() + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((nuint)0x0000007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((nuint)0x00000080, NumberHelper.CreateSaturating(0x80)); + Assert.Equal((nuint)0x000000FF, NumberHelper.CreateSaturating(0xFF)); + } + + [Fact] + public static void CreateSaturatingFromCharTest() + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating((char)0x0000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateSaturating((char)0x0001)); + Assert.Equal((nuint)0x00007FFF, NumberHelper.CreateSaturating((char)0x7FFF)); + Assert.Equal((nuint)0x00008000, NumberHelper.CreateSaturating((char)0x8000)); + Assert.Equal((nuint)0x0000FFFF, NumberHelper.CreateSaturating((char)0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromInt16Test() + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((nuint)0x00007FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(unchecked((short)0x8000))); + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(unchecked((short)0xFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt32Test() + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((nuint)0x7FFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(unchecked((int)0x80000000))); + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(unchecked((int)0xFFFFFFFF))); + } + + [Fact] + public static void CreateSaturatingFromInt64Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateSaturating(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateSaturating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(unchecked((long)0x8000000000000000))); + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + } + + [Fact] + public static void CreateSaturatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.CreateSaturating(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateSaturating(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating((nint)0x00000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateSaturating((nint)0x00000001)); + Assert.Equal((nuint)0x7FFFFFFF, NumberHelper.CreateSaturating((nint)0x7FFFFFFF)); + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(unchecked((nint)0x80000000))); + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateSaturatingFromSByteTest() + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(0x00)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateSaturating(0x01)); + Assert.Equal((nuint)0x0000007F, NumberHelper.CreateSaturating(0x7F)); + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(unchecked((sbyte)0x80))); + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(unchecked((sbyte)0xFF))); + } + + [Fact] + public static void CreateSaturatingFromUInt16Test() + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(0x0000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateSaturating(0x0001)); + Assert.Equal((nuint)0x00007FFF, NumberHelper.CreateSaturating(0x7FFF)); + Assert.Equal((nuint)0x00008000, NumberHelper.CreateSaturating(0x8000)); + Assert.Equal((nuint)0x0000FFFF, NumberHelper.CreateSaturating(0xFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt32Test() + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(0x00000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateSaturating(0x00000001)); + Assert.Equal((nuint)0x7FFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFF)); + Assert.Equal((nuint)0x80000000, NumberHelper.CreateSaturating(0x80000000)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateSaturating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateSaturatingFromUInt64Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((nuint)0x8000000000000000), NumberHelper.CreateSaturating(0x8000000000000000)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(0xFFFFFFFFFFFFFFFF)); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating(0x0000000000000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateSaturating(0x0000000000000001)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateSaturating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateSaturating(0x8000000000000000)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateSaturating(0xFFFFFFFFFFFFFFFF)); + } + } + + [Fact] + public static void CreateSaturatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.CreateSaturating(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x8000000000000000), NumberHelper.CreateSaturating(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateSaturating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateSaturating((nuint)0x00000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateSaturating((nuint)0x00000001)); + Assert.Equal((nuint)0x7FFFFFFF, NumberHelper.CreateSaturating((nuint)0x7FFFFFFF)); + Assert.Equal((nuint)0x80000000, NumberHelper.CreateSaturating((nuint)0x80000000)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateSaturating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void CreateTruncatingFromByteTest() + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((nuint)0x0000007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((nuint)0x00000080, NumberHelper.CreateTruncating(0x80)); + Assert.Equal((nuint)0x000000FF, NumberHelper.CreateTruncating(0xFF)); + } + + [Fact] + public static void CreateTruncatingFromCharTest() + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateTruncating((char)0x0000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateTruncating((char)0x0001)); + Assert.Equal((nuint)0x00007FFF, NumberHelper.CreateTruncating((char)0x7FFF)); + Assert.Equal((nuint)0x00008000, NumberHelper.CreateTruncating((char)0x8000)); + Assert.Equal((nuint)0x0000FFFF, NumberHelper.CreateTruncating((char)0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromInt16Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateTruncating(0x0000)); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.CreateTruncating(0x0001)); + Assert.Equal(unchecked((nuint)0x0000000000007FFF), NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFF8000), NumberHelper.CreateTruncating(unchecked((short)0x8000))); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((short)0xFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((nuint)0x00007FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((nuint)0xFFFF8000, NumberHelper.CreateTruncating(unchecked((short)0x8000))); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateTruncating(unchecked((short)0xFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromInt32Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal(unchecked((nuint)0x000000007FFFFFFF), NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal(unchecked((nuint)0xFFFFFFFF80000000), NumberHelper.CreateTruncating(unchecked((int)0x80000000))); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((int)0xFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((nuint)0x7FFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((nuint)0x80000000, NumberHelper.CreateTruncating(unchecked((int)0x80000000))); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateTruncating(unchecked((int)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromInt64Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((nuint)0x8000000000000000), NumberHelper.CreateTruncating(unchecked((long)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((nuint)0x00000000, NumberHelper.CreateTruncating(unchecked((long)0x8000000000000000))); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateTruncating(unchecked((long)0xFFFFFFFFFFFFFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.CreateTruncating(unchecked((nint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x8000000000000000), NumberHelper.CreateTruncating(unchecked((nint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateTruncating((nint)0x00000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateTruncating((nint)0x00000001)); + Assert.Equal((nuint)0x7FFFFFFF, NumberHelper.CreateTruncating((nint)0x7FFFFFFF)); + Assert.Equal((nuint)0x80000000, NumberHelper.CreateTruncating(unchecked((nint)0x80000000))); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateTruncating(unchecked((nint)0xFFFFFFFF))); + } + } + + [Fact] + public static void CreateTruncatingFromSByteTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateTruncating(0x00)); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.CreateTruncating(0x01)); + Assert.Equal(unchecked((nuint)0x000000000000007F), NumberHelper.CreateTruncating(0x7F)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFF80), NumberHelper.CreateTruncating(unchecked((sbyte)0x80))); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((sbyte)0xFF))); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateTruncating(0x00)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateTruncating(0x01)); + Assert.Equal((nuint)0x0000007F, NumberHelper.CreateTruncating(0x7F)); + Assert.Equal((nuint)0xFFFFFF80, NumberHelper.CreateTruncating(unchecked((sbyte)0x80))); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateTruncating(unchecked((sbyte)0xFF))); + } + } + + [Fact] + public static void CreateTruncatingFromUInt16Test() + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateTruncating(0x0000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateTruncating(0x0001)); + Assert.Equal((nuint)0x00007FFF, NumberHelper.CreateTruncating(0x7FFF)); + Assert.Equal((nuint)0x00008000, NumberHelper.CreateTruncating(0x8000)); + Assert.Equal((nuint)0x0000FFFF, NumberHelper.CreateTruncating(0xFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt32Test() + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateTruncating(0x00000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateTruncating(0x00000001)); + Assert.Equal((nuint)0x7FFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFF)); + Assert.Equal((nuint)0x80000000, NumberHelper.CreateTruncating(0x80000000)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateTruncating(0xFFFFFFFF)); + } + + [Fact] + public static void CreateTruncatingFromUInt64Test() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal(unchecked((nuint)0x8000000000000000), NumberHelper.CreateTruncating(0x8000000000000000)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(0xFFFFFFFFFFFFFFFF)); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateTruncating(0x0000000000000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateTruncating(0x0000000000000001)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateTruncating(0x7FFFFFFFFFFFFFFF)); + Assert.Equal((nuint)0x00000000, NumberHelper.CreateTruncating(0x8000000000000000)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateTruncating(0xFFFFFFFFFFFFFFFF)); + } + } + + [Fact] + public static void CreateTruncatingFromUIntPtrTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.CreateTruncating(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x8000000000000000), NumberHelper.CreateTruncating(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), NumberHelper.CreateTruncating(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.CreateTruncating((nuint)0x00000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.CreateTruncating((nuint)0x00000001)); + Assert.Equal((nuint)0x7FFFFFFF, NumberHelper.CreateTruncating((nuint)0x7FFFFFFF)); + Assert.Equal((nuint)0x80000000, NumberHelper.CreateTruncating((nuint)0x80000000)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.CreateTruncating((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void DivRemTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal((unchecked((nuint)0x0000000000000000), unchecked((nuint)0x0000000000000000)), NumberHelper.DivRem(unchecked((nuint)0x0000000000000000), (nuint)2)); + Assert.Equal((unchecked((nuint)0x0000000000000000), unchecked((nuint)0x0000000000000001)), NumberHelper.DivRem(unchecked((nuint)0x0000000000000001), (nuint)2)); + Assert.Equal((unchecked((nuint)0x3FFFFFFFFFFFFFFF), unchecked((nuint)0x0000000000000001)), NumberHelper.DivRem(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)2)); + Assert.Equal((unchecked((nuint)0x4000000000000000), unchecked((nuint)0x0000000000000000)), NumberHelper.DivRem(unchecked((nuint)0x8000000000000000), (nuint)2)); + Assert.Equal((unchecked((nuint)0x7FFFFFFFFFFFFFFF), unchecked((nuint)0x0000000000000001)), NumberHelper.DivRem(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)2)); + } + else + { + Assert.Equal(((nuint)0x00000000, (nuint)0x00000000), NumberHelper.DivRem((nuint)0x00000000, (nuint)2)); + Assert.Equal(((nuint)0x00000000, (nuint)0x00000001), NumberHelper.DivRem((nuint)0x00000001, (nuint)2)); + Assert.Equal(((nuint)0x3FFFFFFF, (nuint)0x00000001), NumberHelper.DivRem((nuint)0x7FFFFFFF, (nuint)2)); + Assert.Equal(((nuint)0x40000000, (nuint)0x00000000), NumberHelper.DivRem((nuint)0x80000000, (nuint)2)); + Assert.Equal(((nuint)0x7FFFFFFF, (nuint)0x00000001), NumberHelper.DivRem((nuint)0xFFFFFFFF, (nuint)2)); + } + } + + [Fact] + public static void MaxTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Max(unchecked((nuint)0x0000000000000000), (nuint)1)); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Max(unchecked((nuint)0x0000000000000001), (nuint)1)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), NumberHelper.Max(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)1)); + Assert.Equal(unchecked((nuint)0x8000000000000000), NumberHelper.Max(unchecked((nuint)0x8000000000000000), (nuint)1)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), NumberHelper.Max(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)1)); + } + else + { + Assert.Equal((nuint)0x00000001, NumberHelper.Max((nuint)0x00000000, (nuint)1)); + Assert.Equal((nuint)0x00000001, NumberHelper.Max((nuint)0x00000001, (nuint)1)); + Assert.Equal((nuint)0x7FFFFFFF, NumberHelper.Max((nuint)0x7FFFFFFF, (nuint)1)); + Assert.Equal((nuint)0x80000000, NumberHelper.Max((nuint)0x80000000, (nuint)1)); + Assert.Equal((nuint)0xFFFFFFFF, NumberHelper.Max((nuint)0xFFFFFFFF, (nuint)1)); + } + } + + [Fact] + public static void MinTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.Min(unchecked((nuint)0x0000000000000000), (nuint)1)); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Min(unchecked((nuint)0x0000000000000001), (nuint)1)); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Min(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)1)); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Min(unchecked((nuint)0x8000000000000000), (nuint)1)); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Min(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)1)); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.Min((nuint)0x00000000, (nuint)1)); + Assert.Equal((nuint)0x00000001, NumberHelper.Min((nuint)0x00000001, (nuint)1)); + Assert.Equal((nuint)0x00000001, NumberHelper.Min((nuint)0x7FFFFFFF, (nuint)1)); + Assert.Equal((nuint)0x00000001, NumberHelper.Min((nuint)0x80000000, (nuint)1)); + Assert.Equal((nuint)0x00000001, NumberHelper.Min((nuint)0xFFFFFFFF, (nuint)1)); + } + } + + [Fact] + public static void SignTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), NumberHelper.Sign(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Sign(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Sign(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Sign(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000001), NumberHelper.Sign(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, NumberHelper.Sign((nuint)0x00000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.Sign((nuint)0x00000001)); + Assert.Equal((nuint)0x00000001, NumberHelper.Sign((nuint)0x7FFFFFFF)); + Assert.Equal((nuint)0x00000001, NumberHelper.Sign((nuint)0x80000000)); + Assert.Equal((nuint)0x00000001, NumberHelper.Sign((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void TryCreateFromByteTest() + { + nuint result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((nuint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((nuint)0x0000007F, result); + + Assert.True(NumberHelper.TryCreate(0x80, out result)); + Assert.Equal((nuint)0x00000080, result); + + Assert.True(NumberHelper.TryCreate(0xFF, out result)); + Assert.Equal((nuint)0x000000FF, result); + } + + [Fact] + public static void TryCreateFromCharTest() + { + nuint result; + + Assert.True(NumberHelper.TryCreate((char)0x0000, out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate((char)0x0001, out result)); + Assert.Equal((nuint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate((char)0x7FFF, out result)); + Assert.Equal((nuint)0x00007FFF, result); + + Assert.True(NumberHelper.TryCreate((char)0x8000, out result)); + Assert.Equal((nuint)0x00008000, result); + + Assert.True(NumberHelper.TryCreate((char)0xFFFF, out result)); + Assert.Equal((nuint)0x0000FFFF, result); + } + + [Fact] + public static void TryCreateFromInt16Test() + { + nuint result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((nuint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((nuint)0x00007FFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((short)0x8000), out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((short)0xFFFF), out result)); + Assert.Equal((nuint)0x00000000, result); + } + + [Fact] + public static void TryCreateFromInt32Test() + { + nuint result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((nuint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((nuint)0x7FFFFFFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((int)0x80000000), out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((int)0xFFFFFFFF), out result)); + Assert.Equal((nuint)0x00000000, result); + } + + [Fact] + public static void TryCreateFromInt64Test() + { + nuint result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal(unchecked((nuint)0x0000000000000000), result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal(unchecked((nuint)0x0000000000000001), result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0x8000000000000000), out result)); + Assert.Equal(unchecked((nuint)0x0000000000000000), result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((nuint)0x0000000000000000), result); + } + else + { + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((nuint)0x00000001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0x8000000000000000), out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((long)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal((nuint)0x00000000, result); + } + } + + [Fact] + public static void TryCreateFromIntPtrTest() + { + nuint result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000000), out result)); + Assert.Equal(unchecked((nuint)0x0000000000000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x0000000000000001), out result)); + Assert.Equal(unchecked((nuint)0x0000000000000001), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x8000000000000000), out result)); + Assert.Equal(unchecked((nuint)0x0000000000000000), result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((nuint)0x0000000000000000), result); + } + else + { + Assert.True(NumberHelper.TryCreate((nint)0x00000000, out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate((nint)0x00000001, out result)); + Assert.Equal((nuint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate((nint)0x7FFFFFFF, out result)); + Assert.Equal((nuint)0x7FFFFFFF, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0x80000000), out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((nint)0xFFFFFFFF), out result)); + Assert.Equal((nuint)0x00000000, result); + } + } + + [Fact] + public static void TryCreateFromSByteTest() + { + nuint result; + + Assert.True(NumberHelper.TryCreate(0x00, out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x01, out result)); + Assert.Equal((nuint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7F, out result)); + Assert.Equal((nuint)0x0000007F, result); + + Assert.False(NumberHelper.TryCreate(unchecked((sbyte)0x80), out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(unchecked((sbyte)0xFF), out result)); + Assert.Equal((nuint)0x00000000, result); + } + + [Fact] + public static void TryCreateFromUInt16Test() + { + nuint result; + + Assert.True(NumberHelper.TryCreate(0x0000, out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0001, out result)); + Assert.Equal((nuint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFF, out result)); + Assert.Equal((nuint)0x00007FFF, result); + + Assert.True(NumberHelper.TryCreate(0x8000, out result)); + Assert.Equal((nuint)0x00008000, result); + + Assert.True(NumberHelper.TryCreate(0xFFFF, out result)); + Assert.Equal((nuint)0x0000FFFF, result); + } + + [Fact] + public static void TryCreateFromUInt32Test() + { + nuint result; + + Assert.True(NumberHelper.TryCreate(0x00000000, out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x00000001, out result)); + Assert.Equal((nuint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFF, out result)); + Assert.Equal((nuint)0x7FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(0x80000000, out result)); + Assert.Equal((nuint)0x80000000, result); + + Assert.True(NumberHelper.TryCreate(0xFFFFFFFF, out result)); + Assert.Equal((nuint)0xFFFFFFFF, result); + } + + [Fact] + public static void TryCreateFromUInt64Test() + { + nuint result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal(unchecked((nuint)0x0000000000000000), result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal(unchecked((nuint)0x00000000000000001), result); + + Assert.True(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), result); + + Assert.True(NumberHelper.TryCreate(0x8000000000000000, out result)); + Assert.Equal(unchecked((nuint)0x8000000000000000), result); + + Assert.True(NumberHelper.TryCreate(0xFFFFFFFFFFFFFFFF, out result)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), result); + } + else + { + Assert.True(NumberHelper.TryCreate(0x0000000000000000, out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate(0x0000000000000001, out result)); + Assert.Equal((nuint)0x00000001, result); + + Assert.False(NumberHelper.TryCreate(0x7FFFFFFFFFFFFFFF, out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(0x8000000000000000, out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.False(NumberHelper.TryCreate(0xFFFFFFFFFFFFFFFF, out result)); + Assert.Equal((nuint)0x00000000, result); + } + } + + [Fact] + public static void TryCreateFromUIntPtrTest() + { + nuint result; + + if (Environment.Is64BitProcess) + { + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000000), out result)); + Assert.Equal(unchecked((nuint)0x0000000000000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x0000000000000001), out result)); + Assert.Equal(unchecked((nuint)0x0000000000000001), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x7FFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x8000000000000000), out result)); + Assert.Equal(unchecked((nuint)0x8000000000000000), result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFFFFFFFFFF), out result)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), result); + } + else + { + Assert.True(NumberHelper.TryCreate((nuint)0x00000000, out result)); + Assert.Equal((nuint)0x00000000, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x00000001, out result)); + Assert.Equal((nuint)0x00000001, result); + + Assert.True(NumberHelper.TryCreate((nuint)0x7FFFFFFF, out result)); + Assert.Equal((nuint)0x7FFFFFFF, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0x80000000), out result)); + Assert.Equal((nuint)0x80000000, result); + + Assert.True(NumberHelper.TryCreate(unchecked((nuint)0xFFFFFFFF), out result)); + Assert.Equal((nuint)0xFFFFFFFF, result); + } + } + + [Fact] + + public static void op_LeftShiftTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), ShiftOperatorsHelper.op_LeftShift(unchecked((nuint)0x0000000000000000), 1)); + Assert.Equal(unchecked((nuint)0x0000000000000002), ShiftOperatorsHelper.op_LeftShift(unchecked((nuint)0x0000000000000001), 1)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFE), ShiftOperatorsHelper.op_LeftShift(unchecked((nuint)0x7FFFFFFFFFFFFFFF), 1)); + Assert.Equal(unchecked((nuint)0x0000000000000000), ShiftOperatorsHelper.op_LeftShift(unchecked((nuint)0x8000000000000000), 1)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFE), ShiftOperatorsHelper.op_LeftShift(unchecked((nuint)0xFFFFFFFFFFFFFFFF), 1)); + } + else + { + Assert.Equal((nuint)0x00000000, ShiftOperatorsHelper.op_LeftShift((nuint)0x00000000, 1)); + Assert.Equal((nuint)0x00000002, ShiftOperatorsHelper.op_LeftShift((nuint)0x00000001, 1)); + Assert.Equal((nuint)0xFFFFFFFE, ShiftOperatorsHelper.op_LeftShift((nuint)0x7FFFFFFF, 1)); + Assert.Equal((nuint)0x00000000, ShiftOperatorsHelper.op_LeftShift((nuint)0x80000000, 1)); + Assert.Equal((nuint)0xFFFFFFFE, ShiftOperatorsHelper.op_LeftShift((nuint)0xFFFFFFFF, 1)); + } + } + + [Fact] + public static void op_RightShiftTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), ShiftOperatorsHelper.op_RightShift(unchecked((nuint)0x0000000000000000), 1)); + Assert.Equal(unchecked((nuint)0x0000000000000000), ShiftOperatorsHelper.op_RightShift(unchecked((nuint)0x0000000000000001), 1)); + Assert.Equal(unchecked((nuint)0x3FFFFFFFFFFFFFFF), ShiftOperatorsHelper.op_RightShift(unchecked((nuint)0x7FFFFFFFFFFFFFFF), 1)); + Assert.Equal(unchecked((nuint)0x4000000000000000), ShiftOperatorsHelper.op_RightShift(unchecked((nuint)0x8000000000000000), 1)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), ShiftOperatorsHelper.op_RightShift(unchecked((nuint)0xFFFFFFFFFFFFFFFF), 1)); + } + else + { + Assert.Equal((nuint)0x00000000, ShiftOperatorsHelper.op_RightShift((nuint)0x00000000, 1)); + Assert.Equal((nuint)0x00000000, ShiftOperatorsHelper.op_RightShift((nuint)0x00000001, 1)); + Assert.Equal((nuint)0x3FFFFFFF, ShiftOperatorsHelper.op_RightShift((nuint)0x7FFFFFFF, 1)); + Assert.Equal((nuint)0x40000000, ShiftOperatorsHelper.op_RightShift((nuint)0x80000000, 1)); + Assert.Equal((nuint)0x7FFFFFFF, ShiftOperatorsHelper.op_RightShift((nuint)0xFFFFFFFF, 1)); + } + } + + [Fact] + public static void op_SubtractionTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), SubtractionOperatorsHelper.op_Subtraction(unchecked((nuint)0x0000000000000000), (nuint)1)); + Assert.Equal(unchecked((nuint)0x0000000000000000), SubtractionOperatorsHelper.op_Subtraction(unchecked((nuint)0x0000000000000001), (nuint)1)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFE), SubtractionOperatorsHelper.op_Subtraction(unchecked((nuint)0x7FFFFFFFFFFFFFFF), (nuint)1)); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), SubtractionOperatorsHelper.op_Subtraction(unchecked((nuint)0x8000000000000000), (nuint)1)); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFE), SubtractionOperatorsHelper.op_Subtraction(unchecked((nuint)0xFFFFFFFFFFFFFFFF), (nuint)1)); + } + else + { + Assert.Equal((nuint)0xFFFFFFFF, SubtractionOperatorsHelper.op_Subtraction((nuint)0x00000000, (nuint)1)); + Assert.Equal((nuint)0x00000000, SubtractionOperatorsHelper.op_Subtraction((nuint)0x00000001, (nuint)1)); + Assert.Equal((nuint)0x7FFFFFFE, SubtractionOperatorsHelper.op_Subtraction((nuint)0x7FFFFFFF, (nuint)1)); + Assert.Equal((nuint)0x7FFFFFFF, SubtractionOperatorsHelper.op_Subtraction((nuint)0x80000000, (nuint)1)); + Assert.Equal((nuint)0xFFFFFFFE, SubtractionOperatorsHelper.op_Subtraction((nuint)0xFFFFFFFF, (nuint)1)); + } + } + + [Fact] + public static void op_UnaryNegationTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x8000000000000001), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x8000000000000000), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000001), UnaryNegationOperatorsHelper.op_UnaryNegation(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, UnaryNegationOperatorsHelper.op_UnaryNegation((nuint)0x00000000)); + Assert.Equal((nuint)0xFFFFFFFF, UnaryNegationOperatorsHelper.op_UnaryNegation((nuint)0x00000001)); + Assert.Equal((nuint)0x80000001, UnaryNegationOperatorsHelper.op_UnaryNegation((nuint)0x7FFFFFFF)); + Assert.Equal((nuint)0x80000000, UnaryNegationOperatorsHelper.op_UnaryNegation((nuint)0x80000000)); + Assert.Equal((nuint)0x00000001, UnaryNegationOperatorsHelper.op_UnaryNegation((nuint)0xFFFFFFFF)); + } + } + + [Fact] + public static void op_UnaryPlusTest() + { + if (Environment.Is64BitProcess) + { + Assert.Equal(unchecked((nuint)0x0000000000000000), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((nuint)0x0000000000000000))); + Assert.Equal(unchecked((nuint)0x0000000000000001), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((nuint)0x0000000000000001))); + Assert.Equal(unchecked((nuint)0x7FFFFFFFFFFFFFFF), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((nuint)0x7FFFFFFFFFFFFFFF))); + Assert.Equal(unchecked((nuint)0x8000000000000000), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((nuint)0x8000000000000000))); + Assert.Equal(unchecked((nuint)0xFFFFFFFFFFFFFFFF), UnaryPlusOperatorsHelper.op_UnaryPlus(unchecked((nuint)0xFFFFFFFFFFFFFFFF))); + } + else + { + Assert.Equal((nuint)0x00000000, UnaryPlusOperatorsHelper.op_UnaryPlus((nuint)0x00000000)); + Assert.Equal((nuint)0x00000001, UnaryPlusOperatorsHelper.op_UnaryPlus((nuint)0x00000001)); + Assert.Equal((nuint)0x7FFFFFFF, UnaryPlusOperatorsHelper.op_UnaryPlus((nuint)0x7FFFFFFF)); + Assert.Equal((nuint)0x80000000, UnaryPlusOperatorsHelper.op_UnaryPlus((nuint)0x80000000)); + Assert.Equal((nuint)0xFFFFFFFF, UnaryPlusOperatorsHelper.op_UnaryPlus((nuint)0xFFFFFFFF)); + } + } + + [Theory] + [MemberData(nameof(UIntPtrTests.Parse_Valid_TestData), MemberType = typeof(UIntPtrTests))] + public static void ParseValidStringTest(string value, NumberStyles style, IFormatProvider provider, nuint expected) + { + nuint result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.True(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Equal(expected, ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.True(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(UIntPtrTests.Parse_Invalid_TestData), MemberType = typeof(UIntPtrTests))] + public static void ParseInvalidStringTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + nuint result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(ParseableHelper.TryParse(value, provider, out result)); + Assert.Equal(default(nuint), result); + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Default provider + if (provider is null) + { + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + + // Substitute default NumberFormatInfo + Assert.False(NumberHelper.TryParse(value, style, new NumberFormatInfo(), out result)); + Assert.Equal(default(nuint), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, new NumberFormatInfo())); + } + + // Default style + if (style == NumberStyles.Integer) + { + Assert.Throws(exceptionType, () => ParseableHelper.Parse(value, provider)); + } + + // Full overloads + Assert.False(NumberHelper.TryParse(value, style, provider, out result)); + Assert.Equal(default(nuint), result); + Assert.Throws(exceptionType, () => NumberHelper.Parse(value, style, provider)); + } + + [Theory] + [MemberData(nameof(UIntPtrTests.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(UIntPtrTests))] + public static void ParseValidSpanTest(string value, int offset, int count, NumberStyles style, IFormatProvider provider, nuint expected) + { + nuint result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(SpanParseableHelper.TryParse(value.AsSpan(offset, count), provider, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, NumberHelper.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(NumberHelper.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(UIntPtrTests.Parse_Invalid_TestData), MemberType = typeof(UIntPtrTests))] + public static void ParseInvalidSpanTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value is null) + { + return; + } + + nuint result; + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(SpanParseableHelper.TryParse(value.AsSpan(), provider, out result)); + Assert.Equal(default(nuint), result); + } + + Assert.Throws(exceptionType, () => NumberHelper.Parse(value.AsSpan(), style, provider)); + + Assert.False(NumberHelper.TryParse(value.AsSpan(), style, provider, out result)); + Assert.Equal(default(nuint), result); + } + } +} From a31d0d57eff985a68828c37775d103b91032f786 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 11 Jul 2021 14:13:35 -0400 Subject: [PATCH 009/133] Move PosixSignalRegistration to corelib (#55433) * Move PosixSignalRegistration to corelib * Fix trimming issue on mobile targets --- .../TestUtilities/System/PlatformDetection.cs | 3 +- .../System.Private.CoreLib.Shared.projitems | 15 +++++++++ .../Runtime/InteropServices/PosixSignal.cs | 0 .../InteropServices/PosixSignalContext.cs | 0 ...ignalRegistration.PlatformNotSupported.cs} | 3 ++ .../PosixSignalRegistration.Unix.cs | 0 .../PosixSignalRegistration.Windows.cs | 0 .../PosixSignalRegistration.cs | 0 .../src/Resources/Strings.resx | 3 -- .../src/System.Runtime.InteropServices.csproj | 27 ++-------------- ...ystem.Runtime.InteropServices.Tests.csproj | 5 +-- .../PosixSignalRegistrationTests.Browser.cs | 29 ----------------- .../PosixSignalRegistrationTests.Unix.cs | 32 +++++++++++++------ 13 files changed, 46 insertions(+), 71 deletions(-) rename src/libraries/{System.Runtime.InteropServices => System.Private.CoreLib}/src/System/Runtime/InteropServices/PosixSignal.cs (100%) rename src/libraries/{System.Runtime.InteropServices => System.Private.CoreLib}/src/System/Runtime/InteropServices/PosixSignalContext.cs (100%) rename src/libraries/{System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalRegistration.Unsupported.cs => System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.PlatformNotSupported.cs} (80%) rename src/libraries/{System.Runtime.InteropServices => System.Private.CoreLib}/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs (100%) rename src/libraries/{System.Runtime.InteropServices => System.Private.CoreLib}/src/System/Runtime/InteropServices/PosixSignalRegistration.Windows.cs (100%) rename src/libraries/{System.Runtime.InteropServices => System.Private.CoreLib}/src/System/Runtime/InteropServices/PosixSignalRegistration.cs (100%) delete mode 100644 src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/PosixSignalRegistrationTests.Browser.cs diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index e11da7e8d4d9..90f21d02f5c4 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -37,7 +37,8 @@ public static partial class PlatformDetection public static bool IsSolaris => RuntimeInformation.IsOSPlatform(OSPlatform.Create("SOLARIS")); public static bool IsBrowser => RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")); public static bool IsNotBrowser => !IsBrowser; - public static bool IsNotMobile => IsNotBrowser && !IsMacCatalyst && !IsiOS && !IstvOS && !IsAndroid; + public static bool IsMobile => IsBrowser || IsMacCatalyst || IsiOS || IstvOS || IsAndroid; + public static bool IsNotMobile => !IsMobile; public static bool IsNotNetFramework => !IsNetFramework; public static bool IsArmProcess => RuntimeInformation.ProcessArchitecture == Architecture.Arm; diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index a1e270dbde97..3dcfc2c7313a 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -837,6 +837,9 @@ + + + @@ -1235,6 +1238,7 @@ + @@ -1651,6 +1655,9 @@ Common\Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs + + Common\Interop\Windows\Interop.SetConsoleCtrlHandler.cs + Common\Interop\Windows\Kernel32\Interop.SetCurrentDirectory.cs @@ -1833,6 +1840,7 @@ + @@ -2129,6 +2137,10 @@ Common\Interop\Unix\System.Native\Interop.GetPid.cs + + + + + diff --git a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignal.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignal.cs similarity index 100% rename from src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignal.cs rename to src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignal.cs diff --git a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalContext.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalContext.cs similarity index 100% rename from src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalContext.cs rename to src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalContext.cs diff --git a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalRegistration.Unsupported.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.PlatformNotSupported.cs similarity index 80% rename from src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalRegistration.Unsupported.cs rename to src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.PlatformNotSupported.cs index 4bfd2eae0be6..3ccc169b1a84 100644 --- a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalRegistration.Unsupported.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.PlatformNotSupported.cs @@ -1,12 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; + namespace System.Runtime.InteropServices { public sealed partial class PosixSignalRegistration { private PosixSignalRegistration() { } + [DynamicDependency("#ctor")] // Prevent the private ctor and the IDisposable implementation from getting linked away public static partial PosixSignalRegistration Create(PosixSignal signal, Action handler) { if (handler is null) diff --git a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs similarity index 100% rename from src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs rename to src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs diff --git a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalRegistration.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Windows.cs similarity index 100% rename from src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalRegistration.Windows.cs rename to src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Windows.cs diff --git a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalRegistration.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.cs similarity index 100% rename from src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalRegistration.cs rename to src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.cs diff --git a/src/libraries/System.Runtime.InteropServices/src/Resources/Strings.resx b/src/libraries/System.Runtime.InteropServices/src/Resources/Strings.resx index d7644789f28c..08d7e045a580 100644 --- a/src/libraries/System.Runtime.InteropServices/src/Resources/Strings.resx +++ b/src/libraries/System.Runtime.InteropServices/src/Resources/Strings.resx @@ -111,7 +111,4 @@ Specified file length was too large for the file system. - - Cannot create '{0}' because a file or directory with the same name already exists. - diff --git a/src/libraries/System.Runtime.InteropServices/src/System.Runtime.InteropServices.csproj b/src/libraries/System.Runtime.InteropServices/src/System.Runtime.InteropServices.csproj index 7137155589b2..3bc59997f7f0 100644 --- a/src/libraries/System.Runtime.InteropServices/src/System.Runtime.InteropServices.csproj +++ b/src/libraries/System.Runtime.InteropServices/src/System.Runtime.InteropServices.csproj @@ -2,7 +2,7 @@ true enable - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser + $(NetCoreAppCurrent) @@ -33,9 +33,6 @@ - - - @@ -51,27 +48,7 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + diff --git a/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj b/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj index 33204d0ee7cc..f0ffe70d47c3 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj @@ -1,7 +1,7 @@ true - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix true true @@ -192,7 +192,4 @@ - - - diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/PosixSignalRegistrationTests.Browser.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/PosixSignalRegistrationTests.Browser.cs deleted file mode 100644 index 2202ed25744e..000000000000 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/PosixSignalRegistrationTests.Browser.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Linq; -using System.Runtime.InteropServices; -using System.Collections.Generic; - -namespace System.Tests -{ - public partial class PosixSignalRegistrationTests - { - public static IEnumerable UninstallableSignals() => Enumerable.Empty(); - - public static IEnumerable SupportedSignals() => Enumerable.Empty(); - - public static IEnumerable UnsupportedSignals() - { - foreach (PosixSignal signal in Enum.GetValues()) - { - yield return new object[] { signal }; - } - - yield return new object[] { 0 }; - yield return new object[] { 3 }; - yield return new object[] { -1000 }; - yield return new object[] { 1000 }; - } - } -} diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/PosixSignalRegistrationTests.Unix.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/PosixSignalRegistrationTests.Unix.cs index 1716a3ba24f7..e08d77565513 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/PosixSignalRegistrationTests.Unix.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/PosixSignalRegistrationTests.Unix.cs @@ -14,23 +14,37 @@ public partial class PosixSignalRegistrationTests { public static IEnumerable UninstallableSignals() { - yield return new object[] { (PosixSignal)9 }; + if (PlatformDetection.IsNotMobile) + { + yield return new object[] { (PosixSignal)9 }; + } } public static IEnumerable SupportedSignals() { - foreach (PosixSignal value in Enum.GetValues(typeof(PosixSignal))) - yield return new object[] { value }; + if (PlatformDetection.IsNotMobile) + { + foreach (PosixSignal value in Enum.GetValues(typeof(PosixSignal))) + yield return new object[] { value }; + } } public static IEnumerable UnsupportedSignals() { + if (PlatformDetection.IsMobile) + { + foreach (PosixSignal value in Enum.GetValues(typeof(PosixSignal))) + yield return new object[] { value }; + } + yield return new object[] { 0 }; yield return new object[] { -1000 }; yield return new object[] { 1000 }; } - [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public static bool NotMobileAndRemoteExecutable => PlatformDetection.IsNotMobile && RemoteExecutor.IsSupported; + + [ConditionalTheory(nameof(NotMobileAndRemoteExecutable))] [MemberData(nameof(SupportedSignals))] public void SignalHandlerCalledForKnownSignals(PosixSignal s) { @@ -55,7 +69,7 @@ public void SignalHandlerCalledForKnownSignals(PosixSignal s) }, s.ToString()).Dispose(); } - [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [ConditionalTheory(nameof(NotMobileAndRemoteExecutable))] [MemberData(nameof(PosixSignalAsRawValues))] public void SignalHandlerCalledForRawSignals(PosixSignal s) { @@ -80,7 +94,7 @@ public void SignalHandlerCalledForRawSignals(PosixSignal s) }, s.ToString()).Dispose(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile))] public void SignalHandlerWorksForSecondRegistration() { PosixSignal signal = PosixSignal.SIGCONT; @@ -104,7 +118,7 @@ public void SignalHandlerWorksForSecondRegistration() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile))] public void SignalHandlerNotCalledWhenDisposed() { PosixSignal signal = PosixSignal.SIGCONT; @@ -118,7 +132,7 @@ public void SignalHandlerNotCalledWhenDisposed() Thread.Sleep(100); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile))] public void SignalHandlerNotCalledWhenFinalized() { PosixSignal signal = PosixSignal.SIGCONT; @@ -140,7 +154,7 @@ void CreateDanglingRegistration() } } - [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [ConditionalTheory(nameof(NotMobileAndRemoteExecutable))] [InlineData(PosixSignal.SIGINT, true, 0)] [InlineData(PosixSignal.SIGINT, false, 130)] [InlineData(PosixSignal.SIGTERM, true, 0)] From 7d518fe3d0963f9f2cbe7aa971df200d2f8e67c7 Mon Sep 17 00:00:00 2001 From: Zoltan Varga Date: Sun, 11 Jul 2021 15:59:37 -0400 Subject: [PATCH 010/133] [mono] Improve aot code generation for isinst for sealed classes. (#55450) Generate the same code as in the non-aot case, i.e. compare the vtable with a constant. --- src/mono/mono/mini/type-checking.c | 46 ++++++++++++------------------ 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/src/mono/mono/mini/type-checking.c b/src/mono/mono/mini/type-checking.c index 8f6f93e1f356..b623c603741a 100644 --- a/src/mono/mono/mini/type-checking.c +++ b/src/mono/mono/mini/type-checking.c @@ -429,15 +429,11 @@ handle_castclass (MonoCompile *cfg, MonoClass *klass, MonoInst *src, int context MONO_EMIT_NEW_LOAD_MEMBASE (cfg, vtable_reg, obj_reg, MONO_STRUCT_OFFSET (MonoObject, vtable)); - if (!m_class_get_rank (klass) && !cfg->compile_aot && mono_class_is_sealed (klass)) { - /* the remoting code is broken, access the class for now */ - if (0) { /*FIXME what exactly is broken? This change refers to r39380 from 2005 and mention some remoting fixes were due.*/ - MonoVTable *vt = mono_class_vtable_checked (klass, cfg->error); - if (!is_ok (cfg->error)) { - mono_cfg_set_exception (cfg, MONO_EXCEPTION_MONO_ERROR); - return NULL; - } - MONO_EMIT_NEW_BIALU_IMM (cfg, OP_COMPARE_IMM, -1, vtable_reg, (gsize)vt); + if (!m_class_get_rank (klass) && mono_class_is_sealed (klass)) { + if (cfg->compile_aot) { + MonoInst *vtable_ins; + EMIT_NEW_AOTCONST (cfg, vtable_ins, MONO_PATCH_INFO_VTABLE, klass); + MONO_EMIT_NEW_BIALU (cfg, OP_COMPARE, -1, vtable_reg, vtable_ins->dreg); } else { MONO_EMIT_NEW_LOAD_MEMBASE (cfg, klass_reg, vtable_reg, MONO_STRUCT_OFFSET (MonoVTable, klass)); MONO_EMIT_NEW_BIALU_IMM (cfg, OP_COMPARE_IMM, -1, klass_reg, (gsize)klass); @@ -584,28 +580,22 @@ handle_isinst (MonoCompile *cfg, MonoClass *klass, MonoInst *src, int context_us MONO_EMIT_NEW_LOAD_MEMBASE (cfg, klass_reg, vtable_reg, MONO_STRUCT_OFFSET (MonoVTable, klass)); /* the is_null_bb target simply copies the input register to the output */ mini_emit_isninst_cast (cfg, klass_reg, m_class_get_cast_class (klass), false_bb, is_null_bb); - } else { - if (!cfg->compile_aot && mono_class_is_sealed (klass)) { - g_assert (!context_used); - /* the remoting code is broken, access the class for now */ - if (0) {/*FIXME what exactly is broken? This change refers to r39380 from 2005 and mention some remoting fixes were due.*/ - MonoVTable *vt = mono_class_vtable_checked (klass, cfg->error); - if (!is_ok (cfg->error)) { - mono_cfg_set_exception (cfg, MONO_EXCEPTION_MONO_ERROR); - return NULL; - } - MONO_EMIT_NEW_BIALU_IMM (cfg, OP_COMPARE_IMM, -1, vtable_reg, (gsize)vt); - } else { - MONO_EMIT_NEW_LOAD_MEMBASE (cfg, klass_reg, vtable_reg, MONO_STRUCT_OFFSET (MonoVTable, klass)); - MONO_EMIT_NEW_BIALU_IMM (cfg, OP_COMPARE_IMM, -1, klass_reg, (gsize)klass); - } - MONO_EMIT_NEW_BRANCH_BLOCK (cfg, OP_PBNE_UN, false_bb); - MONO_EMIT_NEW_BRANCH_BLOCK (cfg, OP_BR, is_null_bb); + } else if (mono_class_is_sealed (klass)) { + g_assert (!context_used); + if (cfg->compile_aot) { + MonoInst *vtable_ins; + EMIT_NEW_AOTCONST (cfg, vtable_ins, MONO_PATCH_INFO_VTABLE, klass); + MONO_EMIT_NEW_BIALU (cfg, OP_COMPARE, -1, vtable_reg, vtable_ins->dreg); } else { MONO_EMIT_NEW_LOAD_MEMBASE (cfg, klass_reg, vtable_reg, MONO_STRUCT_OFFSET (MonoVTable, klass)); - /* the is_null_bb target simply copies the input register to the output */ - mini_emit_isninst_cast_inst (cfg, klass_reg, klass, klass_inst, false_bb, is_null_bb); + MONO_EMIT_NEW_BIALU_IMM (cfg, OP_COMPARE_IMM, -1, klass_reg, (gsize)klass); } + MONO_EMIT_NEW_BRANCH_BLOCK (cfg, OP_PBNE_UN, false_bb); + MONO_EMIT_NEW_BRANCH_BLOCK (cfg, OP_BR, is_null_bb); + } else { + MONO_EMIT_NEW_LOAD_MEMBASE (cfg, klass_reg, vtable_reg, MONO_STRUCT_OFFSET (MonoVTable, klass)); + /* the is_null_bb target simply copies the input register to the output */ + mini_emit_isninst_cast_inst (cfg, klass_reg, klass, klass_inst, false_bb, is_null_bb); } } From c721c87704434363ee2310ab04b97407c0a12eac Mon Sep 17 00:00:00 2001 From: Noah Falk Date: Sun, 11 Jul 2021 17:30:04 -0700 Subject: [PATCH 011/133] Improve MetricsEventSource error handling (#55466) Previously exceptions that escaped user provided callbacks for observable instruments would have terminated all further metric collection. Now those exceptions are caught and reported, only potentially interfering with other observable instruments in the same collection interval. --- .../Diagnostics/Metrics/AggregationManager.cs | 14 ++++++- .../Diagnostics/Metrics/MetricsEventSource.cs | 9 ++++- .../tests/MetricEventSourceTests.cs | 38 +++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/AggregationManager.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/AggregationManager.cs index 41bb991cda7c..d181fbdf9a66 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/AggregationManager.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/AggregationManager.cs @@ -41,6 +41,7 @@ internal sealed class AggregationManager private readonly Action _collectionError; private readonly Action _timeSeriesLimitReached; private readonly Action _histogramLimitReached; + private readonly Action _observableInstrumentCallbackError; public AggregationManager( int maxTimeSeries, @@ -54,7 +55,8 @@ public AggregationManager( Action initialInstrumentEnumerationComplete, Action collectionError, Action timeSeriesLimitReached, - Action histogramLimitReached) + Action histogramLimitReached, + Action observableInstrumentCallbackError) { _maxTimeSeries = maxTimeSeries; _maxHistograms = maxHistograms; @@ -68,6 +70,7 @@ public AggregationManager( _collectionError = collectionError; _timeSeriesLimitReached = timeSeriesLimitReached; _histogramLimitReached = histogramLimitReached; + _observableInstrumentCallbackError = observableInstrumentCallbackError; _listener = new MeterListener() { @@ -351,7 +354,14 @@ private bool CheckHistogramAllowed() internal void Collect() { - _listener.RecordObservableInstruments(); + try + { + _listener.RecordObservableInstruments(); + } + catch (Exception e) + { + _observableInstrumentCallbackError(e); + } foreach (KeyValuePair kv in _instrumentStates) { diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MetricsEventSource.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MetricsEventSource.cs index 80baa185a09f..38c4216bdcdd 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MetricsEventSource.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MetricsEventSource.cs @@ -169,6 +169,12 @@ public void HistogramLimitReached(string sessionId) WriteEvent(13, sessionId); } + [Event(14, Keywords = Keywords.TimeSeriesValues)] + public void ObservableInstrumentCallbackError(string sessionId, string errorMessage) + { + WriteEvent(14, sessionId, errorMessage); + } + /// /// Called when the EventSource gets a command from a EventListener or ETW. /// @@ -303,7 +309,8 @@ public void OnEventCommand(EventCommandEventArgs command) () => Log.InitialInstrumentEnumerationComplete(sessionId), e => Log.Error(sessionId, e.ToString()), () => Log.TimeSeriesLimitReached(sessionId), - () => Log.HistogramLimitReached(sessionId)); + () => Log.HistogramLimitReached(sessionId), + e => Log.ObservableInstrumentCallbackError(sessionId, e.ToString())); _aggregationManager.SetCollectionPeriod(TimeSpan.FromSeconds(refreshIntervalSecs)); diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs index 9cd9501d8c5e..ce68dcf98f81 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs @@ -619,6 +619,33 @@ public void EventSourceEnforcesHistogramLimit() AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + public void EventSourceHandlesObservableCallbackException() + { + using Meter meter = new Meter("TestMeter15"); + Counter c = meter.CreateCounter("counter1"); + ObservableCounter oc = meter.CreateObservableCounter("observableCounter1", + (Func)(() => { throw new Exception("Example user exception"); })); + + EventWrittenEventArgs[] events; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter15")) + { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + c.Add(12); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); + events = listener.Events.ToArray(); + } + + AssertBeginInstrumentReportingEventsPresent(events, c, oc); + AssertInitialEnumerationCompleteEventPresent(events); + AssertCounterEventsPresent(events, meter.Name, c.Name, "", "", "5", "12"); + AssertObservableCallbackErrorPresent(events); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); + } + private void AssertBeginInstrumentReportingEventsPresent(EventWrittenEventArgs[] events, params Instrument[] expectedInstruments) { var beginReportEvents = events.Where(e => e.EventName == "BeginInstrumentReporting").Select(e => @@ -833,6 +860,17 @@ private void AssertCollectStartStopEventsPresent(EventWrittenEventArgs[] events, Assert.Equal(expectedPairs, startEventsSeen); Assert.Equal(expectedPairs, stopEventsSeen); } + + private void AssertObservableCallbackErrorPresent(EventWrittenEventArgs[] events) + { + var errorEvents = events.Where(e => e.EventName == "ObservableInstrumentCallbackError").Select(e => + new + { + ErrorText = e.Payload[1].ToString(), + }).ToArray(); + Assert.NotEmpty(errorEvents); + Assert.Contains("Example user exception", errorEvents[0].ErrorText); + } } class MetricsEventListener : EventListener From a4590197c1011384ba8a8469926ade0e72fa9e97 Mon Sep 17 00:00:00 2001 From: Adeel Mujahid <3840695+am11@users.noreply.github.com> Date: Mon, 12 Jul 2021 04:07:13 +0300 Subject: [PATCH 012/133] Use init rather than ms extension syntax (#55475) --- src/coreclr/jit/emit.cpp | 4 ++-- src/coreclr/jit/jitstd/list.h | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/emit.cpp b/src/coreclr/jit/emit.cpp index ca6cd0c4a713..21e37879eed7 100644 --- a/src/coreclr/jit/emit.cpp +++ b/src/coreclr/jit/emit.cpp @@ -8130,8 +8130,8 @@ void emitter::emitInitIG(insGroup* ig) #ifdef DEBUG ig->lastGeneratedBlock = nullptr; - // Explicitly call the constructor, since IGs don't actually have a constructor. - ig->igBlocks.jitstd::list::list(emitComp->getAllocator(CMK_LoopOpt)); + // Explicitly call init, since IGs don't actually have a constructor. + ig->igBlocks.jitstd::list::init(emitComp->getAllocator(CMK_LoopOpt)); #endif } diff --git a/src/coreclr/jit/jitstd/list.h b/src/coreclr/jit/jitstd/list.h index 070d94361f2a..b573a3952fb1 100644 --- a/src/coreclr/jit/jitstd/list.h +++ b/src/coreclr/jit/jitstd/list.h @@ -160,6 +160,17 @@ class list Node* m_pNode; }; +#ifdef DEBUG + void init(const Allocator& a) + { + m_pHead = nullptr; + m_pTail = nullptr; + m_nSize = 0; + m_allocator = a; + m_nodeAllocator = a; + } +#endif + explicit list(const Allocator&); list(size_type n, const T& value, const Allocator&); From 9f98044e3e0c38ed739e90db1eaf998e35b8351e Mon Sep 17 00:00:00 2001 From: Juan Hoyos Date: Sun, 11 Jul 2021 18:59:57 -0700 Subject: [PATCH 013/133] Fix mono CORDBI build (#55482) --- src/mono/dlls/mscordbi/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mono/dlls/mscordbi/CMakeLists.txt b/src/mono/dlls/mscordbi/CMakeLists.txt index 32e61b129f34..47924e8b9a7a 100644 --- a/src/mono/dlls/mscordbi/CMakeLists.txt +++ b/src/mono/dlls/mscordbi/CMakeLists.txt @@ -123,6 +123,8 @@ add_subdirectory(${CLR_DIR}/md/compiler md/compiler) include(${CLR_DIR}/clrdefinitions.cmake) include_directories(${CMAKE_CURRENT_BINARY_DIR}/../../) include_directories(${CMAKE_CURRENT_BINARY_DIR}/../../inc/) +include_directories(${CLR_DIR}/minipal) + add_subdirectory(${CLR_DIR}/md/enc md/enc) add_subdirectory(${CLR_DIR}/utilcode utilcode) if (CLR_CMAKE_HOST_UNIX) From 3b724ff05ef419ef18c65b937e35b4a0bf8c3889 Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Sun, 11 Jul 2021 19:27:46 -0700 Subject: [PATCH 014/133] disable Quic AcceptStream_ConnectionAborted_ByClient_Throws on Linux (#55481) --- .../System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs index 8c81ce800dc8..41b8d549ed74 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs @@ -36,6 +36,7 @@ public async Task TestConnect() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/55242", TestPlatforms.Linux)] public async Task AcceptStream_ConnectionAborted_ByClient_Throws() { const int ExpectedErrorCode = 1234; From 6ece5d3c70052b01a62bd19d1bba72839cf31aed Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Sun, 11 Jul 2021 20:17:19 -0700 Subject: [PATCH 015/133] use updated fedora image with msquic (#55424) * use updated fedora image with msquic * fix ping --- eng/pipelines/libraries/helix-queues-setup.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/pipelines/libraries/helix-queues-setup.yml b/eng/pipelines/libraries/helix-queues-setup.yml index 7d33d26edb01..75d6cbd21221 100644 --- a/eng/pipelines/libraries/helix-queues-setup.yml +++ b/eng/pipelines/libraries/helix-queues-setup.yml @@ -57,7 +57,7 @@ jobs: - (Centos.8.Amd64.Open)Ubuntu.1604.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-8-helix-20201229003624-c1bf759 - RedHat.7.Amd64.Open - SLES.15.Amd64.Open - - (Fedora.32.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-32-helix-20200512010618-efb9f14 + - (Fedora.32.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-32-helix-20210710031120-870c408 - (Ubuntu.1910.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-19.10-helix-amd64-cfcfd50-20191030180623 - (Debian.10.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-bfcd90a-20200121150006 - ${{ if or(ne(parameters.jobParameters.testScope, 'outerloop'), ne(parameters.jobParameters.runtimeFlavor, 'mono')) }}: @@ -70,7 +70,7 @@ jobs: - SLES.12.Amd64.Open - SLES.15.Amd64.Open - (Fedora.30.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-30-helix-20200512010621-4f8cef7 - - (Fedora.32.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-32-helix-20200512010618-efb9f14 + - (Fedora.32.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-32-helix-20210710031120-870c408 - (Ubuntu.1910.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-19.10-helix-amd64-cfcfd50-20191030180623 - (Debian.10.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-bfcd90a-20200121150006 - ${{ if eq(parameters.jobParameters.isFullMatrix, false) }}: @@ -80,7 +80,7 @@ jobs: - Ubuntu.1604.Amd64.Open - Ubuntu.1804.Amd64.Open - SLES.15.Amd64.Open - - (Fedora.30.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-30-helix-20200512010621-4f8cef7 + - (Fedora.32.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-32-helix-20210710031120-870c408 - ${{ if or(eq(parameters.jobParameters.interpreter, 'true'), eq(parameters.jobParameters.isSingleFile, true)) }}: # Limiting interp runs as we don't need as much coverage. - Debian.9.Amd64.Open From d766727db87d01c9bdf321565a99d7cfe65d5f6b Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Sun, 11 Jul 2021 20:32:52 -0700 Subject: [PATCH 016/133] make quic tracing easir to correlate with MsQuic (#55483) --- .../MsQuic/MsQuicConnection.cs | 18 ++++++++++--- .../Implementations/MsQuic/MsQuicStream.cs | 27 ++++++++++++------- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs index dd651ad0ab51..c5df9a21b468 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs @@ -83,6 +83,7 @@ public void RemoveStream(MsQuicStream? stream) if (releaseHandles) { + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"{TraceId()} releasing handle after last stream."); Handle?.Dispose(); } } @@ -123,8 +124,15 @@ public void SetClosing() _closing = true; } } + + internal string TraceId() + { + return $"[MsQuicConnection#{this.GetHashCode()}/{Handle?.DangerousGetHandle():x}]"; + } } + internal string TraceId() => _state.TraceId(); + // constructor for inbound connections public MsQuicConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SafeMsQuicConnectionHandle handle, bool remoteCertificateRequired = false, X509RevocationMode revocationMode = X509RevocationMode.Offline, RemoteCertificateValidationCallback? remoteCertificateValidationCallback = null) { @@ -162,7 +170,7 @@ public MsQuicConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, Saf if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(_state, $"[Connection#{_state.GetHashCode()}] inbound connection created"); + NetEventSource.Info(_state, $"{TraceId()} inbound connection created"); } } @@ -201,7 +209,7 @@ public MsQuicConnection(QuicClientConnectionOptions options) if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(_state, $"[Connection#{_state.GetHashCode()}] outbound connection created"); + NetEventSource.Info(_state, $"{TraceId()} outbound connection created"); } } @@ -621,7 +629,7 @@ private static uint NativeCallbackHandler( if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(state, $"[Connection#{state.GetHashCode()}] received event {connectionEvent.Type}"); + NetEventSource.Info(state, $"{state.TraceId()} received event {connectionEvent.Type}"); } try @@ -697,6 +705,8 @@ private void Dispose(bool disposing) return; } + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(_state, $"{TraceId()} disposing {disposing}"); + bool releaseHandles = false; lock (_state) { @@ -716,6 +726,8 @@ private void Dispose(bool disposing) _configuration?.Dispose(); if (releaseHandles) { + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(_state, $"{TraceId()} releasing handle"); + // We may not be fully initialized if constructor fails. _state.Handle?.Dispose(); } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs index fed61e11e569..f962dbc81fd7 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs @@ -69,6 +69,8 @@ private sealed class State public void Cleanup() { + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"{TraceId()} releasing handles."); + ShutdownState = ShutdownState.Finished; CleanupSendState(this); Handle?.Dispose(); @@ -77,8 +79,15 @@ public void Cleanup() if (StateGCHandle.IsAllocated) StateGCHandle.Free(); ConnectionState?.RemoveStream(null); } + + internal string TraceId() + { + return $"[MsQuicStream#{this.GetHashCode()}/{Handle?.DangerousGetHandle():x}]"; + } } + internal string TraceId() => _state.TraceId(); + // inbound. internal MsQuicStream(MsQuicConnection.State connectionState, SafeMsQuicStreamHandle streamHandle, QUIC_STREAM_OPEN_FLAGS flags) { @@ -117,8 +126,8 @@ internal MsQuicStream(MsQuicConnection.State connectionState, SafeMsQuicStreamHa { NetEventSource.Info( _state, - $"[Stream#{_state.GetHashCode()}] inbound {(flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL) ? "uni" : "bi")}directional stream created " + - $"in Connection#{_state.ConnectionState.GetHashCode()}."); + $"{TraceId()} inbound {(flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL) ? "uni" : "bi")}directional stream created " + + $"in {_state.ConnectionState.TraceId()}."); } } @@ -170,8 +179,8 @@ internal MsQuicStream(MsQuicConnection.State connectionState, QUIC_STREAM_OPEN_F { NetEventSource.Info( _state, - $"[Stream#{_state.GetHashCode()}] outbound {(flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL) ? "uni" : "bi")}directional stream created " + - $"in Connection#{_state.ConnectionState.GetHashCode()}."); + $"{TraceId()} outbound {(flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL) ? "uni" : "bi")}directional stream created " + + $"in {_state.ConnectionState.TraceId()}."); } } @@ -647,6 +656,9 @@ private void Dispose(bool disposing) return; } + + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(_state, $"{TraceId()} disposing {disposing}"); + bool callShutdown = false; bool abortRead = false; bool releaseHandles = false; @@ -698,10 +710,7 @@ private void Dispose(bool disposing) _state.Cleanup(); } - if (NetEventSource.Log.IsEnabled()) - { - NetEventSource.Info(_state, $"[Stream#{_state.GetHashCode()}] disposed"); - } + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(_state, $"{TraceId()} disposed"); } private void EnableReceive() @@ -726,7 +735,7 @@ private static uint HandleEvent(State state, ref StreamEvent evt) { if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(state, $"[Stream#{state.GetHashCode()}] received event {evt.Type}"); + NetEventSource.Info(state, $"{state.TraceId()} received event {evt.Type}"); } try From 98b7ed1a3b0543a31b5a0f9069cf44cb70c9230c Mon Sep 17 00:00:00 2001 From: Geoff Kizer Date: Sun, 11 Jul 2021 22:29:04 -0700 Subject: [PATCH 017/133] remove waiting on start event (#55442) Co-authored-by: Geoffrey Kizer --- .../Implementations/MsQuic/MsQuicStream.cs | 65 ++++++------------- 1 file changed, 21 insertions(+), 44 deletions(-) diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs index f962dbc81fd7..f66f05fdb1ce 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs @@ -26,9 +26,6 @@ internal sealed class MsQuicStream : QuicStreamProvider // Backing for StreamId private long _streamId = -1; - // Used to check if StartAsync has been called. - private bool _started; - private int _disposed; private sealed class State @@ -53,7 +50,7 @@ private sealed class State public int SendBufferMaxCount; public int SendBufferCount; - // Resettable completions to be used for multiple calls to send, start, and shutdown. + // Resettable completions to be used for multiple calls to send. public readonly ResettableCompletionSource SendResettableCompletionSource = new ResettableCompletionSource(); public ShutdownWriteState ShutdownWriteState; @@ -94,7 +91,6 @@ internal MsQuicStream(MsQuicConnection.State connectionState, SafeMsQuicStreamHa _state.Handle = streamHandle; _canRead = true; _canWrite = !flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL); - _started = true; if (!_canWrite) { _state.SendState = SendState.Closed; @@ -217,7 +213,7 @@ internal override async ValueTask WriteAsync(ReadOnlySequence buffers, boo { ThrowIfDisposed(); - using CancellationTokenRegistration registration = await HandleWriteStartState(cancellationToken).ConfigureAwait(false); + using CancellationTokenRegistration registration = HandleWriteStartState(cancellationToken); await SendReadOnlySequenceAsync(buffers, endStream ? QUIC_SEND_FLAGS.FIN : QUIC_SEND_FLAGS.NONE).ConfigureAwait(false); @@ -233,7 +229,7 @@ internal override async ValueTask WriteAsync(ReadOnlyMemory { ThrowIfDisposed(); - using CancellationTokenRegistration registration = await HandleWriteStartState(cancellationToken).ConfigureAwait(false); + using CancellationTokenRegistration registration = HandleWriteStartState(cancellationToken); await SendReadOnlyMemoryListAsync(buffers, endStream ? QUIC_SEND_FLAGS.FIN : QUIC_SEND_FLAGS.NONE).ConfigureAwait(false); @@ -244,14 +240,14 @@ internal override async ValueTask WriteAsync(ReadOnlyMemory buffer, bool e { ThrowIfDisposed(); - using CancellationTokenRegistration registration = await HandleWriteStartState(cancellationToken).ConfigureAwait(false); + using CancellationTokenRegistration registration = HandleWriteStartState(cancellationToken); await SendReadOnlyMemoryAsync(buffer, endStream ? QUIC_SEND_FLAGS.FIN : QUIC_SEND_FLAGS.NONE).ConfigureAwait(false); HandleWriteCompletedState(); } - private async ValueTask HandleWriteStartState(CancellationToken cancellationToken) + private CancellationTokenRegistration HandleWriteStartState(CancellationToken cancellationToken) { if (_state.SendState == SendState.Closed) { @@ -277,14 +273,7 @@ private async ValueTask HandleWriteStartState(Can } } - throw new System.OperationCanceledException(cancellationToken); - } - - // Make sure start has completed - if (!_started) - { - await _state.SendResettableCompletionSource.GetTypelessValueTask().ConfigureAwait(false); - _started = true; + throw new OperationCanceledException(cancellationToken); } // if token was already cancelled, this would execute synchronously @@ -372,7 +361,7 @@ internal override async ValueTask ReadAsync(Memory destination, Cance } } - throw new System.OperationCanceledException(cancellationToken); + throw new OperationCanceledException(cancellationToken); } if (NetEventSource.Log.IsEnabled()) @@ -740,12 +729,12 @@ private static uint HandleEvent(State state, ref StreamEvent evt) try { - switch ((QUIC_STREAM_EVENT_TYPE)evt.Type) + switch (evt.Type) { // Stream has started. // Will only be done for outbound streams (inbound streams have already started) case QUIC_STREAM_EVENT_TYPE.START_COMPLETE: - return HandleEventStartComplete(state); + return HandleEventStartComplete(state, ref evt); // Received data on the stream case QUIC_STREAM_EVENT_TYPE.RECEIVE: return HandleEventRecv(state, ref evt); @@ -778,12 +767,10 @@ private static uint HandleEvent(State state, ref StreamEvent evt) { if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Error(state, $"[Stream#{state.GetHashCode()}] Exception occurred during handling {(QUIC_STREAM_EVENT_TYPE)evt.Type} event: {ex}"); + NetEventSource.Error(state, $"[Stream#{state.GetHashCode()}] Exception occurred during handling {evt.Type} event: {ex}"); } - // This is getting hit currently - // See https://github.com/dotnet/runtime/issues/55302 - //Debug.Fail($"[Stream#{state.GetHashCode()}] Exception occurred during handling {(QUIC_STREAM_EVENT_TYPE)evt.Type} event: {ex}"); + Debug.Fail($"[Stream#{state.GetHashCode()}] Exception occurred during handling {evt.Type} event: {ex}"); return MsQuicStatusCodes.InternalError; } @@ -840,22 +827,10 @@ private static uint HandleEventPeerRecvAborted(State state, ref StreamEvent evt) return MsQuicStatusCodes.Success; } - private static uint HandleEventStartComplete(State state) + private static uint HandleEventStartComplete(State state, ref StreamEvent evt) { - bool shouldComplete = false; - lock (state) - { - // Check send state before completing as send cancellation is shared between start and send. - if (state.SendState == SendState.None) - { - shouldComplete = true; - } - } - - if (shouldComplete) - { - state.SendResettableCompletionSource.Complete(MsQuicStatusCodes.Success); - } + // TODO: We should probably check for a failure as indicated by the event data (or at least assert no failure if we aren't expecting it). + // However, since there is no definition for START_COMPLETE event data currently, we can't do this right now. return MsQuicStatusCodes.Success; } @@ -1336,7 +1311,7 @@ private enum ReadState /// /// The stream is open, but there is no data available. /// - None, + None = 0, /// /// Data is available in . @@ -1366,7 +1341,7 @@ private enum ReadState private enum ShutdownWriteState { - None, + None = 0, Canceled, Finished, ConnectionClosed @@ -1374,7 +1349,7 @@ private enum ShutdownWriteState private enum ShutdownState { - None, + None = 0, Canceled, Pending, Finished, @@ -1383,10 +1358,12 @@ private enum ShutdownState private enum SendState { - None, + None = 0, Pending, - Aborted, Finished, + + // Terminal states + Aborted, ConnectionClosed, Closed } From f463d8d644f1f2f70028a45a864b93e8c04f2cf4 Mon Sep 17 00:00:00 2001 From: Matt Thalman Date: Mon, 12 Jul 2021 01:30:10 -0700 Subject: [PATCH 018/133] Update tag references to dotnet-buildtools/prereqs (#55426) Updating Docker image tag references that are obsolete and replaced by references to mcr.microsoft.com/dotnet-buildtools/prereqs. See dotnet/dotnet-docker#2848. --- .../System.Data.Odbc/src/DatabaseSetupInstructions.md | 4 ++-- src/tests/Common/scripts/arm32_ci_script.sh | 8 ++++---- src/tests/Common/scripts/run-pmi-diffs.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Data.Odbc/src/DatabaseSetupInstructions.md b/src/libraries/System.Data.Odbc/src/DatabaseSetupInstructions.md index 4c3b16f43fc5..25bcc7225760 100644 --- a/src/libraries/System.Data.Odbc/src/DatabaseSetupInstructions.md +++ b/src/libraries/System.Data.Odbc/src/DatabaseSetupInstructions.md @@ -17,8 +17,8 @@ - followed [dockerfile](https://github.com/dotnet/dotnet-buildtools-prereqs-docker/blob/master/src/debian/8.2/Dockerfile) instructions for debian 8.2 - dependencies: libkrb5-dev, cmake -Get the tag name from https://hub.docker.com/r/microsoft/dotnet-buildtools-prereqs/tags/ and use in docker run below -- `docker run -it microsoft/dotnet-buildtools-prereqs:debian-8.2-SHA-YMD.. /bin/sh` +Get the tag name from https://mcr.microsoft.com/v2/dotnet-buildtools/prereqs/tags/list and use in docker run below +- `docker run -it mcr.microsoft.com/dotnet-buildtools/prereqs:debian-8.2-SHA-YMD.. /bin/sh` - `docker images` shows _id for Debian 8.2 to use in command below - `docker exec -it _id /bin/sh` diff --git a/src/tests/Common/scripts/arm32_ci_script.sh b/src/tests/Common/scripts/arm32_ci_script.sh index 8866d49536df..33d884f4f990 100755 --- a/src/tests/Common/scripts/arm32_ci_script.sh +++ b/src/tests/Common/scripts/arm32_ci_script.sh @@ -246,13 +246,13 @@ function cross_build_coreclr_with_docker { # TODO: For arm, we are going to embed RootFS inside Docker image. case $__linuxCodeName in trusty) - __dockerImage=" microsoft/dotnet-buildtools-prereqs:ubuntu-14.04-cross-0cd4667-20172211042239" + __dockerImage=" mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-14.04-cross-0cd4667-20172211042239" __skipRootFS=1 __dockerEnvironmentVariables+=" -e ROOTFS_DIR=/crossrootfs/arm" __runtimeOS="ubuntu.14.04" ;; xenial) - __dockerImage=" microsoft/dotnet-buildtools-prereqs:ubuntu-16.04-cross-ef0ac75-20175511035548" + __dockerImage=" mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-16.04-cross-ef0ac75-20175511035548" __skipRootFS=1 __dockerEnvironmentVariables+=" -e ROOTFS_DIR=/crossrootfs/arm" __runtimeOS="ubuntu.16.04" @@ -372,12 +372,12 @@ function run_tests_using_docker { if [ "$__buildArch" == "arm" ]; then case $__linuxCodeName in trusty) - __dockerImage=" microsoft/dotnet-buildtools-prereqs:ubuntu1404_cross_prereqs_v3" + __dockerImage=" mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu1404_cross_prereqs_v3" __skipRootFS=1 __dockerEnvironmentVariables=" -e ROOTFS_DIR=/crossrootfs/arm" ;; xenial) - __dockerImage=" microsoft/dotnet-buildtools-prereqs:ubuntu1604_cross_prereqs_v3" + __dockerImage=" mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu1604_cross_prereqs_v3" __skipRootFS=1 __dockerEnvironmentVariables=" -e ROOTFS_DIR=/crossrootfs/arm" ;; diff --git a/src/tests/Common/scripts/run-pmi-diffs.py b/src/tests/Common/scripts/run-pmi-diffs.py index 106137eaeaef..f711030e9cb4 100755 --- a/src/tests/Common/scripts/run-pmi-diffs.py +++ b/src/tests/Common/scripts/run-pmi-diffs.py @@ -48,10 +48,10 @@ # The Docker file and possibly options should be hoisted out to a text file to be shared between scripts. -Docker_name_arm32 = 'microsoft/dotnet-buildtools-prereqs:ubuntu-14.04-cross-e435274-20180426002420' +Docker_name_arm32 = 'mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-14.04-cross-e435274-20180426002420' Docker_opts_arm32 = '-e ROOTFS_DIR=/crossrootfs/arm' -Docker_name_arm64 = 'microsoft/dotnet-buildtools-prereqs:ubuntu-16.04-cross-arm64-a3ae44b-20180315221921' +Docker_name_arm64 = 'mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-16.04-cross-arm64-a3ae44b-20180315221921' Docker_opts_arm64 = '-e ROOTFS_DIR=/crossrootfs/arm64' Is_illumos = ('illumos' in subprocess.Popen(["uname", "-o"], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0].decode('utf-8')) From e4cc8f9d37eb6a893d391b458b900f5a20eb9cca Mon Sep 17 00:00:00 2001 From: Geoff Kizer Date: Mon, 12 Jul 2021 02:06:32 -0700 Subject: [PATCH 019/133] Minor QUIC test improvements (#55494) * reduce timeout in SetListenerTimeoutWorksWithSmallTimeout * replace AssertArrayEqual with AssertExtensions.SequenceEqual Co-authored-by: Geoffrey Kizer --- .../tests/FunctionalTests/MsQuicTests.cs | 6 ++-- .../tests/FunctionalTests/QuicStreamTests.cs | 6 ++-- .../tests/FunctionalTests/QuicTestBase.cs | 34 ------------------- 3 files changed, 6 insertions(+), 40 deletions(-) diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs index b8097a43a6c6..6ce1540fba59 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs @@ -216,7 +216,7 @@ public async Task WaitForAvailableBidirectionStreamsAsyncWorks() public async Task SetListenerTimeoutWorksWithSmallTimeout() { var quicOptions = new QuicListenerOptions(); - quicOptions.IdleTimeout = TimeSpan.FromSeconds(10); + quicOptions.IdleTimeout = TimeSpan.FromSeconds(1); quicOptions.ServerAuthenticationOptions = GetSslServerAuthenticationOptions(); quicOptions.ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0); @@ -476,7 +476,7 @@ await RunClientServer( byte[] buffer = new byte[data.Length]; int bytesRead = await ReadAll(stream, buffer); Assert.Equal(data.Length, bytesRead); - AssertArrayEqual(data, buffer); + AssertExtensions.SequenceEqual(data, buffer); for (int pos = 0; pos < data.Length; pos += writeSize) { @@ -499,7 +499,7 @@ await RunClientServer( byte[] buffer = new byte[data.Length]; int bytesRead = await ReadAll(stream, buffer); Assert.Equal(data.Length, bytesRead); - AssertArrayEqual(data, buffer); + AssertExtensions.SequenceEqual(data, buffer); await stream.ShutdownCompleted(); } diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs index 7b4090365182..b21135000aca 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs @@ -190,7 +190,7 @@ await RunClientServer( byte[] buffer = new byte[data.Length]; int bytesRead = await ReadAll(stream, buffer); Assert.Equal(data.Length, bytesRead); - AssertArrayEqual(data, buffer); + AssertExtensions.SequenceEqual(data, buffer); for (int pos = 0; pos < data.Length; pos += writeSize) { @@ -213,7 +213,7 @@ await RunClientServer( byte[] buffer = new byte[data.Length]; int bytesRead = await ReadAll(stream, buffer); Assert.Equal(data.Length, bytesRead); - AssertArrayEqual(data, buffer); + AssertExtensions.SequenceEqual(data, buffer); await stream.ShutdownCompleted(); } @@ -391,7 +391,7 @@ await RunClientServer( } Assert.Equal(testBuffer.Length, totalBytesRead); - AssertArrayEqual(testBuffer, receiveBuffer); + AssertExtensions.SequenceEqual(testBuffer, receiveBuffer); await serverStream.ShutdownCompleted(); }); diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs index 4d9ab4de6a3a..803b2d407059 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs @@ -171,40 +171,6 @@ internal static async Task WriteForever(QuicStream stream) await stream.WriteAsync(buffer); } } - - internal static void AssertArrayEqual(byte[] expected, byte[] actual) - { - for (int i = 0; i < expected.Length; ++i) - { - if (expected[i] == actual[i]) - { - continue; - } - - var message = $"Wrong data starting from idx={i}\n" + - $"Expected: {ToStringAroundIndex(expected, i)}\n" + - $"Actual: {ToStringAroundIndex(actual, i)}"; - - Assert.True(expected[i] == actual[i], message); - } - } - - private static string ToStringAroundIndex(byte[] arr, int idx, int dl = 3, int dr = 7) - { - var sb = new StringBuilder(idx - (dl+1) >= 0 ? "[..., " : "["); - - for (int i = idx - dl; i <= idx + dr; ++i) - { - if (i >= 0 && i < arr.Length) - { - sb.Append($"{arr[i]}, "); - } - } - - sb.Append(idx + (dr+1) < arr.Length ? "...]" : "]"); - - return sb.ToString(); - } } public interface IQuicImplProviderFactory From 067aa165d97372f3e281786801d014fccea8b12c Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 12 Jul 2021 14:24:54 +0200 Subject: [PATCH 020/133] Expose a global switch to disable IPV6 (#55012) Fixes #47583. Resolves #52287, #54807 and similar issues, without changing customer application code. Introduces an AppContext switch `System.Net.DisableIPv6` and environment variable `DOTNET_SYSTEM_NET_DISABLEIPV6` to emulate the lack of OS-level IPv6 support. This is useful to workaround dual-stack socket issues when the OS reports support of IPv6, but some other underlying infrastructure element (typically VPN) doesn't function well with IPv6 request. For consistency, this switch also impacts NameResolution and Ping, not only Sockets. --- .../Net/SocketProtocolSupportPal.Unix.cs | 6 +- .../Net/SocketProtocolSupportPal.Windows.cs | 6 +- .../System/Net/SocketProtocolSupportPal.cs | 36 +++++++++++ .../src/System.Net.NameResolution.csproj | 2 + .../src/System/Net/NameResolutionPal.Unix.cs | 6 +- .../tests/FunctionalTests/GetHostEntryTest.cs | 59 ++++++++++++++++--- ...System.Net.NameResolution.Pal.Tests.csproj | 2 + .../src/System.Net.Ping.csproj | 2 + .../src/System.Net.Sockets.csproj | 2 + .../tests/FunctionalTests/OSSupport.cs | 32 ++++++++++ 10 files changed, 133 insertions(+), 20 deletions(-) create mode 100644 src/libraries/Common/src/System/Net/SocketProtocolSupportPal.cs diff --git a/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs index d06ce8117fc5..8de1e2a0f4ce 100644 --- a/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs +++ b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs @@ -7,12 +7,8 @@ namespace System.Net { - internal static class SocketProtocolSupportPal + internal static partial class SocketProtocolSupportPal { - public static bool OSSupportsIPv6 { get; } = IsSupported(AddressFamily.InterNetworkV6); - public static bool OSSupportsIPv4 { get; } = IsSupported(AddressFamily.InterNetwork); - public static bool OSSupportsUnixDomainSockets { get; } = IsSupported(AddressFamily.Unix); - private static unsafe bool IsSupported(AddressFamily af) { IntPtr invalid = (IntPtr)(-1); diff --git a/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Windows.cs b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Windows.cs index de0465a51c6c..50e7db176e2f 100644 --- a/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Windows.cs +++ b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Windows.cs @@ -9,12 +9,8 @@ namespace System.Net { - internal static class SocketProtocolSupportPal + internal static partial class SocketProtocolSupportPal { - public static bool OSSupportsIPv6 { get; } = IsSupported(AddressFamily.InterNetworkV6); - public static bool OSSupportsIPv4 { get; } = IsSupported(AddressFamily.InterNetwork); - public static bool OSSupportsUnixDomainSockets { get; } = IsSupported(AddressFamily.Unix); - private static bool IsSupported(AddressFamily af) { Interop.Winsock.EnsureInitialized(); diff --git a/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.cs b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.cs new file mode 100644 index 000000000000..a61f47a0fa45 --- /dev/null +++ b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Sockets; + +namespace System.Net +{ + internal static partial class SocketProtocolSupportPal + { + private const string DisableIPv6AppCtxSwitch = "System.Net.DisableIPv6"; + private const string DisableIPv6EnvironmentVariable = "DOTNET_SYSTEM_NET_DISABLEIPV6"; + + public static bool OSSupportsIPv6 { get; } = IsSupported(AddressFamily.InterNetworkV6) && !IsIPv6Disabled(); + public static bool OSSupportsIPv4 { get; } = IsSupported(AddressFamily.InterNetwork); + public static bool OSSupportsUnixDomainSockets { get; } = IsSupported(AddressFamily.Unix); + + private static bool IsIPv6Disabled() + { + // First check for the AppContext switch, giving it priority over the environment variable. + if (AppContext.TryGetSwitch(DisableIPv6AppCtxSwitch, out bool disabled)) + { + return disabled; + } + + // AppContext switch wasn't used. Check the environment variable. + string? envVar = Environment.GetEnvironmentVariable(DisableIPv6EnvironmentVariable); + + if (envVar is not null) + { + return envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase); + } + + return false; + } + } +} diff --git a/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj b/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj index d2d9b9b46910..1642f1fff33d 100644 --- a/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj +++ b/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj @@ -31,6 +31,8 @@ Link="Common\System\Net\IPAddressParserStatics.cs" /> + diff --git a/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs b/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs index 46858edea5df..74c62684a82c 100644 --- a/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs +++ b/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs @@ -76,9 +76,11 @@ private static unsafe void ParseHostEntry(Interop.Sys.HostEntry hostEntry, bool Interop.Sys.IPAddress* addressHandle = hostEntry.IPAddressList; for (int i = 0; i < hostEntry.IPAddressCount; i++) { - if (Array.IndexOf(nativeAddresses, addressHandle[i], 0, nativeAddressCount) == -1) + Interop.Sys.IPAddress nativeAddr = addressHandle[i]; + if (Array.IndexOf(nativeAddresses, nativeAddr, 0, nativeAddressCount) == -1 && + (!nativeAddr.IsIPv6 || SocketProtocolSupportPal.OSSupportsIPv6)) // Do not include IPv6 addresses if IPV6 support is force-disabled { - nativeAddresses[nativeAddressCount++] = addressHandle[i]; + nativeAddresses[nativeAddressCount++] = nativeAddr; } } diff --git a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs index f860a2274e81..7a5d4c899e92 100644 --- a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs +++ b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs @@ -6,7 +6,7 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; - +using Microsoft.DotNet.RemoteExecutor; using Microsoft.DotNet.XUnitExtensions; using Xunit; @@ -21,9 +21,16 @@ public async Task Dns_GetHostEntryAsync_IPAddress_Ok() await TestGetHostEntryAsync(() => Dns.GetHostEntryAsync(localIPAddress)); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArm64Process))] // [ActiveIssue("https://github.com/dotnet/runtime/issues/27622")] + + public static bool GetHostEntryWorks = + // [ActiveIssue("https://github.com/dotnet/runtime/issues/27622")] + PlatformDetection.IsNotArmNorArm64Process && + // [ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)] + PlatformDetection.IsNotOSX && + // [ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] + !PlatformDetection.IsiOS && !PlatformDetection.IstvOS && !PlatformDetection.IsMacCatalyst; + + [ConditionalTheory(nameof(GetHostEntryWorks))] [InlineData("")] [InlineData(TestSettings.LocalHost)] public async Task Dns_GetHostEntry_HostString_Ok(string hostName) @@ -77,12 +84,10 @@ public async Task Dns_GetHostEntry_HostString_Ok(string hostName) } } - [ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArm64Process))] // [ActiveIssue("https://github.com/dotnet/runtime/issues/27622")] + [ConditionalTheory(nameof(GetHostEntryWorks))] [InlineData("")] [InlineData(TestSettings.LocalHost)] - public async Task Dns_GetHostEntryAsync_HostString_Ok(string hostName) + public async Task Dns_GetHostEntryAsync_HostString_Ok(string hostName) { if (PlatformDetection.IsSLES) { @@ -112,6 +117,44 @@ private static async Task TestGetHostEntryAsync(Func> getHostE Assert.Equal(list1, list2); } + public static bool GetHostEntry_DisableIPv6_Condition = GetHostEntryWorks && RemoteExecutor.IsSupported; + + [ConditionalTheory(nameof(GetHostEntry_DisableIPv6_Condition))] + [InlineData("")] + [InlineData(TestSettings.LocalHost)] + public void Dns_GetHostEntry_DisableIPv6_ExcludesIPv6Addresses(string hostnameOuter) + { + RemoteExecutor.Invoke(RunTest, hostnameOuter).Dispose(); + + static void RunTest(string hostnameInner) + { + AppContext.SetSwitch("System.Net.DisableIPv6", true); + IPHostEntry entry = Dns.GetHostEntry(hostnameInner); + foreach (IPAddress address in entry.AddressList) + { + Assert.NotEqual(AddressFamily.InterNetworkV6, address.AddressFamily); + } + } + } + + [ConditionalTheory(nameof(GetHostEntry_DisableIPv6_Condition))] + [InlineData("")] + [InlineData(TestSettings.LocalHost)] + public void Dns_GetHostEntryAsync_DisableIPv6_ExcludesIPv6Addresses(string hostnameOuter) + { + RemoteExecutor.Invoke(RunTest, hostnameOuter).Dispose(); + + static async Task RunTest(string hostnameInner) + { + AppContext.SetSwitch("System.Net.DisableIPv6", true); + IPHostEntry entry = await Dns.GetHostEntryAsync(hostnameInner); + foreach (IPAddress address in entry.AddressList) + { + Assert.NotEqual(AddressFamily.InterNetworkV6, address.AddressFamily); + } + } + } + [Fact] public async Task Dns_GetHostEntry_NullStringHost_Fail() { diff --git a/src/libraries/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj b/src/libraries/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj index 7b2b9ee2c9fd..3b9a81c4ee5d 100644 --- a/src/libraries/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj +++ b/src/libraries/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj @@ -28,6 +28,8 @@ Link="Common\System\Net\IPEndPointStatics.cs" /> + + diff --git a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj index 35d10bb679f1..496c628956f8 100644 --- a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj +++ b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj @@ -65,6 +65,8 @@ Link="Common\System\Net\SocketAddress.cs" /> + diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/OSSupport.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/OSSupport.cs index 3e2886baf52b..9ed625aecd0f 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/OSSupport.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/OSSupport.cs @@ -3,6 +3,7 @@ using System.Threading; +using Microsoft.DotNet.RemoteExecutor; using Xunit; namespace System.Net.Sockets.Tests @@ -25,6 +26,37 @@ public void SupportsIPv6_MatchesOSSupportsIPv6() #pragma warning restore } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void DisableIPv6_OSSupportsIPv6_False() + { + RemoteInvokeOptions options = new RemoteInvokeOptions(); + options.StartInfo.EnvironmentVariables["DOTNET_SYSTEM_NET_DISABLEIPV6"] = "1"; + RemoteExecutor.Invoke(RunTest, options).Dispose(); + + static void RunTest() + { + Assert.False(Socket.OSSupportsIPv6); + } + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void DisableIPv6_SocketConstructor_CreatesIPv4Socket() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + AppContext.SetSwitch("System.Net.DisableIPv6", true); + using Socket socket1 = new Socket(SocketType.Stream, ProtocolType.Tcp); + using Socket socket2 = new Socket(SocketType.Dgram, ProtocolType.Udp); + + Assert.Equal(AddressFamily.InterNetwork, socket1.AddressFamily); + Assert.Equal(AddressFamily.InterNetwork, socket2.AddressFamily); + Assert.False(socket1.DualMode); + Assert.False(socket2.DualMode); + } + } + [Fact] public void IOControl_FIONREAD_Success() { From 375ca44e80fb60c7183b3a3cc8ea82b77dd84430 Mon Sep 17 00:00:00 2001 From: Fan Yang <52458914+fanyang-mono@users.noreply.github.com> Date: Mon, 12 Jul 2021 09:24:55 -0400 Subject: [PATCH 021/133] Fix android uninstall command and add validations (#55431) --- .../Coreclr.TestWrapper/MobileAppHandler.cs | 154 +++++++++++------- src/tests/run.proj | 9 +- 2 files changed, 102 insertions(+), 61 deletions(-) diff --git a/src/tests/Common/Coreclr.TestWrapper/MobileAppHandler.cs b/src/tests/Common/Coreclr.TestWrapper/MobileAppHandler.cs index b594455f1071..f4d179ce4ce2 100644 --- a/src/tests/Common/Coreclr.TestWrapper/MobileAppHandler.cs +++ b/src/tests/Common/Coreclr.TestWrapper/MobileAppHandler.cs @@ -8,43 +8,24 @@ namespace CoreclrTestLib { public class MobileAppHandler { - public void InstallMobileApp(string platform, string category, string testBinaryBase, string reportBase) + public int InstallMobileApp(string platform, string category, string testBinaryBase, string reportBase) { - HandleMobileApp("install", platform, category, testBinaryBase, reportBase); + return HandleMobileApp("install", platform, category, testBinaryBase, reportBase); } - public void UninstallMobileApp(string platform, string category, string testBinaryBase, string reportBase) + public int UninstallMobileApp(string platform, string category, string testBinaryBase, string reportBase) { - HandleMobileApp("uninstall", platform, category, testBinaryBase, reportBase); + return HandleMobileApp("uninstall", platform, category, testBinaryBase, reportBase); } - private static void HandleMobileApp(string action, string platform, string category, string testBinaryBase, string reportBase) + private static int HandleMobileApp(string action, string platform, string category, string testBinaryBase, string reportBase) { //install or uninstall mobile app + int exitCode = -100; string outputFile = Path.Combine(reportBase, action, $"{category}_{action}.output.txt"); string errorFile = Path.Combine(reportBase, action, $"{category}_{action}.error.txt"); - string dotnetCmd_raw = System.Environment.GetEnvironmentVariable("__TestDotNetCmd"); - string xharnessCmd_raw = System.Environment.GetEnvironmentVariable("XHARNESS_CLI_PATH"); - int timeout = 600000; // Set timeout to 4 mins, because the installation on Android arm64/32 devices could take up to 10 mins on CI - - string dotnetCmd = string.IsNullOrEmpty(dotnetCmd_raw) ? "dotnet" : dotnetCmd_raw; - string xharnessCmd = string.IsNullOrEmpty(xharnessCmd_raw) ? "xharness" : $"exec {xharnessCmd_raw}"; - string appExtension = platform == "android" ? "apk" : "app"; - string cmdStr = $"{dotnetCmd} {xharnessCmd} {platform} {action} --output-directory={reportBase}/{action}"; - - if (action == "install") - { - cmdStr += $" --app={testBinaryBase}/{category}.{appExtension}"; - } - else if (platform != "android") - { - cmdStr += $" --app=net.dot.{category}"; - } - - if (platform == "android") - { - cmdStr += $" --package-name=net.dot.{category}"; - } + bool platformValueFlag = true; + bool actionValueFlag = true; Directory.CreateDirectory(Path.Combine(reportBase, action)); var outputStream = new FileStream(outputFile, FileMode.Create); @@ -52,55 +33,110 @@ private static void HandleMobileApp(string action, string platform, string categ using (var outputWriter = new StreamWriter(outputStream)) using (var errorWriter = new StreamWriter(errorStream)) - using (Process process = new Process()) { - if (OperatingSystem.IsWindows()) + //Validate inputs + if ((platform != "android") && (platform != "apple")) { - process.StartInfo.FileName = "cmd.exe"; + outputWriter.WriteLine($"Incorrect value of platform. Provided {platform}. Valid strings are android and apple."); + platformValueFlag = false; } - else + + if ((action != "install") && (action != "uninstall")) { - process.StartInfo.FileName = "/bin/bash"; + outputWriter.WriteLine($"Incorrect value of action. Provided {action}. Valid strings are install and uninstall."); + actionValueFlag = false; } - process.StartInfo.Arguments = ConvertCmd2Arg(cmdStr); - process.StartInfo.UseShellExecute = false; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.RedirectStandardError = true; + if (platformValueFlag && actionValueFlag) + { + int timeout = 240000; // Set timeout to 4 mins, because the installation on Android arm64/32 devices could take up to 10 mins on CI + string dotnetCmd_raw = System.Environment.GetEnvironmentVariable("__TestDotNetCmd"); + string xharnessCmd_raw = System.Environment.GetEnvironmentVariable("XHARNESS_CLI_PATH"); + string dotnetCmd = string.IsNullOrEmpty(dotnetCmd_raw) ? "dotnet" : dotnetCmd_raw; + string xharnessCmd = string.IsNullOrEmpty(xharnessCmd_raw) ? "xharness" : $"exec {xharnessCmd_raw}"; + string appExtension = platform == "android" ? "apk" : "app"; - DateTime startTime = DateTime.Now; - process.Start(); + string cmdStr = $"{dotnetCmd} {xharnessCmd} {platform} {action}"; - var cts = new CancellationTokenSource(); - Task copyOutput = process.StandardOutput.BaseStream.CopyToAsync(outputStream, 4096, cts.Token); - Task copyError = process.StandardError.BaseStream.CopyToAsync(errorStream, 4096, cts.Token); + if (platform == "android") + { + cmdStr += $" --package-name=net.dot.{category}"; - if (process.WaitForExit(timeout)) - { - Task.WaitAll(copyOutput, copyError); - } - else - { - //Time out - DateTime endTime = DateTime.Now; + if (action == "install") + { + cmdStr += $" --app={testBinaryBase}/{category}.{appExtension} --output-directory={reportBase}/{action}"; + } + } + else // platform is apple + { + cmdStr += $" --output-directory={reportBase}/{action} --target=ios-simulator-64"; //To Do: target should be either emulator or device + + if (action == "install") + { + cmdStr += $" --app={testBinaryBase}/{category}.{appExtension}"; + } + else // action is uninstall + { + cmdStr += $" --app=net.dot.{category}"; + } + } - try + using (Process process = new Process()) { - cts.Cancel(); + if (OperatingSystem.IsWindows()) + { + process.StartInfo.FileName = "cmd.exe"; + } + else + { + process.StartInfo.FileName = "/bin/bash"; + } + + process.StartInfo.Arguments = ConvertCmd2Arg(cmdStr); + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + + DateTime startTime = DateTime.Now; + process.Start(); + + var cts = new CancellationTokenSource(); + Task copyOutput = process.StandardOutput.BaseStream.CopyToAsync(outputStream, 4096, cts.Token); + Task copyError = process.StandardError.BaseStream.CopyToAsync(errorStream, 4096, cts.Token); + + if (process.WaitForExit(timeout)) + { + // Process completed. + exitCode = process.ExitCode; + Task.WaitAll(copyOutput, copyError); + } + else + { + //Time out. + DateTime endTime = DateTime.Now; + + try + { + cts.Cancel(); + } + catch {} + + outputWriter.WriteLine("\ncmdLine:{0} Timed Out (timeout in milliseconds: {1}, start: {2}, end: {3})", + cmdStr, timeout, startTime.ToString(), endTime.ToString()); + errorWriter.WriteLine("\ncmdLine:{0} Timed Out (timeout in milliseconds: {1}, start: {2}, end: {3})", + cmdStr, timeout, startTime.ToString(), endTime.ToString()); + + process.Kill(entireProcessTree: true); + } } - catch {} - - outputWriter.WriteLine("\ncmdLine:{0} Timed Out (timeout in milliseconds: {1}, start: {2}, end: {3})", - cmdStr, timeout, startTime.ToString(), endTime.ToString()); - errorWriter.WriteLine("\ncmdLine:{0} Timed Out (timeout in milliseconds: {1}, start: {2}, end: {3})", - cmdStr, timeout, startTime.ToString(), endTime.ToString()); - - process.Kill(entireProcessTree: true); } + outputWriter.WriteLine("xharness exitcode is : " + exitCode.ToString()); outputWriter.Flush(); errorWriter.Flush(); } + + return exitCode; } private static string ConvertCmd2Arg(string cmd) diff --git a/src/tests/run.proj b/src/tests/run.proj index a64233555d20..3289e9c9ee64 100644 --- a/src/tests/run.proj +++ b/src/tests/run.proj @@ -272,6 +272,8 @@ namespace $([System.String]::Copy($(Category)).Replace(".","_").Replace("\",""). coreRoot = Environment.GetEnvironmentVariable(%22CORE_ROOT%22)%3B category = "$([System.String]::Copy($(Category)).Replace(".","_").Replace("\","").Replace("-","_"))"%3B helixUploadRoot = Environment.GetEnvironmentVariable(%22HELIX_WORKITEM_UPLOAD_ROOT%22)%3B + int retCode = -100%3B + if (!String.IsNullOrEmpty(helixUploadRoot)) { reportBase = Path.Combine(Path.GetFullPath(helixUploadRoot), "Reports")%3B } @@ -293,12 +295,15 @@ namespace $([System.String]::Copy($(Category)).Replace(".","_").Replace("\",""). string operatingSystem = Environment.GetEnvironmentVariable("OS")%3B runningInWindows = (operatingSystem != null && operatingSystem.StartsWith("Windows"))%3B - handler.InstallMobileApp(%22$(MobilePlatform)%22, category, testBinaryBase, reportBase)%3B + retCode = handler.InstallMobileApp(%22$(MobilePlatform)%22, category, testBinaryBase, reportBase)%3B + Assert.True(retCode == 0, "Failed to install mobile app.")%3B } public void Dispose() { - handler.UninstallMobileApp(%22$(MobilePlatform)%22, category, testBinaryBase, reportBase)%3B + int retCode = -100%3B + retCode = handler.UninstallMobileApp(%22$(MobilePlatform)%22, category, testBinaryBase, reportBase)%3B + Assert.True(retCode == 0, "Failed to uninstall mobile app.")%3B } } From 633e04481f21a23d567cf49177f76fbef3a34cfe Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 12 Jul 2021 10:18:04 -0400 Subject: [PATCH 022/133] Enable CA1419 (SafeHandle public parameterless ctor) (#55460) --- eng/CodeAnalysis.ruleset | 1 + .../Interop.Keychain.macOS.cs | 2 +- .../src/Interop/Windows/HttpApi/Interop.HttpApi.cs | 2 +- .../src/Interop/Windows/SspiCli/SecuritySafeHandles.cs | 2 +- .../Win32/SafeHandles/Asn1SafeHandles.Unix.cs | 4 ++-- .../SafeHandles/GssSafeHandles.PlatformNotSupported.cs | 7 ++++--- .../Win32/SafeHandles/SafeUnicodeStringHandle.cs | 2 ++ .../System/Net/Security/SecurityContextTokenHandle.cs | 2 +- .../System/Net/Security/Unix/SafeDeleteNegoContext.cs | 2 ++ .../System/Net/Security/Unix/SafeDeleteSslContext.cs | 2 ++ .../System/Net/Security/Unix/SafeFreeCertContext.cs | 2 ++ .../Net/Security/Unix/SafeFreeNegoCredentials.cs | 2 ++ .../System/Net/Security/Unix/SafeFreeSslCredentials.cs | 2 ++ .../src/System/Data/Odbc/OdbcConnectionHandle.cs | 2 ++ .../src/System/Data/Odbc/OdbcHandle.cs | 2 ++ .../src/System/Data/Odbc/OdbcStatementHandle.cs | 2 ++ .../System.Data.Odbc/src/System/Data/Odbc/OdbcUtils.cs | 2 ++ .../System.Data.OleDb/src/OleDbTransaction.cs | 2 ++ src/libraries/System.Data.OleDb/src/OleDbWrapper.cs | 6 ++++-- src/libraries/System.Data.OleDb/src/PropertyIDSet.cs | 2 ++ src/libraries/System.Data.OleDb/src/PropertyInfoSet.cs | 2 ++ src/libraries/System.Data.OleDb/src/RowBinding.cs | 2 ++ src/libraries/System.Data.OleDb/src/SafeHandles.cs | 2 ++ .../src/System/Data/ProviderBase/DbConnectionPool.cs | 2 +- .../DirectoryServices/AccountManagement/AuthZSet.cs | 5 +++-- .../DirectoryServices/Protocols/Interop/SafeHandles.cs | 2 ++ .../Drawing/Drawing2D/SafeCustomLineCapHandle.cs | 4 ++++ .../src/System/Net/Http/WinHttpChannelBinding.cs | 3 ++- .../src/System/Net/Windows/HttpServerSessionHandle.cs | 2 ++ .../Quic/Implementations/MsQuic/Internal/MsQuicApi.cs | 10 +++++----- .../MsQuic/Interop/SafeMsQuicConfigurationHandle.cs | 2 +- .../MsQuic/Interop/SafeMsQuicConnectionHandle.cs | 2 +- .../MsQuic/Interop/SafeMsQuicListenerHandle.cs | 2 +- .../MsQuic/Interop/SafeMsQuicRegistrationHandle.cs | 2 +- .../MsQuic/Interop/SafeMsQuicStreamHandle.cs | 2 +- .../Net/Security/Pal.Android/SafeDeleteSslContext.cs | 2 ++ .../Security/Pal.Managed/SafeChannelBindingHandle.cs | 2 ++ .../Net/Security/Pal.Managed/SafeFreeSslCredentials.cs | 2 ++ .../Net/Security/Pal.OSX/SafeDeleteSslContext.cs | 2 ++ .../src/System/Security/SecureString.cs | 2 ++ ...m.Private.Runtime.InteropServices.JavaScript.csproj | 1 + .../src/System/Security/Cryptography/CngKeyLite.cs | 2 ++ .../Win32/SafeHandles/SafeCertContextHandle.cs | 2 ++ .../Win32/SafeHandles/SafeProvOrNCryptKeyHandleUwp.cs | 2 ++ .../Cryptography/Pal.Windows/Native/SafeHandles.cs | 2 ++ .../Microsoft/Win32/SafeHandles/SafePasswordHandle.cs | 2 ++ .../Microsoft/Win32/SafeHandles/SafeServiceHandle.cs | 4 ++++ 47 files changed, 94 insertions(+), 25 deletions(-) diff --git a/eng/CodeAnalysis.ruleset b/eng/CodeAnalysis.ruleset index 8faa50e9a6f0..133252bae8f7 100644 --- a/eng/CodeAnalysis.ruleset +++ b/eng/CodeAnalysis.ruleset @@ -68,6 +68,7 @@ + diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.macOS.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.macOS.cs index 862760aa2c8d..c93319994bbd 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.macOS.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.macOS.cs @@ -401,7 +401,7 @@ internal sealed class SafeTemporaryKeychainHandle : SafeKeychainHandle private static readonly Dictionary s_lookup = new Dictionary(); - internal SafeTemporaryKeychainHandle() + public SafeTemporaryKeychainHandle() { } diff --git a/src/libraries/Common/src/Interop/Windows/HttpApi/Interop.HttpApi.cs b/src/libraries/Common/src/Interop/Windows/HttpApi/Interop.HttpApi.cs index e0982d907c68..27c22800a42d 100644 --- a/src/libraries/Common/src/Interop/Windows/HttpApi/Interop.HttpApi.cs +++ b/src/libraries/Common/src/Interop/Windows/HttpApi/Interop.HttpApi.cs @@ -508,7 +508,7 @@ internal sealed class SafeLocalFreeChannelBinding : ChannelBinding { private int _size; - private SafeLocalFreeChannelBinding() { } + public SafeLocalFreeChannelBinding() { } public override int Size { diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs index 0f2cb05603ac..43b718524d1c 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs @@ -128,7 +128,7 @@ public static int SetContextAttributes( internal sealed class SafeFreeContextBuffer_SECURITY : SafeFreeContextBuffer { - internal SafeFreeContextBuffer_SECURITY() : base() { } + public SafeFreeContextBuffer_SECURITY() : base() { } protected override bool ReleaseHandle() { diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs index 193533551bce..f8b26951cf22 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs @@ -68,7 +68,7 @@ public override bool IsInvalid internal sealed class SafeSharedAsn1IntegerHandle : SafeInteriorHandle { - private SafeSharedAsn1IntegerHandle() : + public SafeSharedAsn1IntegerHandle() : base(IntPtr.Zero, ownsHandle: true) { } @@ -76,7 +76,7 @@ private SafeSharedAsn1IntegerHandle() : internal sealed class SafeSharedAsn1OctetStringHandle : SafeInteriorHandle { - private SafeSharedAsn1OctetStringHandle() : + public SafeSharedAsn1OctetStringHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.PlatformNotSupported.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.PlatformNotSupported.cs index 0c6a8304620a..20eeb6dfbf81 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.PlatformNotSupported.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.PlatformNotSupported.cs @@ -18,7 +18,8 @@ public override bool IsInvalid } protected override bool ReleaseHandle() => throw new PlatformNotSupportedException(); - private SafeGssNameHandle() + + public SafeGssNameHandle() : base(IntPtr.Zero, true) { } @@ -27,7 +28,7 @@ private SafeGssNameHandle() [UnsupportedOSPlatform("tvos")] internal sealed class SafeGssCredHandle : SafeHandle { - private SafeGssCredHandle() + public SafeGssCredHandle() : base(IntPtr.Zero, true) { } @@ -43,7 +44,7 @@ public override bool IsInvalid [UnsupportedOSPlatform("tvos")] internal sealed class SafeGssContextHandle : SafeHandle { - private SafeGssContextHandle() + public SafeGssContextHandle() : base(IntPtr.Zero, true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeUnicodeStringHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeUnicodeStringHandle.cs index e58bfd19695e..0c27a2004957 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeUnicodeStringHandle.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeUnicodeStringHandle.cs @@ -4,6 +4,8 @@ using System; using System.Runtime.InteropServices; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace Microsoft.Win32.SafeHandles { /// diff --git a/src/libraries/Common/src/System/Net/Security/SecurityContextTokenHandle.cs b/src/libraries/Common/src/System/Net/Security/SecurityContextTokenHandle.cs index 8e889f852afc..4f4c9951a72e 100644 --- a/src/libraries/Common/src/System/Net/Security/SecurityContextTokenHandle.cs +++ b/src/libraries/Common/src/System/Net/Security/SecurityContextTokenHandle.cs @@ -16,7 +16,7 @@ internal sealed class SecurityContextTokenHandle : CriticalHandleZeroOrMinusOneI #endif private int _disposed; - private SecurityContextTokenHandle() : base() + public SecurityContextTokenHandle() : base() { } diff --git a/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs b/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs index 123a28ace0f4..2e5a0b856002 100644 --- a/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs +++ b/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs @@ -7,6 +7,8 @@ using System.Text; using Microsoft.Win32.SafeHandles; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Net.Security { internal sealed class SafeDeleteNegoContext : SafeDeleteContext diff --git a/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteSslContext.cs b/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteSslContext.cs index 9b27cc0d969f..3f550d36d7f4 100644 --- a/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteSslContext.cs +++ b/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteSslContext.cs @@ -11,6 +11,8 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Net.Security { internal sealed class SafeDeleteSslContext : SafeDeleteContext diff --git a/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeCertContext.cs b/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeCertContext.cs index c5fa820ab927..ca7438a33b48 100644 --- a/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeCertContext.cs +++ b/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeCertContext.cs @@ -8,6 +8,8 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Net.Security { #if DEBUG diff --git a/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeNegoCredentials.cs b/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeNegoCredentials.cs index 58d1942829e5..fef571d5f7db 100644 --- a/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeNegoCredentials.cs +++ b/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeNegoCredentials.cs @@ -7,6 +7,8 @@ using System.Text; using Microsoft.Win32.SafeHandles; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Net.Security { internal sealed class SafeFreeNegoCredentials : SafeFreeCredentials diff --git a/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeSslCredentials.cs b/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeSslCredentials.cs index 8867027d0704..ff378ab42513 100644 --- a/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeSslCredentials.cs +++ b/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeSslCredentials.cs @@ -10,6 +10,8 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Net.Security { internal sealed class SafeFreeSslCredentials : SafeFreeCredentials diff --git a/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionHandle.cs b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionHandle.cs index 6cd776f00fa2..b2c7ee7cb336 100644 --- a/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionHandle.cs +++ b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionHandle.cs @@ -6,6 +6,8 @@ using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Data.Odbc { internal sealed class OdbcConnectionHandle : OdbcHandle diff --git a/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcHandle.cs b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcHandle.cs index 77900ae013f1..a5432b824053 100644 --- a/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcHandle.cs +++ b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcHandle.cs @@ -7,6 +7,8 @@ using System.Runtime.InteropServices; using System.Text; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Data.Odbc { internal abstract class OdbcHandle : SafeHandle diff --git a/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcStatementHandle.cs b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcStatementHandle.cs index 871478cae501..f8384b7a20b4 100644 --- a/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcStatementHandle.cs +++ b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcStatementHandle.cs @@ -4,6 +4,8 @@ using System.Data.Common; using System.Runtime.InteropServices; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Data.Odbc { internal readonly struct SQLLEN diff --git a/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcUtils.cs b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcUtils.cs index 370bb9771dc8..6df1b3c9ec9d 100644 --- a/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcUtils.cs +++ b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcUtils.cs @@ -6,6 +6,8 @@ using System.Runtime.InteropServices; using System.Text; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Data.Odbc { internal sealed class CNativeBuffer : System.Data.ProviderBase.DbBuffer diff --git a/src/libraries/System.Data.OleDb/src/OleDbTransaction.cs b/src/libraries/System.Data.OleDb/src/OleDbTransaction.cs index 2181a6cbe75a..4a169181f2b5 100644 --- a/src/libraries/System.Data.OleDb/src/OleDbTransaction.cs +++ b/src/libraries/System.Data.OleDb/src/OleDbTransaction.cs @@ -7,6 +7,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Data.OleDb { public sealed class OleDbTransaction : DbTransaction diff --git a/src/libraries/System.Data.OleDb/src/OleDbWrapper.cs b/src/libraries/System.Data.OleDb/src/OleDbWrapper.cs index 23ef6c6b0d49..bc458f68223e 100644 --- a/src/libraries/System.Data.OleDb/src/OleDbWrapper.cs +++ b/src/libraries/System.Data.OleDb/src/OleDbWrapper.cs @@ -7,6 +7,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Data.OleDb { // SafeHandle wrapper around 'DataLinks' object which pools the native OLE DB providers. @@ -92,7 +94,7 @@ internal sealed class DataSourceWrapper : WrappedIUnknown // we expect to store IDBInitialize instance pointer in base.handle // construct a DataSourceWrapper and used as a ref parameter to GetDataSource - internal DataSourceWrapper() : base() + public DataSourceWrapper() : base() { } @@ -229,7 +231,7 @@ internal sealed class SessionWrapper : WrappedIUnknown // since we maintain an AddRef on IDBCreateCommand it is safe to use the delegate without rechecking its function pointer private UnsafeNativeMethods.IDBCreateCommandCreateCommand? DangerousIDBCreateCommandCreateCommand; - internal SessionWrapper() : base() + public SessionWrapper() : base() { } diff --git a/src/libraries/System.Data.OleDb/src/PropertyIDSet.cs b/src/libraries/System.Data.OleDb/src/PropertyIDSet.cs index 52b854cd0dc6..5aca3735e559 100644 --- a/src/libraries/System.Data.OleDb/src/PropertyIDSet.cs +++ b/src/libraries/System.Data.OleDb/src/PropertyIDSet.cs @@ -5,6 +5,8 @@ using System.Data.ProviderBase; using System.Runtime.InteropServices; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Data.OleDb { internal sealed class PropertyIDSet : DbBuffer diff --git a/src/libraries/System.Data.OleDb/src/PropertyInfoSet.cs b/src/libraries/System.Data.OleDb/src/PropertyInfoSet.cs index 60b44bab7de4..67f95789eb04 100644 --- a/src/libraries/System.Data.OleDb/src/PropertyInfoSet.cs +++ b/src/libraries/System.Data.OleDb/src/PropertyInfoSet.cs @@ -7,6 +7,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Data.OleDb { internal sealed class OleDbPropertyInfo diff --git a/src/libraries/System.Data.OleDb/src/RowBinding.cs b/src/libraries/System.Data.OleDb/src/RowBinding.cs index 7f94bf563d5f..75312475fcdd 100644 --- a/src/libraries/System.Data.OleDb/src/RowBinding.cs +++ b/src/libraries/System.Data.OleDb/src/RowBinding.cs @@ -6,6 +6,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Data.OleDb { internal sealed class RowBinding : System.Data.ProviderBase.DbBuffer diff --git a/src/libraries/System.Data.OleDb/src/SafeHandles.cs b/src/libraries/System.Data.OleDb/src/SafeHandles.cs index d435f7f45bcd..edd87806063a 100644 --- a/src/libraries/System.Data.OleDb/src/SafeHandles.cs +++ b/src/libraries/System.Data.OleDb/src/SafeHandles.cs @@ -9,6 +9,8 @@ using System.Runtime.Versioning; using static System.Data.Common.UnsafeNativeMethods; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Data.OleDb { internal sealed class DualCoTaskMem : SafeHandle diff --git a/src/libraries/System.Data.OleDb/src/System/Data/ProviderBase/DbConnectionPool.cs b/src/libraries/System.Data.OleDb/src/System/Data/ProviderBase/DbConnectionPool.cs index e411fc550101..521ea909f11d 100644 --- a/src/libraries/System.Data.OleDb/src/System/Data/ProviderBase/DbConnectionPool.cs +++ b/src/libraries/System.Data.OleDb/src/System/Data/ProviderBase/DbConnectionPool.cs @@ -233,7 +233,7 @@ private sealed class PoolWaitHandles : DbBuffer private readonly int _releaseFlags; - internal PoolWaitHandles() : base(3 * IntPtr.Size) + public PoolWaitHandles() : base(3 * IntPtr.Size) { bool mustRelease1 = false, mustRelease2 = false, mustRelease3 = false; diff --git a/src/libraries/System.DirectoryServices.AccountManagement/src/System/DirectoryServices/AccountManagement/AuthZSet.cs b/src/libraries/System.DirectoryServices.AccountManagement/src/System/DirectoryServices/AccountManagement/AuthZSet.cs index 4c6491929ad3..56edd76877c0 100644 --- a/src/libraries/System.DirectoryServices.AccountManagement/src/System/DirectoryServices/AccountManagement/AuthZSet.cs +++ b/src/libraries/System.DirectoryServices.AccountManagement/src/System/DirectoryServices/AccountManagement/AuthZSet.cs @@ -566,8 +566,9 @@ public override void Dispose() // private sealed class SafeMemoryPtr : SafeHandle { - private SafeMemoryPtr() : base(IntPtr.Zero, true) - { } + public SafeMemoryPtr() : base(IntPtr.Zero, true) + { + } internal SafeMemoryPtr(IntPtr handle) : base(IntPtr.Zero, true) { diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.cs index 3dc2a589a8d5..4f2ac1c3086c 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.cs @@ -4,6 +4,8 @@ using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.DirectoryServices.Protocols { internal sealed class HGlobalMemHandle : SafeHandleZeroOrMinusOneIsInvalid diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/SafeCustomLineCapHandle.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/SafeCustomLineCapHandle.cs index beb8d42bb6ff..b01b88392b94 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/SafeCustomLineCapHandle.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/SafeCustomLineCapHandle.cs @@ -11,6 +11,10 @@ namespace System.Drawing.Drawing2D { internal sealed class SafeCustomLineCapHandle : SafeHandle { + public SafeCustomLineCapHandle() : base(IntPtr.Zero, true) + { + } + // Create a SafeHandle, informing the base class // that this SafeHandle instance "owns" the handle, // and therefore SafeHandle should call diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpChannelBinding.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpChannelBinding.cs index 5e862724d0e7..8011f2859d0d 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpChannelBinding.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpChannelBinding.cs @@ -5,9 +5,10 @@ using System.Runtime.InteropServices; using System.Security.Authentication.ExtendedProtection; using System.Text; - using SafeWinHttpHandle = Interop.WinHttp.SafeWinHttpHandle; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Net.Http { internal sealed class WinHttpChannelBinding : ChannelBinding diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpServerSessionHandle.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpServerSessionHandle.cs index 671eae6386b5..f180fefecd11 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpServerSessionHandle.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpServerSessionHandle.cs @@ -4,6 +4,8 @@ using Microsoft.Win32.SafeHandles; using System.Threading; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Net { // diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs index 2bf1b9b1955e..cba34f6dbbaf 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs @@ -16,11 +16,11 @@ internal unsafe sealed class MsQuicApi // This is workaround for a bug in ILTrimmer. // Without these DynamicDependency attributes, .ctor() will be removed from the safe handles. // Remove once fixed: https://github.com/mono/linker/issues/1660 - [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(SafeMsQuicRegistrationHandle))] - [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(SafeMsQuicConfigurationHandle))] - [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(SafeMsQuicListenerHandle))] - [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(SafeMsQuicConnectionHandle))] - [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(SafeMsQuicStreamHandle))] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, typeof(SafeMsQuicRegistrationHandle))] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, typeof(SafeMsQuicConfigurationHandle))] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, typeof(SafeMsQuicListenerHandle))] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, typeof(SafeMsQuicConnectionHandle))] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, typeof(SafeMsQuicStreamHandle))] private MsQuicApi(NativeApi* vtable) { uint status; diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConfigurationHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConfigurationHandle.cs index f80f33f53ae4..df48e0db377a 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConfigurationHandle.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConfigurationHandle.cs @@ -21,7 +21,7 @@ internal sealed class SafeMsQuicConfigurationHandle : SafeHandle public override bool IsInvalid => handle == IntPtr.Zero; - private SafeMsQuicConfigurationHandle() + public SafeMsQuicConfigurationHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConnectionHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConnectionHandle.cs index 74f806c9818b..9354ce04a18e 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConnectionHandle.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConnectionHandle.cs @@ -9,7 +9,7 @@ internal sealed class SafeMsQuicConnectionHandle : SafeHandle { public override bool IsInvalid => handle == IntPtr.Zero; - private SafeMsQuicConnectionHandle() + public SafeMsQuicConnectionHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicListenerHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicListenerHandle.cs index 87e59b69a220..f0f75556921c 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicListenerHandle.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicListenerHandle.cs @@ -9,7 +9,7 @@ internal sealed class SafeMsQuicListenerHandle : SafeHandle { public override bool IsInvalid => handle == IntPtr.Zero; - private SafeMsQuicListenerHandle() + public SafeMsQuicListenerHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicRegistrationHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicRegistrationHandle.cs index d9c6e7c70adc..798636bd7e43 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicRegistrationHandle.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicRegistrationHandle.cs @@ -9,7 +9,7 @@ internal sealed class SafeMsQuicRegistrationHandle : SafeHandle { public override bool IsInvalid => handle == IntPtr.Zero; - private SafeMsQuicRegistrationHandle() + public SafeMsQuicRegistrationHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicStreamHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicStreamHandle.cs index 3037f60956a7..179bdd3d15e1 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicStreamHandle.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicStreamHandle.cs @@ -9,7 +9,7 @@ internal sealed class SafeMsQuicStreamHandle : SafeHandle { public override bool IsInvalid => handle == IntPtr.Zero; - private SafeMsQuicStreamHandle() + public SafeMsQuicStreamHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Android/SafeDeleteSslContext.cs b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Android/SafeDeleteSslContext.cs index b0177cbc4323..99cb5fa68cf9 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Android/SafeDeleteSslContext.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Android/SafeDeleteSslContext.cs @@ -13,6 +13,8 @@ using PAL_KeyAlgorithm = Interop.AndroidCrypto.PAL_KeyAlgorithm; using PAL_SSLStreamStatus = Interop.AndroidCrypto.PAL_SSLStreamStatus; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Net { internal sealed class SafeDeleteSslContext : SafeDeleteContext diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Managed/SafeChannelBindingHandle.cs b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Managed/SafeChannelBindingHandle.cs index 0308355c8898..e65b0aacde22 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Managed/SafeChannelBindingHandle.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Managed/SafeChannelBindingHandle.cs @@ -6,6 +6,8 @@ using System.Security.Authentication.ExtendedProtection; using System.Text; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Net.Security { internal sealed class SafeChannelBindingHandle : ChannelBinding diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Managed/SafeFreeSslCredentials.cs b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Managed/SafeFreeSslCredentials.cs index 24a4e7be7843..c047b88e5c52 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Managed/SafeFreeSslCredentials.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Managed/SafeFreeSslCredentials.cs @@ -6,6 +6,8 @@ using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Net { internal sealed class SafeFreeSslCredentials : SafeFreeCredentials diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs index 44c8757c51e8..9b4cef4873dd 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs @@ -9,6 +9,8 @@ using System.Security.Cryptography.X509Certificates; using Microsoft.Win32.SafeHandles; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace System.Net { internal sealed class SafeDeleteSslContext : SafeDeleteContext diff --git a/src/libraries/System.Private.CoreLib/src/System/Security/SecureString.cs b/src/libraries/System.Private.CoreLib/src/System/Security/SecureString.cs index 3f1cf733cb6b..721c74149ca0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Security/SecureString.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Security/SecureString.cs @@ -429,7 +429,9 @@ private sealed class UnmanagedBuffer : SafeBuffer // A local copy of byte length to be able to access it in ReleaseHandle without the risk of throwing exceptions private int _byteLength; +#pragma warning disable CA1419 // not intended for use with P/Invoke private UnmanagedBuffer() : base(true) { } +#pragma warning restore CA1419 public static UnmanagedBuffer Allocate(int byteLength) { diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj index 08f018bc7149..9d779477a880 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj @@ -3,6 +3,7 @@ true enable $(NetCoreAppCurrent)-Browser + $(NoWarn);CA1419 diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CngKeyLite.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CngKeyLite.cs index c7c38e7bcc4c..3670b98ffc04 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CngKeyLite.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CngKeyLite.cs @@ -724,6 +724,8 @@ internal sealed class SafeNCryptSecretHandle : SafeNCryptHandle { } +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + internal sealed class DuplicateSafeNCryptKeyHandle : SafeNCryptKeyHandle { public DuplicateSafeNCryptKeyHandle(SafeNCryptKeyHandle original) diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeCertContextHandle.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeCertContextHandle.cs index 50293c45860b..5496b23d1bb9 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeCertContextHandle.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeCertContextHandle.cs @@ -7,6 +7,8 @@ using static Interop.Crypt32; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace Microsoft.Win32.SafeHandles { internal sealed class SafeCertContextHandle : SafeHandle diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeProvOrNCryptKeyHandleUwp.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeProvOrNCryptKeyHandleUwp.cs index a1ac99fff124..884bf569481b 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeProvOrNCryptKeyHandleUwp.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeProvOrNCryptKeyHandleUwp.cs @@ -7,6 +7,8 @@ using ErrorCode = Interop.NCrypt.ErrorCode; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace Microsoft.Win32.SafeHandles { // diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/SafeHandles.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/SafeHandles.cs index 4c3ba2011b67..0f67d4244f6c 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/SafeHandles.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/SafeHandles.cs @@ -7,6 +7,8 @@ using System.Runtime.InteropServices; using System.Security.Cryptography; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace Internal.Cryptography.Pal.Native { /// diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.cs index 6ebaa978de95..f528eae37b0f 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.cs @@ -5,6 +5,8 @@ using System.Runtime.InteropServices; using System.Security; +#pragma warning disable CA1419 // TODO https://github.com/dotnet/roslyn-analyzers/issues/5232: not intended for use with P/Invoke + namespace Microsoft.Win32.SafeHandles { /// diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/Microsoft/Win32/SafeHandles/SafeServiceHandle.cs b/src/libraries/System.ServiceProcess.ServiceController/src/Microsoft/Win32/SafeHandles/SafeServiceHandle.cs index 48f039278960..77b1ea855bae 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/Microsoft/Win32/SafeHandles/SafeServiceHandle.cs +++ b/src/libraries/System.ServiceProcess.ServiceController/src/Microsoft/Win32/SafeHandles/SafeServiceHandle.cs @@ -11,6 +11,10 @@ namespace Microsoft.Win32.SafeHandles /// internal sealed class SafeServiceHandle : SafeHandle { + public SafeServiceHandle() : base(IntPtr.Zero, true) + { + } + internal SafeServiceHandle(IntPtr handle) : base(IntPtr.Zero, true) { SetHandle(handle); From 102fc35e821a983ab6cab87e25322eb950b07d6b Mon Sep 17 00:00:00 2001 From: Natalia Kondratyeva Date: Mon, 12 Jul 2021 17:22:18 +0200 Subject: [PATCH 023/133] QUIC logging update (#55500) Together with #55483 this updates all logging to use msquic handles instead of hash code - for listeners, connections and streams. The format of handle is the same as msquic one. --- .../src/System.Net.Quic.csproj | 1 + .../MsQuic/Interop/MsQuicTraceHelper.cs | 30 +++++++++++++++ .../MsQuic/MsQuicConnection.cs | 35 +++++++++-------- .../Implementations/MsQuic/MsQuicListener.cs | 12 ++++++ .../Implementations/MsQuic/MsQuicStream.cs | 38 +++++++++---------- 5 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicTraceHelper.cs diff --git a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj index 6d8de7f5d6c8..1cd17da1d873 100644 --- a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj +++ b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj @@ -20,6 +20,7 @@ + diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicTraceHelper.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicTraceHelper.cs new file mode 100644 index 000000000000..33ef7b948c9b --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicTraceHelper.cs @@ -0,0 +1,30 @@ +// 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.InteropServices; + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal static class MsQuicTraceHelper + { + internal static string GetTraceId(SafeMsQuicStreamHandle handle) + { + return $"[strm][0x{GetIntPtrHex(handle)}]"; + } + + internal static string GetTraceId(SafeMsQuicConnectionHandle handle) + { + return $"[conn][0x{GetIntPtrHex(handle)}]"; + } + + internal static string GetTraceId(SafeMsQuicListenerHandle handle) + { + return $"[list][0x{GetIntPtrHex(handle)}]"; + } + + private static string GetIntPtrHex(SafeHandle handle) + { + return handle.DangerousGetHandle().ToString("X11"); + } + } +} \ No newline at end of file diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs index c5df9a21b468..ec1710316c12 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs @@ -43,6 +43,8 @@ internal sealed class MsQuicConnection : QuicConnectionProvider internal sealed class State { public SafeMsQuicConnectionHandle Handle = null!; // set inside of MsQuicConnection ctor. + public string TraceId = null!; // set inside of MsQuicConnection ctor. + public GCHandle StateGCHandle; // These exists to prevent GC of the MsQuicConnection in the middle of an async op (Connect or Shutdown). @@ -83,7 +85,7 @@ public void RemoveStream(MsQuicStream? stream) if (releaseHandles) { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"{TraceId()} releasing handle after last stream."); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"{TraceId} releasing handle after last stream."); Handle?.Dispose(); } } @@ -124,14 +126,9 @@ public void SetClosing() _closing = true; } } - - internal string TraceId() - { - return $"[MsQuicConnection#{this.GetHashCode()}/{Handle?.DangerousGetHandle():x}]"; - } } - internal string TraceId() => _state.TraceId(); + internal string TraceId() => _state.TraceId; // constructor for inbound connections public MsQuicConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SafeMsQuicConnectionHandle handle, bool remoteCertificateRequired = false, X509RevocationMode revocationMode = X509RevocationMode.Offline, RemoteCertificateValidationCallback? remoteCertificateValidationCallback = null) @@ -168,9 +165,10 @@ public MsQuicConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, Saf throw; } + _state.TraceId = MsQuicTraceHelper.GetTraceId(_state.Handle); if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(_state, $"{TraceId()} inbound connection created"); + NetEventSource.Info(_state, $"{TraceId()} Inbound connection created"); } } @@ -207,9 +205,10 @@ public MsQuicConnection(QuicClientConnectionOptions options) throw; } + _state.TraceId = MsQuicTraceHelper.GetTraceId(_state.Handle); if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(_state, $"{TraceId()} outbound connection created"); + NetEventSource.Info(_state, $"{TraceId()} Outbound connection created"); } } @@ -390,7 +389,7 @@ private static uint HandleEventPeerCertificateReceived(State state, ref Connecti if (certificate == null) { - if (NetEventSource.Log.IsEnabled() && connection._remoteCertificateRequired) NetEventSource.Error(state.Connection, "Remote certificate required, but no remote certificate received"); + if (NetEventSource.Log.IsEnabled() && connection._remoteCertificateRequired) NetEventSource.Error(state, $"{state.TraceId} Remote certificate required, but no remote certificate received"); sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNotAvailable; } else @@ -420,18 +419,18 @@ private static uint HandleEventPeerCertificateReceived(State state, ref Connecti { bool success = connection._remoteCertificateValidationCallback(connection, certificate, chain, sslPolicyErrors); if (!success && NetEventSource.Log.IsEnabled()) - NetEventSource.Error(state, $"[Connection#{state.GetHashCode()}] remote certificate rejected by verification callback"); + NetEventSource.Error(state, $"{state.TraceId} Remote certificate rejected by verification callback"); return success ? MsQuicStatusCodes.Success : MsQuicStatusCodes.HandshakeFailure; } if (NetEventSource.Log.IsEnabled()) - NetEventSource.Info(state, $"[Connection#{state.GetHashCode()}] certificate validation for '${certificate?.Subject}' finished with ${sslPolicyErrors}"); + NetEventSource.Info(state, $"{state.TraceId} Certificate validation for '${certificate?.Subject}' finished with ${sslPolicyErrors}"); return (sslPolicyErrors == SslPolicyErrors.None) ? MsQuicStatusCodes.Success : MsQuicStatusCodes.HandshakeFailure; } catch (Exception ex) { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(state, $"[Connection#{state.GetHashCode()}] certificate validation failed ${ex.Message}"); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(state, $"{state.TraceId} Certificate validation failed ${ex.Message}"); } return MsQuicStatusCodes.InternalError; @@ -629,7 +628,7 @@ private static uint NativeCallbackHandler( if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(state, $"{state.TraceId()} received event {connectionEvent.Type}"); + NetEventSource.Info(state, $"{state.TraceId} Connection received event {connectionEvent.Type}"); } try @@ -658,10 +657,10 @@ private static uint NativeCallbackHandler( { if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Error(state, $"[Connection#{state.GetHashCode()}] Exception occurred during handling {connectionEvent.Type} connection callback: {ex}"); + NetEventSource.Error(state, $"{state.TraceId} Exception occurred during handling {connectionEvent.Type} connection callback: {ex}"); } - Debug.Fail($"[Connection#{state.GetHashCode()}] Exception occurred during handling {connectionEvent.Type} connection callback: {ex}"); + Debug.Fail($"{state.TraceId} Exception occurred during handling {connectionEvent.Type} connection callback: {ex}"); // TODO: trigger an exception on any outstanding async calls. @@ -705,7 +704,7 @@ private void Dispose(bool disposing) return; } - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(_state, $"{TraceId()} disposing {disposing}"); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(_state, $"{TraceId()} Stream disposing {disposing}"); bool releaseHandles = false; lock (_state) @@ -726,7 +725,7 @@ private void Dispose(bool disposing) _configuration?.Dispose(); if (releaseHandles) { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(_state, $"{TraceId()} releasing handle"); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(_state, $"{TraceId()} Connection releasing handle"); // We may not be fully initialized if constructor fails. _state.Handle?.Dispose(); diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs index c5ef5d3fdce3..391d26a93ddd 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs @@ -29,6 +29,7 @@ private sealed class State { // set immediately in ctor, but we need a GCHandle to State in order to create the handle. public SafeMsQuicListenerHandle Handle = null!; + public string TraceId = null!; // set in ctor. public readonly SafeMsQuicConfigurationHandle ConnectionConfiguration; public readonly Channel AcceptConnectionQueue; @@ -75,7 +76,18 @@ internal MsQuicListener(QuicListenerOptions options) throw; } + _state.TraceId = MsQuicTraceHelper.GetTraceId(_state.Handle); + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info(_state, $"{_state.TraceId} Listener created"); + } + _listenEndPoint = Start(options); + + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info(_state, $"{_state.TraceId} Listener started"); + } } internal override IPEndPoint ListenEndPoint diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs index f66f05fdb1ce..437f81ce7759 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs @@ -33,6 +33,7 @@ private sealed class State public SafeMsQuicStreamHandle Handle = null!; // set in ctor. public GCHandle StateGCHandle; public MsQuicConnection.State ConnectionState = null!; // set in ctor. + public string TraceId = null!; // set in ctor. public ReadState ReadState; public long ReadErrorCode = -1; @@ -66,7 +67,7 @@ private sealed class State public void Cleanup() { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"{TraceId()} releasing handles."); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"{TraceId} releasing handles."); ShutdownState = ShutdownState.Finished; CleanupSendState(this); @@ -76,14 +77,9 @@ public void Cleanup() if (StateGCHandle.IsAllocated) StateGCHandle.Free(); ConnectionState?.RemoveStream(null); } - - internal string TraceId() - { - return $"[MsQuicStream#{this.GetHashCode()}/{Handle?.DangerousGetHandle():x}]"; - } } - internal string TraceId() => _state.TraceId(); + internal string TraceId() => _state.TraceId; // inbound. internal MsQuicStream(MsQuicConnection.State connectionState, SafeMsQuicStreamHandle streamHandle, QUIC_STREAM_OPEN_FLAGS flags) @@ -118,12 +114,13 @@ internal MsQuicStream(MsQuicConnection.State connectionState, SafeMsQuicStreamHa _state.ConnectionState = connectionState; + _state.TraceId = MsQuicTraceHelper.GetTraceId(_state.Handle); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info( _state, - $"{TraceId()} inbound {(flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL) ? "uni" : "bi")}directional stream created " + - $"in {_state.ConnectionState.TraceId()}."); + $"{TraceId()} Inbound {(flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL) ? "uni" : "bi")}directional stream created " + + $"in connection {_state.ConnectionState.TraceId}."); } } @@ -171,12 +168,13 @@ internal MsQuicStream(MsQuicConnection.State connectionState, QUIC_STREAM_OPEN_F _state.ConnectionState = connectionState; + _state.TraceId = MsQuicTraceHelper.GetTraceId(_state.Handle); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info( _state, - $"{TraceId()} outbound {(flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL) ? "uni" : "bi")}directional stream created " + - $"in {_state.ConnectionState.TraceId()}."); + $"{_state.TraceId} Outbound {(flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL) ? "uni" : "bi")}directional stream created " + + $"in connection {_state.ConnectionState.TraceId}."); } } @@ -366,7 +364,7 @@ internal override async ValueTask ReadAsync(Memory destination, Cance if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(_state, $"[Stream#{_state.GetHashCode()}] reading into Memory of '{destination.Length}' bytes."); + NetEventSource.Info(_state, $"{TraceId()} Stream reading into Memory of '{destination.Length}' bytes."); } lock (_state) @@ -646,7 +644,7 @@ private void Dispose(bool disposing) } - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(_state, $"{TraceId()} disposing {disposing}"); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(_state, $"{TraceId()} Stream disposing {disposing}"); bool callShutdown = false; bool abortRead = false; @@ -699,7 +697,7 @@ private void Dispose(bool disposing) _state.Cleanup(); } - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(_state, $"{TraceId()} disposed"); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(_state, $"{TraceId()} Stream disposed"); } private void EnableReceive() @@ -724,7 +722,7 @@ private static uint HandleEvent(State state, ref StreamEvent evt) { if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(state, $"{state.TraceId()} received event {evt.Type}"); + NetEventSource.Info(state, $"{state.TraceId} Stream received event {evt.Type}"); } try @@ -767,10 +765,10 @@ private static uint HandleEvent(State state, ref StreamEvent evt) { if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Error(state, $"[Stream#{state.GetHashCode()}] Exception occurred during handling {evt.Type} event: {ex}"); + NetEventSource.Error(state, $"{state.TraceId} Exception occurred during handling Stream {evt.Type} event: {ex}"); } - Debug.Fail($"[Stream#{state.GetHashCode()}] Exception occurred during handling {evt.Type} event: {ex}"); + Debug.Fail($"{state.TraceId} Exception occurred during handling Stream {evt.Type} event: {ex}"); return MsQuicStatusCodes.InternalError; } @@ -871,7 +869,7 @@ private static uint HandleEventShutdownComplete(State state, ref StreamEvent evt lock (state) { // This event won't occur within the middle of a receive. - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(state, $"[Stream#{state.GetHashCode()}] completing resettable event source."); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(state, $"{state.TraceId} Stream completing resettable event source."); if (state.ReadState == ReadState.None) { @@ -950,7 +948,7 @@ private static uint HandleEventPeerSendShutdown(State state) lock (state) { // This event won't occur within the middle of a receive. - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(state, $"[Stream#{state.GetHashCode()}] completing resettable event source."); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(state, $"{state.TraceId} Stream completing resettable event source."); if (state.ReadState == ReadState.None) { @@ -1240,7 +1238,7 @@ private static uint HandleEventConnectionClose(State state) long errorCode = state.ConnectionState.AbortErrorCode; if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(state, $"[Stream#{state.GetHashCode()}] handling Connection#{state.ConnectionState.GetHashCode()} close" + + NetEventSource.Info(state, $"{state.TraceId} Stream handling connection {state.ConnectionState.TraceId} close" + (errorCode != -1 ? $" with code {errorCode}" : "")); } From f28318bf770f49aeaf8b2b67b440250732b7fbf3 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Mon, 12 Jul 2021 17:43:53 +0200 Subject: [PATCH 024/133] [wasm] WebSocket is already disposed by after abort. (#55075) _innerWebSocket is already disposed by after abort. --- .../BrowserWebSockets/BrowserWebSocket.cs | 19 +++++++++++++++++-- .../tests/AbortTest.cs | 10 +++++----- .../tests/ConnectTest.cs | 1 - 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs index 0eae170576a7..b052020b11e0 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs @@ -574,7 +574,15 @@ private Task CloseAsyncCore(WebSocketCloseStatus closeStatus, string? statusDesc _innerWebSocketCloseStatus = closeStatus; _innerWebSocketCloseStatusDescription = statusDescription; _innerWebSocket!.Invoke("close", (int)closeStatus, statusDescription); - _closeStatus = (int)_innerWebSocket.GetObjectProperty("readyState"); + if (_innerWebSocket != null && !_innerWebSocket.IsDisposed && _state != (int)InternalState.Aborted) + { + _closeStatus = (int)_innerWebSocket.GetObjectProperty("readyState"); + } + else + { + _closeStatus = 3; // (CLOSED) + } + return _tcsClose.Task; } catch (Exception exc) @@ -612,7 +620,14 @@ private Task CloseOutputAsyncCore(WebSocketCloseStatus closeStatus, string? stat _innerWebSocketCloseStatus = closeStatus; _innerWebSocketCloseStatusDescription = statusDescription; _innerWebSocket!.Invoke("close", (int)closeStatus, statusDescription); - _closeStatus = (int)_innerWebSocket.GetObjectProperty("readyState"); + if (_innerWebSocket != null && !_innerWebSocket.IsDisposed && _state != (int)InternalState.Aborted) + { + _closeStatus = (int)_innerWebSocket.GetObjectProperty("readyState"); + } + else + { + _closeStatus = 3; // (CLOSED) + } OnCloseCallback(null, cancellationToken); return Task.CompletedTask; } diff --git a/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs index 6e9a60f06479..209a6f10ff23 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs @@ -15,7 +15,7 @@ public class AbortTest : ClientWebSocketTestBase { public AbortTest(ITestOutputHelper output) : base(output) { } - [OuterLoop] + [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task Abort_ConnectAndAbort_ThrowsWebSocketExceptionWithmessage(Uri server) { @@ -40,7 +40,7 @@ public async Task Abort_ConnectAndAbort_ThrowsWebSocketExceptionWithmessage(Uri } } - [OuterLoop] + [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task Abort_SendAndAbort_Success(Uri server) { @@ -60,7 +60,7 @@ await TestCancellation(async (cws) => }, server); } - [OuterLoop] + [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task Abort_ReceiveAndAbort_Success(Uri server) { @@ -84,7 +84,7 @@ await cws.SendAsync( }, server); } - [OuterLoop] + [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task Abort_CloseAndAbort_Success(Uri server) { @@ -108,7 +108,7 @@ await cws.SendAsync( }, server); } - [OuterLoop] + [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task ClientWebSocket_Abort_CloseOutputAsync(Uri server) { diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs index 0fb1314a6068..296bffbc620a 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs @@ -282,7 +282,6 @@ public async Task ConnectAsync_CancellationRequestedInflightConnect_ThrowsOperat [ConditionalFact(nameof(WebSocketsSupported))] [ActiveIssue("https://github.com/dotnet/runtime/issues/34690", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/54152", TestPlatforms.Browser)] public async Task ConnectAsync_CancellationRequestedAfterConnect_ThrowsOperationCanceledException() { var releaseServer = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); From f57b6e7308e9db32e63488aa5e8f938934f54cf0 Mon Sep 17 00:00:00 2001 From: Mike McLaughlin Date: Mon, 12 Jul 2021 08:55:14 -0700 Subject: [PATCH 025/133] Add crash report to createdump for Linux Watson (#55438) Add crash report to createdump for Linux Watson For Linux Watson the crash report will contains the .NET Core version, the faulting process name (the module/assembly containing Main) and the managed exception info (including the exception HRESULT) and thread stack trace of the thread the caused the crash. Add the CLRDATA_MODULE_IS_MAIN_MODULE flag to the DAC's IXCLRDataModule::GetFlags API. Add code to get the managed method name and write it out as "method_name" (even on MacOS). Add native frame symbolization (unmanaged_name) using dladdr. Only get the image info once and save in a global Demangle the stack frame symbols Only write PH_HDR_CANARY section if neccessary --- src/coreclr/debug/createdump/CMakeLists.txt | 1 + src/coreclr/debug/createdump/crashinfo.cpp | 87 ++++++++--- src/coreclr/debug/createdump/crashinfo.h | 11 +- src/coreclr/debug/createdump/crashinfomac.cpp | 36 +++++ .../debug/createdump/crashinfounix.cpp | 10 ++ .../debug/createdump/crashreportwriter.cpp | 146 +++++++++++------- .../debug/createdump/crashreportwriter.h | 6 +- src/coreclr/debug/createdump/createdump.h | 2 + src/coreclr/debug/createdump/datatarget.h | 4 + .../debug/createdump/dumpwriterelf.cpp | 35 +++-- src/coreclr/debug/createdump/dumpwriterelf.h | 4 + .../debug/createdump/dumpwritermacho.h | 4 + src/coreclr/debug/createdump/moduleinfo.h | 49 +++--- src/coreclr/debug/createdump/stackframe.h | 42 ++++- src/coreclr/debug/createdump/threadinfo.cpp | 18 ++- src/coreclr/debug/createdump/threadinfo.h | 25 ++- .../debug/createdump/threadinfomac.cpp | 1 + .../debug/createdump/threadinfounix.cpp | 3 +- src/coreclr/debug/daccess/task.cpp | 11 +- src/coreclr/inc/xclrdata.idl | 1 + src/coreclr/pal/prebuilt/inc/xclrdata.h | 3 +- 21 files changed, 364 insertions(+), 135 deletions(-) diff --git a/src/coreclr/debug/createdump/CMakeLists.txt b/src/coreclr/debug/createdump/CMakeLists.txt index 5d766d53da18..f0093b7cb666 100644 --- a/src/coreclr/debug/createdump/CMakeLists.txt +++ b/src/coreclr/debug/createdump/CMakeLists.txt @@ -86,6 +86,7 @@ endif(CLR_CMAKE_HOST_OSX) dbgutil # share the PAL in the dac module mscordaccore + dl ) add_dependencies(createdump mscordaccore) diff --git a/src/coreclr/debug/createdump/crashinfo.cpp b/src/coreclr/debug/createdump/crashinfo.cpp index bc444815415c..807036fd2008 100644 --- a/src/coreclr/debug/createdump/crashinfo.cpp +++ b/src/coreclr/debug/createdump/crashinfo.cpp @@ -6,13 +6,17 @@ // This is for the PAL_VirtualUnwindOutOfProc read memory adapter. CrashInfo* g_crashInfo; +static bool ModuleInfoCompare(const ModuleInfo* lhs, const ModuleInfo* rhs) { return lhs->BaseAddress() < rhs->BaseAddress(); } + CrashInfo::CrashInfo(pid_t pid, bool gatherFrames, pid_t crashThread, uint32_t signal) : m_ref(1), m_pid(pid), m_ppid(-1), + m_hdac(nullptr), m_gatherFrames(gatherFrames), m_crashThread(crashThread), - m_signal(signal) + m_signal(signal), + m_moduleInfos(&ModuleInfoCompare) { g_crashInfo = this; #ifdef __APPLE__ @@ -31,6 +35,20 @@ CrashInfo::~CrashInfo() delete thread; } m_threads.clear(); + + // Clean up the modules + for (ModuleInfo* module : m_moduleInfos) + { + delete module; + } + m_moduleInfos.clear(); + + // Unload DAC module + if (m_hdac != nullptr) + { + FreeLibrary(m_hdac); + m_hdac = nullptr; + } #ifdef __APPLE__ if (m_task != 0) { @@ -191,7 +209,6 @@ CrashInfo::EnumerateMemoryRegionsWithDAC(MINIDUMP_TYPE minidumpType) PFN_CLRDataCreateInstance pfnCLRDataCreateInstance = nullptr; ICLRDataEnumMemoryRegions* pClrDataEnumRegions = nullptr; IXCLRDataProcess* pClrDataProcess = nullptr; - HMODULE hdac = nullptr; HRESULT hr = S_OK; bool result = false; @@ -205,13 +222,13 @@ CrashInfo::EnumerateMemoryRegionsWithDAC(MINIDUMP_TYPE minidumpType) dacPath.append(MAKEDLLNAME_A("mscordaccore")); // Load and initialize the DAC - hdac = LoadLibraryA(dacPath.c_str()); - if (hdac == nullptr) + m_hdac = LoadLibraryA(dacPath.c_str()); + if (m_hdac == nullptr) { fprintf(stderr, "LoadLibraryA(%s) FAILED %d\n", dacPath.c_str(), GetLastError()); goto exit; } - pfnCLRDataCreateInstance = (PFN_CLRDataCreateInstance)GetProcAddress(hdac, "CLRDataCreateInstance"); + pfnCLRDataCreateInstance = (PFN_CLRDataCreateInstance)GetProcAddress(m_hdac, "CLRDataCreateInstance"); if (pfnCLRDataCreateInstance == nullptr) { fprintf(stderr, "GetProcAddress(CLRDataCreateInstance) FAILED %d\n", GetLastError()); @@ -262,10 +279,6 @@ CrashInfo::EnumerateMemoryRegionsWithDAC(MINIDUMP_TYPE minidumpType) { pClrDataProcess->Release(); } - if (hdac != nullptr) - { - FreeLibrary(hdac); - } return result; } @@ -347,10 +360,13 @@ CrashInfo::EnumerateManagedModules(IXCLRDataProcess* pClrDataProcess) bool CrashInfo::UnwindAllThreads(IXCLRDataProcess* pClrDataProcess) { + ReleaseHolder pSos = nullptr; + pClrDataProcess->QueryInterface(__uuidof(ISOSDacInterface), (void**)&pSos); + // For each native and managed thread for (ThreadInfo* thread : m_threads) { - if (!thread->UnwindThread(pClrDataProcess)) { + if (!thread->UnwindThread(pClrDataProcess, pSos)) { return false; } } @@ -426,9 +442,9 @@ CrashInfo::GetBaseAddressFromAddress(uint64_t address) uint64_t CrashInfo::GetBaseAddressFromName(const char* moduleName) { - for (const ModuleInfo& moduleInfo : m_moduleInfos) + for (const ModuleInfo* moduleInfo : m_moduleInfos) { - std::string name = GetFileName(moduleInfo.ModuleName()); + std::string name = GetFileName(moduleInfo->ModuleName()); #ifdef __APPLE__ // Module names are case insenstive on MacOS if (strcasecmp(name.c_str(), moduleName) == 0) @@ -436,7 +452,7 @@ CrashInfo::GetBaseAddressFromName(const char* moduleName) if (name.compare(moduleName) == 0) #endif { - return moduleInfo.BaseAddress(); + return moduleInfo->BaseAddress(); } } return 0; @@ -445,14 +461,14 @@ CrashInfo::GetBaseAddressFromName(const char* moduleName) // // Return the module info for the base address // -const ModuleInfo* +ModuleInfo* CrashInfo::GetModuleInfoFromBaseAddress(uint64_t baseAddress) { ModuleInfo search(baseAddress); - const auto& found = m_moduleInfos.find(search); + const auto& found = m_moduleInfos.find(&search); if (found != m_moduleInfos.end()) { - return &*found; + return *found; } return nullptr; } @@ -475,11 +491,12 @@ void CrashInfo::AddModuleInfo(bool isManaged, uint64_t baseAddress, IXCLRDataModule* pClrDataModule, const std::string& moduleName) { ModuleInfo moduleInfo(baseAddress); - const auto& found = m_moduleInfos.find(moduleInfo); + const auto& found = m_moduleInfos.find(&moduleInfo); if (found == m_moduleInfos.end()) { uint32_t timeStamp = 0; uint32_t imageSize = 0; + bool isMainModule = false; GUID mvid; if (isManaged) { @@ -511,11 +528,18 @@ CrashInfo::AddModuleInfo(bool isManaged, uint64_t baseAddress, IXCLRDataModule* } if (pClrDataModule != nullptr) { + ULONG32 flags = 0; + pClrDataModule->GetFlags(&flags); + isMainModule = (flags & CLRDATA_MODULE_IS_MAIN_MODULE) != 0; pClrDataModule->GetVersionId(&mvid); } - TRACE("MODULE: timestamp %08x size %08x %s %s\n", timeStamp, imageSize, FormatGuid(&mvid).c_str(), moduleName.c_str()); + TRACE("MODULE: timestamp %08x size %08x %s %s%s\n", timeStamp, imageSize, FormatGuid(&mvid).c_str(), isMainModule ? "*" : "", moduleName.c_str()); + } + ModuleInfo* moduleInfo = new ModuleInfo(isManaged, baseAddress, timeStamp, imageSize, &mvid, moduleName); + if (isMainModule) { + m_mainModule = moduleInfo; } - m_moduleInfos.insert(ModuleInfo(isManaged, baseAddress, timeStamp, imageSize, &mvid, moduleName)); + m_moduleInfos.insert(moduleInfo); } } @@ -737,6 +761,31 @@ CrashInfo::TraceVerbose(const char* format, ...) } } +// +// Lookup a symbol in a module. The caller needs to call "free()" on symbol returned. +// +const char* +ModuleInfo::GetSymbolName(uint64_t address) +{ + LoadModule(); + + if (m_localBaseAddress != 0) + { + uint64_t localAddress = m_localBaseAddress + (address - m_baseAddress); + Dl_info info; + if (dladdr((void*)localAddress, &info) != 0) + { + if (info.dli_sname != nullptr) + { + int status = -1; + char *demangled = abi::__cxa_demangle(info.dli_sname, nullptr, 0, &status); + return status == 0 ? demangled : strdup(info.dli_sname); + } + } + } + return nullptr; +} + // // Returns just the file name portion of a file path // diff --git a/src/coreclr/debug/createdump/crashinfo.h b/src/coreclr/debug/createdump/crashinfo.h index f315b98dd287..199144e17540 100644 --- a/src/coreclr/debug/createdump/crashinfo.h +++ b/src/coreclr/debug/createdump/crashinfo.h @@ -46,6 +46,7 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, pid_t m_pid; // pid pid_t m_ppid; // parent pid pid_t m_tgid; // process group + HMODULE m_hdac; // dac module handle when loaded bool m_gatherFrames; // if true, add the native and managed stack frames to the thread info pid_t m_crashThread; // crashing thread id or 0 if none uint32_t m_signal; // crash signal code or 0 if none @@ -68,7 +69,12 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, std::set m_otherMappings; // other memory mappings std::set m_memoryRegions; // memory regions from DAC, etc. std::set m_moduleAddresses; // memory region to module base address - std::set m_moduleInfos; // module infos (base address and module name) + std::set m_moduleInfos; // module infos (base address and module name) + ModuleInfo* m_mainModule; // the module containing "Main" + + // no public copy constructor + CrashInfo(const CrashInfo&) = delete; + void operator=(const CrashInfo&) = delete; public: CrashInfo(pid_t pid, bool gatherFrames, pid_t crashThread, uint32_t signal); @@ -82,7 +88,7 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, bool ReadProcessMemory(void* address, void* buffer, size_t size, size_t* read); // read raw memory uint64_t GetBaseAddressFromAddress(uint64_t address); uint64_t GetBaseAddressFromName(const char* moduleName); - const ModuleInfo* GetModuleInfoFromBaseAddress(uint64_t baseAddress); + ModuleInfo* GetModuleInfoFromBaseAddress(uint64_t baseAddress); void AddModuleAddressRange(uint64_t startAddress, uint64_t endAddress, uint64_t baseAddress); void AddModuleInfo(bool isManaged, uint64_t baseAddress, IXCLRDataModule* pClrDataModule, const std::string& moduleName); void InsertMemoryRegion(uint64_t address, size_t size); @@ -98,6 +104,7 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, inline const pid_t CrashThread() const { return m_crashThread; } inline const uint32_t Signal() const { return m_signal; } inline const std::string& Name() const { return m_name; } + inline const ModuleInfo* MainModule() const { return m_mainModule; } inline const std::vector Threads() const { return m_threads; } inline const std::set ModuleMappings() const { return m_moduleMappings; } diff --git a/src/coreclr/debug/createdump/crashinfomac.cpp b/src/coreclr/debug/createdump/crashinfomac.cpp index 4461d2a8c96a..ad9c247e37df 100644 --- a/src/coreclr/debug/createdump/crashinfomac.cpp +++ b/src/coreclr/debug/createdump/crashinfomac.cpp @@ -380,3 +380,39 @@ CrashInfo::ReadProcessMemory(void* address, void* buffer, size_t size, size_t* r *read = numberOfBytesRead; return size == 0 || numberOfBytesRead > 0; } + +const struct dyld_all_image_infos* g_image_infos = nullptr; + +void +ModuleInfo::LoadModule() +{ + if (m_module == nullptr) + { + m_module = dlopen(m_moduleName.c_str(), RTLD_LAZY); + if (m_module != nullptr) + { + if (g_image_infos == nullptr) + { + struct task_dyld_info dyld_info; + mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; + kern_return_t result = task_info(mach_task_self_, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count); + if (result == KERN_SUCCESS) + { + g_image_infos = (const struct dyld_all_image_infos*)dyld_info.all_image_info_addr; + } + } + if (g_image_infos != nullptr) + { + for (int i = 0; i < g_image_infos->infoArrayCount; ++i) + { + const struct dyld_image_info* image = g_image_infos->infoArray + i; + if (strcasecmp(image->imageFilePath, m_moduleName.c_str()) == 0) + { + m_localBaseAddress = (uint64_t)image->imageLoadAddress; + break; + } + } + } + } + } +} diff --git a/src/coreclr/debug/createdump/crashinfounix.cpp b/src/coreclr/debug/createdump/crashinfounix.cpp index 2cc1eb6c688f..2f9605554ad1 100644 --- a/src/coreclr/debug/createdump/crashinfounix.cpp +++ b/src/coreclr/debug/createdump/crashinfounix.cpp @@ -414,3 +414,13 @@ GetStatus(pid_t pid, pid_t* ppid, pid_t* tgid, std::string* name) fclose(statusFile); return true; } + +void +ModuleInfo::LoadModule() +{ + if (m_module == nullptr) + { + m_module = dlopen(m_moduleName.c_str(), RTLD_LAZY); + m_localBaseAddress = ((struct link_map*)m_module)->l_addr; + } +} diff --git a/src/coreclr/debug/createdump/crashreportwriter.cpp b/src/coreclr/debug/createdump/crashreportwriter.cpp index 9cac899968f8..40d8dbdba767 100644 --- a/src/coreclr/debug/createdump/crashreportwriter.cpp +++ b/src/coreclr/debug/createdump/crashreportwriter.cpp @@ -51,20 +51,19 @@ CrashReportWriter::WriteCrashReport(const std::string& dumpFileName) } } -#ifdef __APPLE__ - void CrashReportWriter::WriteCrashReport() { - const char* exceptionType = nullptr; OpenObject("payload"); - WriteValue("protocol_version", "0.0.7"); + WriteValue("protocol_version", "1.0.0"); OpenObject("configuration"); #if defined(__x86_64__) WriteValue("architecture", "amd64"); #elif defined(__aarch64__) WriteValue("architecture", "arm64"); +#elif defined(__arm__) + WriteValue("architecture", "arm"); #endif std::string version; assert(strncmp(sccsid, "@(#)Version ", 12) == 0); @@ -73,6 +72,13 @@ CrashReportWriter::WriteCrashReport() WriteValue("version", version.c_str()); CloseObject(); // configuration + // The main module was saved away in the crash info + if (m_crashInfo.MainModule()->BaseAddress() != 0) + { + WriteValue("process_name", GetFileName(m_crashInfo.MainModule()->ModuleName()).c_str()); + } + + const char* exceptionType = nullptr; OpenArray("threads"); for (const ThreadInfo* thread : m_crashInfo.Threads()) { @@ -131,6 +137,10 @@ CrashReportWriter::WriteCrashReport() { WriteValue("managed_exception_type", thread->ManagedExceptionType().c_str()); } + if (thread->ManagedExceptionHResult() != 0) + { + WriteValue32("managed_exception_hresult", thread->ManagedExceptionHResult()); + } WriteValue64("native_thread_id", thread->Tid()); OpenObject("ctx"); WriteValue64("IP", thread->GetInstructionPointer()); @@ -148,7 +158,7 @@ CrashReportWriter::WriteCrashReport() } CloseArray(); // threads CloseObject(); // payload - +#ifdef __APPLE__ OpenObject("parameters"); if (exceptionType != nullptr) { @@ -158,8 +168,35 @@ CrashReportWriter::WriteCrashReport() WriteSysctl("hw.model", "SystemModel"); WriteValue("SystemManufacturer", "apple"); CloseObject(); // parameters +#endif // __APPLE__ } +#ifdef __APPLE__ + +void +CrashReportWriter::WriteSysctl(const char* sysctlname, const char* valueName) +{ + size_t size = 0; + if (sysctlbyname(sysctlname, nullptr, &size, NULL, 0) >= 0) + { + ArrayHolder buffer = new char[size]; + if (sysctlbyname(sysctlname, buffer, &size, NULL, 0) >= 0) + { + WriteValue(valueName, buffer); + } + else + { + TRACE("sysctlbyname(%s) 1 FAILED %s\n", sysctlname, strerror(errno)); + } + } + else + { + TRACE("sysctlbyname(%s) 2 FAILED %s\n", sysctlname, strerror(errno)); + } +} + +#endif // __APPLE__ + void CrashReportWriter::WriteStackFrame(const StackFrame& frame) { @@ -167,16 +204,26 @@ CrashReportWriter::WriteStackFrame(const StackFrame& frame) WriteValueBool("is_managed", frame.IsManaged()); WriteValue64("module_address", frame.ModuleAddress()); WriteValue64("stack_pointer", frame.StackPointer()); - WriteValue64("native_address", frame.ReturnAddress()); + WriteValue64("native_address", frame.InstructionPointer()); WriteValue64("native_offset", frame.NativeOffset()); if (frame.IsManaged()) { WriteValue32("token", frame.Token()); WriteValue32("il_offset", frame.ILOffset()); } + IXCLRDataMethodInstance* pMethod = frame.GetMethod(); + if (pMethod != nullptr) + { + ArrayHolder wszUnicodeName = new WCHAR[MAX_LONGPATH + 1]; + if (SUCCEEDED(pMethod->GetName(0, MAX_LONGPATH, nullptr, wszUnicodeName))) + { + std::string methodName = FormatString("%S", wszUnicodeName.GetPtr()); + WriteValue("method_name", methodName.c_str()); + } + } if (frame.ModuleAddress() != 0) { - const ModuleInfo* moduleInfo = m_crashInfo.GetModuleInfoFromBaseAddress(frame.ModuleAddress()); + ModuleInfo* moduleInfo = m_crashInfo.GetModuleInfoFromBaseAddress(frame.ModuleAddress()); if (moduleInfo != nullptr) { std::string moduleName = GetFileName(moduleInfo->ModuleName()); @@ -189,6 +236,12 @@ CrashReportWriter::WriteStackFrame(const StackFrame& frame) } else { + const char* symbol = moduleInfo->GetSymbolName(frame.InstructionPointer()); + if (symbol != nullptr) + { + WriteValue("unmanaged_name", symbol); + free((void*)symbol); + } WriteValue("native_module", moduleName.c_str()); } } @@ -196,38 +249,8 @@ CrashReportWriter::WriteStackFrame(const StackFrame& frame) CloseObject(); } -void -CrashReportWriter::WriteSysctl(const char* sysctlname, const char* valueName) -{ - size_t size = 0; - if (sysctlbyname(sysctlname, nullptr, &size, NULL, 0) >= 0) - { - ArrayHolder buffer = new char[size]; - if (sysctlbyname(sysctlname, buffer, &size, NULL, 0) >= 0) - { - WriteValue(valueName, buffer); - } - else - { - TRACE("sysctlbyname(%s) 1 FAILED %s\n", sysctlname, strerror(errno)); - } - } - else - { - TRACE("sysctlbyname(%s) 2 FAILED %s\n", sysctlname, strerror(errno)); - } -} - -#else // __APPLE__ - -void -CrashReportWriter::WriteCrashReport() -{ -} - -#endif // __APPLE__ - -bool CrashReportWriter::OpenWriter(const char* fileName) +bool +CrashReportWriter::OpenWriter(const char* fileName) { m_fd = open(fileName, O_WRONLY|O_CREAT|O_TRUNC, 0664); if (m_fd == -1) @@ -239,13 +262,15 @@ bool CrashReportWriter::OpenWriter(const char* fileName) return true; } -void CrashReportWriter::CloseWriter() +void +CrashReportWriter::CloseWriter() { assert(m_indent == JSON_INDENT_VALUE); Write("\n}\n"); } -void CrashReportWriter::Write(const std::string& text) +void +CrashReportWriter::Write(const std::string& text) { if (!DumpWriter::WriteData(m_fd, (void*)text.c_str(), text.length())) { @@ -253,19 +278,22 @@ void CrashReportWriter::Write(const std::string& text) } } -void CrashReportWriter::Write(const char* buffer) +void +CrashReportWriter::Write(const char* buffer) { std::string text(buffer); Write(text); } -void CrashReportWriter::Indent(std::string& text) +void +CrashReportWriter::Indent(std::string& text) { assert(m_indent >= 0); text.append(m_indent, ' '); } -void CrashReportWriter::WriteSeperator(std::string& text) +void +CrashReportWriter::WriteSeperator(std::string& text) { if (m_comma) { @@ -275,7 +303,8 @@ void CrashReportWriter::WriteSeperator(std::string& text) Indent(text); } -void CrashReportWriter::OpenValue(const char* key, char marker) +void +CrashReportWriter::OpenValue(const char* key, char marker) { std::string text; WriteSeperator(text); @@ -292,7 +321,8 @@ void CrashReportWriter::OpenValue(const char* key, char marker) Write(text); } -void CrashReportWriter::CloseValue(char marker) +void +CrashReportWriter::CloseValue(char marker) { std::string text; text.append(1, '\n'); @@ -304,7 +334,8 @@ void CrashReportWriter::CloseValue(char marker) Write(text); } -void CrashReportWriter::WriteValue(const char* key, const char* value) +void +CrashReportWriter::WriteValue(const char* key, const char* value) { std::string text; WriteSeperator(text); @@ -317,41 +348,48 @@ void CrashReportWriter::WriteValue(const char* key, const char* value) Write(text); } -void CrashReportWriter::WriteValueBool(const char* key, bool value) +void +CrashReportWriter::WriteValueBool(const char* key, bool value) { WriteValue(key, value ? "true" : "false"); } -void CrashReportWriter::WriteValue32(const char* key, uint32_t value) +void +CrashReportWriter::WriteValue32(const char* key, uint32_t value) { char buffer[16]; snprintf(buffer, sizeof(buffer), "0x%x", value); WriteValue(key, buffer); } -void CrashReportWriter::WriteValue64(const char* key, uint64_t value) +void +CrashReportWriter::WriteValue64(const char* key, uint64_t value) { char buffer[32]; snprintf(buffer, sizeof(buffer), "0x%" PRIx64, value); WriteValue(key, buffer); } -void CrashReportWriter::OpenObject(const char* key) +void +CrashReportWriter::OpenObject(const char* key) { OpenValue(key, '{'); } -void CrashReportWriter::CloseObject() +void +CrashReportWriter::CloseObject() { CloseValue('}'); } -void CrashReportWriter::OpenArray(const char* key) +void +CrashReportWriter::OpenArray(const char* key) { OpenValue(key, '['); } -void CrashReportWriter::CloseArray() +void +CrashReportWriter::CloseArray() { CloseValue(']'); } diff --git a/src/coreclr/debug/createdump/crashreportwriter.h b/src/coreclr/debug/createdump/crashreportwriter.h index ef77bfcac559..e5f0f618d944 100644 --- a/src/coreclr/debug/createdump/crashreportwriter.h +++ b/src/coreclr/debug/createdump/crashreportwriter.h @@ -13,6 +13,10 @@ class CrashReportWriter bool m_comma; CrashInfo& m_crashInfo; + // no public copy constructor + CrashReportWriter(const CrashReportWriter&) = delete; + void operator=(const CrashReportWriter&) = delete; + public: CrashReportWriter(CrashInfo& crashInfo); virtual ~CrashReportWriter(); @@ -21,9 +25,9 @@ class CrashReportWriter private: void WriteCrashReport(); #ifdef __APPLE__ - void WriteStackFrame(const StackFrame& frame); void WriteSysctl(const char* sysctlname, const char* valueName); #endif + void WriteStackFrame(const StackFrame& frame); void Write(const std::string& text); void Write(const char* buffer); void Indent(std::string& text); diff --git a/src/coreclr/debug/createdump/createdump.h b/src/coreclr/debug/createdump/createdump.h index 95f63f460e4a..f588867c7926 100644 --- a/src/coreclr/debug/createdump/createdump.h +++ b/src/coreclr/debug/createdump/createdump.h @@ -71,6 +71,8 @@ typedef int T_CONTEXT; #endif #include #include +#include +#include #ifdef __APPLE__ #include #else diff --git a/src/coreclr/debug/createdump/datatarget.h b/src/coreclr/debug/createdump/datatarget.h index 954ff5328b4e..792947bafe21 100644 --- a/src/coreclr/debug/createdump/datatarget.h +++ b/src/coreclr/debug/createdump/datatarget.h @@ -9,6 +9,10 @@ class DumpDataTarget : public ICLRDataTarget LONG m_ref; // reference count CrashInfo& m_crashInfo; + // no public copy constructor + DumpDataTarget(const DumpDataTarget&) = delete; + void operator=(const DumpDataTarget&) = delete; + public: DumpDataTarget(CrashInfo& crashInfo); virtual ~DumpDataTarget(); diff --git a/src/coreclr/debug/createdump/dumpwriterelf.cpp b/src/coreclr/debug/createdump/dumpwriterelf.cpp index 57249d83f7e6..afd403212b24 100644 --- a/src/coreclr/debug/createdump/dumpwriterelf.cpp +++ b/src/coreclr/debug/createdump/dumpwriterelf.cpp @@ -32,12 +32,10 @@ DumpWriter::WriteDump() ehdr.e_type = ET_CORE; ehdr.e_machine = ELF_ARCH; ehdr.e_version = EV_CURRENT; - ehdr.e_shoff = sizeof(Ehdr); - ehdr.e_phoff = sizeof(Ehdr) + sizeof(Shdr); + ehdr.e_phoff = sizeof(Ehdr); ehdr.e_ehsize = sizeof(Ehdr); ehdr.e_phentsize = sizeof(Phdr); - ehdr.e_shentsize = sizeof(Shdr); // The ELF header only allows UINT16 for the number of program // headers. In a core dump this equates to PT_NODE and PT_LOAD. @@ -60,26 +58,33 @@ DumpWriter::WriteDump() } else { ehdr.e_phnum = PH_HDR_CANARY; + ehdr.e_phoff = sizeof(Ehdr) + sizeof(Shdr); + ehdr.e_shnum = 1; + ehdr.e_shoff = sizeof(Ehdr); + ehdr.e_shentsize = sizeof(Shdr); } if (!WriteData(&ehdr, sizeof(Ehdr))) { return false; } - size_t offset = sizeof(Ehdr) + sizeof(Shdr) + (phnum * sizeof(Phdr)); + size_t offset = sizeof(Ehdr) + (phnum * sizeof(Phdr)); size_t filesz = GetProcessInfoSize() + GetAuxvInfoSize() + GetThreadInfoSize() + GetNTFileInfoSize(); - // Add single section containing the actual count - // of the program headers to be written. - Shdr shdr; - memset(&shdr, 0, sizeof(shdr)); - shdr.sh_info = phnum; - // When section header offset is present but ehdr section num = 0 - // then is is expected that the sh_size indicates the size of the - // section array or 1 in our case. - shdr.sh_size = 1; - if (!WriteData(&shdr, sizeof(shdr))) { - return false; + if (ehdr.e_phnum == PH_HDR_CANARY) + { + // Add single section containing the actual count of the program headers to be written. + Shdr shdr; + memset(&shdr, 0, sizeof(shdr)); + shdr.sh_info = phnum; + shdr.sh_size = 1; + offset += sizeof(Shdr); + + // When section header offset is present but ehdr section num = 0 then is is expected that + // the sh_size indicates the size of the section array or 1 in our case. + if (!WriteData(&shdr, sizeof(shdr))) { + return false; + } } // PT_NOTE header diff --git a/src/coreclr/debug/createdump/dumpwriterelf.h b/src/coreclr/debug/createdump/dumpwriterelf.h index ac4dc10fd2f1..6da55da2f137 100644 --- a/src/coreclr/debug/createdump/dumpwriterelf.h +++ b/src/coreclr/debug/createdump/dumpwriterelf.h @@ -36,6 +36,10 @@ class DumpWriter CrashInfo& m_crashInfo; BYTE m_tempBuffer[0x4000]; + // no public copy constructor + DumpWriter(const DumpWriter&) = delete; + void operator=(const DumpWriter&) = delete; + public: DumpWriter(CrashInfo& crashInfo); virtual ~DumpWriter(); diff --git a/src/coreclr/debug/createdump/dumpwritermacho.h b/src/coreclr/debug/createdump/dumpwritermacho.h index 6be2aa7742b6..704ea0848821 100644 --- a/src/coreclr/debug/createdump/dumpwritermacho.h +++ b/src/coreclr/debug/createdump/dumpwritermacho.h @@ -30,6 +30,10 @@ class DumpWriter std::vector m_threadLoadCommands; BYTE m_tempBuffer[0x4000]; + // no public copy constructor + DumpWriter(const DumpWriter&) = delete; + void operator=(const DumpWriter&) = delete; + public: DumpWriter(CrashInfo& crashInfo); virtual ~DumpWriter(); diff --git a/src/coreclr/debug/createdump/moduleinfo.h b/src/coreclr/debug/createdump/moduleinfo.h index f07e50d592f0..2876562fba2d 100644 --- a/src/coreclr/debug/createdump/moduleinfo.h +++ b/src/coreclr/debug/createdump/moduleinfo.h @@ -10,10 +10,27 @@ struct ModuleInfo GUID m_mvid; std::string m_moduleName; bool m_isManaged; + void* m_module; + uint64_t m_localBaseAddress; + + // no public copy constructor + ModuleInfo(const ModuleInfo&) = delete; + void operator=(const ModuleInfo&) = delete; + + void LoadModule(); public: + ModuleInfo() : + m_baseAddress(0), + m_module(nullptr), + m_localBaseAddress(0) + { + } + ModuleInfo(uint64_t baseAddress) : - m_baseAddress(baseAddress) + m_baseAddress(baseAddress), + m_module(nullptr), + m_localBaseAddress(0) { } @@ -23,23 +40,19 @@ struct ModuleInfo m_imageSize(imageSize), m_mvid(*mvid), m_moduleName(moduleName), - m_isManaged(isManaged) - { - } - - // copy constructor - ModuleInfo(const ModuleInfo& moduleInfo) : - m_baseAddress(moduleInfo.m_baseAddress), - m_timeStamp(moduleInfo.m_timeStamp), - m_imageSize(moduleInfo.m_imageSize), - m_mvid(moduleInfo.m_mvid), - m_moduleName(moduleInfo.m_moduleName), - m_isManaged(moduleInfo.m_isManaged) + m_isManaged(isManaged), + m_module(nullptr), + m_localBaseAddress(0) { } ~ModuleInfo() { + if (m_module != nullptr) + { + dlclose(m_module); + m_module = nullptr; + } } inline bool IsManaged() const { return m_isManaged; } @@ -49,13 +62,5 @@ struct ModuleInfo inline const GUID* Mvid() const { return &m_mvid; } inline const std::string& ModuleName() const { return m_moduleName; } - bool operator<(const ModuleInfo& rhs) const - { - return m_baseAddress < rhs.m_baseAddress; - } - - void Trace() const - { - TRACE("%" PRIA PRIx64 " %s\n", m_baseAddress, m_moduleName.c_str()); - } + const char* GetSymbolName(uint64_t address); }; diff --git a/src/coreclr/debug/createdump/stackframe.h b/src/coreclr/debug/createdump/stackframe.h index 90db2697b812..75e20d93120c 100644 --- a/src/coreclr/debug/createdump/stackframe.h +++ b/src/coreclr/debug/createdump/stackframe.h @@ -5,45 +5,75 @@ struct StackFrame { private: uint64_t m_moduleAddress; - uint64_t m_returnAddress; + uint64_t m_instructionPointer; uint64_t m_stackPointer; uint32_t m_nativeOffset; uint32_t m_token; uint32_t m_ilOffset; + IXCLRDataMethodInstance* m_pMethod; bool m_isManaged; public: // Create native stack frame - StackFrame(uint64_t moduleAddress, uint64_t returnAddress, uint64_t stackPointer, uint32_t nativeOffset) : + StackFrame(uint64_t moduleAddress, uint64_t instructionPointer, uint64_t stackPointer, uint32_t nativeOffset) : m_moduleAddress(moduleAddress), - m_returnAddress(returnAddress), + m_instructionPointer(instructionPointer), m_stackPointer(stackPointer), m_nativeOffset(nativeOffset), m_token(0), m_ilOffset(0), + m_pMethod(nullptr), m_isManaged(false) { } // Create managed stack frame - StackFrame(uint64_t moduleAddress, uint64_t returnAddress, uint64_t stackPointer, uint32_t nativeOffset, uint64_t token, uint32_t ilOffset) : + StackFrame(uint64_t moduleAddress, uint64_t instructionPointer, uint64_t stackPointer, IXCLRDataMethodInstance* pMethod, uint32_t nativeOffset, uint64_t token, uint32_t ilOffset) : m_moduleAddress(moduleAddress), - m_returnAddress(returnAddress), + m_instructionPointer(instructionPointer), m_stackPointer(stackPointer), m_nativeOffset(nativeOffset), m_token(token), m_ilOffset(ilOffset), + m_pMethod(pMethod), m_isManaged(true) { } + // copy constructor + StackFrame(const StackFrame& frame) : + m_moduleAddress(frame.m_moduleAddress), + m_instructionPointer(frame.m_instructionPointer), + m_stackPointer(frame.m_stackPointer), + m_nativeOffset(frame.m_nativeOffset), + m_token(frame.m_token), + m_ilOffset(frame.m_ilOffset), + m_pMethod(frame.m_pMethod), + m_isManaged(frame.m_isManaged) + { + if (m_pMethod != nullptr) + { + m_pMethod->AddRef(); + } + } + + ~StackFrame() + { + if (m_pMethod != nullptr) + { + m_pMethod->Release(); + m_pMethod = nullptr; + } + } + inline uint64_t ModuleAddress() const { return m_moduleAddress; } - inline uint64_t ReturnAddress() const { return m_returnAddress; } + inline uint64_t InstructionPointer() const { return m_instructionPointer; } inline uint64_t StackPointer() const { return m_stackPointer; } inline uint32_t NativeOffset() const { return m_nativeOffset; } inline uint32_t Token() const { return m_token; } inline uint32_t ILOffset() const { return m_ilOffset; } inline bool IsManaged() const { return m_isManaged; } + inline IXCLRDataMethodInstance* GetMethod() const { return m_pMethod; } bool operator<(const StackFrame& rhs) const { diff --git a/src/coreclr/debug/createdump/threadinfo.cpp b/src/coreclr/debug/createdump/threadinfo.cpp index 99284ed04024..2107c6c1bafb 100644 --- a/src/coreclr/debug/createdump/threadinfo.cpp +++ b/src/coreclr/debug/createdump/threadinfo.cpp @@ -107,7 +107,7 @@ ThreadInfo::UnwindNativeFrames(CONTEXT* pContext) } bool -ThreadInfo::UnwindThread(IXCLRDataProcess* pClrDataProcess) +ThreadInfo::UnwindThread(IXCLRDataProcess* pClrDataProcess, ISOSDacInterface* pSos) { TRACE("Unwind: thread %04x\n", Tid()); @@ -152,7 +152,15 @@ ThreadInfo::UnwindThread(IXCLRDataProcess* pClrDataProcess) if (SUCCEEDED(pExceptionValue->GetAddress(&exceptionObject))) { m_exceptionObject = exceptionObject; - TRACE("Unwind: exception object %p\n", (void*)exceptionObject); + if (pSos != nullptr) + { + DacpExceptionObjectData exceptionData; + if (SUCCEEDED(exceptionData.Request(pSos, exceptionObject))) + { + m_exceptionHResult = exceptionData.HResult; + } + } + TRACE("Unwind: exception object %p exception hresult %08x\n", (void*)m_exceptionObject, m_exceptionHResult); } ReleaseHolder pExceptionType; if (SUCCEEDED(pExceptionValue->GetType(&pExceptionType))) @@ -202,6 +210,7 @@ ThreadInfo::GatherStackFrames(CONTEXT* pContext, IXCLRDataStackWalk* pStackwalk) mdMethodDef token = 0; uint32_t nativeOffset = 0; uint32_t ilOffset = 0; + ReleaseHolder pMethod; ReleaseHolder pFrame; if (SUCCEEDED(pStackwalk->GetFrame(&pFrame))) @@ -212,7 +221,6 @@ ThreadInfo::GatherStackFrames(CONTEXT* pContext, IXCLRDataStackWalk* pStackwalk) if ((simpleType & (CLRDATA_SIMPFRAME_MANAGED_METHOD | CLRDATA_SIMPFRAME_RUNTIME_MANAGED_CODE)) != 0) { - ReleaseHolder pMethod; if (SUCCEEDED(pFrame->GetMethodInstance(&pMethod))) { ReleaseHolder pModule; @@ -258,7 +266,7 @@ ThreadInfo::GatherStackFrames(CONTEXT* pContext, IXCLRDataStackWalk* pStackwalk) } // Add managed stack frame for the crash info notes - StackFrame frame(moduleAddress, ip, sp, nativeOffset, token, ilOffset); + StackFrame frame(moduleAddress, ip, sp, pMethod.Detach(), nativeOffset, token, ilOffset); AddStackFrame(frame); } @@ -270,7 +278,7 @@ ThreadInfo::AddStackFrame(const StackFrame& frame) { TRACE("Unwind: sp %p ip %p off %08x mod %p%c\n", (void*)frame.StackPointer(), - (void*)frame.ReturnAddress(), + (void*)frame.InstructionPointer(), frame.NativeOffset(), (void*)frame.ModuleAddress(), frame.IsManaged() ? '*' : ' '); diff --git a/src/coreclr/debug/createdump/threadinfo.h b/src/coreclr/debug/createdump/threadinfo.h index 1a690157908c..7ce0df5f1ec1 100644 --- a/src/coreclr/debug/createdump/threadinfo.h +++ b/src/coreclr/debug/createdump/threadinfo.h @@ -42,7 +42,8 @@ class ThreadInfo pid_t m_tgid; // thread group bool m_managed; // if true, thread has managed code running uint64_t m_exceptionObject; // exception object address - std::string m_exceptionType; // exception type + std::string m_exceptionType; // exception type + int32_t m_exceptionHResult; // exception HRESULT std::set m_frames; // stack frames #ifdef __APPLE__ @@ -64,6 +65,10 @@ class ThreadInfo #endif #endif // __APPLE__ + // no public copy constructor + ThreadInfo(const ThreadInfo&) = delete; + void operator=(const ThreadInfo&) = delete; + public: #ifdef __APPLE__ ThreadInfo(CrashInfo& crashInfo, pid_t tid, mach_port_t port); @@ -73,7 +78,7 @@ class ThreadInfo #endif ~ThreadInfo(); bool Initialize(); - bool UnwindThread(IXCLRDataProcess* pClrDataProcess); + bool UnwindThread(IXCLRDataProcess* pClrDataProcess, ISOSDacInterface* pSos); void GetThreadStack(); void GetThreadContext(uint32_t flags, CONTEXT* context) const; @@ -83,6 +88,7 @@ class ThreadInfo inline bool IsManaged() const { return m_managed; } inline uint64_t ManagedExceptionObject() const { return m_exceptionObject; } + inline int32_t ManagedExceptionHResult() const { return m_exceptionHResult; } inline std::string ManagedExceptionType() const { return m_exceptionType; } inline const std::set StackFrames() const { return m_frames; } @@ -108,16 +114,19 @@ class ThreadInfo #elif defined(__arm__) && defined(__VFP_FP__) && !defined(__SOFTFP__) inline const user_vfpregs_struct* VFPRegisters() const { return &m_vfpRegisters; } #endif - inline const uint64_t GetStackPointer() const - { #if defined(__x86_64__) - return m_gpRegisters.rsp; + inline const uint64_t GetInstructionPointer() const { return m_gpRegisters.rip; } + inline const uint64_t GetStackPointer() const { return m_gpRegisters.rsp; } + inline const uint64_t GetFramePointer() const { return m_gpRegisters.rbp; } #elif defined(__aarch64__) - return MCREG_Sp(m_gpRegisters); + inline const uint64_t GetInstructionPointer() const { return MCREG_Pc(m_gpRegisters); } + inline const uint64_t GetStackPointer() const { return MCREG_Sp(m_gpRegisters); } + inline const uint64_t GetFramePointer() const { return MCREG_Fp(m_gpRegisters); } #elif defined(__arm__) - return m_gpRegisters.ARM_sp; + inline const uint64_t GetInstructionPointer() const { return m_gpRegisters.ARM_pc; } + inline const uint64_t GetStackPointer() const { return m_gpRegisters.ARM_sp; } + inline const uint64_t GetFramePointer() const { return m_gpRegisters.ARM_fp; } #endif - } #endif // __APPLE__ private: diff --git a/src/coreclr/debug/createdump/threadinfomac.cpp b/src/coreclr/debug/createdump/threadinfomac.cpp index 3ea9151a6494..92b9339088fc 100644 --- a/src/coreclr/debug/createdump/threadinfomac.cpp +++ b/src/coreclr/debug/createdump/threadinfomac.cpp @@ -8,6 +8,7 @@ ThreadInfo::ThreadInfo(CrashInfo& crashInfo, pid_t tid, mach_port_t port) : m_tid(tid), m_managed(false), m_exceptionObject(0), + m_exceptionHResult(0), m_port(port) { } diff --git a/src/coreclr/debug/createdump/threadinfounix.cpp b/src/coreclr/debug/createdump/threadinfounix.cpp index c1e5ca1154cc..90a4c8ab9ffe 100644 --- a/src/coreclr/debug/createdump/threadinfounix.cpp +++ b/src/coreclr/debug/createdump/threadinfounix.cpp @@ -26,7 +26,8 @@ ThreadInfo::ThreadInfo(CrashInfo& crashInfo, pid_t tid) : m_crashInfo(crashInfo), m_tid(tid), m_managed(false), - m_exceptionObject(0) + m_exceptionObject(0), + m_exceptionHResult(0) { } diff --git a/src/coreclr/debug/daccess/task.cpp b/src/coreclr/debug/daccess/task.cpp index b16f85d8773e..c3143bf243b7 100644 --- a/src/coreclr/debug/daccess/task.cpp +++ b/src/coreclr/debug/daccess/task.cpp @@ -2503,7 +2503,16 @@ ClrDataModule::GetFlags( { (*flags) |= CLRDATA_MODULE_IS_MEMORY_STREAM; } - + PTR_Assembly pAssembly = m_module->GetAssembly(); + PTR_BaseDomain pBaseDomain = pAssembly->GetDomain(); + if (pBaseDomain->IsAppDomain()) + { + AppDomain* pAppDomain = pBaseDomain->AsAppDomain(); + if (pAssembly == pAppDomain->GetRootAssembly()) + { + (*flags) |= CLRDATA_MODULE_IS_MAIN_MODULE; + } + } status = S_OK; } EX_CATCH diff --git a/src/coreclr/inc/xclrdata.idl b/src/coreclr/inc/xclrdata.idl index 818915a29cce..aeddf9529bee 100644 --- a/src/coreclr/inc/xclrdata.idl +++ b/src/coreclr/inc/xclrdata.idl @@ -1014,6 +1014,7 @@ typedef enum CLRDATA_MODULE_DEFAULT = 0x00000000, CLRDATA_MODULE_IS_DYNAMIC = 0x00000001, CLRDATA_MODULE_IS_MEMORY_STREAM = 0x00000002, + CLRDATA_MODULE_IS_MAIN_MODULE = 0x00000004, } CLRDataModuleFlag; typedef enum diff --git a/src/coreclr/pal/prebuilt/inc/xclrdata.h b/src/coreclr/pal/prebuilt/inc/xclrdata.h index dbee62b9b122..b28463dc86c7 100644 --- a/src/coreclr/pal/prebuilt/inc/xclrdata.h +++ b/src/coreclr/pal/prebuilt/inc/xclrdata.h @@ -3300,7 +3300,8 @@ enum __MIDL___MIDL_itf_xclrdata_0000_0008_0001 { CLRDATA_MODULE_DEFAULT = 0, CLRDATA_MODULE_IS_DYNAMIC = 0x1, - CLRDATA_MODULE_IS_MEMORY_STREAM = 0x2 + CLRDATA_MODULE_IS_MEMORY_STREAM = 0x2, + CLRDATA_MODULE_IS_MAIN_MODULE = 0x4 } CLRDataModuleFlag; typedef /* [public][public][public] */ From 9fb28c806a40bafb5a579dd2218558ea0b9bf569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksey=20Kliger=20=28=CE=BBgeek=29?= Date: Mon, 12 Jul 2021 12:40:16 -0400 Subject: [PATCH 026/133] [hot_reload] Add support for row modifications; CustomAttribute updates (#55445) This fixes https://github.com/dotnet/runtime/issues/55097 - which allows us to support C# nullability analysis once again in hot reload deltas. Specifically we allow EnC deltas to include modifications of existing rows in the CustomAttribute table as long as the Parent and Type columns stay the same (that is: a custom attribute modification that still applies to the same element - and uses the same custom attribute constructor, but may have a different value). To support this, we add a new BaselineInfo:any_modified_rows array that keeps track for each table whether any rows have been modified (as opposed to added) by any EnC delta. When the runtime calls mono_metadata_decode_row, if there have been any deltas that modified a row in the requested table, we call hot_reload_effective_table_slow which find the latest delta that modified that row. If there have only been additions, we stop at the first delta that added the row we're looking for, if there are modifications, we look through all the deltas and return the latest one. * [hot_reload] Add test for updating custom attribute ctor values Just changing the arguments of a custom attribute constructor should generate an update to the CustomAttributes table with the same parent and .ctor. That kind of change should be allowed by Mono and CoreCLR * [hot_reload] Allow custom attribute row modifications if Parent and Type unchanged. Allows updates to the constructor arguments (or property values) * [hot_reload] Implement table lookup of modified rows Add a bool array on the base image to keep track of whether each table had any row modifications (as opposed to row additions) in any generation. If there was a modification, take the slow path in mono_image_effective_table even if the index we're looking at is in the base image. Update hot_reload_effective_table_slow so that if there was a modified row, we look at all the deltas to see if there's an even later update to that row. (When there are only additions, keep same behavior as before - only look as far as the generation that added the row we wanted to find). Also refine the assertion in hot_reload_relative_delta_index to properly account for EnCMap entries that correspond to modifications - in that case we might stop searching a metadata delta before we hit the end of the table if the EnCmap entries start pointing to rows that are past the one we wanted to look up. * Update the CustomAttributeUpdates test to check attribute value Check that we get the updated custom attribute string property value. * Re-enable nullability for hot reload tests Mono can now deal with the custom attributes modifications that Roslyn emits --- .../CustomAttributeUpdate.cs | 35 ++++ .../CustomAttributeUpdate_v1.cs | 35 ++++ ...lyUpdate.Test.CustomAttributeUpdate.csproj | 11 ++ .../deltascript.json | 6 + ...tadata.ApplyUpdate.Test.MethodBody1.csproj | 1 - .../tests/ApplyUpdateTest.cs | 29 +++ .../tests/System.Runtime.Loader.Tests.csproj | 4 + src/mono/mono/component/hot_reload-stub.c | 9 + src/mono/mono/component/hot_reload.c | 173 +++++++++++++----- src/mono/mono/component/hot_reload.h | 2 + src/mono/mono/metadata/metadata-internals.h | 5 +- src/mono/mono/metadata/metadata-update.c | 5 + .../ApplyUpdateReferencedAssembly.csproj | 1 - 13 files changed, 270 insertions(+), 46 deletions(-) create mode 100644 src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/CustomAttributeUpdate.cs create mode 100644 src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/CustomAttributeUpdate_v1.cs create mode 100644 src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate.csproj create mode 100644 src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/deltascript.json diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/CustomAttributeUpdate.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/CustomAttributeUpdate.cs new file mode 100644 index 000000000000..f1590848bea5 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/CustomAttributeUpdate.cs @@ -0,0 +1,35 @@ +// 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 System.Reflection.Metadata.ApplyUpdate.Test +{ + [AttributeUsage (AttributeTargets.Method, AllowMultiple=true)] + public class MyAttribute : Attribute + { + public MyAttribute (string stringValue) { StringValue = stringValue; } + + public MyAttribute (Type typeValue) { TypeValue = typeValue; } + + public MyAttribute (int x) { IntValue = x; } + + public string StringValue { get; set; } + public Type TypeValue {get; set; } + public int IntValue {get; set; } + } + + public class ClassWithCustomAttributeUpdates + { + [MyAttribute ("abcd")] + public static string Method1 () => null; + + [MyAttribute (typeof(Exception))] + public static string Method2 () => null; + + [MyAttribute (42, StringValue = "hijkl", TypeValue = typeof(Type))] + [MyAttribute (17, StringValue = "", TypeValue = typeof(object))] + public static string Method3 () => null; + + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/CustomAttributeUpdate_v1.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/CustomAttributeUpdate_v1.cs new file mode 100644 index 000000000000..516914d76d8e --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/CustomAttributeUpdate_v1.cs @@ -0,0 +1,35 @@ +// 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 System.Reflection.Metadata.ApplyUpdate.Test +{ + [AttributeUsage (AttributeTargets.Method, AllowMultiple=true)] + public class MyAttribute : Attribute + { + public MyAttribute (string stringValue) { StringValue = stringValue; } + + public MyAttribute (Type typeValue) { TypeValue = typeValue; } + + public MyAttribute (int x) { IntValue = x; } + + public string StringValue { get; set; } + public Type TypeValue {get; set; } + public int IntValue {get; set; } + } + + public class ClassWithCustomAttributeUpdates + { + [MyAttribute ("rstuv")] + public static string Method1 () => null; + + [MyAttribute (typeof(ArgumentException))] + public static string Method2 () => null; + + [MyAttribute (2042, StringValue = "qwerty", TypeValue = typeof(byte[]))] + [MyAttribute (17, StringValue = "", TypeValue = typeof(object))] + public static string Method3 () => null; + + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate.csproj b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate.csproj new file mode 100644 index 000000000000..985424ab3486 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate.csproj @@ -0,0 +1,11 @@ + + + System.Runtime.Loader.Tests + $(NetCoreAppCurrent) + true + deltascript.json + + + + + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/deltascript.json b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/deltascript.json new file mode 100644 index 000000000000..3dcb95912dbe --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.CustomAttributeUpdate/deltascript.json @@ -0,0 +1,6 @@ +{ + "changes": [ + {"document": "CustomAttributeUpdate.cs", "update": "CustomAttributeUpdate_v1.cs"}, + ] +} + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1.csproj b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1.csproj index 87a4a28d0001..57ba4f3ec529 100644 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1.csproj +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1.csproj @@ -5,7 +5,6 @@ true deltascript.json true - disable diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs index c2f06b86b938..6976f8a6f777 100644 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs @@ -83,6 +83,35 @@ void ClassWithCustomAttributes() }); } + [ConditionalFact(typeof(ApplyUpdateUtil), nameof (ApplyUpdateUtil.IsSupported))] + public void CustomAttributeUpdates() + { + // Test that _modifying_ custom attribute constructor/property argumments works as expected. + // For this test, we don't change which constructor is called, or how many custom attributes there are. + ApplyUpdateUtil.TestCase(static () => + { + var assm = typeof(System.Reflection.Metadata.ApplyUpdate.Test.ClassWithCustomAttributeUpdates).Assembly; + + ApplyUpdateUtil.ApplyUpdate(assm); + ApplyUpdateUtil.ClearAllReflectionCaches(); + + // Just check the updated value on one method + + Type attrType = typeof(System.Reflection.Metadata.ApplyUpdate.Test.MyAttribute); + Type ty = assm.GetType("System.Reflection.Metadata.ApplyUpdate.Test.ClassWithCustomAttributeUpdates"); + Assert.NotNull(ty); + MethodInfo mi = ty.GetMethod(nameof(System.Reflection.Metadata.ApplyUpdate.Test.ClassWithCustomAttributeUpdates.Method1), BindingFlags.Public | BindingFlags.Static); + Assert.NotNull(mi); + var cattrs = Attribute.GetCustomAttributes(mi, attrType); + Assert.NotNull(cattrs); + Assert.Equal(1, cattrs.Length); + Assert.NotNull(cattrs[0]); + Assert.Equal(attrType, cattrs[0].GetType()); + string p = (cattrs[0] as System.Reflection.Metadata.ApplyUpdate.Test.MyAttribute).StringValue; + Assert.Equal("rstuv", p); + }); + } + class NonRuntimeAssembly : Assembly { } diff --git a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj index eeae81e75cc6..a5f2b3b0cce7 100644 --- a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj +++ b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj @@ -39,6 +39,7 @@ + @@ -58,6 +59,9 @@ + diff --git a/src/mono/mono/component/hot_reload-stub.c b/src/mono/mono/component/hot_reload-stub.c index 718ce3e453a1..48033c1c7a0c 100644 --- a/src/mono/mono/component/hot_reload-stub.c +++ b/src/mono/mono/component/hot_reload-stub.c @@ -59,6 +59,9 @@ hot_reload_stub_table_bounds_check (MonoImage *base_image, int table_index, int static gboolean hot_reload_stub_delta_heap_lookup (MonoImage *base_image, MetadataHeapGetterFunc get_heap, uint32_t orig_index, MonoImage **image_out, uint32_t *index_out); +static gboolean +hot_reload_stub_has_modified_rows (const MonoTableInfo *table); + static MonoComponentHotReload fn_table = { { MONO_COMPONENT_ITF_VERSION, &hot_reload_stub_available }, &hot_reload_stub_set_fastpath_data, @@ -75,6 +78,7 @@ static MonoComponentHotReload fn_table = { &hot_reload_stub_get_updated_method_rva, &hot_reload_stub_table_bounds_check, &hot_reload_stub_delta_heap_lookup, + &hot_reload_stub_has_modified_rows, }; static bool @@ -172,6 +176,11 @@ hot_reload_stub_delta_heap_lookup (MonoImage *base_image, MetadataHeapGetterFunc g_assert_not_reached (); } +static gboolean +hot_reload_stub_has_modified_rows (const MonoTableInfo *table){ + return FALSE; +} + MONO_COMPONENT_EXPORT_ENTRYPOINT MonoComponentHotReload * mono_component_hot_reload_init (void) diff --git a/src/mono/mono/component/hot_reload.c b/src/mono/mono/component/hot_reload.c index 0997e8f859c2..76861056eda1 100644 --- a/src/mono/mono/component/hot_reload.c +++ b/src/mono/mono/component/hot_reload.c @@ -73,6 +73,9 @@ hot_reload_table_bounds_check (MonoImage *base_image, int table_index, int token static gboolean hot_reload_delta_heap_lookup (MonoImage *base_image, MetadataHeapGetterFunc get_heap, uint32_t orig_index, MonoImage **image_out, uint32_t *index_out); +static gboolean +hot_reload_has_modified_rows (const MonoTableInfo *table); + static MonoComponentHotReload fn_table = { { MONO_COMPONENT_ITF_VERSION, &hot_reload_available }, &hot_reload_set_fastpath_data, @@ -89,6 +92,7 @@ static MonoComponentHotReload fn_table = { &hot_reload_get_updated_method_rva, &hot_reload_table_bounds_check, &hot_reload_delta_heap_lookup, + &hot_reload_has_modified_rows, }; MonoComponentHotReload * @@ -161,6 +165,9 @@ typedef struct _BaselineInfo { /* Maps MethodDef token indices to a boolean flag that there's an update for the method */ GHashTable *method_table_update; + + /* TRUE if any published update modified an existing row */ + gboolean any_modified_rows [MONO_TABLE_NUM]; } BaselineInfo; #define DOTNET_MODIFIABLE_ASSEMBLIES "DOTNET_MODIFIABLE_ASSEMBLIES" @@ -419,6 +426,28 @@ table_info_get_base_image (const MonoTableInfo *t) return image; } +/* Given a table, find the base image that it came from and its table index */ +static gboolean +table_info_find_in_base (const MonoTableInfo *table, MonoImage **base_out, int *tbl_index) +{ + g_assert (base_out); + *base_out = NULL; + MonoImage *base = table_info_get_base_image (table); + if (!base) + return FALSE; + + *base_out = base; + + /* Invariant: `table` must be a `MonoTableInfo` of the base image. */ + g_assert (base->tables < table && table < &base->tables [MONO_TABLE_LAST]); + + if (tbl_index) { + size_t s = ALIGN_TO (sizeof (MonoTableInfo), sizeof (gpointer)); + *tbl_index = ((intptr_t) table - (intptr_t) base->tables) / s; + } + return TRUE; +} + static MonoImage* image_open_dmeta_from_data (MonoImage *base_image, uint32_t generation, gconstpointer dmeta_bytes, uint32_t dmeta_length); @@ -724,67 +753,66 @@ dump_update_summary (MonoImage *image_base, MonoImage *image_dmeta) void hot_reload_effective_table_slow (const MonoTableInfo **t, int *idx) { - if (G_LIKELY (*idx < table_info_get_rows (*t))) - return; - /* FIXME: don't let any thread other than the updater thread see values from a delta image * with a generation past update_published */ - MonoImage *base = table_info_get_base_image (*t); - if (!base) + MonoImage *base; + int tbl_index; + if (!table_info_find_in_base (*t, &base, &tbl_index)) return; BaselineInfo *info = baseline_info_lookup (base); if (!info) return; + gboolean any_modified = info->any_modified_rows[tbl_index]; + + if (G_LIKELY (*idx < table_info_get_rows (*t) && !any_modified)) + return; + GList *list = info->delta_image; MonoImage *dmeta; int ridx; MonoTableInfo *table; - - /* Invariant: `*t` must be a `MonoTableInfo` of the base image. */ - g_assert (base->tables < *t && *t < &base->tables [MONO_TABLE_LAST]); - - size_t s = ALIGN_TO (sizeof (MonoTableInfo), sizeof (gpointer)); - int tbl_index = ((intptr_t) *t - (intptr_t) base->tables) / s; - - /* FIXME: I don't understand how ReplaceMethodOften works - it always has a - * EnCMap entry 2: 0x06000002 (MethodDef) for every revision. Shouldn't the number of methodDef rows be going up? - - * Apparently not - because conceptually the EnC log is saying to overwrite the existing rows. - */ - - /* FIXME: so if the tables are conceptually mutated by each delta, we can't just stop at the - * first lookup that gets a relative index in the right range, can we? that will always be - * the oldest delta. - */ - - /* FIXME: the other problem is that the EnClog is a sequence of actions to MUTATE rows. So when looking up an existing row we have to be able to make it so that naive callers decoding that row see the updated data. - * - * That's the main thing that PAss1 should eb doing for us. - * - * I think we can't get away from mutating. The format is just too geared toward it. - * - * We should make the mutations atomic, though. (And I guess the heap extension is probably unavoidable) - * - * 1. Keep a table of inv - */ int g = 0; + /* Candidate: the last delta that had updates for the requested row */ + MonoImage *cand_dmeta = NULL; + MonoTableInfo *cand_table = NULL; + int cand_ridx = -1; + int cand_g = 0; + + gboolean cont; do { g_assertf (list, "couldn't find idx=0x%08x in assembly=%s", *idx, dmeta && dmeta->name ? dmeta->name : "unknown image"); dmeta = (MonoImage*)list->data; list = list->next; table = &dmeta->tables [tbl_index]; - ridx = hot_reload_relative_delta_index (dmeta, mono_metadata_make_token (tbl_index, *idx + 1)) - 1; + int rel_row = hot_reload_relative_delta_index (dmeta, mono_metadata_make_token (tbl_index, *idx + 1)); + g_assert (rel_row == -1 || (rel_row > 0 && rel_row <= table_info_get_rows (table))); g++; - } while (ridx < 0 || ridx >= table_info_get_rows (table)); + if (rel_row != -1) { + cand_dmeta = dmeta; + cand_table = table; + cand_ridx = rel_row - 1; + cand_g = g; + } + ridx = rel_row - 1; + if (!any_modified) { + /* if the table only got additions, not modifications, don't continue after we find the first image that has the right number of rows */ + cont = ridx < 0 || ridx >= table_info_get_rows (table); + } else { + /* otherwise, keep going in case a later generation modified the row again */ + cont = list != NULL; + } + } while (cont); - mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "effective table for %s: 0x%08x -> 0x%08x (rows = 0x%08x) (gen %d, g %d)", mono_meta_table_name (tbl_index), *idx, ridx, table_info_get_rows (table), metadata_update_local_generation (base, info, dmeta), g); + if (cand_ridx != -1) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "effective table for %s: 0x%08x -> 0x%08x (rows = 0x%08x) (gen %d, g %d)", mono_meta_table_name (tbl_index), *idx, cand_ridx, table_info_get_rows (cand_table), metadata_update_local_generation (base, info, cand_dmeta), cand_g); - *t = table; - *idx = ridx; + *t = cand_table; + *idx = cand_ridx; + } } /* @@ -835,6 +863,11 @@ hot_reload_relative_delta_index (MonoImage *image_dmeta, int token) mono_metadata_decode_row (encmap, index_map - 1, cols, MONO_ENCMAP_SIZE); int map_entry = cols [MONO_ENCMAP_TOKEN]; + /* we're looking at the beginning of a sequence of encmap rows that are all the + * modifications+additions for the table we are looking for (or we're looking at an entry + * for the next table after the one we wanted). the map entries will have tokens in + * increasing order. skip over the rows where the tokens are not the one we want, until we + * hit the rows for the next table or we hit the end of the encmap */ while (mono_metadata_token_table (map_entry) == table && mono_metadata_token_index (map_entry) < index && index_map < encmap_rows) { mono_metadata_decode_row (encmap, ++index_map - 1, cols, MONO_ENCMAP_SIZE); map_entry = cols [MONO_ENCMAP_TOKEN]; @@ -848,12 +881,18 @@ hot_reload_relative_delta_index (MonoImage *image_dmeta, int token) mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "relative index for token 0x%08x -> table 0x%02x row 0x%08x", token, table, return_val); return return_val; } else { - /* otherwise the last entry in the encmap is for this table, but is still less than the index - the index is in the next generation */ - g_assert (mono_metadata_token_index (map_entry) < index && index_map == encmap_rows); + /* Otherwise we stopped either: because we saw an an entry for a row after + * the one we wanted - we were looking for a modification, but the encmap + * has an addition; or, because we saw the last entry in the encmap and it + * still wasn't for a row as high as the one we wanted. either way, the + * update we want is not in the delta we're looking at. + */ + g_assert ((mono_metadata_token_index (map_entry) > index) || (mono_metadata_token_index (map_entry) < index && index_map == encmap_rows)); return -1; } } else { - /* otherwise there are no more encmap entries for this table, and we didn't see the index, so there index is in the next generation */ + /* otherwise there are no more encmap entries for this table, and we didn't see the + * index, so there was no modification/addition for that index in this delta. */ g_assert (mono_metadata_token_table (map_entry) > table); return -1; } @@ -925,9 +964,10 @@ delta_info_compute_table_records (MonoImage *image_dmeta, MonoImage *image_base, g_assert (table != MONO_TABLE_ENCMAP); g_assert (table >= prev_table); /* FIXME: check bounds - is it < or <=. */ - if (rid < delta_info->count[table].prev_gen_rows) + if (rid < delta_info->count[table].prev_gen_rows) { + base_info->any_modified_rows[table] = TRUE; delta_info->count[table].modified_rows++; - else + } else delta_info->count[table].inserted_rows++; if (table == prev_table) continue; @@ -1041,6 +1081,7 @@ apply_enclog_pass1 (MonoImage *image_base, MonoImage *image_dmeta, gconstpointer continue; } case MONO_TABLE_METHODSEMANTICS: { + /* FIXME: this should get the current table size, not the base stable size */ if (token_index > table_info_get_rows (&image_base->tables [token_table])) { /* new rows are fine, as long as they point at existing methods */ guint32 sema_cols [MONO_METHOD_SEMA_SIZE]; @@ -1073,6 +1114,35 @@ apply_enclog_pass1 (MonoImage *image_base, MonoImage *image_dmeta, gconstpointer continue; } } + case MONO_TABLE_CUSTOMATTRIBUTE: { + /* FIXME: this should get the current table size, not the base stable size */ + if (token_index <= table_info_get_rows (&image_base->tables [token_table])) { + /* modifying existing rows is ok, as long as the parent and ctor are the same */ + guint32 ca_upd_cols [MONO_CUSTOM_ATTR_SIZE]; + guint32 ca_base_cols [MONO_CUSTOM_ATTR_SIZE]; + int mapped_token = hot_reload_relative_delta_index (image_dmeta, mono_metadata_make_token (token_table, token_index)); + g_assert (mapped_token != -1); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "row[0x%02x]:0x%08x CUSTOM_ATTR update. mapped index = 0x%08x\n", i, log_token, mapped_token); + + mono_metadata_decode_row (&image_dmeta->tables [MONO_TABLE_CUSTOMATTRIBUTE], mapped_token - 1, ca_upd_cols, MONO_CUSTOM_ATTR_SIZE); + mono_metadata_decode_row (&image_base->tables [MONO_TABLE_CUSTOMATTRIBUTE], token_index - 1, ca_base_cols, MONO_CUSTOM_ATTR_SIZE); + + /* compare the ca_upd_cols [MONO_CUSTOM_ATTR_PARENT] to ca_base_cols [MONO_CUSTOM_ATTR_PARENT]. */ + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "row[0x%02x]:0x%08x CUSTOM_ATTR update. Old Parent 0x%08x New Parent 0x%08x\n", i, log_token, ca_base_cols [MONO_CUSTOM_ATTR_PARENT], ca_upd_cols [MONO_CUSTOM_ATTR_PARENT]); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "row[0x%02x]:0x%08x CUSTOM_ATTR update. Old ctor 0x%08x New ctor 0x%08x\n", i, log_token, ca_base_cols [MONO_CUSTOM_ATTR_TYPE], ca_upd_cols [MONO_CUSTOM_ATTR_TYPE]); + + if (ca_base_cols [MONO_CUSTOM_ATTR_PARENT] != ca_upd_cols [MONO_CUSTOM_ATTR_PARENT] || + ca_base_cols [MONO_CUSTOM_ATTR_TYPE] != ca_upd_cols [MONO_CUSTOM_ATTR_TYPE]) { + mono_error_set_type_load_name (error, NULL, image_base->name, "EnC: we do not support patching of existing CA table cols with a different Parent or Type. token=0x%08x", log_token); + unsupported_edits = TRUE; + continue; + } + break; + } else { + /* Added a row. ok */ + break; + } + } default: /* FIXME: this bounds check is wrong for cumulative updates - need to look at the DeltaInfo:count.prev_gen_rows */ if (token_index <= table_info_get_rows (&image_base->tables [token_table])) { @@ -1211,6 +1281,10 @@ apply_enclog_pass2 (MonoImage *image_base, BaselineInfo *base_info, uint32_t gen /* assuming that property attributes and type haven't changed. */ break; } + case MONO_TABLE_CUSTOMATTRIBUTE: { + /* ok, pass1 checked for disallowed modifications */ + break; + } default: { g_assert (token_index > table_info_get_rows (&image_base->tables [token_table])); if (mono_trace_is_traced (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE)) @@ -1478,3 +1552,16 @@ hot_reload_delta_heap_lookup (MonoImage *base_image, MetadataHeapGetterFunc get_ return (cur != NULL); } +static gboolean +hot_reload_has_modified_rows (const MonoTableInfo *table) +{ + MonoImage *base; + int tbl_index; + if (!table_info_find_in_base (table, &base, &tbl_index)) + return FALSE; + BaselineInfo *info = baseline_info_lookup (base); + if (!info) + return FALSE; + return info->any_modified_rows[tbl_index]; +} + diff --git a/src/mono/mono/component/hot_reload.h b/src/mono/mono/component/hot_reload.h index 4994664103cc..0bdb63d121d0 100644 --- a/src/mono/mono/component/hot_reload.h +++ b/src/mono/mono/component/hot_reload.h @@ -29,6 +29,8 @@ typedef struct _MonoComponentHotReload { gpointer (*get_updated_method_rva) (MonoImage *base_image, uint32_t idx); gboolean (*table_bounds_check) (MonoImage *base_image, int table_index, int token_index); gboolean (*delta_heap_lookup) (MonoImage *base_image, MetadataHeapGetterFunc get_heap, uint32_t orig_index, MonoImage **image_out, uint32_t *index_out); + gboolean (*has_modified_rows) (const MonoTableInfo *table); + } MonoComponentHotReload; MONO_COMPONENT_EXPORT_ENTRYPOINT diff --git a/src/mono/mono/metadata/metadata-internals.h b/src/mono/mono/metadata/metadata-internals.h index 545693ef12a5..20f36c60e8f5 100644 --- a/src/mono/mono/metadata/metadata-internals.h +++ b/src/mono/mono/metadata/metadata-internals.h @@ -805,11 +805,14 @@ mono_metadata_has_updates (void) void mono_image_effective_table_slow (const MonoTableInfo **t, int *idx); +gboolean +mono_metadata_update_has_modified_rows (const MonoTableInfo *t); + static inline void mono_image_effective_table (const MonoTableInfo **t, int *idx) { if (G_UNLIKELY (mono_metadata_has_updates ())) { - if (G_UNLIKELY (*idx >= table_info_get_rows ((*t)))) { + if (G_UNLIKELY (*idx >= table_info_get_rows ((*t)) || mono_metadata_update_has_modified_rows (*t))) { mono_image_effective_table_slow (t, idx); } } diff --git a/src/mono/mono/metadata/metadata-update.c b/src/mono/mono/metadata/metadata-update.c index d1d646c49ba2..8787b28bca65 100644 --- a/src/mono/mono/metadata/metadata-update.c +++ b/src/mono/mono/metadata/metadata-update.c @@ -121,3 +121,8 @@ mono_metadata_update_delta_heap_lookup (MonoImage *base_image, MetadataHeapGette } +gboolean +mono_metadata_update_has_modified_rows (const MonoTableInfo *table) +{ + return mono_component_hot_reload ()->has_modified_rows (table); +} diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/ApplyUpdateReferencedAssembly/ApplyUpdateReferencedAssembly.csproj b/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/ApplyUpdateReferencedAssembly/ApplyUpdateReferencedAssembly.csproj index 06190d7b86fc..46e260f24e15 100644 --- a/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/ApplyUpdateReferencedAssembly/ApplyUpdateReferencedAssembly.csproj +++ b/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/ApplyUpdateReferencedAssembly/ApplyUpdateReferencedAssembly.csproj @@ -11,7 +11,6 @@ false false - disable From 738b09bee351a60f1a7c037d5968b1e4145f6c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Mon, 12 Jul 2021 18:53:50 +0200 Subject: [PATCH 027/133] Use inline Vector{128|256}.Create for constants (#54827) * Used VectorXYZ.Create for constants in Base64 * Used VectorXYZ.Create for constants in BitArray * Remove conditional compilation It's only built for NetCoreAppCurrent, so no need to special case older runtimes. --- .../src/System/Collections/BitArray.cs | 45 +++-- .../src/System/Buffers/Text/Base64.cs | 8 - .../src/System/Buffers/Text/Base64Decoder.cs | 172 ++++++++---------- .../src/System/Buffers/Text/Base64Encoder.cs | 99 +++++----- 4 files changed, 147 insertions(+), 177 deletions(-) diff --git a/src/libraries/System.Collections/src/System/Collections/BitArray.cs b/src/libraries/System.Collections/src/System/Collections/BitArray.cs index 6921b13347be..9f8531a9e818 100644 --- a/src/libraries/System.Collections/src/System/Collections/BitArray.cs +++ b/src/libraries/System.Collections/src/System/Collections/BitArray.cs @@ -120,10 +120,6 @@ public BitArray(byte[] bytes) _version = 0; } - private static readonly Vector128 s_bitMask128 = BitConverter.IsLittleEndian ? - Vector128.Create(0x80402010_08040201).AsByte() : - Vector128.Create(0x01020408_10204080).AsByte(); - private const uint Vector128ByteCount = 16; private const uint Vector128IntCount = 4; private const uint Vector256ByteCount = 32; @@ -190,6 +186,10 @@ public unsafe BitArray(bool[] values) // However comparison against zero can be replaced to cmeq against zero (vceqzq_s8) // See dotnet/runtime#33972 for details Vector128 zero = Vector128.Zero; + Vector128 bitMask128 = BitConverter.IsLittleEndian ? + Vector128.Create(0x80402010_08040201).AsByte() : + Vector128.Create(0x01020408_10204080).AsByte(); + fixed (bool* ptr = values) { for (; (i + Vector128ByteCount * 2u) <= (uint)values.Length; i += Vector128ByteCount * 2u) @@ -199,7 +199,7 @@ public unsafe BitArray(bool[] values) // and combine by ORing all of them together (In this case, adding all of them does the same thing) Vector128 lowerVector = AdvSimd.LoadVector128((byte*)ptr + i); Vector128 lowerIsFalse = AdvSimd.CompareEqual(lowerVector, zero); - Vector128 bitsExtracted1 = AdvSimd.And(lowerIsFalse, s_bitMask128); + Vector128 bitsExtracted1 = AdvSimd.And(lowerIsFalse, bitMask128); bitsExtracted1 = AdvSimd.Arm64.AddPairwise(bitsExtracted1, bitsExtracted1); bitsExtracted1 = AdvSimd.Arm64.AddPairwise(bitsExtracted1, bitsExtracted1); bitsExtracted1 = AdvSimd.Arm64.AddPairwise(bitsExtracted1, bitsExtracted1); @@ -207,7 +207,7 @@ public unsafe BitArray(bool[] values) Vector128 upperVector = AdvSimd.LoadVector128((byte*)ptr + i + Vector128.Count); Vector128 upperIsFalse = AdvSimd.CompareEqual(upperVector, zero); - Vector128 bitsExtracted2 = AdvSimd.And(upperIsFalse, s_bitMask128); + Vector128 bitsExtracted2 = AdvSimd.And(upperIsFalse, bitMask128); bitsExtracted2 = AdvSimd.Arm64.AddPairwise(bitsExtracted2, bitsExtracted2); bitsExtracted2 = AdvSimd.Arm64.AddPairwise(bitsExtracted2, bitsExtracted2); bitsExtracted2 = AdvSimd.Arm64.AddPairwise(bitsExtracted2, bitsExtracted2); @@ -857,12 +857,6 @@ public int Length } } - // The mask used when shuffling a single int into Vector128/256. - // On little endian machines, the lower 8 bits of int belong in the first byte, next lower 8 in the second and so on. - // We place the bytes that contain the bits to its respective byte so that we can mask out only the relevant bits later. - private static readonly Vector128 s_lowerShuffleMask_CopyToBoolArray = Vector128.Create(0, 0x01010101_01010101).AsByte(); - private static readonly Vector128 s_upperShuffleMask_CopyToBoolArray = Vector128.Create(0x02020202_02020202, 0x03030303_03030303).AsByte(); - public unsafe void CopyTo(Array array, int index) { if (array == null) @@ -953,9 +947,15 @@ public unsafe void CopyTo(Array array, int index) if (m_length < BitsPerInt32) goto LessThan32; + // The mask used when shuffling a single int into Vector128/256. + // On little endian machines, the lower 8 bits of int belong in the first byte, next lower 8 in the second and so on. + // We place the bytes that contain the bits to its respective byte so that we can mask out only the relevant bits later. + Vector128 lowerShuffleMask_CopyToBoolArray = Vector128.Create(0, 0x01010101_01010101).AsByte(); + Vector128 upperShuffleMask_CopyToBoolArray = Vector128.Create(0x02020202_02020202, 0x03030303_03030303).AsByte(); + if (Avx2.IsSupported) { - Vector256 shuffleMask = Vector256.Create(s_lowerShuffleMask_CopyToBoolArray, s_upperShuffleMask_CopyToBoolArray); + Vector256 shuffleMask = Vector256.Create(lowerShuffleMask_CopyToBoolArray, upperShuffleMask_CopyToBoolArray); Vector256 bitMask = Vector256.Create(0x80402010_08040201).AsByte(); Vector256 ones = Vector256.Create((byte)1); @@ -977,9 +977,12 @@ public unsafe void CopyTo(Array array, int index) } else if (Ssse3.IsSupported) { - Vector128 lowerShuffleMask = s_lowerShuffleMask_CopyToBoolArray; - Vector128 upperShuffleMask = s_upperShuffleMask_CopyToBoolArray; + Vector128 lowerShuffleMask = lowerShuffleMask_CopyToBoolArray; + Vector128 upperShuffleMask = upperShuffleMask_CopyToBoolArray; Vector128 ones = Vector128.Create((byte)1); + Vector128 bitMask128 = BitConverter.IsLittleEndian ? + Vector128.Create(0x80402010_08040201).AsByte() : + Vector128.Create(0x01020408_10204080).AsByte(); fixed (bool* destination = &boolArray[index]) { @@ -989,12 +992,12 @@ public unsafe void CopyTo(Array array, int index) Vector128 scalar = Vector128.CreateScalarUnsafe(bits); Vector128 shuffledLower = Ssse3.Shuffle(scalar.AsByte(), lowerShuffleMask); - Vector128 extractedLower = Sse2.And(shuffledLower, s_bitMask128); + Vector128 extractedLower = Sse2.And(shuffledLower, bitMask128); Vector128 normalizedLower = Sse2.Min(extractedLower, ones); Sse2.Store((byte*)destination + i, normalizedLower); Vector128 shuffledHigher = Ssse3.Shuffle(scalar.AsByte(), upperShuffleMask); - Vector128 extractedHigher = Sse2.And(shuffledHigher, s_bitMask128); + Vector128 extractedHigher = Sse2.And(shuffledHigher, bitMask128); Vector128 normalizedHigher = Sse2.Min(extractedHigher, ones); Sse2.Store((byte*)destination + i + Vector128.Count, normalizedHigher); } @@ -1003,6 +1006,10 @@ public unsafe void CopyTo(Array array, int index) else if (AdvSimd.IsSupported) { Vector128 ones = Vector128.Create((byte)1); + Vector128 bitMask128 = BitConverter.IsLittleEndian ? + Vector128.Create(0x80402010_08040201).AsByte() : + Vector128.Create(0x01020408_10204080).AsByte(); + fixed (bool* destination = &boolArray[index]) { for (; (i + Vector128ByteCount * 2u) <= (uint)m_length; i += Vector128ByteCount * 2u) @@ -1028,12 +1035,12 @@ public unsafe void CopyTo(Array array, int index) vector = AdvSimd.Arm64.ZipLow(vector, vector); Vector128 shuffledLower = AdvSimd.Arm64.ZipLow(vector, vector); - Vector128 extractedLower = AdvSimd.And(shuffledLower, s_bitMask128); + Vector128 extractedLower = AdvSimd.And(shuffledLower, bitMask128); Vector128 normalizedLower = AdvSimd.Min(extractedLower, ones); AdvSimd.Store((byte*)destination + i, normalizedLower); Vector128 shuffledHigher = AdvSimd.Arm64.ZipHigh(vector, vector); - Vector128 extractedHigher = AdvSimd.And(shuffledHigher, s_bitMask128); + Vector128 extractedHigher = AdvSimd.And(shuffledHigher, bitMask128); Vector128 normalizedHigher = AdvSimd.Min(extractedHigher, ones); AdvSimd.Store((byte*)destination + i + Vector128.Count, normalizedHigher); } diff --git a/src/libraries/System.Memory/src/System/Buffers/Text/Base64.cs b/src/libraries/System.Memory/src/System/Buffers/Text/Base64.cs index 60d1558a50f5..7506cbe1eb01 100644 --- a/src/libraries/System.Memory/src/System/Buffers/Text/Base64.cs +++ b/src/libraries/System.Memory/src/System/Buffers/Text/Base64.cs @@ -2,20 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using Internal.Runtime.CompilerServices; namespace System.Buffers.Text { public static partial class Base64 { - private static TVector ReadVector(ReadOnlySpan data) - { - ref sbyte tmp = ref MemoryMarshal.GetReference(data); - return Unsafe.As(ref tmp); - } - [Conditional("DEBUG")] private static unsafe void AssertRead(byte* src, byte* srcStart, int srcLength) { diff --git a/src/libraries/System.Memory/src/System/Buffers/Text/Base64Decoder.cs b/src/libraries/System.Memory/src/System/Buffers/Text/Base64Decoder.cs index b6c639781c2c..e71b3b7f2d2f 100644 --- a/src/libraries/System.Memory/src/System/Buffers/Text/Base64Decoder.cs +++ b/src/libraries/System.Memory/src/System/Buffers/Text/Base64Decoder.cs @@ -100,7 +100,7 @@ public static unsafe OperationStatus DecodeFromUtf8(ReadOnlySpan utf8, Spa maxSrcLength = (destLength / 3) * 4; } - ref sbyte decodingMap = ref MemoryMarshal.GetReference(s_decodingMap); + ref sbyte decodingMap = ref MemoryMarshal.GetReference(DecodingMap); srcMax = srcBytes + (uint)maxSrcLength; while (src < srcMax) @@ -275,7 +275,7 @@ public static unsafe OperationStatus DecodeFromUtf8InPlace(Span buffer, ou if (bufferLength == 0) goto DoneExit; - ref sbyte decodingMap = ref MemoryMarshal.GetReference(s_decodingMap); + ref sbyte decodingMap = ref MemoryMarshal.GetReference(DecodingMap); while (sourceIndex < bufferLength - 4) { @@ -362,14 +362,59 @@ private static unsafe void Avx2Decode(ref byte* srcBytes, ref byte* destBytes, b // See SSSE3-version below for an explanation of how the code works. // The JIT won't hoist these "constants", so help it - Vector256 lutHi = ReadVector>(s_avxDecodeLutHi); - Vector256 lutLo = ReadVector>(s_avxDecodeLutLo); - Vector256 lutShift = ReadVector>(s_avxDecodeLutShift); + Vector256 lutHi = Vector256.Create( + 0x10, 0x10, 0x01, 0x02, + 0x04, 0x08, 0x04, 0x08, + 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x01, 0x02, + 0x04, 0x08, 0x04, 0x08, + 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10); + + Vector256 lutLo = Vector256.Create( + 0x15, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x13, 0x1A, + 0x1B, 0x1B, 0x1B, 0x1A, + 0x15, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x13, 0x1A, + 0x1B, 0x1B, 0x1B, 0x1A); + + Vector256 lutShift = Vector256.Create( + 0, 16, 19, 4, + -65, -65, -71, -71, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 16, 19, 4, + -65, -65, -71, -71, + 0, 0, 0, 0, + 0, 0, 0, 0); + + Vector256 packBytesInLaneMask = Vector256.Create( + 2, 1, 0, 6, + 5, 4, 10, 9, + 8, 14, 13, 12, + -1, -1, -1, -1, + 2, 1, 0, 6, + 5, 4, 10, 9, + 8, 14, 13, 12, + -1, -1, -1, -1); + + Vector256 packLanesControl = Vector256.Create( + 0, 0, 0, 0, + 1, 0, 0, 0, + 2, 0, 0, 0, + 4, 0, 0, 0, + 5, 0, 0, 0, + 6, 0, 0, 0, + -1, -1, -1, -1, + -1, -1, -1, -1).AsInt32(); + Vector256 mask2F = Vector256.Create((sbyte)'/'); Vector256 mergeConstant0 = Vector256.Create(0x01400140).AsSByte(); Vector256 mergeConstant1 = Vector256.Create(0x00011000).AsInt16(); - Vector256 packBytesInLaneMask = ReadVector>(s_avxDecodePackBytesInLaneMask); - Vector256 packLanesControl = ReadVector>(s_avxDecodePackLanesControl).AsInt32(); byte* src = srcBytes; byte* dest = destBytes; @@ -508,13 +553,33 @@ private static unsafe void Ssse3Decode(ref byte* srcBytes, ref byte* destBytes, // 1111 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 // The JIT won't hoist these "constants", so help it - Vector128 lutHi = ReadVector>(s_sseDecodeLutHi); - Vector128 lutLo = ReadVector>(s_sseDecodeLutLo); - Vector128 lutShift = ReadVector>(s_sseDecodeLutShift); + Vector128 lutHi = Vector128.Create( + 0x10, 0x10, 0x01, 0x02, + 0x04, 0x08, 0x04, 0x08, + 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10); + + Vector128 lutLo = Vector128.Create( + 0x15, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x13, 0x1A, + 0x1B, 0x1B, 0x1B, 0x1A); + + Vector128 lutShift = Vector128.Create( + 0, 16, 19, 4, + -65, -65, -71, -71, + 0, 0, 0, 0, + 0, 0, 0, 0); + + Vector128 packBytesMask = Vector128.Create( + 2, 1, 0, 6, + 5, 4, 10, 9, + 8, 14, 13, 12, + -1, -1, -1, -1); + Vector128 mask2F = Vector128.Create((sbyte)'/'); Vector128 mergeConstant0 = Vector128.Create(0x01400140).AsSByte(); Vector128 mergeConstant1 = Vector128.Create(0x00011000).AsInt16(); - Vector128 packBytesMask = ReadVector>(s_sseDecodePackBytesMask); Vector128 zero = Vector128.Zero; byte* src = srcBytes; @@ -613,7 +678,7 @@ private static unsafe void WriteThreeLowOrderBytes(byte* destination, int value) } // Pre-computing this table using a custom string(s_characters) and GenerateDecodingMapAndVerify (found in tests) - private static ReadOnlySpan s_decodingMap => new sbyte[] { + private static ReadOnlySpan DecodingMap => new sbyte[] { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, //62 is placed at index 43 (for +), 63 at index 47 (for /) @@ -631,88 +696,5 @@ private static unsafe void WriteThreeLowOrderBytes(byte* destination, int value) -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; - - private static ReadOnlySpan s_sseDecodePackBytesMask => new sbyte[] { - 2, 1, 0, 6, - 5, 4, 10, 9, - 8, 14, 13, 12, - -1, -1, -1, -1 - }; - - private static ReadOnlySpan s_sseDecodeLutLo => new sbyte[] { - 0x15, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x13, 0x1A, - 0x1B, 0x1B, 0x1B, 0x1A - }; - - private static ReadOnlySpan s_sseDecodeLutHi => new sbyte[] { - 0x10, 0x10, 0x01, 0x02, - 0x04, 0x08, 0x04, 0x08, - 0x10, 0x10, 0x10, 0x10, - 0x10, 0x10, 0x10, 0x10 - }; - - private static ReadOnlySpan s_sseDecodeLutShift => new sbyte[] { - 0, 16, 19, 4, - -65, -65, -71, -71, - 0, 0, 0, 0, - 0, 0, 0, 0 - }; - - private static ReadOnlySpan s_avxDecodePackBytesInLaneMask => new sbyte[] { - 2, 1, 0, 6, - 5, 4, 10, 9, - 8, 14, 13, 12, - -1, -1, -1, -1, - 2, 1, 0, 6, - 5, 4, 10, 9, - 8, 14, 13, 12, - -1, -1, -1, -1 - }; - - private static ReadOnlySpan s_avxDecodePackLanesControl => new sbyte[] { - 0, 0, 0, 0, - 1, 0, 0, 0, - 2, 0, 0, 0, - 4, 0, 0, 0, - 5, 0, 0, 0, - 6, 0, 0, 0, - -1, -1, -1, -1, - -1, -1, -1, -1 - }; - - private static ReadOnlySpan s_avxDecodeLutLo => new sbyte[] { - 0x15, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x13, 0x1A, - 0x1B, 0x1B, 0x1B, 0x1A, - 0x15, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x13, 0x1A, - 0x1B, 0x1B, 0x1B, 0x1A - }; - - private static ReadOnlySpan s_avxDecodeLutHi => new sbyte[] { - 0x10, 0x10, 0x01, 0x02, - 0x04, 0x08, 0x04, 0x08, - 0x10, 0x10, 0x10, 0x10, - 0x10, 0x10, 0x10, 0x10, - 0x10, 0x10, 0x01, 0x02, - 0x04, 0x08, 0x04, 0x08, - 0x10, 0x10, 0x10, 0x10, - 0x10, 0x10, 0x10, 0x10 - }; - - private static ReadOnlySpan s_avxDecodeLutShift => new sbyte[] { - 0, 16, 19, 4, - -65, -65, -71, -71, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 16, 19, 4, - -65, -65, -71, -71, - 0, 0, 0, 0, - 0, 0, 0, 0 - }; } } diff --git a/src/libraries/System.Memory/src/System/Buffers/Text/Base64Encoder.cs b/src/libraries/System.Memory/src/System/Buffers/Text/Base64Encoder.cs index 0ee99479128b..99add7b72e8a 100644 --- a/src/libraries/System.Memory/src/System/Buffers/Text/Base64Encoder.cs +++ b/src/libraries/System.Memory/src/System/Buffers/Text/Base64Encoder.cs @@ -85,7 +85,7 @@ public static unsafe OperationStatus EncodeToUtf8(ReadOnlySpan bytes, Span } } - ref byte encodingMap = ref MemoryMarshal.GetReference(s_encodingMap); + ref byte encodingMap = ref MemoryMarshal.GetReference(EncodingMap); uint result = 0; srcMax -= 2; @@ -189,7 +189,7 @@ public static unsafe OperationStatus EncodeToUtf8InPlace(Span buffer, int uint destinationIndex = (uint)(encodedLength - 4); uint sourceIndex = (uint)(dataLength - leftover); uint result = 0; - ref byte encodingMap = ref MemoryMarshal.GetReference(s_encodingMap); + ref byte encodingMap = ref MemoryMarshal.GetReference(EncodingMap); // encode last pack to avoid conditional in the main loop if (leftover != 0) @@ -241,14 +241,32 @@ private static unsafe void Avx2Encode(ref byte* srcBytes, ref byte* destBytes, b // l k j i h g f e d c b a 0 0 0 0 // The JIT won't hoist these "constants", so help it - Vector256 shuffleVec = ReadVector>(s_avxEncodeShuffleVec); + Vector256 shuffleVec = Vector256.Create( + 5, 4, 6, 5, + 8, 7, 9, 8, + 11, 10, 12, 11, + 14, 13, 15, 14, + 1, 0, 2, 1, + 4, 3, 5, 4, + 7, 6, 8, 7, + 10, 9, 11, 10); + + Vector256 lut = Vector256.Create( + 65, 71, -4, -4, + -4, -4, -4, -4, + -4, -4, -4, -4, + -19, -16, 0, 0, + 65, 71, -4, -4, + -4, -4, -4, -4, + -4, -4, -4, -4, + -19, -16, 0, 0); + Vector256 maskAC = Vector256.Create(0x0fc0fc00).AsSByte(); Vector256 maskBB = Vector256.Create(0x003f03f0).AsSByte(); Vector256 shiftAC = Vector256.Create(0x04000040).AsUInt16(); Vector256 shiftBB = Vector256.Create(0x01000010).AsInt16(); Vector256 const51 = Vector256.Create((byte)51); Vector256 const25 = Vector256.Create((sbyte)25); - Vector256 lut = ReadVector>(s_avxEncodeLut); byte* src = srcBytes; byte* dest = destBytes; @@ -258,7 +276,15 @@ private static unsafe void Avx2Encode(ref byte* srcBytes, ref byte* destBytes, b Vector256 str = Avx.LoadVector256(src).AsSByte(); // shift by 4 bytes, as required by Reshuffle - str = Avx2.PermuteVar8x32(str.AsInt32(), ReadVector>(s_avxEncodePermuteVec).AsInt32()).AsSByte(); + str = Avx2.PermuteVar8x32(str.AsInt32(), Vector256.Create( + 0, 0, 0, 0, + 0, 0, 0, 0, + 1, 0, 0, 0, + 2, 0, 0, 0, + 3, 0, 0, 0, + 4, 0, 0, 0, + 5, 0, 0, 0, + 6, 0, 0, 0).AsInt32()).AsSByte(); // Next loads are done at src-4, as required by Reshuffle, so shift it once src -= 4; @@ -380,14 +406,24 @@ private static unsafe void Ssse3Encode(ref byte* srcBytes, ref byte* destBytes, // 0 0 0 0 l k j i h g f e d c b a // The JIT won't hoist these "constants", so help it - Vector128 shuffleVec = ReadVector>(s_sseEncodeShuffleVec); + Vector128 shuffleVec = Vector128.Create( + 1, 0, 2, 1, + 4, 3, 5, 4, + 7, 6, 8, 7, + 10, 9, 11, 10); + + Vector128 lut = Vector128.Create( + 65, 71, -4, -4, + -4, -4, -4, -4, + -4, -4, -4, -4, + -19, -16, 0, 0); + Vector128 maskAC = Vector128.Create(0x0fc0fc00).AsSByte(); Vector128 maskBB = Vector128.Create(0x003f03f0).AsSByte(); Vector128 shiftAC = Vector128.Create(0x04000040).AsUInt16(); Vector128 shiftBB = Vector128.Create(0x01000010).AsInt16(); Vector128 const51 = Vector128.Create((byte)51); Vector128 const25 = Vector128.Create((sbyte)25); - Vector128 lut = ReadVector>(s_sseEncodeLut); byte* src = srcBytes; byte* dest = destBytes; @@ -543,7 +579,7 @@ private static unsafe uint EncodeAndPadTwo(byte* oneByte, ref byte encodingMap) private const int MaximumEncodeLength = (int.MaxValue / 4) * 3; // 1610612733 // Pre-computing this table using a custom string(s_characters) and GenerateEncodingMapAndVerify (found in tests) - private static ReadOnlySpan s_encodingMap => new byte[] { + private static ReadOnlySpan EncodingMap => new byte[] { 65, 66, 67, 68, 69, 70, 71, 72, //A..H 73, 74, 75, 76, 77, 78, 79, 80, //I..P 81, 82, 83, 84, 85, 86, 87, 88, //Q..X @@ -553,52 +589,5 @@ private static unsafe uint EncodeAndPadTwo(byte* oneByte, ref byte encodingMap) 119, 120, 121, 122, 48, 49, 50, 51, //w..z, 0..3 52, 53, 54, 55, 56, 57, 43, 47 //4..9, +, / }; - - private static ReadOnlySpan s_sseEncodeShuffleVec => new sbyte[] { - 1, 0, 2, 1, - 4, 3, 5, 4, - 7, 6, 8, 7, - 10, 9, 11, 10 - }; - - private static ReadOnlySpan s_sseEncodeLut => new sbyte[] { - 65, 71, -4, -4, - -4, -4, -4, -4, - -4, -4, -4, -4, - -19, -16, 0, 0 - }; - - private static ReadOnlySpan s_avxEncodePermuteVec => new sbyte[] { - 0, 0, 0, 0, - 0, 0, 0, 0, - 1, 0, 0, 0, - 2, 0, 0, 0, - 3, 0, 0, 0, - 4, 0, 0, 0, - 5, 0, 0, 0, - 6, 0, 0, 0 - }; - - private static ReadOnlySpan s_avxEncodeShuffleVec => new sbyte[] { - 5, 4, 6, 5, - 8, 7, 9, 8, - 11, 10, 12, 11, - 14, 13, 15, 14, - 1, 0, 2, 1, - 4, 3, 5, 4, - 7, 6, 8, 7, - 10, 9, 11, 10 - }; - - private static ReadOnlySpan s_avxEncodeLut => new sbyte[] { - 65, 71, -4, -4, - -4, -4, -4, -4, - -4, -4, -4, -4, - -19, -16, 0, 0, - 65, 71, -4, -4, - -4, -4, -4, -4, - -4, -4, -4, -4, - -19, -16, 0, 0 - }; } } From 4156116824c18be1cf1d29a42278b5586d16ffcf Mon Sep 17 00:00:00 2001 From: Zoltan Varga Date: Mon, 12 Jul 2021 13:59:13 -0400 Subject: [PATCH 028/133] [mono] Add some WASM AOT documention. (#55498) --- docs/design/mono/wasm-aot.md | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 docs/design/mono/wasm-aot.md diff --git a/docs/design/mono/wasm-aot.md b/docs/design/mono/wasm-aot.md new file mode 100644 index 000000000000..667d124ed9c6 --- /dev/null +++ b/docs/design/mono/wasm-aot.md @@ -0,0 +1,66 @@ +# WebAssembly AOT code generation + +## Basic operation + +The LLVM backend of the Mono JIT is used to generate an llvm .bc file for each assembly, then the .bc files are +compiled to webassembly using emscripten, then the resulting wasm files are linked into the final app. The 'bitcode'/'llvmonly' +variant of the LLVM backend is used since webassembly doesn't support inline assembly etc. + +## GC Support + +On wasm, the execution stack is not stored in linear memory, so its not possible to scan it for GC references. However, there +is an additional C stack which stores variables whose addresses are taken. Variables which hold GC references are marked as +'volatile' in the llvm backend, forcing llvm to spill those to the C stack so they can be scanned. + +## Interpreter support + +Its possible for AOTed and interpreted code to interop, this is called mixed mode. +For the AOT -> interpreter case, every call from AOTed code which might end up in the interpreter is +emitted as an indirect call. When the callee is not found, a wrapper function is used which +packages up the arguments into an array and passes control to the interpreter. +For the interpreter -> AOT case, and similar wrapper function is used which receives the +arguments and a return value pointer from the interpreter in an array, and calls the +AOTed code. There is usually one aot->interp and interp->aot wrapper for each signature, with +some sharing. These wrappers are generated by the AOT compiler when the 'interp' aot option +is used. + +## Null checks + +Since wasm has no signal support, we generate explicit null checks. + +## Issues + +The generated code is in general much bigger than the code generated on ios etc. Some of the +current issues are described below. + +### Function pointers + +The runtime needs to be able to do a IL method -> wasm function lookup. To do this, every +AOT image includes a table mapping from a method index to wasm functions. This means that +every generated AOT method has its address taken, which severely limits the interprocedural +optimizations that LLVM can do, since it cannot determine the set of callers for a function. +This means that it cannot remove functions corresponding to unused IL methods, cannot +specialize functions for constant/nonnull arguments, etc. +The dotnet linker includes some support for adding a [DisablePrivateReflection] attribute to +methods which cannot be called using reflection, and the AOT compiler could use this +to avoid generating function pointers for methods which are not called from outside the +AOT image. This is not enabled right now because the linker support is not complete. + +### Null checks + +The explicit null checking code adds a lot of size overhead since null checks are very common. + +### Virtual calls + +Vtable slots are lazily initialized on the first call, i.e. every virtual call looks like this: +```C +vt_entry = vtable [slot]; +if (vt_entry == null) + vt_entry = init_vt_entry (); +``` + +### GC overhead + +Since GC variables are marked as volatile and stored on the C stack, they are loaded/stored on every access, +even if there is no GC safe point between the accesses. Instead, they should only be loaded/stored around +GC safe points. From efd0bb58bdc2d7591d77e604b810c096a7a5d58e Mon Sep 17 00:00:00 2001 From: Juan Hoyos Date: Mon, 12 Jul 2021 12:24:17 -0700 Subject: [PATCH 029/133] Remove NuGet config in HTTP stress (#55519) --- .../tests/StressTests/HttpStress/NuGet.config | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 src/libraries/System.Net.Http/tests/StressTests/HttpStress/NuGet.config diff --git a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/NuGet.config b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/NuGet.config deleted file mode 100644 index 0992c432038a..000000000000 --- a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/NuGet.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file From cd4df7dac9efed87702b69c8fbf86b265372a533 Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Mon, 12 Jul 2021 12:57:21 -0700 Subject: [PATCH 030/133] Implement part of SslCertificateTrust (#55104) * snatpshot * snapshot * add csproj' * update * more ckeanup * more cleanup * feedback from review * fix mail tests * feedabck from review * feedback from review * make constructor private --- .../Interop/Windows/SspiCli/Interop.SSPI.cs | 22 +++++++ .../src/System.Net.Http.csproj | 4 +- .../src/System.Net.Mail.csproj | 2 + .../Unit/System.Net.Mail.Unit.Tests.csproj | 2 + .../ref/System.Net.Security.cs | 17 ++++- .../src/Resources/Strings.resx | 6 ++ .../src/System.Net.Security.csproj | 4 ++ .../src/System/Net/Security/SecureChannel.cs | 15 ++++- .../Net/Security/SslCertificateTrust.cs | 64 +++++++++++++++++++ .../Net/Security/SslStream.Implementation.cs | 2 +- .../SslStreamCertificateContext.Linux.cs | 3 +- .../SslStreamCertificateContext.OSX.cs | 5 +- .../SslStreamCertificateContext.Windows.cs | 5 +- .../Security/SslStreamCertificateContext.cs | 15 +++-- .../Net/Security/SslStreamPal.Windows.cs | 37 +++++++++-- .../SslStreamCredentialCacheTest.cs | 8 +-- 16 files changed, 188 insertions(+), 23 deletions(-) create mode 100644 src/libraries/System.Net.Security/src/System/Net/Security/SslCertificateTrust.cs diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs index b675f7f02247..1694ca539a86 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs @@ -65,6 +65,7 @@ internal enum ContextAttribute SECPKG_ATTR_LOCAL_CERT_CONTEXT = 0x54, // returns PCCERT_CONTEXT SECPKG_ATTR_ROOT_STORE = 0x55, // returns HCERTCONTEXT to the root store SECPKG_ATTR_ISSUER_LIST_EX = 0x59, // returns SecPkgContext_IssuerListInfoEx + SECPKG_ATTR_CLIENT_CERT_POLICY = 0x60, // sets SecPkgCred_ClientCertCtlPolicy SECPKG_ATTR_CONNECTION_INFO = 0x5A, // returns SecPkgContext_ConnectionInfo SECPKG_ATTR_CIPHER_INFO = 0x64, // returns SecPkgContext_CipherInfo SECPKG_ATTR_UI_INFO = 0x68, // sets SEcPkgContext_UiInfo @@ -315,6 +316,20 @@ public SecBufferDesc(int count) } } + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct SecPkgCred_ClientCertPolicy + { + public uint dwFlags; + public Guid guidPolicyId; + public uint dwCertFlags; + public uint dwUrlRetrievalTimeout; + public BOOL fCheckRevocationFreshnessTime; + public uint dwRevocationFreshnessTime; + public BOOL fOmitUsageCheck; + public char* pwszSslCtlStoreName; + public char* pwszSslCtlIdentifier; + } + [DllImport(Interop.Libraries.SspiCli, ExactSpelling = true, SetLastError = true)] internal static extern int EncryptMessage( ref CredHandle contextHandle, @@ -472,5 +487,12 @@ internal static extern SECURITY_STATUS SspiEncodeStringsAsAuthIdentity( [In] string domainName, [In] string password, [Out] out SafeSspiAuthDataHandle authData); + + [DllImport(Interop.Libraries.SspiCli, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern SECURITY_STATUS SetCredentialsAttributesW( + [In] ref CredHandle handlePtr, + [In] long ulAttribute, + [In] ref SecPkgCred_ClientCertPolicy pBuffer, + [In] long cbBuffer); } } diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index 49bc41d756e4..0bbe56c0d879 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -396,10 +396,12 @@ + + Link="Common\Interop\Windows\Interop.UNICODE_STRING.cs" /> + + Setting an SNI hostname is not supported on this API level. + + Only LocalMachine stores are supported on Windows. + + + Sending trust from collection is not supported on Windows. + diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj index 100115a0920a..ca89fca29fa5 100644 --- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj +++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Android;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-tvOS;$(NetCoreAppCurrent) $(DefineConstants);PRODUCT + $(DefineConstants);TARGET_WINDOWS enable @@ -29,6 +30,7 @@ + @@ -156,6 +158,8 @@ + buffer, out int outputOffset, out --*/ //This method validates a remote certificate. - internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remoteCertValidationCallback, ref ProtocolToken? alertToken, out SslPolicyErrors sslPolicyErrors, out X509ChainStatusFlags chainStatus) + internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remoteCertValidationCallback, SslCertificateTrust? trust, ref ProtocolToken? alertToken, out SslPolicyErrors sslPolicyErrors, out X509ChainStatusFlags chainStatus) { sslPolicyErrors = SslPolicyErrors.None; chainStatus = X509ChainStatusFlags.NoError; @@ -965,6 +965,19 @@ internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remot chain.ChainPolicy.ExtraStore.AddRange(remoteCertificateStore); } + if (trust != null) + { + chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; + if (trust._store != null) + { + chain.ChainPolicy.CustomTrustStore.AddRange(trust._store.Certificates); + } + if (trust._trustList != null) + { + chain.ChainPolicy.CustomTrustStore.AddRange(trust._trustList); + } + } + sslPolicyErrors |= CertificateValidationPal.VerifyCertificateProperties( _securityContext!, chain, diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslCertificateTrust.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslCertificateTrust.cs new file mode 100644 index 000000000000..982d206c8593 --- /dev/null +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslCertificateTrust.cs @@ -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.Runtime.Versioning; +using System.Security.Cryptography.X509Certificates; + +namespace System.Net.Security +{ + public sealed class SslCertificateTrust + { + internal X509Store? _store; + internal X509Certificate2Collection? _trustList; + internal bool _sendTrustInHandshake; + + public static SslCertificateTrust CreateForX509Store(X509Store store, bool sendTrustInHandshake = false) + { + +#if TARGET_WINDOWS + if (sendTrustInHandshake && store.Location != StoreLocation.LocalMachine) + { + throw new PlatformNotSupportedException(SR.net_ssl_trust_store); + } +#else + if (sendTrustInHandshake) + { + // to be removed when implemented. + throw new PlatformNotSupportedException("Not supported yet."); + } +#endif + if (!store.IsOpen) + { + store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + } + + var trust = new SslCertificateTrust(); + trust._store = store; + trust._sendTrustInHandshake = sendTrustInHandshake; + return trust; + } + + [UnsupportedOSPlatform("windows")] + public static SslCertificateTrust CreateForX509Collection(X509Certificate2Collection trustList, bool sendTrustInHandshake = false) + { + if (sendTrustInHandshake) + { + // to be removed when implemented. + throw new PlatformNotSupportedException("Not supported yet."); + } + +#if TARGET_WINDOWS + if (sendTrustInHandshake) + { + throw new PlatformNotSupportedException(SR.net_ssl_trust_collection); + } +#endif + var trust = new SslCertificateTrust(); + trust._trustList = trustList; + trust._sendTrustInHandshake = sendTrustInHandshake; + return trust; + } + + private SslCertificateTrust() { } + } +} diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs index 17858bb0e68b..2ecc2a3932ab 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs @@ -664,7 +664,7 @@ private bool CompleteHandshake(ref ProtocolToken? alertToken, out SslPolicyError return true; } - if (!_context.VerifyRemoteCertificate(_sslAuthenticationOptions!.CertValidationDelegate, ref alertToken, out sslPolicyErrors, out chainStatus)) + if (!_context.VerifyRemoteCertificate(_sslAuthenticationOptions!.CertValidationDelegate, _sslAuthenticationOptions!.CertificateContext?.Trust, ref alertToken, out sslPolicyErrors, out chainStatus)) { _handshakeCompleted = false; return false; diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Linux.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Linux.cs index f5e9dd2114d6..facb437f02b4 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Linux.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Linux.cs @@ -9,10 +9,11 @@ public partial class SslStreamCertificateContext { private const bool TrimRootCertificate = true; - private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates) + private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates, SslCertificateTrust? trust) { Certificate = target; IntermediateCertificates = intermediates; + Trust = trust; } internal static SslStreamCertificateContext Create(X509Certificate2 target) => Create(target, null); diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.OSX.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.OSX.cs index 4579f15407fd..9cef66806ac0 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.OSX.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.OSX.cs @@ -10,16 +10,17 @@ public partial class SslStreamCertificateContext // No leaf, no root. private const bool TrimRootCertificate = true; - private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates) + private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates, SslCertificateTrust? trust) { Certificate = target; IntermediateCertificates = intermediates; + Trust = trust; } internal static SslStreamCertificateContext Create(X509Certificate2 target) { // On OSX we do not need to build chain unless we are asked for it. - return new SslStreamCertificateContext(target, Array.Empty()); + return new SslStreamCertificateContext(target, Array.Empty(), null); } } } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Windows.cs index fcb84b2dda6a..699a5c8c0123 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Windows.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Windows.cs @@ -13,10 +13,10 @@ public partial class SslStreamCertificateContext internal static SslStreamCertificateContext Create(X509Certificate2 target) { // On Windows we do not need to build chain unless we are asked for it. - return new SslStreamCertificateContext(target, Array.Empty()); + return new SslStreamCertificateContext(target, Array.Empty(), null); } - private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates) + private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates, SslCertificateTrust? trust) { if (intermediates.Length > 0) { @@ -103,6 +103,7 @@ private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] Certificate = target; IntermediateCertificates = intermediates; + Trust = trust; } } } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.cs index 4ae127aadcc1..c585d7e033de 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel; using System.Security.Cryptography.X509Certificates; namespace System.Net.Security @@ -9,8 +10,15 @@ public partial class SslStreamCertificateContext { internal readonly X509Certificate2 Certificate; internal readonly X509Certificate2[] IntermediateCertificates; + internal readonly SslCertificateTrust? Trust; - public static SslStreamCertificateContext Create(X509Certificate2 target, X509Certificate2Collection? additionalCertificates, bool offline = false) + [EditorBrowsable(EditorBrowsableState.Never)] + public static SslStreamCertificateContext Create(X509Certificate2 target, X509Certificate2Collection? additionalCertificates, bool offline) + { + return Create(target, additionalCertificates, offline, null); + } + + public static SslStreamCertificateContext Create(X509Certificate2 target, X509Certificate2Collection? additionalCertificates, bool offline = false, SslCertificateTrust? trust = null) { if (!target.HasPrivateKey) { @@ -81,13 +89,12 @@ public static SslStreamCertificateContext Create(X509Certificate2 target, X509Ce } } - return new SslStreamCertificateContext(target, intermediates); + return new SslStreamCertificateContext(target, intermediates, trust); } internal SslStreamCertificateContext Duplicate() { - return new SslStreamCertificateContext(new X509Certificate2(Certificate), IntermediateCertificates); - + return new SslStreamCertificateContext(new X509Certificate2(Certificate), IntermediateCertificates, Trust); } } } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs index 4060296c4ed8..29ab3bed0ee8 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs @@ -4,11 +4,13 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Authentication; using System.Security.Authentication.ExtendedProtection; using System.Security.Cryptography.X509Certificates; using System.Security.Principal; +using System.Text; using Microsoft.Win32.SafeHandles; namespace System.Net.Security @@ -121,14 +123,41 @@ public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentials public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateContext? certificateContext, SslProtocols protocols, EncryptionPolicy policy, bool isServer) { // New crypto API supports TLS1.3 but it does not allow to force NULL encryption. - return !UseNewCryptoApi || policy == EncryptionPolicy.NoEncryption ? + SafeFreeCredentials cred = !UseNewCryptoApi || policy == EncryptionPolicy.NoEncryption ? AcquireCredentialsHandleSchannelCred(certificateContext?.Certificate, protocols, policy, isServer) : AcquireCredentialsHandleSchCredentials(certificateContext?.Certificate, protocols, policy, isServer); + if (certificateContext != null && certificateContext.Trust != null && certificateContext.Trust._sendTrustInHandshake) + { + AttachCertificateStore(cred, certificateContext.Trust._store!); + } + + return cred; + } + + private static unsafe void AttachCertificateStore(SafeFreeCredentials cred, X509Store store) + { + Interop.SspiCli.SecPkgCred_ClientCertPolicy clientCertPolicy = default; + fixed (char* ptr = store.Name) + { + clientCertPolicy.pwszSslCtlStoreName = ptr; + Interop.SECURITY_STATUS errorCode = Interop.SspiCli.SetCredentialsAttributesW( + ref cred._handle, + (long)Interop.SspiCli.ContextAttribute.SECPKG_ATTR_CLIENT_CERT_POLICY, + ref clientCertPolicy, + sizeof(Interop.SspiCli.SecPkgCred_ClientCertPolicy)); + + if (errorCode != Interop.SECURITY_STATUS.OK) + { + throw new Win32Exception((int)errorCode); + } + } + + return; } // This is legacy crypto API used on .NET Framework and older Windows versions. // It only supports TLS up to 1.2 - public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchannelCred(X509Certificate? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer) + public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchannelCred(X509Certificate2? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer) { int protocolFlags = GetProtocolFlagsFromSslProtocols(protocols, isServer); Interop.SspiCli.SCHANNEL_CRED.Flags flags; @@ -174,12 +203,11 @@ public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchannelCred(X5 } // This function uses new crypto API to support TLS 1.3 and beyond. - public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchCredentials(X509Certificate? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer) + public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchCredentials(X509Certificate2? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer) { int protocolFlags = GetProtocolFlagsFromSslProtocols(protocols, isServer); Interop.SspiCli.SCH_CREDENTIALS.Flags flags; Interop.SspiCli.CredentialUse direction; - if (isServer) { direction = Interop.SspiCli.CredentialUse.SECPKG_CRED_INBOUND; @@ -215,7 +243,6 @@ public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchCredentials( Interop.SspiCli.SCH_CREDENTIALS credential = default; credential.dwVersion = Interop.SspiCli.SCH_CREDENTIALS.CurrentVersion; credential.dwFlags = flags; - Interop.Crypt32.CERT_CONTEXT *certificateHandle = null; if (certificate != null) { diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs index e2e8314249d3..f13509547632 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs @@ -28,15 +28,13 @@ public async Task SslStream_SameCertUsedForClientAndServer_Ok() X509Certificate2Collection clientCertificateCollection = new X509Certificate2Collection(certificate); - var tasks = new Task[2]; - - tasks[0] = server.AuthenticateAsServerAsync(certificate, true, false); - tasks[1] = client.AuthenticateAsClientAsync( + Task t1 = server.AuthenticateAsServerAsync(certificate, true, false); + Task t2 = client.AuthenticateAsClientAsync( certificate.GetNameInfo(X509NameType.SimpleName, false), clientCertificateCollection, false); - await Task.WhenAll(tasks).WaitAsync(TestConfiguration.PassingTestTimeout); + await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2); if (!PlatformDetection.IsWindows7 || Capability.IsTrustedRootCertificateInstalled()) From e544ccffbd6f7018372923871dd39129810e4b11 Mon Sep 17 00:00:00 2001 From: John Salem Date: Mon, 12 Jul 2021 13:02:28 -0700 Subject: [PATCH 031/133] Prevent AV in processinfo2 while suspended (#55379) --- .../vm/eventing/eventpipe/ep-rt-coreclr.h | 17 ++++++++- .../diagnosticport/diagnosticport.cs | 36 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h index 607286d048ba..7ee83ae8c5f8 100644 --- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h +++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h @@ -1169,7 +1169,22 @@ ep_rt_entrypoint_assembly_name_get_utf8 (void) { STATIC_CONTRACT_NOTHROW; - return reinterpret_cast(GetAppDomain ()->GetRootAssembly ()->GetSimpleName ()); + AppDomain *app_domain_ref = nullptr; + Assembly *assembly_ref = nullptr; + + app_domain_ref = GetAppDomain (); + if (app_domain_ref != nullptr) + { + assembly_ref = app_domain_ref->GetRootAssembly (); + if (assembly_ref != nullptr) + { + return reinterpret_cast(assembly_ref->GetSimpleName ()); + } + } + + // fallback to the empty string if we can't get assembly info, e.g., if the runtime is + // suspended before an assembly is loaded. + return reinterpret_cast(""); } static diff --git a/src/tests/tracing/eventpipe/diagnosticport/diagnosticport.cs b/src/tests/tracing/eventpipe/diagnosticport/diagnosticport.cs index a4e6bd641810..b1455d01e211 100644 --- a/src/tests/tracing/eventpipe/diagnosticport/diagnosticport.cs +++ b/src/tests/tracing/eventpipe/diagnosticport/diagnosticport.cs @@ -388,6 +388,42 @@ public static async Task TEST_ConfigValidation() return fSuccess; } + public static async Task TEST_CanGetProcessInfo2WhileSuspended() + { + bool fSuccess = true; + Task subprocessTask = Utils.RunSubprocess( + currentAssembly: Assembly.GetExecutingAssembly(), + environment: new Dictionary + { + { Utils.DiagnosticPortSuspend, "1" } + }, + duringExecution: (int pid) => + { + Stream stream = ConnectionHelper.GetStandardTransport(pid); + + // 0x04 = ProcessCommandSet, 0x04 = ProcessInfo2 + var processInfoMessage = new IpcMessage(0x04, 0x04); + Logger.logger.Log($"Wrote: {processInfoMessage}"); + IpcMessage response = IpcClient.SendMessage(stream, processInfoMessage); + Logger.logger.Log($"Received: [{response.Payload.Select(b => b.ToString("X2") + " ").Aggregate(string.Concat)}]"); + ProcessInfo2 processInfo2 = ProcessInfo2.TryParse(response.Payload); + Utils.Assert(String.IsNullOrEmpty(processInfo2.ManagedEntrypointAssemblyName)); + + // send resume command on this connection + var message = new IpcMessage(0x04,0x01); + Logger.logger.Log($"Sent: {message.ToString()}"); + response = IpcClient.SendMessage(ConnectionHelper.GetStandardTransport(pid), message); + Logger.logger.Log($"Received: {response.ToString()}"); + + return Task.FromResult(true); + } + ); + + fSuccess &= await subprocessTask; + + return fSuccess; + } + public static async Task Main(string[] args) { if (args.Length >= 1) From f280419a07a9445e1c6724e5717b3da7bdc5be7d Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Mon, 12 Jul 2021 15:37:40 -0500 Subject: [PATCH 032/133] Expose the platform directly for apple products (#55381) Co-authored-by: Steve Pfister --- ...T.Workload.Mono.Toolchain.Manifest.pkgproj | 7 +- .../WorkloadManifest.json.in | 108 +++++++++++++----- ...st.targets => WorkloadManifest.targets.in} | 78 +++++++++++-- 3 files changed, 156 insertions(+), 37 deletions(-) rename src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/{WorkloadManifest.targets => WorkloadManifest.targets.in} (55%) diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Manifest.pkgproj b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Manifest.pkgproj index 0f6c403f4104..f89c42b2884d 100644 --- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Manifest.pkgproj +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Manifest.pkgproj @@ -13,11 +13,12 @@ $(IntermediateOutputPath)WorkloadManifest.json + $(IntermediateOutputPath)WorkloadManifest.targets - + @@ -37,6 +38,10 @@ TemplateFile="WorkloadManifest.json.in" Properties="@(_WorkloadManifestValues)" OutputPath="$(WorkloadManifestPath)" /> + diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.json.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.json.in index 05d94220cb8b..d7fec86e3a15 100644 --- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.json.in +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.json.in @@ -44,11 +44,13 @@ "packs": [ "Microsoft.NETCore.App.Runtime.Mono.ios-arm", "Microsoft.NETCore.App.Runtime.Mono.ios-arm64", - "Microsoft.NETCore.App.Runtime.Mono.iossimulator", + "Microsoft.NETCore.App.Runtime.Mono.iossimulator-arm64", + "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x64", "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x86", "Microsoft.NETCore.App.Runtime.AOT.Cross.ios-arm", "Microsoft.NETCore.App.Runtime.AOT.Cross.ios-arm64", - "Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator", + "Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator-x64", "Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator-x86" ], "extends": [ "microsoft-net-runtime-mono-tooling" ], @@ -58,8 +60,22 @@ "abstract": true, "description": "MacCatalyst Mono Runtime and AOT Workload", "packs": [ - "Microsoft.NETCore.App.Runtime.Mono.maccatalyst", - "Microsoft.NETCore.App.Runtime.AOT.Cross.maccatalyst" + "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-arm64", + "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-x64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.maccatalyst-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.maccatalyst-x64" + ], + "extends": [ "microsoft-net-runtime-mono-tooling" ], + "platforms": [ "osx-arm64", "osx-x64" ] + }, + "microsoft-net-runtime-macos": { + "abstract": true, + "description": "MacOS CoreCLR and Mono Runtime Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.osx-arm64", + "Microsoft.NETCore.App.Runtime.Mono.osx-x64", + "Microsoft.NETCore.App.Runtime.osx-arm64", + "Microsoft.NETCore.App.Runtime.osx-x64" ], "extends": [ "microsoft-net-runtime-mono-tooling" ], "platforms": [ "osx-arm64", "osx-x64" ] @@ -69,9 +85,11 @@ "description": "tvOS Mono Runtime and AOT Workload", "packs": [ "Microsoft.NETCore.App.Runtime.Mono.tvos-arm64", - "Microsoft.NETCore.App.Runtime.Mono.tvossimulator", + "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-arm64", + "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-x64", "Microsoft.NETCore.App.Runtime.AOT.Cross.tvos-arm64", - "Microsoft.NETCore.App.Runtime.AOT.Cross.tvossimulator" + "Microsoft.NETCore.App.Runtime.AOT.Cross.tvossimulator-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.tvossimulator-x64" ], "extends": [ "microsoft-net-runtime-mono-tooling" ], "platforms": [ "osx-arm64", "osx-x64" ] @@ -154,13 +172,29 @@ "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm64" } }, - "Microsoft.NETCore.App.Runtime.Mono.maccatalyst": { + "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-arm64": { + "kind": "framework", + "version": "${PackageVersion}", + }, + "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-64": { + "kind": "framework", + "version": "${PackageVersion}", + }, + "Microsoft.NETCore.App.Runtime.Mono.osx-arm64": { + "kind": "framework", + "version": "${PackageVersion}", + }, + "Microsoft.NETCore.App.Runtime.Mono.osx-x64": { + "kind": "framework", + "version": "${PackageVersion}", + }, + "Microsoft.NETCore.App.Runtime.osx-arm64": { + "kind": "framework", + "version": "${PackageVersion}", + }, + "Microsoft.NETCore.App.Runtime.osx-x64": { "kind": "framework", "version": "${PackageVersion}", - "alias-to": { - "osx-arm64": "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-arm64", - "osx-x64": "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-x64" - } }, "Microsoft.NETCore.App.Runtime.Mono.ios-arm" : { "kind": "framework", @@ -170,20 +204,17 @@ "kind": "framework", "version": "${PackageVersion}" }, - "Microsoft.NETCore.App.Runtime.Mono.iossimulator" : { + "Microsoft.NETCore.App.Runtime.Mono.iossimulator-arm64" : { + "kind": "framework", + "version": "${PackageVersion}", + }, + "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x64" : { "kind": "framework", "version": "${PackageVersion}", - "alias-to": { - "osx-arm64": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-arm64", - "osx-x64": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x64" - } }, "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x86" : { "kind": "framework", "version": "${PackageVersion}", - "alias-to": { - "osx-x64": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x86" - } }, "Microsoft.NETCore.App.Runtime.AOT.Cross.tvos-arm64": { "kind": "Sdk", @@ -197,27 +228,43 @@ "kind": "framework", "version": "${PackageVersion}" }, - "Microsoft.NETCore.App.Runtime.Mono.tvossimulator" : { + "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-arm64" : { "kind": "framework", "version": "${PackageVersion}", + }, + "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-x64" : { + "kind": "framework", + "version": "${PackageVersion}", + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.maccatalyst-arm64": { + "kind": "Sdk", + "version": "${PackageVersion}", "alias-to": { - "osx-arm64": "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-arm64", - "osx-x64": "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-x64" + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.maccatalyst-arm64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.maccatalyst-arm64" } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.maccatalyst": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.maccatalyst-x64": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { - "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.maccatalyst-arm64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.maccatalyst-x64", "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.maccatalyst-x64" } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.tvossimulator": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.tvossimulator-arm64": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvossimulator-arm64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvossimulator-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.tvossimulator-x64": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvossimulator-x64", "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvossimulator-x64" } }, @@ -237,11 +284,19 @@ "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm64", } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator-arm64": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-arm64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator-x64": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-x64", "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-x64" } }, @@ -249,6 +304,7 @@ "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-x86", "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-x86" } }, diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets.in similarity index 55% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets.in index c93e50175e92..26766076edc5 100644 --- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets.in @@ -1,4 +1,7 @@ + + ${PackageVersion} + true $(WasmNativeWorkload) @@ -21,31 +24,47 @@ - + - - - + - + - + + + + + + + - + - + + + + - + - + + + + + @@ -54,4 +73,43 @@ + + <_MonoWorkloadTargetsMobile>true + <_MonoWorkloadRuntimePackPackageVersion>$(RuntimePackInWorkloadVersion) + + + + + + + + + From 67ecbe844f5fd69b35024f40faa615fb7557b82a Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Mon, 12 Jul 2021 23:22:17 +0200 Subject: [PATCH 033/133] File.Unix: Replace: increase Windows compatibility (#50234) * File.Unix: Replace: increase Windows compatibility - When source and destination are not a file throw UnauthorizedAccessException. - When source and destination are the same file throw IOException. * Include paths in exception message Co-authored-by: Jeff Handley --- .../src/Resources/Strings.resx | 290 ++++++++++++++++++ .../tests/File/Replace.cs | 31 ++ .../src/Resources/Strings.resx | 6 + .../src/System/IO/FileSystem.Unix.cs | 30 ++ 4 files changed, 357 insertions(+) create mode 100644 src/libraries/System.IO.FileSystem/src/Resources/Strings.resx diff --git a/src/libraries/System.IO.FileSystem/src/Resources/Strings.resx b/src/libraries/System.IO.FileSystem/src/Resources/Strings.resx new file mode 100644 index 000000000000..3c391ca6bcde --- /dev/null +++ b/src/libraries/System.IO.FileSystem/src/Resources/Strings.resx @@ -0,0 +1,290 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The target file '{0}' is a directory, not a file. + + + Handle does not support asynchronous operations. The parameters to the FileStream constructor may need to be changed to indicate that the handle was opened synchronously (that is, it was not opened for overlapped I/O). + + + Handle does not support synchronous operations. The parameters to the FileStream constructor may need to be changed to indicate that the handle was opened asynchronously (that is, it was opened explicitly for overlapped I/O). + + + Invalid File or Directory attributes value. + + + Invalid handle. + + + Drive name must be a root directory (i.e. 'C:\') or a drive letter ('C'). + + + Second path fragment must not be a drive or UNC name. + + + Path must not be a drive. + + + Buffer cannot be null. + + + File name cannot be null. + + + Path cannot be null. + + + Enum value was out of legal range. + + + Specified file length was too large for the file system. + + + Non-negative number required. + + + Positive number required. + + + Empty file name is not legal. + + + Empty path name is not legal. + + + The stream's length cannot be changed. + + + Append access can be requested only in write-only mode. + + + Combining FileMode: {0} with FileAccess: {1} is invalid. + + + Illegal characters in path '{0}'. + + + Invalid seek origin. + + + The directory specified, '{0}', is not a subdirectory of '{1}'. + + + Path cannot be the empty string or all whitespace. + + + Cannot create '{0}' because a file or directory with the same name already exists. + + + BindHandle for ThreadPool failed on this handle. + + + The specified directory '{0}' cannot be created. + + + The source '{0}' and destination '{1}' are the same file. + + + Unable to read beyond the end of the stream. + + + The file '{0}' already exists. + + + Unable to find the specified file. + + + Could not find file '{0}'. + + + The OS handle's position is not what FileStream expected. Do not use a handle simultaneously in one FileStream and in Win32 code or another FileStream. This may cause data loss. + + + The file is too long. This operation is currently limited to supporting files less than 2 gigabytes in size. + + + IO operation will not work. Most likely the file will become too long or the handle was not opened to support synchronous IO operations. + + + The specified path '{0}' is not a file. + + + Could not find a part of the path. + + + Could not find a part of the path '{0}'. + + + The specified file name or path is too long, or a component of the specified path is too long. + + + Unable seek backward to overwrite data that previously existed in a file opened in Append mode. + + + Unable to truncate data that previously existed in a file opened in Append mode. + + + The process cannot access the file '{0}' because it is being used by another process. + + + The process cannot access the file because it is being used by another process. + + + Source and destination path must be different. + + + Source and destination path must have identical roots. Move will not work across volumes. + + + Synchronous operations should not be performed on the UI thread. Consider wrapping this method in Task.Run. + + + [Unknown] + + + Probable I/O race condition detected while copying memory. The I/O package is not thread safe by default. In multithreaded applications, a stream must be accessed in a thread-safe way, such as a thread-safe wrapper returned by TextReader's or TextWriter's Synchronized methods. This also applies to classes like StreamWriter and StreamReader. + + + Stream does not support reading. + + + Stream does not support seeking. + + + Stream does not support writing. + + + Cannot access a closed file. + + + Access to the path is denied. + + + Access to the path '{0}' is denied. + + + Cannot access a closed stream. + + + File encryption is not supported on this platform. + + + The path '{0}' is too long, or a component of the specified path is too long. + + diff --git a/src/libraries/System.IO.FileSystem/tests/File/Replace.cs b/src/libraries/System.IO.FileSystem/tests/File/Replace.cs index 2cd2a3011a3b..8bebeef1bb04 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/Replace.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/Replace.cs @@ -109,6 +109,37 @@ public void InvalidFileNames() Assert.Throws(() => Replace(testFile, testFile2, "*\0*")); } + [Fact] + public void SourceCannotBeADirectory() + { + string testFile = GetTestFilePath(); + File.Create(testFile).Dispose(); + string testDir = GetTestFilePath(); + Directory.CreateDirectory(testDir); + + Assert.Throws(() => File.Replace(testDir, testFile, null)); + } + + [Fact] + public void DestinationCannotBeADirectory() + { + string testFile = GetTestFilePath(); + File.Create(testFile).Dispose(); + string testDir = GetTestFilePath(); + Directory.CreateDirectory(testDir); + + Assert.Throws(() => File.Replace(testFile, testDir, null)); + } + + [Fact] + public void SourceAndDestinationCannotBeTheSame() + { + string testFile = GetTestFilePath(); + File.Create(testFile).Dispose(); + + Assert.Throws(() => File.Replace(testFile, testFile, null)); + } + #endregion } diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 23a96351e049..a9910865f006 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3778,6 +3778,12 @@ The specified directory '{0}' cannot be created. + + The source '{0}' and destination '{1}' are the same file. + + + The specified path '{0}' is not a file. + Source and destination path must be different. diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs index c80e585eda86..0151499893aa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs @@ -103,6 +103,36 @@ private static void LinkOrCopyFile (string sourceFullPath, string destFullPath) public static void ReplaceFile(string sourceFullPath, string destFullPath, string? destBackupFullPath, bool ignoreMetadataErrors) { + // Unix rename works in more cases, we limit to what is allowed by Windows File.Replace. + // These checks are not atomic, the file could change after a check was performed and before it is renamed. + Interop.Sys.FileStatus sourceStat; + if (Interop.Sys.LStat(sourceFullPath, out sourceStat) != 0) + { + Interop.ErrorInfo errno = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(errno, sourceFullPath); + } + // Check source is not a directory. + if ((sourceStat.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR) + { + throw new UnauthorizedAccessException(SR.Format(SR.IO_NotAFile, sourceFullPath)); + } + + Interop.Sys.FileStatus destStat; + if (Interop.Sys.LStat(destFullPath, out destStat) == 0) + { + // Check destination is not a directory. + if ((destStat.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR) + { + throw new UnauthorizedAccessException(SR.Format(SR.IO_NotAFile, destFullPath)); + } + // Check source and destination are not the same. + if (sourceStat.Dev == destStat.Dev && + sourceStat.Ino == destStat.Ino) + { + throw new IOException(SR.Format(SR.IO_CannotReplaceSameFile, sourceFullPath, destFullPath)); + } + } + if (destBackupFullPath != null) { // We're backing up the destination file to the backup file, so we need to first delete the backup From d91e11a2bc2f21f3f9df6e16603aa097bea1bd16 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 12 Jul 2021 14:47:52 -0700 Subject: [PATCH 034/133] Look up the ICustomMarshaler implementation methods based on runtime type (#55439) --- src/coreclr/vm/custommarshalerinfo.cpp | 20 ++++--- .../Primitives/ICustomMarshaler.cs | 56 ++++++++++++++++++- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/coreclr/vm/custommarshalerinfo.cpp b/src/coreclr/vm/custommarshalerinfo.cpp index 67acbff136ec..0af48f8e7157 100644 --- a/src/coreclr/vm/custommarshalerinfo.cpp +++ b/src/coreclr/vm/custommarshalerinfo.cpp @@ -67,13 +67,6 @@ CustomMarshalerInfo::CustomMarshalerInfo(LoaderAllocator *pLoaderAllocator, Type STRINGREF CookieStringObj = StringObject::NewString(strCookie, cCookieStrBytes); GCPROTECT_BEGIN(CookieStringObj); #endif - - // Load the method desc's for all the methods in the ICustomMarshaler interface. - m_pMarshalNativeToManagedMD = GetCustomMarshalerMD(CustomMarshalerMethods_MarshalNativeToManaged, hndCustomMarshalerType); - m_pMarshalManagedToNativeMD = GetCustomMarshalerMD(CustomMarshalerMethods_MarshalManagedToNative, hndCustomMarshalerType); - m_pCleanUpNativeDataMD = GetCustomMarshalerMD(CustomMarshalerMethods_CleanUpNativeData, hndCustomMarshalerType); - m_pCleanUpManagedDataMD = GetCustomMarshalerMD(CustomMarshalerMethods_CleanUpManagedData, hndCustomMarshalerType); - // Load the method desc for the static method to retrieve the instance. MethodDesc *pGetCustomMarshalerMD = GetCustomMarshalerMD(CustomMarshalerMethods_GetInstance, hndCustomMarshalerType); @@ -103,7 +96,9 @@ CustomMarshalerInfo::CustomMarshalerInfo(LoaderAllocator *pLoaderAllocator, Type }; // Call the GetCustomMarshaler method to retrieve the custom marshaler to use. - OBJECTREF CustomMarshalerObj = getCustomMarshaler.Call_RetOBJECTREF(GetCustomMarshalerArgs); + OBJECTREF CustomMarshalerObj = NULL; + GCPROTECT_BEGIN(CustomMarshalerObj); + CustomMarshalerObj = getCustomMarshaler.Call_RetOBJECTREF(GetCustomMarshalerArgs); if (!CustomMarshalerObj) { DefineFullyQualifiedNameForClassW() @@ -111,7 +106,16 @@ CustomMarshalerInfo::CustomMarshalerInfo(LoaderAllocator *pLoaderAllocator, Type IDS_EE_NOCUSTOMMARSHALER, GetFullyQualifiedNameForClassW(hndCustomMarshalerType.GetMethodTable())); } + // Load the method desc's for all the methods in the ICustomMarshaler interface based on the type of the marshaler object. + TypeHandle customMarshalerObjType = CustomMarshalerObj->GetMethodTable(); + + m_pMarshalNativeToManagedMD = GetCustomMarshalerMD(CustomMarshalerMethods_MarshalNativeToManaged, customMarshalerObjType); + m_pMarshalManagedToNativeMD = GetCustomMarshalerMD(CustomMarshalerMethods_MarshalManagedToNative, customMarshalerObjType); + m_pCleanUpNativeDataMD = GetCustomMarshalerMD(CustomMarshalerMethods_CleanUpNativeData, customMarshalerObjType); + m_pCleanUpManagedDataMD = GetCustomMarshalerMD(CustomMarshalerMethods_CleanUpManagedData, customMarshalerObjType); + m_hndCustomMarshaler = pLoaderAllocator->AllocateHandle(CustomMarshalerObj); + GCPROTECT_END(); // Retrieve the size of the native data. if (m_bDataIsByValue) diff --git a/src/tests/Interop/ICustomMarshaler/Primitives/ICustomMarshaler.cs b/src/tests/Interop/ICustomMarshaler/Primitives/ICustomMarshaler.cs index 271c7485d190..78d7d541872a 100644 --- a/src/tests/Interop/ICustomMarshaler/Primitives/ICustomMarshaler.cs +++ b/src/tests/Interop/ICustomMarshaler/Primitives/ICustomMarshaler.cs @@ -365,7 +365,7 @@ public void Parameter_NotICustomMarshaler_ThrowsApplicationException() { Assert.Throws(() => NonICustomMarshalerMethod("")); } - + [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)] public static extern int NonICustomMarshalerMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(string))] string str); @@ -509,7 +509,7 @@ public void CleanUpNativeData(IntPtr pNativeData) { } [Fact] public void Parameter_GetInstanceMethodThrows_ThrowsActualException() - { + { Assert.Throws(() => ThrowingGetInstanceMethod("")); } @@ -588,6 +588,58 @@ public struct StructWithCustomMarshalerField [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)] public static extern int StructWithCustomMarshalerFieldMethod(StructWithCustomMarshalerField c); + + [Fact] + public void Parameter_DifferentCustomMarshalerType_MarshalsCorrectly() + { + Assert.Equal(234, DifferentCustomMarshalerType("5678")); + } + + public class OuterCustomMarshaler : ICustomMarshaler + { + public void CleanUpManagedData(object ManagedObj) => throw new NotImplementedException(); + public void CleanUpNativeData(IntPtr pNativeData) => throw new NotImplementedException(); + + public int GetNativeDataSize() => throw new NotImplementedException(); + + public IntPtr MarshalManagedToNative(object ManagedObj) => throw new NotImplementedException(); + public object MarshalNativeToManaged(IntPtr pNativeData) => throw new NotImplementedException(); + + public static ICustomMarshaler GetInstance(string cookie) => new InnerCustomMarshaler(); + + private interface ILargeInterface + { + void Method1(); + void Method2(); + void Method3(); + void Method4(); + void Method5(); + void Method6(); + } + + private class InnerCustomMarshaler : ILargeInterface, ICustomMarshaler + { + public void Method1() => throw new InvalidOperationException(); + public void Method2() => throw new InvalidOperationException(); + public void Method3() => throw new InvalidOperationException(); + public void Method4() => throw new InvalidOperationException(); + public void Method5() => throw new InvalidOperationException(); + public void Method6() => throw new InvalidOperationException(); + + public void CleanUpManagedData(object ManagedObj) { } + public void CleanUpNativeData(IntPtr pNativeData) => Marshal.FreeCoTaskMem(pNativeData); + + public int GetNativeDataSize() => IntPtr.Size; + + public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi("234"); + public object MarshalNativeToManaged(IntPtr pNativeData) => null; + } + } + + [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)] + public static extern int DifferentCustomMarshalerType([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(OuterCustomMarshaler))] string str); + + public static int Main(String[] args) { return new ICustomMarshalerTests().RunTests(); From 1509b1a0e66aae1504137488fb25c00bf822e3e6 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 12 Jul 2021 15:55:26 -0700 Subject: [PATCH 035/133] Fix more alloc-dealloc mismatches and use-after-scopes (#55420) * Fix another dynamically-sized allocation to use new/delete instead of the mismatched new[]/delete. * Fix use-after-scope * Fix another alloc-dealloc mismatch * Update src/coreclr/vm/threadstatics.cpp Co-authored-by: Jan Kotas * Use standard size_t instead of custom SIZE_T typedef. * Fix formatting. Co-authored-by: Jan Kotas --- src/coreclr/jit/lsrabuild.cpp | 12 ++++++------ src/coreclr/vm/threadstatics.cpp | 8 +++----- src/coreclr/vm/threadstatics.h | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index f5f4d781d759..da7cb15b7cd0 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -3559,6 +3559,7 @@ int LinearScan::BuildReturn(GenTree* tree) noway_assert(op1->IsMultiRegCall() || op1->IsMultiRegLclVar()); int srcCount; + ReturnTypeDesc nonCallRetTypeDesc; const ReturnTypeDesc* pRetTypeDesc; if (op1->OperIs(GT_CALL)) { @@ -3567,13 +3568,12 @@ int LinearScan::BuildReturn(GenTree* tree) else { assert(compiler->lvaEnregMultiRegVars); - LclVarDsc* varDsc = compiler->lvaGetDesc(op1->AsLclVar()->GetLclNum()); - ReturnTypeDesc retTypeDesc; - retTypeDesc.InitializeStructReturnType(compiler, varDsc->GetStructHnd(), - compiler->info.compCallConv); - pRetTypeDesc = &retTypeDesc; + LclVarDsc* varDsc = compiler->lvaGetDesc(op1->AsLclVar()->GetLclNum()); + nonCallRetTypeDesc.InitializeStructReturnType(compiler, varDsc->GetStructHnd(), + compiler->info.compCallConv); + pRetTypeDesc = &nonCallRetTypeDesc; assert(compiler->lvaGetDesc(op1->AsLclVar()->GetLclNum())->lvFieldCnt == - retTypeDesc.GetReturnRegCount()); + nonCallRetTypeDesc.GetReturnRegCount()); } srcCount = pRetTypeDesc->GetReturnRegCount(); // For any source that's coming from a different register file, we need to ensure that diff --git a/src/coreclr/vm/threadstatics.cpp b/src/coreclr/vm/threadstatics.cpp index 2644d7ad5fc9..bac0f082fb8e 100644 --- a/src/coreclr/vm/threadstatics.cpp +++ b/src/coreclr/vm/threadstatics.cpp @@ -97,7 +97,7 @@ void ThreadLocalBlock::FreeTable() SpinLock::Holder lock(&m_TLMTableLock); // Free the table itself - delete m_pTLMTable; + delete[] m_pTLMTable; m_pTLMTable = NULL; } @@ -136,7 +136,7 @@ void ThreadLocalBlock::EnsureModuleIndex(ModuleIndex index) // If this allocation fails, we will throw. If it succeeds, // then we are good to go - PTR_TLMTableEntry pNewModuleSlots = (PTR_TLMTableEntry) (void*) new BYTE[sizeof(TLMTableEntry) * aModuleIndices]; + PTR_TLMTableEntry pNewModuleSlots = new TLMTableEntry[aModuleIndices]; // Zero out the new TLM table memset(pNewModuleSlots, 0 , sizeof(TLMTableEntry) * aModuleIndices); @@ -704,9 +704,7 @@ PTR_ThreadLocalModule ThreadStatics::AllocateTLM(Module * pModule) SIZE_T size = pModule->GetThreadLocalModuleSize(); - _ASSERTE(size >= ThreadLocalModule::OffsetOfDataBlob()); - - PTR_ThreadLocalModule pThreadLocalModule = (ThreadLocalModule*)new BYTE[size]; + PTR_ThreadLocalModule pThreadLocalModule = new({ pModule }) ThreadLocalModule; // We guarantee alignment for 64-bit regular thread statics on 32-bit platforms even without FEATURE_64BIT_ALIGNMENT for performance reasons. diff --git a/src/coreclr/vm/threadstatics.h b/src/coreclr/vm/threadstatics.h index 1755bf7230d2..ddb59b5cbc20 100644 --- a/src/coreclr/vm/threadstatics.h +++ b/src/coreclr/vm/threadstatics.h @@ -450,6 +450,20 @@ struct ThreadLocalModule return GetPrecomputedStaticsClassData()[classID] & ClassInitFlags::INITIALIZED_FLAG; } + void* operator new(size_t) = delete; + + struct ParentModule { PTR_Module pModule; }; + + void* operator new(size_t baseSize, ParentModule parentModule) + { + size_t size = parentModule.pModule->GetThreadLocalModuleSize(); + + _ASSERTE(size >= baseSize); + _ASSERTE(size >= ThreadLocalModule::OffsetOfDataBlob()); + + return ::operator new(size); + } + #ifndef DACCESS_COMPILE FORCEINLINE void EnsureClassAllocated(MethodTable * pMT) From f787f38f4b75982baf9091c08eb6e71ed7e3f0c1 Mon Sep 17 00:00:00 2001 From: Levi Broderick Date: Mon, 12 Jul 2021 16:14:26 -0700 Subject: [PATCH 036/133] Improve perf of Utf8Parser.TryParse(out [u]long, default) (#52423) --- .../Utf8Parser/Utf8Parser.Integer.Signed.D.cs | 160 ++++++++++-------- .../Utf8Parser.Integer.Unsigned.D.cs | 104 ++++++------ 2 files changed, 142 insertions(+), 122 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.D.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.D.cs index ee2e046901fe..07723d04810f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.D.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.D.cs @@ -336,107 +336,121 @@ private static bool TryParseInt32D(ReadOnlySpan source, out int value, out private static bool TryParseInt64D(ReadOnlySpan source, out long value, out int bytesConsumed) { - if (source.Length < 1) - { - bytesConsumed = 0; - value = default; - return false; - } + long sign = 0; // 0 if the value is positive, -1 if the value is negative + int idx = 0; - int indexOfFirstDigit = 0; - int sign = 1; - if (source[0] == '-') + // We use 'nuint' for the firstChar and nextChar data types in this method because + // it gives us a free early zero-extension to 64 bits when running on a 64-bit platform. + + nuint firstChar; + while (true) { - indexOfFirstDigit = 1; - sign = -1; + if ((uint)idx >= (uint)source.Length) { goto FalseExit; } + firstChar = (uint)source[idx] - '0'; + if ((uint)firstChar <= 9) { break; } - if (source.Length <= indexOfFirstDigit) + // We saw something that wasn't a digit. If it's a '+' or a '-', + // we'll set the 'sign' value appropriately and resume the "read + // first char" loop from the next index. If this loops more than + // once (idx != 0), it means we saw a sign character followed by + // a non-digit character, which should be considered an error. + + if (idx != 0) { - bytesConsumed = 0; - value = default; - return false; + goto FalseExit; } - } - else if (source[0] == '+') - { - indexOfFirstDigit = 1; - if (source.Length <= indexOfFirstDigit) + idx++; + + if ((uint)firstChar == unchecked((uint)('-' - '0'))) + { + sign--; // set to -1 + } + else if ((uint)firstChar != unchecked((uint)('+' - '0'))) { - bytesConsumed = 0; - value = default; - return false; + goto FalseExit; // not a digit, not '-', and not '+'; fail } } - int overflowLength = ParserHelpers.Int64OverflowLength + indexOfFirstDigit; + ulong parsedValue = firstChar; + int overflowLength = ParserHelpers.Int64OverflowLength + idx; // +idx to account for any sign char we read + idx++; - // Parse the first digit separately. If invalid here, we need to return false. - long firstDigit = source[indexOfFirstDigit] - 48; // '0' - if (firstDigit < 0 || firstDigit > 9) - { - bytesConsumed = 0; - value = default; - return false; - } - ulong parsedValue = (ulong)firstDigit; + // At this point, we successfully read a single digit character. + // The only failure condition from here on out is integer overflow. if (source.Length < overflowLength) { - // Length is less than Parsers.Int64OverflowLength; overflow is not possible - for (int index = indexOfFirstDigit + 1; index < source.Length; index++) + // If the input span is short enough such that integer overflow isn't an issue, + // don't bother performing overflow checks. Just keep shifting in new digits + // until we see a non-digit character or until we've exhausted our input buffer. + + while (true) { - long nextDigit = source[index] - 48; // '0' - if (nextDigit < 0 || nextDigit > 9) - { - bytesConsumed = index; - value = ((long)parsedValue) * sign; - return true; - } - parsedValue = parsedValue * 10 + (ulong)nextDigit; + if ((uint)idx >= (uint)source.Length) { break; } // EOF + nuint nextChar = (uint)source[idx] - '0'; + if ((uint)nextChar > 9) { break; } // not a digit + parsedValue = parsedValue * 10 + nextChar; + idx++; } } else { - // Length is greater than Parsers.Int64OverflowLength; overflow is only possible after Parsers.Int64OverflowLength - // digits. There may be no overflow after Parsers.Int64OverflowLength if there are leading zeroes. - for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) - { - long nextDigit = source[index] - 48; // '0' - if (nextDigit < 0 || nextDigit > 9) - { - bytesConsumed = index; - value = ((long)parsedValue) * sign; - return true; - } - parsedValue = parsedValue * 10 + (ulong)nextDigit; - } - for (int index = overflowLength - 1; index < source.Length; index++) + while (true) { - long nextDigit = source[index] - 48; // '0' - if (nextDigit < 0 || nextDigit > 9) + if ((uint)idx >= (uint)source.Length) { break; } // EOF + nuint nextChar = (uint)source[idx] - '0'; + if ((uint)nextChar > 9) { break; } // not a digit + idx++; + + // The const below is the smallest unsigned x for which "x * 10 + 9" + // might overflow long.MaxValue. If the current accumulator is below + // this const, there's no risk of overflowing. + + const ulong OverflowRisk = 0x0CCC_CCCC_CCCC_CCCCul; + + if (parsedValue < OverflowRisk) { - bytesConsumed = index; - value = ((long)parsedValue) * sign; - return true; + parsedValue = parsedValue * 10 + nextChar; + continue; } - // If parsedValue > (long.MaxValue / 10), any more appended digits will cause overflow. - // if parsedValue == (long.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. - bool positive = sign > 0; - bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); - if (parsedValue > long.MaxValue / 10 || parsedValue == long.MaxValue / 10 && nextDigitTooLarge) + + // If the current accumulator is exactly equal to the const above, + // then "accumulator * 10 + 7" is the highest we can go without overflowing + // long.MaxValue. (If we know the value is negative, we can instead allow + // +8, since the range of negative numbers is one higher than the range of + // positive numbers.) This also implies that if the current accumulator + // is higher than the const above, there's no hope that we'll succeed, + // so we may as well just fail now. + // + // The (nextChar + sign) trick below works because sign is 0 or -1, + // so if sign is -1 then this actually checks that nextChar > 8. + // n.b. signed arithmetic below because nextChar may be 0. + + if (parsedValue != OverflowRisk || (int)nextChar + (int)sign > 7) { - bytesConsumed = 0; - value = default; - return false; + goto FalseExit; } - parsedValue = parsedValue * 10 + (ulong)nextDigit; + + parsedValue = OverflowRisk * 10 + nextChar; } } - bytesConsumed = source.Length; - value = ((long)parsedValue) * sign; + // 'sign' is 0 for non-negative and -1 for negative. This allows us to perform + // cheap arithmetic + bitwise operations to mimic a multiplication by 1 or -1 + // without incurring the cost of an actual multiplication operation. + // + // If sign = 0, this becomes value = (parsedValue ^ 0) - 0 = parsedValue + // If sign = -1, this becomes value = (parsedValue ^ -1) - (-1) = ~parsedValue + 1 = -parsedValue + + bytesConsumed = idx; + value = ((long)parsedValue ^ sign) - sign; return true; + + FalseExit: + bytesConsumed = 0; + value = default; + return false; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.D.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.D.cs index 0b9cca720b42..dd4572afaf7f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.D.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.D.cs @@ -277,77 +277,83 @@ private static bool TryParseUInt32D(ReadOnlySpan source, out uint value, o private static bool TryParseUInt64D(ReadOnlySpan source, out ulong value, out int bytesConsumed) { - if (source.Length < 1) + if (source.IsEmpty) { - bytesConsumed = 0; - value = default; - return false; + goto FalseExit; } + // We use 'nuint' for the firstDigit and nextChar data types in this method because + // it gives us a free early zero-extension to 64 bits when running on a 64-bit platform. + // // Parse the first digit separately. If invalid here, we need to return false. - ulong firstDigit = source[0] - 48u; // '0' - if (firstDigit > 9) - { - bytesConsumed = 0; - value = default; - return false; - } + + nuint firstDigit = (uint)source[0] - '0'; + if ((uint)firstDigit > 9) { goto FalseExit; } ulong parsedValue = firstDigit; - if (source.Length < ParserHelpers.Int64OverflowLength) + // At this point, we successfully read a single digit character. + // The only failure condition from here on out is integer overflow. + + int idx = 1; + if (source.Length < ParserHelpers.UInt64OverflowLength) { - // Length is less than Parsers.Int64OverflowLength; overflow is not possible - for (int index = 1; index < source.Length; index++) + // If the input span is short enough such that integer overflow isn't an issue, + // don't bother performing overflow checks. Just keep shifting in new digits + // until we see a non-digit character or until we've exhausted our input buffer. + + while (true) { - ulong nextDigit = source[index] - 48u; // '0' - if (nextDigit > 9) - { - bytesConsumed = index; - value = parsedValue; - return true; - } - parsedValue = parsedValue * 10 + nextDigit; + if ((uint)idx >= (uint)source.Length) { break; } // EOF + nuint nextChar = (uint)source[idx] - '0'; + if ((uint)nextChar > 9) { break; } // not a digit + parsedValue = parsedValue * 10 + nextChar; + idx++; } } else { - // Length is greater than Parsers.Int64OverflowLength; overflow is only possible after Parsers.Int64OverflowLength - // digits. There may be no overflow after Parsers.Int64OverflowLength if there are leading zeroes. - for (int index = 1; index < ParserHelpers.Int64OverflowLength - 1; index++) + while (true) { - ulong nextDigit = source[index] - 48u; // '0' - if (nextDigit > 9) - { - bytesConsumed = index; - value = parsedValue; - return true; - } - parsedValue = parsedValue * 10 + nextDigit; - } - for (int index = ParserHelpers.Int64OverflowLength - 1; index < source.Length; index++) - { - ulong nextDigit = source[index] - 48u; // '0' - if (nextDigit > 9) + if ((uint)idx >= (uint)source.Length) { break; } // EOF + nuint nextChar = (uint)source[idx] - '0'; + if ((uint)nextChar > 9) { break; } // not a digit + idx++; + + // The const below is the smallest unsigned x for which "x * 10 + 9" + // might overflow ulong.MaxValue. If the current accumulator is below + // this const, there's no risk of overflowing. + + const ulong OverflowRisk = 0x1999_9999_9999_9999ul; + + if (parsedValue < OverflowRisk) { - bytesConsumed = index; - value = parsedValue; - return true; + parsedValue = parsedValue * 10 + nextChar; + continue; } - // If parsedValue > (ulong.MaxValue / 10), any more appended digits will cause overflow. - // if parsedValue == (ulong.MaxValue / 10), any nextDigit greater than 5 implies overflow. - if (parsedValue > ulong.MaxValue / 10 || (parsedValue == ulong.MaxValue / 10 && nextDigit > 5)) + + // If the current accumulator is exactly equal to the const above, + // then "accumulator * 10 + 5" is the highest we can go without overflowing + // ulong.MaxValue. This also implies that if the current accumulator + // is higher than the const above, there's no hope that we'll succeed, + // so we may as well just fail now. + + if (parsedValue != OverflowRisk || (uint)nextChar > 5) { - bytesConsumed = 0; - value = default; - return false; + goto FalseExit; } - parsedValue = parsedValue * 10 + nextDigit; + + parsedValue = OverflowRisk * 10 + nextChar; } } - bytesConsumed = source.Length; + bytesConsumed = idx; value = parsedValue; return true; + + FalseExit: + bytesConsumed = 0; + value = default; + return false; } } } From da7bce76053ebd5c5860b838c6b2b9d83471d6ca Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 12 Jul 2021 19:39:21 -0400 Subject: [PATCH 037/133] Change RandomAccess.Write* methods to be void-returning (#55490) --- .../tests/RandomAccess/Mixed.Windows.cs | 22 +- .../tests/RandomAccess/NoBuffering.Windows.cs | 26 +- .../tests/RandomAccess/Write.cs | 18 +- .../tests/RandomAccess/WriteAsync.cs | 15 +- .../tests/RandomAccess/WriteGather.cs | 18 +- .../tests/RandomAccess/WriteGatherAsync.cs | 17 +- ...andle.OverlappedValueTaskSource.Windows.cs | 6 +- ...afeFileHandle.ThreadPoolValueTaskSource.cs | 29 +-- .../src/System/IO/RandomAccess.Unix.cs | 133 ++++++++--- .../src/System/IO/RandomAccess.Windows.cs | 224 ++++++++++-------- .../src/System/IO/RandomAccess.cs | 27 +-- .../IO/Strategies/OSFileStreamStrategy.cs | 13 +- .../IO/Strategies/UnixFileStreamStrategy.cs | 9 +- .../src/System/Threading/Tasks/ValueTask.cs | 25 -- .../System.Runtime/ref/System.Runtime.cs | 8 +- 15 files changed, 346 insertions(+), 244 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/Mixed.Windows.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/Mixed.Windows.cs index dcc7d47924f6..cee09fe2d62b 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/Mixed.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/Mixed.Windows.cs @@ -62,8 +62,17 @@ static async Task Validate(SafeFileHandle handle, FileOptions options, bool[] sy { writeBuffer[0] = (byte)fileOffset; - Assert.Equal(writeBuffer.Length, syncWrite ? RandomAccess.Write(handle, writeBuffer, fileOffset) : await RandomAccess.WriteAsync(handle, writeBuffer, fileOffset)); + if (syncWrite) + { + RandomAccess.Write(handle, writeBuffer, fileOffset); + } + else + { + await RandomAccess.WriteAsync(handle, writeBuffer, fileOffset); + } + Assert.Equal(writeBuffer.Length, syncRead ? RandomAccess.Read(handle, readBuffer, fileOffset) : await RandomAccess.ReadAsync(handle, readBuffer, fileOffset)); + Assert.Equal(writeBuffer[0], readBuffer[0]); fileOffset += 1; @@ -116,8 +125,17 @@ static async Task Validate(SafeFileHandle handle, FileOptions options, bool[] sy writeBuffer_1[0] = (byte)fileOffset; writeBuffer_2[0] = (byte)(fileOffset+1); - Assert.Equal(writeBuffer_1.Length + writeBuffer_2.Length, syncWrite ? RandomAccess.Write(handle, writeBuffers, fileOffset) : await RandomAccess.WriteAsync(handle, writeBuffers, fileOffset)); + if (syncWrite) + { + RandomAccess.Write(handle, writeBuffers, fileOffset); + } + else + { + await RandomAccess.WriteAsync(handle, writeBuffers, fileOffset); + } + Assert.Equal(writeBuffer_1.Length + writeBuffer_2.Length, syncRead ? RandomAccess.Read(handle, readBuffers, fileOffset) : await RandomAccess.ReadAsync(handle, readBuffers, fileOffset)); + Assert.Equal(writeBuffer_1[0], readBuffer_1[0]); Assert.Equal(writeBuffer_2[0], readBuffer_2[0]); diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/NoBuffering.Windows.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/NoBuffering.Windows.cs index 585335c4cdcb..d897cc7b8be2 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/NoBuffering.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/NoBuffering.Windows.cs @@ -118,9 +118,16 @@ public async Task WriteUsingSingleBuffer(bool async) int take = Math.Min(content.Length - total, bufferSize); content.AsSpan(total, take).CopyTo(buffer.GetSpan()); - total += async - ? await RandomAccess.WriteAsync(handle, buffer.Memory, fileOffset: total) - : RandomAccess.Write(handle, buffer.GetSpan(), fileOffset: total); + if (async) + { + await RandomAccess.WriteAsync(handle, buffer.Memory, fileOffset: total); + } + else + { + RandomAccess.Write(handle, buffer.GetSpan(), fileOffset: total); + } + + total += buffer.Memory.Length; } } @@ -154,9 +161,16 @@ public async Task WriteAsyncUsingMultipleBuffers(bool async) content.AsSpan((int)total, bufferSize).CopyTo(buffer_1.GetSpan()); content.AsSpan((int)total + bufferSize, bufferSize).CopyTo(buffer_2.GetSpan()); - total += async - ? await RandomAccess.WriteAsync(handle, buffers, fileOffset: total) - : RandomAccess.Write(handle, buffers, fileOffset: total); + if (async) + { + await RandomAccess.WriteAsync(handle, buffers, fileOffset: total); + } + else + { + RandomAccess.Write(handle, buffers, fileOffset: total); + } + + total += buffer_1.Memory.Length + buffer_2.Memory.Length; } } diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/Write.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/Write.cs index 3cac633f53f7..8a687dc6012d 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/Write.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/Write.cs @@ -10,7 +10,10 @@ namespace System.IO.Tests public class RandomAccess_Write : RandomAccess_Base { protected override int MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset) - => RandomAccess.Write(handle, bytes, fileOffset); + { + RandomAccess.Write(handle, bytes, fileOffset); + return bytes?.Length ?? 0; + } [Theory] [MemberData(nameof(GetSyncAsyncOptions))] @@ -24,11 +27,11 @@ public void ThrowsOnReadAccess(FileOptions options) [Theory] [MemberData(nameof(GetSyncAsyncOptions))] - public void WriteUsingEmptyBufferReturnsZero(FileOptions options) + public void WriteUsingEmptyBufferReturns(FileOptions options) { using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write, options: options)) { - Assert.Equal(0, RandomAccess.Write(handle, Array.Empty(), fileOffset: 0)); + RandomAccess.Write(handle, Array.Empty(), fileOffset: 0); } } @@ -41,7 +44,7 @@ public void CanUseStackAllocatedMemory(FileOptions options) using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Create, FileAccess.Write, options: options)) { - Assert.Equal(stackAllocated.Length, RandomAccess.Write(handle, stackAllocated, fileOffset: 0)); + RandomAccess.Write(handle, stackAllocated, fileOffset: 0); } Assert.Equal(stackAllocated.ToArray(), File.ReadAllBytes(filePath)); @@ -58,17 +61,14 @@ public void WritesBytesFromGivenBufferToGivenFileAtGivenOffset(FileOptions optio using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, options)) { int total = 0; - int current = 0; while (total != fileSize) { Span buffer = content.AsSpan(total, Math.Min(content.Length - total, fileSize / 4)); - current = RandomAccess.Write(handle, buffer, fileOffset: total); - - Assert.InRange(current, 0, buffer.Length); + RandomAccess.Write(handle, buffer, fileOffset: total); - total += current; + total += buffer.Length; } } diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteAsync.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteAsync.cs index b5c2399f9642..370032839820 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteAsync.cs @@ -11,9 +11,9 @@ namespace System.IO.Tests { [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")] - public class RandomAccess_WriteAsync : RandomAccess_Base> + public class RandomAccess_WriteAsync : RandomAccess_Base { - protected override ValueTask MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset) + protected override ValueTask MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset) => RandomAccess.WriteAsync(handle, bytes, fileOffset); [Theory] @@ -44,11 +44,11 @@ public async Task ThrowsOnReadAccess(FileOptions options) [Theory] [MemberData(nameof(GetSyncAsyncOptions))] - public async Task WriteUsingEmptyBufferReturnsZeroAsync(FileOptions options) + public async Task WriteUsingEmptyBufferReturnsAsync(FileOptions options) { using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write, options: options)) { - Assert.Equal(0, await RandomAccess.WriteAsync(handle, Array.Empty(), fileOffset: 0)); + await RandomAccess.WriteAsync(handle, Array.Empty(), fileOffset: 0); } } @@ -63,17 +63,14 @@ public async Task WritesBytesFromGivenBufferToGivenFileAtGivenOffsetAsync(FileOp using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, options)) { int total = 0; - int current = 0; while (total != fileSize) { Memory buffer = content.AsMemory(total, Math.Min(content.Length - total, fileSize / 4)); - current = await RandomAccess.WriteAsync(handle, buffer, fileOffset: total); + await RandomAccess.WriteAsync(handle, buffer, fileOffset: total); - Assert.InRange(current, 0, buffer.Length); - - total += current; + total += buffer.Length; } } diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGather.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGather.cs index 5af52f0c61a1..0f28b7c9c68b 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGather.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGather.cs @@ -12,7 +12,10 @@ namespace System.IO.Tests public class RandomAccess_WriteGather : RandomAccess_Base { protected override long MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset) - => RandomAccess.Write(handle, new ReadOnlyMemory[] { bytes }, fileOffset); + { + RandomAccess.Write(handle, new ReadOnlyMemory[] { bytes }, fileOffset); + return bytes?.Length ?? 0; + } [Theory] [MemberData(nameof(GetSyncAsyncOptions))] @@ -36,11 +39,11 @@ public void ThrowsOnReadAccess(FileOptions options) [Theory] [MemberData(nameof(GetSyncAsyncOptions))] - public void WriteUsingEmptyBufferReturnsZero(FileOptions options) + public void WriteUsingEmptyBufferReturns(FileOptions options) { using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write, options: options)) { - Assert.Equal(0, RandomAccess.Write(handle, new ReadOnlyMemory[] { Array.Empty() }, fileOffset: 0)); + RandomAccess.Write(handle, new ReadOnlyMemory[] { Array.Empty() }, fileOffset: 0); } } @@ -55,7 +58,6 @@ public void WritesBytesFromGivenBuffersToGivenFileAtGivenOffset(FileOptions opti using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, options)) { long total = 0; - long current = 0; while (total != fileSize) { @@ -63,7 +65,7 @@ public void WritesBytesFromGivenBuffersToGivenFileAtGivenOffset(FileOptions opti Memory buffer_1 = content.AsMemory((int)total, firstBufferLength); Memory buffer_2 = content.AsMemory((int)total + firstBufferLength); - current = RandomAccess.Write( + RandomAccess.Write( handle, new ReadOnlyMemory[] { @@ -73,9 +75,7 @@ public void WritesBytesFromGivenBuffersToGivenFileAtGivenOffset(FileOptions opti }, fileOffset: total); - Assert.InRange(current, 0, buffer_1.Length + buffer_2.Length); - - total += current; + total += buffer_1.Length + buffer_2.Length; } } @@ -94,7 +94,7 @@ public void DuplicatedBufferDuplicatesContent(FileOptions options) using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Create, FileAccess.Write, options: options)) { - Assert.Equal(repeatCount, RandomAccess.Write(handle, buffers, fileOffset: 0)); + RandomAccess.Write(handle, buffers, fileOffset: 0); } byte[] actualContent = File.ReadAllBytes(filePath); diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGatherAsync.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGatherAsync.cs index d6bd235efb74..f89cfd3b4fc6 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGatherAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGatherAsync.cs @@ -13,9 +13,9 @@ namespace System.IO.Tests { [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")] - public class RandomAccess_WriteGatherAsync : RandomAccess_Base> + public class RandomAccess_WriteGatherAsync : RandomAccess_Base { - protected override ValueTask MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset) + protected override ValueTask MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset) => RandomAccess.WriteAsync(handle, new ReadOnlyMemory[] { bytes }, fileOffset); [Theory] @@ -56,11 +56,11 @@ public async Task ThrowsOnReadAccess(FileOptions options) [Theory] [MemberData(nameof(GetSyncAsyncOptions))] - public async Task WriteUsingEmptyBufferReturnsZeroAsync(FileOptions options) + public async Task WriteUsingEmptyBufferReturnsAsync(FileOptions options) { using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write, options: options)) { - Assert.Equal(0, await RandomAccess.WriteAsync(handle, new ReadOnlyMemory[] { Array.Empty() }, fileOffset: 0)); + await RandomAccess.WriteAsync(handle, new ReadOnlyMemory[] { Array.Empty() }, fileOffset: 0); } } @@ -75,7 +75,6 @@ public async Task WritesBytesFromGivenBufferToGivenFileAtGivenOffsetAsync(FileOp using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, options)) { long total = 0; - long current = 0; while (total != fileSize) { @@ -83,7 +82,7 @@ public async Task WritesBytesFromGivenBufferToGivenFileAtGivenOffsetAsync(FileOp Memory buffer_1 = content.AsMemory((int)total, firstBufferLength); Memory buffer_2 = content.AsMemory((int)total + firstBufferLength); - current = await RandomAccess.WriteAsync( + await RandomAccess.WriteAsync( handle, new ReadOnlyMemory[] { @@ -93,9 +92,7 @@ public async Task WritesBytesFromGivenBufferToGivenFileAtGivenOffsetAsync(FileOp }, fileOffset: total); - Assert.InRange(current, 0, buffer_1.Length + buffer_2.Length); - - total += current; + total += buffer_1.Length + buffer_2.Length; } } @@ -114,7 +111,7 @@ public async Task DuplicatedBufferDuplicatesContentAsync(FileOptions options) using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Create, FileAccess.Write, options: options)) { - Assert.Equal(repeatCount, await RandomAccess.WriteAsync(handle, buffers, fileOffset: 0)); + await RandomAccess.WriteAsync(handle, buffers, fileOffset: 0); } byte[] actualContent = File.ReadAllBytes(filePath); diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs index cd4e18e7486d..367184e9a455 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs @@ -86,10 +86,8 @@ internal static Exception GetIOError(int errorCode, string? path) public ValueTaskSourceStatus GetStatus(short token) => _source.GetStatus(token); public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => _source.OnCompleted(continuation, state, token, flags); - void IValueTaskSource.GetResult(short token) => GetResultAndRelease(token); - int IValueTaskSource.GetResult(short token) => GetResultAndRelease(token); - - private int GetResultAndRelease(short token) + void IValueTaskSource.GetResult(short token) => GetResult(token); + public int GetResult(short token) { try { diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.ThreadPoolValueTaskSource.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.ThreadPoolValueTaskSource.cs index 49638af80f34..abb6238b5213 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.ThreadPoolValueTaskSource.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.ThreadPoolValueTaskSource.cs @@ -54,7 +54,15 @@ private void ValidateInvariants() Debug.Assert(op == Operation.None, $"An operation was queued before the previous {op}'s completion."); } - private long GetResultAndRelease(short token) + public ValueTaskSourceStatus GetStatus(short token) => + _source.GetStatus(token); + + public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => + _source.OnCompleted(continuation, state, token, flags); + + void IValueTaskSource.GetResult(short token) => GetResult(token); + int IValueTaskSource.GetResult(short token) => (int)GetResult(token); + public long GetResult(short token) { try { @@ -67,13 +75,6 @@ private long GetResultAndRelease(short token) } } - public ValueTaskSourceStatus GetStatus(short token) => _source.GetStatus(token); - public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => - _source.OnCompleted(continuation, state, token, flags); - int IValueTaskSource.GetResult(short token) => (int) GetResultAndRelease(token); - long IValueTaskSource.GetResult(short token) => GetResultAndRelease(token); - void IValueTaskSource.GetResult(short token) => GetResultAndRelease(token); - private void ExecuteInternal() { Debug.Assert(_operation >= Operation.Read && _operation <= Operation.WriteGather); @@ -96,7 +97,7 @@ private void ExecuteInternal() result = RandomAccess.ReadAtOffset(_fileHandle, writableSingleSegment.Span, _fileOffset); break; case Operation.Write: - result = RandomAccess.WriteAtOffset(_fileHandle, _singleSegment.Span, _fileOffset); + RandomAccess.WriteAtOffset(_fileHandle, _singleSegment.Span, _fileOffset); break; case Operation.ReadScatter: Debug.Assert(_readScatterBuffers != null); @@ -104,7 +105,7 @@ private void ExecuteInternal() break; case Operation.WriteGather: Debug.Assert(_writeGatherBuffers != null); - result = RandomAccess.WriteGatherAtOffset(_fileHandle, _writeGatherBuffers, _fileOffset); + RandomAccess.WriteGatherAtOffset(_fileHandle, _writeGatherBuffers, _fileOffset); break; } } @@ -164,7 +165,7 @@ public ValueTask QueueRead(Memory buffer, long fileOffset, Cancellati return new ValueTask(this, _source.Version); } - public ValueTask QueueWrite(ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) + public ValueTask QueueWrite(ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) { ValidateInvariants(); @@ -174,7 +175,7 @@ public ValueTask QueueWrite(ReadOnlyMemory buffer, long fileOffset, C _cancellationToken = cancellationToken; QueueToThreadPool(); - return new ValueTask(this, _source.Version); + return new ValueTask(this, _source.Version); } public ValueTask QueueReadScatter(IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) @@ -190,7 +191,7 @@ public ValueTask QueueReadScatter(IReadOnlyList> buffers, lon return new ValueTask(this, _source.Version); } - public ValueTask QueueWriteGather(IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) + public ValueTask QueueWriteGather(IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) { ValidateInvariants(); @@ -200,7 +201,7 @@ public ValueTask QueueWriteGather(IReadOnlyList> buff _cancellationToken = cancellationToken; QueueToThreadPool(); - return new ValueTask(this, _source.Version); + return new ValueTask(this, _source.Version); } private enum Operation : byte diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs index 41de56fcbea7..b82627112f35 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs @@ -3,7 +3,9 @@ using System.Buffers; using System.Collections.Generic; +using System.Diagnostics; using System.IO.Strategies; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -79,41 +81,116 @@ private static ValueTask ReadScatterAtOffsetAsync(SafeFileHandle handle, I long fileOffset, CancellationToken cancellationToken) => ScheduleSyncReadScatterAtOffsetAsync(handle, buffers, fileOffset, cancellationToken); - internal static unsafe int WriteAtOffset(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) + internal static unsafe void WriteAtOffset(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) { - fixed (byte* bufPtr = &MemoryMarshal.GetReference(buffer)) + while (!buffer.IsEmpty) { - // The Windows implementation uses WriteFile, which ignores the offset if the handle - // isn't seekable. We do the same manually with PWrite vs Write, in order to enable - // the function to be used by FileStream for all the same situations. - int result = handle.CanSeek ? - Interop.Sys.PWrite(handle, bufPtr, buffer.Length, fileOffset) : - Interop.Sys.Write(handle, bufPtr, buffer.Length); - FileStreamHelpers.CheckFileCall(result, handle.Path); - return result; + fixed (byte* bufPtr = &MemoryMarshal.GetReference(buffer)) + { + // The Windows implementation uses WriteFile, which ignores the offset if the handle + // isn't seekable. We do the same manually with PWrite vs Write, in order to enable + // the function to be used by FileStream for all the same situations. + int bytesWritten = handle.CanSeek ? + Interop.Sys.PWrite(handle, bufPtr, GetNumberOfBytesToWrite(buffer.Length), fileOffset) : + Interop.Sys.Write(handle, bufPtr, GetNumberOfBytesToWrite(buffer.Length)); + + FileStreamHelpers.CheckFileCall(bytesWritten, handle.Path); + if (bytesWritten == buffer.Length) + { + break; + } + + // The write completed successfully but for fewer bytes than requested. + // We need to try again for the remainder. + buffer = buffer.Slice(bytesWritten); + fileOffset += bytesWritten; + } } } - internal static unsafe long WriteGatherAtOffset(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetNumberOfBytesToWrite(int byteCount) { - MemoryHandle[] handles = new MemoryHandle[buffers.Count]; - Span vectors = buffers.Count <= IovStackThreshold ? stackalloc Interop.Sys.IOVector[IovStackThreshold] : new Interop.Sys.IOVector[buffers.Count ]; +#if DEBUG + // In debug only, to assist with testing, simulate writing fewer than the requested number of bytes. + if (byteCount > 1 && // ensure we don't turn the read into a zero-byte read + byteCount < 512) // avoid on larger buffers that might have a length used to meet an alignment requirement + { + byteCount /= 2; + } +#endif + return byteCount; + } - long result; - try + internal static unsafe void WriteGatherAtOffset(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset) + { + int buffersCount = buffers.Count; + if (buffersCount == 0) { - int buffersCount = buffers.Count; - for (int i = 0; i < buffersCount; i++) - { - ReadOnlyMemory buffer = buffers[i]; - MemoryHandle memoryHandle = buffer.Pin(); - vectors[i] = new Interop.Sys.IOVector { Base = (byte*)memoryHandle.Pointer, Count = (UIntPtr)buffer.Length }; - handles[i] = memoryHandle; - } + return; + } - fixed (Interop.Sys.IOVector* pinnedVectors = &MemoryMarshal.GetReference(vectors)) + var handles = new MemoryHandle[buffersCount]; + Span vectors = buffersCount <= IovStackThreshold ? + stackalloc Interop.Sys.IOVector[IovStackThreshold] : + new Interop.Sys.IOVector[buffersCount]; + + try + { + int buffersOffset = 0, firstBufferOffset = 0; + while (true) { - result = Interop.Sys.PWriteV(handle, pinnedVectors, buffers.Count, fileOffset); + long totalBytesToWrite = 0; + + for (int i = buffersOffset; i < buffersCount; i++) + { + ReadOnlyMemory buffer = buffers[i]; + totalBytesToWrite += buffer.Length; + + MemoryHandle memoryHandle = buffer.Pin(); + vectors[i] = new Interop.Sys.IOVector { Base = firstBufferOffset + (byte*)memoryHandle.Pointer, Count = (UIntPtr)buffer.Length }; + handles[i] = memoryHandle; + + firstBufferOffset = 0; + } + + if (totalBytesToWrite == 0) + { + break; + } + + long bytesWritten; + fixed (Interop.Sys.IOVector* pinnedVectors = &MemoryMarshal.GetReference(vectors)) + { + bytesWritten = Interop.Sys.PWriteV(handle, pinnedVectors, buffersCount, fileOffset); + } + + FileStreamHelpers.CheckFileCall(bytesWritten, handle.Path); + if (bytesWritten == totalBytesToWrite) + { + break; + } + + // The write completed successfully but for fewer bytes than requested. + // We need to try again for the remainder. + for (int i = 0; i < buffersCount; i++) + { + int n = buffers[i].Length; + if (n <= bytesWritten) + { + buffersOffset++; + bytesWritten -= n; + if (bytesWritten == 0) + { + break; + } + } + else + { + firstBufferOffset = (int)(bytesWritten - n); + break; + } + } } } finally @@ -123,14 +200,12 @@ internal static unsafe long WriteGatherAtOffset(SafeFileHandle handle, IReadOnly memoryHandle.Dispose(); } } - - return FileStreamHelpers.CheckFileCall(result, handle.Path); } - internal static ValueTask WriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) + internal static ValueTask WriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) => ScheduleSyncWriteAtOffsetAsync(handle, buffer, fileOffset, cancellationToken); - private static ValueTask WriteGatherAtOffsetAsync(SafeFileHandle handle, IReadOnlyList> buffers, + private static ValueTask WriteGatherAtOffsetAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) => ScheduleSyncWriteGatherAtOffsetAsync(handle, buffers, fileOffset, cancellationToken); } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs index 63639cc8503a..8711ce23e9d2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs @@ -120,11 +120,17 @@ private static unsafe int ReadSyncUsingAsyncHandle(SafeFileHandle handle, Span buffer, long fileOffset) + internal static unsafe void WriteAtOffset(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) { + if (buffer.IsEmpty) + { + return; + } + if (handle.IsAsync) { - return WriteSyncUsingAsyncHandle(handle, buffer, fileOffset); + WriteSyncUsingAsyncHandle(handle, buffer, fileOffset); + return; } NativeOverlapped overlapped = GetNativeOverlappedForSyncHandle(handle, fileOffset); @@ -132,22 +138,28 @@ internal static unsafe int WriteAtOffset(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) + private static unsafe void WriteSyncUsingAsyncHandle(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) { + if (buffer.IsEmpty) + { + return; + } + handle.EnsureThreadPoolBindingInitialized(); CallbackResetEvent resetEvent = new CallbackResetEvent(handle.ThreadPoolBinding!); @@ -173,8 +185,8 @@ private static unsafe int WriteSyncUsingAsyncHandle(SafeFileHandle handle, ReadO int result = 0; if (Interop.Kernel32.GetOverlappedResult(handle, overlapped, ref result, bWait: false)) { - Debug.Assert(result >= 0 && result <= buffer.Length, $"GetOverlappedResult returned {result} for {buffer.Length} bytes request"); - return result; + Debug.Assert(result == buffer.Length, $"GetOverlappedResult returned {result} for {buffer.Length} bytes request"); + return; } errorCode = FileStreamHelpers.GetLastWin32ErrorAndDisposeHandleIfInvalid(handle); @@ -184,7 +196,7 @@ private static unsafe int WriteSyncUsingAsyncHandle(SafeFileHandle handle, ReadO { case Interop.Errors.ERROR_NO_DATA: // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing. - return 0; + return; case Interop.Errors.ERROR_INVALID_PARAMETER: // ERROR_INVALID_PARAMETER may be returned for writes @@ -209,17 +221,28 @@ private static unsafe int WriteSyncUsingAsyncHandle(SafeFileHandle handle, ReadO } internal static ValueTask ReadAtOffsetAsync(SafeFileHandle handle, Memory buffer, long fileOffset, CancellationToken cancellationToken) - => handle.IsAsync - ? Map(QueueAsyncReadFile(handle, buffer, fileOffset, cancellationToken)) - : ScheduleSyncReadAtOffsetAsync(handle, buffer, fileOffset, cancellationToken); + { + if (handle.IsAsync) + { + (SafeFileHandle.OverlappedValueTaskSource? vts, int errorCode) = QueueAsyncReadFile(handle, buffer, fileOffset, cancellationToken); + + if (vts is not null) + { + return new ValueTask(vts, vts.Version); + } + + if (errorCode == 0) + { + return ValueTask.FromResult(0); + } + + return ValueTask.FromException(Win32Marshal.GetExceptionForWin32Error(errorCode)); + } - private static ValueTask Map((SafeFileHandle.OverlappedValueTaskSource? vts, int errorCode) tuple) - => tuple.vts != null - ? new ValueTask(tuple.vts, tuple.vts.Version) - : tuple.errorCode == 0 ? ValueTask.FromResult(0) : ValueTask.FromException(Win32Marshal.GetExceptionForWin32Error(tuple.errorCode)); + return ScheduleSyncReadAtOffsetAsync(handle, buffer, fileOffset, cancellationToken); + } - internal static unsafe (SafeFileHandle.OverlappedValueTaskSource? vts, int errorCode) QueueAsyncReadFile( - SafeFileHandle handle, Memory buffer, long fileOffset, CancellationToken cancellationToken) + internal static unsafe (SafeFileHandle.OverlappedValueTaskSource? vts, int errorCode) QueueAsyncReadFile(SafeFileHandle handle, Memory buffer, long fileOffset, CancellationToken cancellationToken) { handle.EnsureThreadPoolBindingInitialized(); @@ -269,13 +292,29 @@ internal static unsafe (SafeFileHandle.OverlappedValueTaskSource? vts, int error return (vts, -1); } - internal static ValueTask WriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) - => handle.IsAsync - ? Map(QueueAsyncWriteFile(handle, buffer, fileOffset, cancellationToken)) - : ScheduleSyncWriteAtOffsetAsync(handle, buffer, fileOffset, cancellationToken); + internal static ValueTask WriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) + { + if (handle.IsAsync) + { + (SafeFileHandle.OverlappedValueTaskSource? vts, int errorCode) = QueueAsyncWriteFile(handle, buffer, fileOffset, cancellationToken); - internal static unsafe (SafeFileHandle.OverlappedValueTaskSource? vts, int errorCode) QueueAsyncWriteFile( - SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) + if (vts is not null) + { + return new ValueTask(vts, vts.Version); + } + + if (errorCode == 0) + { + return ValueTask.CompletedTask; + } + + return ValueTask.FromException(Win32Marshal.GetExceptionForWin32Error(errorCode)); + } + + return ScheduleSyncWriteAtOffsetAsync(handle, buffer, fileOffset, cancellationToken); + } + + internal static unsafe (SafeFileHandle.OverlappedValueTaskSource? vts, int errorCode) QueueAsyncWriteFile(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) { handle.EnsureThreadPoolBindingInitialized(); @@ -323,7 +362,8 @@ internal static long ReadScatterAtOffset(SafeFileHandle handle, IReadOnlyList span = buffers[i].Span; int read = ReadAtOffset(handle, span, fileOffset + total); @@ -340,26 +380,17 @@ internal static long ReadScatterAtOffset(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset) + internal static void WriteGatherAtOffset(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset) { - long total = 0; - // WriteFileGather does not support sync handles, so we just call WriteFile in a loop - for (int i = 0; i < buffers.Count; i++) + int bytesWritten = 0; + int buffersCount = buffers.Count; + for (int i = 0; i < buffersCount; i++) { ReadOnlySpan span = buffers[i].Span; - int written = WriteAtOffset(handle, span, fileOffset + total); - total += written; - - // We stop on the first incomplete write. - // Most probably the disk became full and the next write is going to throw. - if (written != span.Length) - { - break; - } + WriteAtOffset(handle, span, fileOffset + bytesWritten); + bytesWritten += span.Length; } - - return total; } private static ValueTask ReadScatterAtOffsetAsync(SafeFileHandle handle, IReadOnlyList> buffers, @@ -373,7 +404,8 @@ private static ValueTask ReadScatterAtOffsetAsync(SafeFileHandle handle, I if (CanUseScatterGatherWindowsAPIs(handle)) { long totalBytes = 0; - for (int i = 0; i < buffers.Count; i++) + int buffersCount = buffers.Count; + for (int i = 0; i < buffersCount; i++) { totalBytes += buffers[i].Length; } @@ -392,25 +424,25 @@ private static ValueTask ReadScatterAtOffsetAsync(SafeFileHandle handle, I private static bool CanUseScatterGatherWindowsAPIs(SafeFileHandle handle) => handle.IsAsync && ((handle.GetFileOptions() & SafeFileHandle.NoBuffering) != 0); - private static async ValueTask ReadScatterAtOffsetSingleSyscallAsync(SafeFileHandle handle, - IReadOnlyList> buffers, long fileOffset, int totalBytes, CancellationToken cancellationToken) + private static async ValueTask ReadScatterAtOffsetSingleSyscallAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, int totalBytes, CancellationToken cancellationToken) { - if (buffers.Count == 1) + int buffersCount = buffers.Count; + if (buffersCount == 1) { // we have to await it because we can't cast a VT to VT return await ReadAtOffsetAsync(handle, buffers[0], fileOffset, cancellationToken).ConfigureAwait(false); } // "The array must contain enough elements to store nNumberOfBytesToWrite bytes of data, and one element for the terminating NULL. " - long[] fileSegments = new long[buffers.Count + 1]; - fileSegments[buffers.Count] = 0; + long[] fileSegments = new long[buffersCount + 1]; + fileSegments[buffersCount] = 0; - MemoryHandle[] memoryHandles = new MemoryHandle[buffers.Count]; + MemoryHandle[] memoryHandles = new MemoryHandle[buffersCount]; MemoryHandle pinnedSegments = fileSegments.AsMemory().Pin(); try { - for (int i = 0; i < buffers.Count; i++) + for (int i = 0; i < buffersCount; i++) { Memory buffer = buffers[i]; MemoryHandle memoryHandle = buffer.Pin(); @@ -434,8 +466,7 @@ private static async ValueTask ReadScatterAtOffsetSingleSyscallAsync(SafeF } } - private static unsafe ValueTask ReadFileScatterAsync(SafeFileHandle handle, MemoryHandle pinnedSegments, - int bytesToRead, long fileOffset, CancellationToken cancellationToken) + private static unsafe ValueTask ReadFileScatterAsync(SafeFileHandle handle, MemoryHandle pinnedSegments, int bytesToRead, long fileOffset, CancellationToken cancellationToken) { handle.EnsureThreadPoolBindingInitialized(); @@ -484,12 +515,12 @@ private static unsafe ValueTask ReadFileScatterAsync(SafeFileHandle handle, return new ValueTask(vts, vts.Version); } - private static async ValueTask ReadScatterAtOffsetMultipleSyscallsAsync(SafeFileHandle handle, IReadOnlyList> buffers, - long fileOffset, CancellationToken cancellationToken) + private static async ValueTask ReadScatterAtOffsetMultipleSyscallsAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) { long total = 0; - for (int i = 0; i < buffers.Count; i++) + int buffersCount = buffers.Count; + for (int i = 0; i < buffersCount; i++) { Memory buffer = buffers[i]; int read = await ReadAtOffsetAsync(handle, buffer, fileOffset + total, cancellationToken).ConfigureAwait(false); @@ -504,8 +535,7 @@ private static async ValueTask ReadScatterAtOffsetMultipleSyscallsAsync(Sa return total; } - private static ValueTask WriteGatherAtOffsetAsync(SafeFileHandle handle, IReadOnlyList> buffers, - long fileOffset, CancellationToken cancellationToken) + private static ValueTask WriteGatherAtOffsetAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) { if (!handle.IsAsync) { @@ -529,69 +559,65 @@ private static ValueTask WriteGatherAtOffsetAsync(SafeFileHandle handle, I return WriteGatherAtOffsetMultipleSyscallsAsync(handle, buffers, fileOffset, cancellationToken); } - private static async ValueTask WriteGatherAtOffsetMultipleSyscallsAsync(SafeFileHandle handle, IReadOnlyList> buffers, - long fileOffset, CancellationToken cancellationToken) + private static async ValueTask WriteGatherAtOffsetMultipleSyscallsAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) { - long total = 0; - - for (int i = 0; i < buffers.Count; i++) + long bytesWritten = 0; + int buffersCount = buffers.Count; + for (int i = 0; i < buffersCount; i++) { - ReadOnlyMemory buffer = buffers[i]; - int written = await WriteAtOffsetAsync(handle, buffer, fileOffset + total, cancellationToken).ConfigureAwait(false); - total += written; - - if (written != buffer.Length) - { - break; - } + ReadOnlyMemory rom = buffers[i]; + await WriteAtOffsetAsync(handle, rom, fileOffset + bytesWritten, cancellationToken).ConfigureAwait(false); + bytesWritten += rom.Length; } - - return total; } - private static async ValueTask WriteGatherAtOffsetSingleSyscallAsync(SafeFileHandle handle, IReadOnlyList> buffers, - long fileOffset, int totalBytes, CancellationToken cancellationToken) + private static ValueTask WriteGatherAtOffsetSingleSyscallAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, int totalBytes, CancellationToken cancellationToken) { if (buffers.Count == 1) { - return await WriteAtOffsetAsync(handle, buffers[0], fileOffset, cancellationToken).ConfigureAwait(false); + return WriteAtOffsetAsync(handle, buffers[0], fileOffset, cancellationToken); } - // "The array must contain enough elements to store nNumberOfBytesToWrite bytes of data, and one element for the terminating NULL. " - long[] fileSegments = new long[buffers.Count + 1]; - fileSegments[buffers.Count] = 0; - - MemoryHandle[] memoryHandles = new MemoryHandle[buffers.Count]; - MemoryHandle pinnedSegments = fileSegments.AsMemory().Pin(); + return Core(handle, buffers, fileOffset, totalBytes, cancellationToken); - try + static async ValueTask Core(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, int totalBytes, CancellationToken cancellationToken) { - for (int i = 0; i < buffers.Count; i++) - { - ReadOnlyMemory buffer = buffers[i]; - MemoryHandle memoryHandle = buffer.Pin(); - memoryHandles[i] = memoryHandle; + // "The array must contain enough elements to store nNumberOfBytesToWrite bytes of data, and one element for the terminating NULL. " + int buffersCount = buffers.Count; + long[] fileSegments = new long[buffersCount + 1]; + fileSegments[buffersCount] = 0; - unsafe // awaits can't be in an unsafe context + MemoryHandle[] memoryHandles = new MemoryHandle[buffersCount]; + MemoryHandle pinnedSegments = fileSegments.AsMemory().Pin(); + + try + { + for (int i = 0; i < buffersCount; i++) { - fileSegments[i] = new IntPtr(memoryHandle.Pointer).ToInt64(); + ReadOnlyMemory buffer = buffers[i]; + MemoryHandle memoryHandle = buffer.Pin(); + memoryHandles[i] = memoryHandle; + + unsafe // awaits can't be in an unsafe context + { + fileSegments[i] = new IntPtr(memoryHandle.Pointer).ToInt64(); + } } - } - return await WriteFileGatherAsync(handle, pinnedSegments, totalBytes, fileOffset, cancellationToken).ConfigureAwait(false); - } - finally - { - foreach (MemoryHandle memoryHandle in memoryHandles) + await WriteFileGatherAsync(handle, pinnedSegments, totalBytes, fileOffset, cancellationToken).ConfigureAwait(false); + } + finally { - memoryHandle.Dispose(); + foreach (MemoryHandle memoryHandle in memoryHandles) + { + memoryHandle.Dispose(); + } + pinnedSegments.Dispose(); } - pinnedSegments.Dispose(); } } - private static unsafe ValueTask WriteFileGatherAsync(SafeFileHandle handle, MemoryHandle pinnedSegments, - int bytesToWrite, long fileOffset, CancellationToken cancellationToken) + private static unsafe ValueTask WriteFileGatherAsync(SafeFileHandle handle, MemoryHandle pinnedSegments, int bytesToWrite, long fileOffset, CancellationToken cancellationToken) { handle.EnsureThreadPoolBindingInitialized(); @@ -617,8 +643,8 @@ private static unsafe ValueTask WriteFileGatherAsync(SafeFileHandle handle, // Error. Callback will not be invoked. vts.Dispose(); return errorCode == Interop.Errors.ERROR_NO_DATA // EOF on a pipe. IO callback will not be called. - ? ValueTask.FromResult(0) - : ValueTask.FromException(SafeFileHandle.OverlappedValueTaskSource.GetIOError(errorCode, path: null)); + ? ValueTask.CompletedTask + : ValueTask.FromException(SafeFileHandle.OverlappedValueTaskSource.GetIOError(errorCode, path: null)); } } } @@ -630,7 +656,7 @@ private static unsafe ValueTask WriteFileGatherAsync(SafeFileHandle handle, // Completion handled by callback. vts.FinishedScheduling(); - return new ValueTask(vts, vts.Version); + return new ValueTask(vts, vts.Version); } private static unsafe NativeOverlapped* GetNativeOverlappedForAsyncHandle(ThreadPoolBoundHandle threadPoolBinding, long fileOffset, CallbackResetEvent resetEvent) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.cs b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.cs index 4d768cad53ed..ad190fe2f75e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; @@ -134,7 +135,6 @@ public static ValueTask ReadAsync(SafeFileHandle handle, IReadOnlyListThe file handle. /// A region of memory. This method copies the contents of this region to the file. /// The file position to write to. - /// The total number of bytes written into the file. This can be less than the number of bytes provided in the buffer and it's not an error. /// is . /// is invalid. /// The file is closed. @@ -143,11 +143,11 @@ public static ValueTask ReadAsync(SafeFileHandle handle, IReadOnlyList was not opened for writing. /// An I/O error occurred. /// Position of the file is not advanced. - public static int Write(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) + public static void Write(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) { ValidateInput(handle, fileOffset); - return WriteAtOffset(handle, buffer, fileOffset); + WriteAtOffset(handle, buffer, fileOffset); } /// @@ -156,7 +156,6 @@ public static int Write(SafeFileHandle handle, ReadOnlySpan buffer, long f /// The file handle. /// A list of memory buffers. This method copies the contents of these buffers to the file. /// The file position to write to. - /// The total number of bytes written into the file. This can be less than the number of bytes provided in the buffers and it's not an error. /// or is . /// is invalid. /// The file is closed. @@ -165,12 +164,12 @@ public static int Write(SafeFileHandle handle, ReadOnlySpan buffer, long f /// was not opened for writing. /// An I/O error occurred. /// Position of the file is not advanced. - public static long Write(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset) + public static void Write(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset) { ValidateInput(handle, fileOffset); ValidateBuffers(buffers); - return WriteGatherAtOffset(handle, buffers, fileOffset); + WriteGatherAtOffset(handle, buffers, fileOffset); } /// @@ -180,7 +179,7 @@ public static long Write(SafeFileHandle handle, IReadOnlyListA region of memory. This method copies the contents of this region to the file. /// The file position to write to. /// The token to monitor for cancellation requests. The default value is . - /// The total number of bytes written into the file. This can be less than the number of bytes provided in the buffer and it's not an error. + /// A task representing the asynchronous completion of the write operation. /// is . /// is invalid. /// The file is closed. @@ -189,13 +188,13 @@ public static long Write(SafeFileHandle handle, IReadOnlyList was not opened for writing. /// An I/O error occurred. /// Position of the file is not advanced. - public static ValueTask WriteAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken = default) + public static ValueTask WriteAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken = default) { ValidateInput(handle, fileOffset); if (cancellationToken.IsCancellationRequested) { - return ValueTask.FromCanceled(cancellationToken); + return ValueTask.FromCanceled(cancellationToken); } return WriteAtOffsetAsync(handle, buffer, fileOffset, cancellationToken); @@ -208,7 +207,7 @@ public static ValueTask WriteAsync(SafeFileHandle handle, ReadOnlyMemoryA list of memory buffers. This method copies the contents of these buffers to the file. /// The file position to write to. /// The token to monitor for cancellation requests. The default value is . - /// The total number of bytes written into the file. This can be less than the number of bytes provided in the buffers and it's not an error. + /// A task representing the asynchronous completion of the write operation. /// or is . /// is invalid. /// The file is closed. @@ -217,14 +216,14 @@ public static ValueTask WriteAsync(SafeFileHandle handle, ReadOnlyMemory was not opened for writing. /// An I/O error occurred. /// Position of the file is not advanced. - public static ValueTask WriteAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken = default) + public static ValueTask WriteAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken = default) { ValidateInput(handle, fileOffset); ValidateBuffers(buffers); if (cancellationToken.IsCancellationRequested) { - return ValueTask.FromCanceled(cancellationToken); + return ValueTask.FromCanceled(cancellationToken); } return WriteGatherAtOffsetAsync(handle, buffers, fileOffset, cancellationToken); @@ -276,13 +275,13 @@ private static ValueTask ScheduleSyncReadScatterAtOffsetAsync(SafeFileHand return handle.GetThreadPoolValueTaskSource().QueueReadScatter(buffers, fileOffset, cancellationToken); } - private static ValueTask ScheduleSyncWriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory buffer, + private static ValueTask ScheduleSyncWriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) { return handle.GetThreadPoolValueTaskSource().QueueWrite(buffer, fileOffset, cancellationToken); } - private static ValueTask ScheduleSyncWriteGatherAtOffsetAsync(SafeFileHandle handle, IReadOnlyList> buffers, + private static ValueTask ScheduleSyncWriteGatherAtOffsetAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) { return handle.GetThreadPoolValueTaskSource().QueueWriteGather(buffers, fileOffset, cancellationToken); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs index b49b8ce2f33f..7652fc768c1f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs @@ -290,10 +290,17 @@ public sealed override void Write(ReadOnlySpan buffer) ThrowHelper.ThrowNotSupportedException_UnwritableStream(); } - int r = RandomAccess.WriteAtOffset(_fileHandle, buffer, _filePosition); - Debug.Assert(r >= 0, $"RandomAccess.WriteAtOffset returned {r}."); - _filePosition += r; + try + { + RandomAccess.WriteAtOffset(_fileHandle, buffer, _filePosition); + } + catch + { + _length = -1; // invalidate cached length + throw; + } + _filePosition += buffer.Length; UpdateLengthOnChangePosition(); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs index c88665bf0b8d..6f0f9f9562da 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs @@ -58,14 +58,9 @@ public override void EndWrite(IAsyncResult asyncResult) => TaskToApm.End(asyncResult); public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => - WriteAsyncCore(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask(); + WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask(); - public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken) => -#pragma warning disable CA2012 // The analyzer doesn't know the internal AsValueTask is safe. - WriteAsyncCore(source, cancellationToken).AsValueTask(); -#pragma warning restore CA2012 - - private ValueTask WriteAsyncCore(ReadOnlyMemory source, CancellationToken cancellationToken) + public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken) { if (!CanWrite) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs index 1b74aa3a2367..3118072c7572 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs @@ -571,31 +571,6 @@ public Task AsTask() /// Gets a that may be used at any point in the future. public ValueTask Preserve() => _obj == null ? this : new ValueTask(AsTask()); - /// Casts a to . - internal ValueTask AsValueTask() - { - object? obj = _obj; - Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource); - - if (obj is null) - { - return default; - } - - if (obj is Task t) - { - return new ValueTask(t); - } - - if (obj is IValueTaskSource vts) - { - // This assumes the token used with IVTS is the same as used with IVTS. - return new ValueTask(vts, _token); - } - - return new ValueTask(GetTaskForValueTaskSource(Unsafe.As>(obj))); - } - /// Creates a to represent the . /// /// The is passed in rather than reading and casting diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 67e80443421d..4f0c63dd4309 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -10873,10 +10873,10 @@ public static partial class RandomAccess public static long Read(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList> buffers, long fileOffset) { throw null; } public static System.Threading.Tasks.ValueTask ReadAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Memory buffer, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static System.Threading.Tasks.ValueTask ReadAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList> buffers, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public static int Write(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.ReadOnlySpan buffer, long fileOffset) { throw null; } - public static long Write(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList> buffers, long fileOffset) { throw null; } - public static System.Threading.Tasks.ValueTask WriteAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.ReadOnlyMemory buffer, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public static System.Threading.Tasks.ValueTask WriteAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList> buffers, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static void Write(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.ReadOnlySpan buffer, long fileOffset) { throw null; } + public static void Write(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList> buffers, long fileOffset) { throw null; } + public static System.Threading.Tasks.ValueTask WriteAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.ReadOnlyMemory buffer, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.ValueTask WriteAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList> buffers, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } } namespace System.IO.Enumeration From c786e4f4f982b79410f8f6937c8069e2829d220a Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Mon, 12 Jul 2021 21:27:30 -0300 Subject: [PATCH 038/133] [mono][debugger] Support debug after hot-reload (#55220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add test case for debug after hot reload. * Implementing debug after hot reload, on runtime side, and on client side for wasm. * Reuse some code. * Remove unrelated comment. * Reuse code. * Refactor new code. * Fix log. * Apply suggestions from code review Co-authored-by: Aleksey Kliger (λgeek) * Fixing hot reload without debug information. * replacing spaces with tabs. * Fixing CI. Co-authored-by: Aleksey Kliger (λgeek) --- src/mono/mono/component/debugger-agent.c | 58 ++++++- src/mono/mono/component/debugger-agent.h | 2 +- src/mono/mono/component/debugger-protocol.h | 4 +- src/mono/mono/component/debugger-stub.c | 13 +- src/mono/mono/component/debugger.h | 3 + src/mono/mono/component/hot_reload-stub.c | 17 +- src/mono/mono/component/hot_reload.c | 99 +++++++++-- src/mono/mono/component/hot_reload.h | 4 +- src/mono/mono/component/mini-wasm-debugger.c | 11 +- src/mono/mono/metadata/debug-mono-ppdb.c | 162 ++++++++++-------- src/mono/mono/metadata/debug-mono-ppdb.h | 3 + src/mono/mono/metadata/icall.c | 4 +- src/mono/mono/metadata/metadata-internals.h | 2 +- src/mono/mono/metadata/metadata-update.c | 13 +- src/mono/mono/metadata/metadata-update.h | 3 + src/mono/mono/metadata/mono-debug.c | 8 +- src/mono/mono/mini/interp/transform.c | 3 +- .../debugger/BrowserDebugProxy/DebugStore.cs | 103 +++++++++-- .../debugger/BrowserDebugProxy/MonoProxy.cs | 95 +++++++++- .../BrowserDebugProxy/MonoSDBHelper.cs | 21 ++- .../DebuggerTestSuite/BreakpointTests.cs | 58 +++++++ .../DebuggerTestSuite/DebuggerTestBase.cs | 49 ++++++ .../ApplyUpdateReferencedAssembly.csproj | 31 ++++ .../MethodBody1.cs | 34 ++++ .../MethodBody1_v1.cs | 34 ++++ .../MethodBody1_v2.cs | 34 ++++ .../deltascript.json | 7 + .../tests/debugger-test/debugger-test.cs | 75 ++++++++ .../tests/debugger-test/debugger-test.csproj | 28 ++- .../tests/debugger-test/runtime-debugger.js | 6 +- 30 files changed, 852 insertions(+), 132 deletions(-) create mode 100644 src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/ApplyUpdateReferencedAssembly.csproj create mode 100644 src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/MethodBody1.cs create mode 100644 src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/MethodBody1_v1.cs create mode 100644 src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/MethodBody1_v2.cs create mode 100644 src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/deltascript.json diff --git a/src/mono/mono/component/debugger-agent.c b/src/mono/mono/component/debugger-agent.c index f72c95392e97..bec98edb3c2f 100644 --- a/src/mono/mono/component/debugger-agent.c +++ b/src/mono/mono/component/debugger-agent.c @@ -307,6 +307,14 @@ typedef struct { MonoStackHash *hashes; } EventInfo; +typedef struct { + MonoImage *image; + gconstpointer meta_bytes; + int meta_len; + gconstpointer pdb_bytes; + int pdb_len; +} EnCInfo; + #ifdef HOST_WIN32 #define get_last_sock_error() WSAGetLastError() #define MONO_EWOULDBLOCK WSAEWOULDBLOCK @@ -1604,7 +1612,7 @@ static MonoGHashTable *suspended_objs; #ifdef TARGET_WASM void -mono_init_debugger_agent_for_wasm (int log_level_parm) +mono_init_debugger_agent_for_wasm (int log_level_parm, MonoProfilerHandle *prof) { if (mono_atomic_cas_i32 (&agent_inited, 1, 0) == 1) return; @@ -1615,6 +1623,7 @@ mono_init_debugger_agent_for_wasm (int log_level_parm) ids_init(); objrefs = g_hash_table_new_full (NULL, NULL, NULL, mono_debugger_free_objref); obj_to_objref = g_hash_table_new (NULL, NULL); + pending_assembly_loads = g_ptr_array_new (); log_level = log_level_parm; event_requests = g_ptr_array_new (); @@ -1622,6 +1631,8 @@ mono_init_debugger_agent_for_wasm (int log_level_parm) transport = &transports [0]; memset(&debugger_wasm_thread, 0, sizeof(DebuggerTlsData)); agent_config.enabled = TRUE; + + mono_profiler_set_jit_done_callback (*prof, jit_done); } #endif @@ -3600,6 +3611,9 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx case EVENT_KIND_TYPE_LOAD: buffer_add_typeid (&buf, domain, (MonoClass *)arg); break; + case MDBGPROT_EVENT_KIND_METHOD_UPDATE: + buffer_add_methodid (&buf, domain, (MonoMethod *)arg); + break; case EVENT_KIND_BREAKPOINT: case EVENT_KIND_STEP: { GET_DEBUGGER_TLS(); @@ -3655,6 +3669,15 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx case EVENT_KIND_KEEPALIVE: suspend_policy = SUSPEND_POLICY_NONE; break; + + case MDBGPROT_EVENT_KIND_ENC_UPDATE: { + EnCInfo *ei = (EnCInfo *)arg; + buffer_add_moduleid (&buf, mono_domain_get (), ei->image); + m_dbgprot_buffer_add_byte_array (&buf, (uint8_t *) ei->meta_bytes, ei->meta_len); + m_dbgprot_buffer_add_byte_array (&buf, (uint8_t *) ei->pdb_bytes, ei->pdb_len); + break; + } + default: g_assert_not_reached (); } @@ -4060,6 +4083,9 @@ jit_end (MonoProfiler *prof, MonoMethod *method, MonoJitInfo *jinfo) send_type_load (method->klass); + if (m_class_get_image(method->klass)->has_updates) { + process_profiler_event (MDBGPROT_EVENT_KIND_METHOD_UPDATE, method); + } if (jinfo) mono_de_add_pending_breakpoints (method, jinfo); } @@ -6517,6 +6543,28 @@ get_types_for_source_file (gpointer key, gpointer value, gpointer user_data) } } +static void +send_enc_delta (MonoImage *image, gconstpointer dmeta_bytes, int32_t dmeta_len, gconstpointer dpdb_bytes, int32_t dpdb_len) +{ + //TODO: if it came from debugger we don't need to pass the parameters back, they are already on debugger client side. + if (agent_config.enabled) { + int suspend_policy; + GSList *events; + mono_loader_lock (); + events = create_event_list (MDBGPROT_EVENT_KIND_ENC_UPDATE, NULL, NULL, NULL, &suspend_policy); + mono_loader_unlock (); + + EnCInfo info; + info.image = image; + info.meta_bytes = dpdb_bytes; + info.meta_len = dpdb_len; + info.pdb_bytes = dpdb_bytes; + info.pdb_len = dpdb_len; + + process_event (MDBGPROT_EVENT_KIND_ENC_UPDATE, &info, 0, NULL, events, suspend_policy); + } +} + static gboolean module_apply_changes (MonoImage *image, MonoArray *dmeta, MonoArray *dil, MonoArray *dpdb, MonoError *error) { @@ -6525,9 +6573,9 @@ module_apply_changes (MonoImage *image, MonoArray *dmeta, MonoArray *dil, MonoAr int32_t dmeta_len = mono_array_length_internal (dmeta); gpointer dil_bytes = (gpointer)mono_array_addr_internal (dil, char, 0); int32_t dil_len = mono_array_length_internal (dil); - gpointer dpdb_bytes G_GNUC_UNUSED = !dpdb ? NULL : (gpointer)mono_array_addr_internal (dpdb, char, 0); - int32_t dpdb_len G_GNUC_UNUSED = !dpdb ? 0 : mono_array_length_internal (dpdb); - mono_image_load_enc_delta (image, dmeta_bytes, dmeta_len, dil_bytes, dil_len, error); + gpointer dpdb_bytes = !dpdb ? NULL : (gpointer)mono_array_addr_internal (dpdb, char, 0); + int32_t dpdb_len = !dpdb ? 0 : mono_array_length_internal (dpdb); + mono_image_load_enc_delta (image, dmeta_bytes, dmeta_len, dil_bytes, dil_len, dpdb_bytes, dpdb_len, error); return is_ok (error); } @@ -7239,6 +7287,7 @@ event_commands (int command, guint8 *p, guint8 *end, Buffer *buf) req->info = mono_de_set_breakpoint (NULL, METHOD_EXIT_IL_OFFSET, req, NULL); } else if (req->event_kind == EVENT_KIND_EXCEPTION) { } else if (req->event_kind == EVENT_KIND_TYPE_LOAD) { + } else if (req->event_kind == MDBGPROT_EVENT_KIND_METHOD_UPDATE) { } else { if (req->nmodifiers) { g_free (req); @@ -10278,6 +10327,7 @@ debugger_agent_add_function_pointers(MonoComponentDebugger* fn_table) fn_table->debug_log_is_enabled = debugger_agent_debug_log_is_enabled; fn_table->send_crash = mono_debugger_agent_send_crash; fn_table->transport_handshake = debugger_agent_transport_handshake; + fn_table->send_enc_delta = send_enc_delta; } diff --git a/src/mono/mono/component/debugger-agent.h b/src/mono/mono/component/debugger-agent.h index 60f424482024..ad5a0cb1d0b1 100644 --- a/src/mono/mono/component/debugger-agent.h +++ b/src/mono/mono/component/debugger-agent.h @@ -23,7 +23,7 @@ DebuggerTlsData* mono_wasm_get_tls (void); void -mono_init_debugger_agent_for_wasm (int log_level); +mono_init_debugger_agent_for_wasm (int log_level, MonoProfilerHandle *prof); void mono_wasm_save_thread_context (void); diff --git a/src/mono/mono/component/debugger-protocol.h b/src/mono/mono/component/debugger-protocol.h index 24e8a2e42c27..292b723cc351 100644 --- a/src/mono/mono/component/debugger-protocol.h +++ b/src/mono/mono/component/debugger-protocol.h @@ -294,7 +294,9 @@ typedef enum { MDBGPROT_EVENT_KIND_KEEPALIVE = 14, MDBGPROT_EVENT_KIND_USER_BREAK = 15, MDBGPROT_EVENT_KIND_USER_LOG = 16, - MDBGPROT_EVENT_KIND_CRASH = 17 + MDBGPROT_EVENT_KIND_CRASH = 17, + MDBGPROT_EVENT_KIND_ENC_UPDATE = 18, + MDBGPROT_EVENT_KIND_METHOD_UPDATE = 19, } MdbgProtEventKind; typedef enum { diff --git a/src/mono/mono/component/debugger-stub.c b/src/mono/mono/component/debugger-stub.c index 1c5467adaddd..3628c0fbc532 100644 --- a/src/mono/mono/component/debugger-stub.c +++ b/src/mono/mono/component/debugger-stub.c @@ -66,6 +66,9 @@ stub_mono_wasm_breakpoint_hit (void); static void stub_mono_wasm_single_step_hit (void); +static void +stub_send_enc_delta (MonoImage *image, gconstpointer dmeta_bytes, int32_t dmeta_len, gconstpointer dpdb_bytes, int32_t dpdb_len); + static MonoComponentDebugger fn_table = { { MONO_COMPONENT_ITF_VERSION, &debugger_avaliable }, &stub_debugger_init, @@ -87,7 +90,10 @@ static MonoComponentDebugger fn_table = { //wasm &stub_mono_wasm_breakpoint_hit, - &stub_mono_wasm_single_step_hit + &stub_mono_wasm_single_step_hit, + + //HotReload + &stub_send_enc_delta, }; static bool @@ -201,3 +207,8 @@ static void stub_mono_wasm_single_step_hit (void) { } + +static void +stub_send_enc_delta (MonoImage *image, gconstpointer dmeta_bytes, int32_t dmeta_len, gconstpointer dpdb_bytes, int32_t dpdb_len) +{ +} diff --git a/src/mono/mono/component/debugger.h b/src/mono/mono/component/debugger.h index de50c5641137..81ef259f3ebe 100644 --- a/src/mono/mono/component/debugger.h +++ b/src/mono/mono/component/debugger.h @@ -194,6 +194,9 @@ typedef struct MonoComponentDebugger { void (*mono_wasm_breakpoint_hit) (void); void (*mono_wasm_single_step_hit) (void); + //HotReload + void (*send_enc_delta) (MonoImage *image, gconstpointer dmeta_bytes, int32_t dmeta_len, gconstpointer dpdb_bytes, int32_t dpdb_len); + } MonoComponentDebugger; diff --git a/src/mono/mono/component/hot_reload-stub.c b/src/mono/mono/component/hot_reload-stub.c index 48033c1c7a0c..0ebf79a50c8f 100644 --- a/src/mono/mono/component/hot_reload-stub.c +++ b/src/mono/mono/component/hot_reload-stub.c @@ -15,7 +15,7 @@ static bool hot_reload_stub_available (void); static void -hot_reload_stub_apply_changes (MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, MonoError *error); +hot_reload_stub_apply_changes (MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, gconstpointer dpdb_bytes_orig, uint32_t dpdb_length, MonoError *error); static MonoComponentHotReload * component_hot_reload_stub_init (void); @@ -59,6 +59,9 @@ hot_reload_stub_table_bounds_check (MonoImage *base_image, int table_index, int static gboolean hot_reload_stub_delta_heap_lookup (MonoImage *base_image, MetadataHeapGetterFunc get_heap, uint32_t orig_index, MonoImage **image_out, uint32_t *index_out); +static gpointer +hot_reload_stub_get_updated_method_ppdb (MonoImage *base_image, uint32_t idx); + static gboolean hot_reload_stub_has_modified_rows (const MonoTableInfo *table); @@ -78,6 +81,7 @@ static MonoComponentHotReload fn_table = { &hot_reload_stub_get_updated_method_rva, &hot_reload_stub_table_bounds_check, &hot_reload_stub_delta_heap_lookup, + &hot_reload_stub_get_updated_method_ppdb, &hot_reload_stub_has_modified_rows, }; @@ -143,7 +147,7 @@ hot_reload_stub_relative_delta_index (MonoImage *image_dmeta, int token) } void -hot_reload_stub_apply_changes (MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, MonoError *error) +hot_reload_stub_apply_changes (MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, gconstpointer dpdb_bytes_orig, uint32_t dpdb_length, MonoError *error) { mono_error_set_not_supported (error, "Hot reload not supported in this runtime."); } @@ -176,8 +180,15 @@ hot_reload_stub_delta_heap_lookup (MonoImage *base_image, MetadataHeapGetterFunc g_assert_not_reached (); } +static gpointer +hot_reload_stub_get_updated_method_ppdb (MonoImage *base_image, uint32_t idx) +{ + g_assert_not_reached (); +} + static gboolean -hot_reload_stub_has_modified_rows (const MonoTableInfo *table){ +hot_reload_stub_has_modified_rows (const MonoTableInfo *table) +{ return FALSE; } diff --git a/src/mono/mono/component/hot_reload.c b/src/mono/mono/component/hot_reload.c index 76861056eda1..32820888b263 100644 --- a/src/mono/mono/component/hot_reload.c +++ b/src/mono/mono/component/hot_reload.c @@ -53,7 +53,7 @@ static void hot_reload_effective_table_slow (const MonoTableInfo **t, int *idx); static void -hot_reload_apply_changes (MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, MonoError *error); +hot_reload_apply_changes (MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, gconstpointer dpdb_bytes_orig, uint32_t dpdb_length, MonoError *error); static int hot_reload_relative_delta_index (MonoImage *image_dmeta, int token); @@ -73,6 +73,9 @@ hot_reload_table_bounds_check (MonoImage *base_image, int table_index, int token static gboolean hot_reload_delta_heap_lookup (MonoImage *base_image, MetadataHeapGetterFunc get_heap, uint32_t orig_index, MonoImage **image_out, uint32_t *index_out); +static gpointer +hot_reload_get_updated_method_ppdb (MonoImage *base_image, uint32_t idx); + static gboolean hot_reload_has_modified_rows (const MonoTableInfo *table); @@ -92,6 +95,7 @@ static MonoComponentHotReload fn_table = { &hot_reload_get_updated_method_rva, &hot_reload_table_bounds_check, &hot_reload_delta_heap_lookup, + &hot_reload_get_updated_method_ppdb, &hot_reload_has_modified_rows, }; @@ -150,6 +154,9 @@ typedef struct _DeltaInfo { /* Maps MethodDef token indices to a pointer into the RVA of the delta IL */ GHashTable *method_table_update; + /* Maps MethodDef token indices to a pointer into the RVA of the delta PPDB */ + GHashTable *method_ppdb_table_update; + // for each table, the row in the EncMap table that has the first token for remapping it? uint32_t enc_recs [MONO_TABLE_NUM]; delta_row_count count [MONO_TABLE_NUM]; @@ -319,6 +326,8 @@ delta_info_destroy (DeltaInfo *dinfo) { if (dinfo->method_table_update) g_hash_table_destroy (dinfo->method_table_update); + if (dinfo->method_ppdb_table_update) + g_hash_table_destroy (dinfo->method_ppdb_table_update); g_free (dinfo); } @@ -1173,17 +1182,50 @@ apply_enclog_pass1 (MonoImage *image_base, MonoImage *image_dmeta, gconstpointer } static void -set_update_method (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, MonoImage *image_dmeta, DeltaInfo *delta_info, uint32_t token_index, const char* il_address) +set_update_method (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, MonoImage *image_dmeta, DeltaInfo *delta_info, uint32_t token_index, const char* il_address, const char* pdb_address) { mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, "setting method 0x%08x in g=%d IL=%p", token_index, generation, (void*)il_address); /* FIXME: this is a race if other threads are doing a lookup. */ g_hash_table_insert (base_info->method_table_update, GUINT_TO_POINTER (token_index), GUINT_TO_POINTER (generation)); g_hash_table_insert (delta_info->method_table_update, GUINT_TO_POINTER (token_index), (gpointer) il_address); + g_hash_table_insert (delta_info->method_ppdb_table_update, GUINT_TO_POINTER (token_index), (gpointer) pdb_address); +} + +static const char * +hot_reload_get_method_debug_information (MonoImage *image_dppdb, int idx) +{ + if (!image_dppdb) + return NULL; + + MonoTableInfo *table_encmap = &image_dppdb->tables [MONO_TABLE_ENCMAP]; + int rows = table_info_get_rows (table_encmap); + for (int i = 0; i < rows ; ++i) { + guint32 cols [MONO_ENCMAP_SIZE]; + mono_metadata_decode_row (table_encmap, i, cols, MONO_ENCMAP_SIZE); + int map_token = cols [MONO_ENCMAP_TOKEN]; + int token_table = mono_metadata_token_table (map_token); + if (token_table != MONO_TABLE_METHODBODY) + continue; + int token_index = mono_metadata_token_index (map_token); + if (token_index == idx) + { + guint32 cols [MONO_METHODBODY_SIZE]; + MonoTableInfo *methodbody_table = &image_dppdb->tables [MONO_TABLE_METHODBODY]; + mono_metadata_decode_row (methodbody_table, i, cols, MONO_METHODBODY_SIZE); + if (!cols [MONO_METHODBODY_SEQ_POINTS]) + return NULL; + + const char *ptr = mono_metadata_blob_heap (image_dppdb, cols [MONO_METHODBODY_SEQ_POINTS]); + return ptr; + } + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "pdb encmap i=%d: token=0x%08x (table=%s)", i, map_token, mono_meta_table_name (token_table)); + } + return NULL; } /* do actuall enclog application */ static gboolean -apply_enclog_pass2 (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, MonoImage *image_dmeta, DeltaInfo *delta_info, gconstpointer dil_data, uint32_t dil_length, MonoError *error) +apply_enclog_pass2 (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, MonoImage *image_dmeta, MonoImage *image_dppdb, DeltaInfo *delta_info, gconstpointer dil_data, uint32_t dil_length, MonoError *error) { MonoTableInfo *table_enclog = &image_dmeta->tables [MONO_TABLE_ENCLOG]; int rows = table_info_get_rows (table_enclog); @@ -1254,12 +1296,16 @@ apply_enclog_pass2 (MonoImage *image_base, BaselineInfo *base_info, uint32_t gen base_info->method_table_update = g_hash_table_new (g_direct_hash, g_direct_equal); if (!delta_info->method_table_update) delta_info->method_table_update = g_hash_table_new (g_direct_hash, g_direct_equal); + if (!delta_info->method_ppdb_table_update) + + delta_info->method_ppdb_table_update = g_hash_table_new (g_direct_hash, g_direct_equal); int mapped_token = hot_reload_relative_delta_index (image_dmeta, mono_metadata_make_token (token_table, token_index)); int rva = mono_metadata_decode_row_col (&image_dmeta->tables [MONO_TABLE_METHOD], mapped_token - 1, MONO_METHOD_RVA); if (rva < dil_length) { char *il_address = ((char *) dil_data) + rva; - set_update_method (image_base, base_info, generation, image_dmeta, delta_info, token_index, il_address); + const char *method_debug_information = hot_reload_get_method_debug_information (image_dppdb, token_index); + set_update_method (image_base, base_info, generation, image_dmeta, delta_info, token_index, il_address, method_debug_information); } else { /* rva points probably into image_base IL stream. can this ever happen? */ g_print ("TODO: this case is still a bit contrived. token=0x%08x with rva=0x%04x\n", log_token, rva); @@ -1300,7 +1346,7 @@ apply_enclog_pass2 (MonoImage *image_base, BaselineInfo *base_info, uint32_t gen * LOCKING: Takes the publish_lock */ void -hot_reload_apply_changes (MonoImage *image_base, gconstpointer dmeta_bytes, uint32_t dmeta_length, gconstpointer dil_bytes_orig, uint32_t dil_length, MonoError *error) +hot_reload_apply_changes (MonoImage *image_base, gconstpointer dmeta_bytes, uint32_t dmeta_length, gconstpointer dil_bytes_orig, uint32_t dil_length, gconstpointer dpdb_bytes_orig, uint32_t dpdb_length, MonoError *error) { if (!assembly_update_supported (image_base->assembly)) { mono_error_set_invalid_operation (error, "The assembly can not be edited or changed."); @@ -1335,7 +1381,16 @@ hot_reload_apply_changes (MonoImage *image_base, gconstpointer dmeta_bytes, uint /* makes a copy of dil_bytes_orig */ gpointer dil_bytes = open_dil_data (image_base, dil_bytes_orig, dil_length); - /* TODO: make a copy of the dpdb bytes, once we consume them */ + + MonoImage *image_dpdb = NULL; + if (dpdb_length > 0) + { + MonoImage *image_dpdb = image_open_dmeta_from_data (image_base, generation, dpdb_bytes_orig, dpdb_length); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "pdb image string size: 0x%08x", image_dpdb->heap_strings.size); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "pdb image user string size: 0x%08x", image_dpdb->heap_us.size); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "pdb image blob heap addr: %p", image_dpdb->heap_blob.data); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "pdb image blob heap size: 0x%08x", image_dpdb->heap_blob.size); + } BaselineInfo *base_info = baseline_info_lookup_or_add (image_base); @@ -1391,7 +1446,7 @@ hot_reload_apply_changes (MonoImage *image_base, gconstpointer dmeta_bytes, uint if (mono_trace_is_traced (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE)) dump_update_summary (image_base, image_dmeta); - if (!apply_enclog_pass2 (image_base, base_info, generation, image_dmeta, delta_info, dil_bytes, dil_length, error)) { + if (!apply_enclog_pass2 (image_base, base_info, generation, image_dmeta, image_dpdb, delta_info, dil_bytes, dil_length, error)) { mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, "Error applying delta image to base=%s, due to: %s", basename, mono_error_get_message (error)); hot_reload_update_cancel (generation); return; @@ -1434,7 +1489,7 @@ metadata_update_count_updates (MonoImage *base) } static gpointer -get_method_update_rva (MonoImage *image_base, BaselineInfo *base_info, uint32_t idx) +get_method_update_rva (MonoImage *image_base, BaselineInfo *base_info, uint32_t idx, gboolean is_pdb) { gpointer loc = NULL; uint32_t cur = hot_reload_get_thread_generation (); @@ -1448,8 +1503,13 @@ get_method_update_rva (MonoImage *image_base, BaselineInfo *base_info, uint32_t g_assert (delta_info); if (delta_info->generation > cur) break; - if (delta_info->method_table_update) { - gpointer result = g_hash_table_lookup (delta_info->method_table_update, GUINT_TO_POINTER (idx)); + GHashTable *table = NULL; + if (is_pdb) + table = delta_info->method_ppdb_table_update; + else + table = delta_info->method_table_update; + if (table) { + gpointer result = g_hash_table_lookup (table, GUINT_TO_POINTER (idx)); /* if it's not in the table of a later generation, the * later generation didn't modify the method */ @@ -1463,6 +1523,23 @@ get_method_update_rva (MonoImage *image_base, BaselineInfo *base_info, uint32_t return loc; } +gpointer +hot_reload_get_updated_method_ppdb (MonoImage *base_image, uint32_t idx) +{ + BaselineInfo *info = baseline_info_lookup (base_image); + if (!info) + return NULL; + gpointer loc = NULL; + /* EnC case */ + if (G_UNLIKELY (info->method_table_update)) { + uint32_t gen = GPOINTER_TO_UINT (g_hash_table_lookup (info->method_table_update, GUINT_TO_POINTER (idx))); + if (G_UNLIKELY (gen > 0)) { + loc = get_method_update_rva (base_image, info, idx, TRUE); + } + } + return loc; +} + gpointer hot_reload_get_updated_method_rva (MonoImage *base_image, uint32_t idx) { @@ -1474,7 +1551,7 @@ hot_reload_get_updated_method_rva (MonoImage *base_image, uint32_t idx) if (G_UNLIKELY (info->method_table_update)) { uint32_t gen = GPOINTER_TO_UINT (g_hash_table_lookup (info->method_table_update, GUINT_TO_POINTER (idx))); if (G_UNLIKELY (gen > 0)) { - loc = get_method_update_rva (base_image, info, idx); + loc = get_method_update_rva (base_image, info, idx, FALSE); } } return loc; diff --git a/src/mono/mono/component/hot_reload.h b/src/mono/mono/component/hot_reload.h index 0bdb63d121d0..26ca0089a69e 100644 --- a/src/mono/mono/component/hot_reload.h +++ b/src/mono/mono/component/hot_reload.h @@ -23,14 +23,14 @@ typedef struct _MonoComponentHotReload { void (*cleanup_on_close) (MonoImage *image); void (*effective_table_slow) (const MonoTableInfo **t, int *idx); int (*relative_delta_index) (MonoImage *image_dmeta, int token); - void (*apply_changes) (MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, MonoError *error); + void (*apply_changes) (MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, gconstpointer dpdb_bytes_orig, uint32_t dpdb_length, MonoError *error); void (*image_close_except_pools_all) (MonoImage *base_image); void (*image_close_all) (MonoImage *base_image); gpointer (*get_updated_method_rva) (MonoImage *base_image, uint32_t idx); gboolean (*table_bounds_check) (MonoImage *base_image, int table_index, int token_index); gboolean (*delta_heap_lookup) (MonoImage *base_image, MetadataHeapGetterFunc get_heap, uint32_t orig_index, MonoImage **image_out, uint32_t *index_out); + gpointer (*get_updated_method_ppdb) (MonoImage *base_image, uint32_t idx); gboolean (*has_modified_rows) (const MonoTableInfo *table); - } MonoComponentHotReload; MONO_COMPONENT_EXPORT_ENTRYPOINT diff --git a/src/mono/mono/component/mini-wasm-debugger.c b/src/mono/mono/component/mini-wasm-debugger.c index 4df2172841ef..5dd9d8ce1d40 100644 --- a/src/mono/mono/component/mini-wasm-debugger.c +++ b/src/mono/mono/component/mini-wasm-debugger.c @@ -78,12 +78,6 @@ void wasm_debugger_log (int level, const gchar *format, ...) g_free (mesg); } -static void -jit_done (MonoProfiler *prof, MonoMethod *method, MonoJitInfo *jinfo) -{ - mono_de_add_pending_breakpoints (method, jinfo); -} - static void appdomain_load (MonoProfiler *prof, MonoDomain *domain) { @@ -149,9 +143,9 @@ handle_multiple_ss_requests (void) { static void mono_wasm_enable_debugging_internal (int debug_level) { + log_level = debug_level; PRINT_DEBUG_MSG (1, "DEBUGGING ENABLED\n"); debugger_enabled = TRUE; - log_level = debug_level; } static void @@ -186,7 +180,6 @@ mono_wasm_debugger_init (MonoDefaults *mono_defaults) get_mini_debug_options ()->load_aot_jit_info_eagerly = TRUE; MonoProfilerHandle prof = mono_profiler_create (NULL); - mono_profiler_set_jit_done_callback (prof, jit_done); //FIXME support multiple appdomains mono_profiler_set_domain_loaded_callback (prof, appdomain_load); mono_profiler_set_assembly_loaded_callback (prof, assembly_loaded); @@ -197,7 +190,7 @@ mono_wasm_debugger_init (MonoDefaults *mono_defaults) trans.send = receive_debugger_agent_message; mono_debugger_agent_register_transport (&trans); - mono_init_debugger_agent_for_wasm (log_level); + mono_init_debugger_agent_for_wasm (log_level, &prof); } static void diff --git a/src/mono/mono/metadata/debug-mono-ppdb.c b/src/mono/mono/metadata/debug-mono-ppdb.c index 070f9b8b0cf2..67bd4715d33b 100644 --- a/src/mono/mono/metadata/debug-mono-ppdb.c +++ b/src/mono/mono/metadata/debug-mono-ppdb.c @@ -456,95 +456,57 @@ mono_ppdb_is_embedded (MonoPPDBFile *ppdb) return ppdb->is_embedded; } -void -mono_ppdb_get_seq_points (MonoDebugMethodInfo *minfo, char **source_file, GPtrArray **source_file_list, int **source_files, MonoSymSeqPoint **seq_points, int *n_seq_points) +static int +mono_ppdb_get_seq_points_internal (const char* ptr, MonoSymSeqPoint **seq_points, int *n_seq_points, int docidx, MonoImage *image, MonoPPDBFile *ppdb, GPtrArray **sfiles, char **source_file, int **source_files, GPtrArray **sindexes, gboolean read_doc_value) { - MonoPPDBFile *ppdb = minfo->handle->ppdb; - MonoImage *image = ppdb->image; - MonoMethod *method = minfo->method; - MonoTableInfo *tables = image->tables; - guint32 cols [MONO_METHODBODY_SIZE]; - const char *ptr; - const char *end; - MonoDebugSourceInfo *docinfo; - int i, method_idx, size, docidx, iloffset, delta_il, delta_lines, delta_cols, start_line, start_col, adv_line, adv_col; - gboolean first = TRUE, first_non_hidden = TRUE; GArray *sps; MonoSymSeqPoint sp; - GPtrArray *sfiles = NULL; - GPtrArray *sindexes = NULL; - - if (source_file) - *source_file = NULL; - if (source_file_list) - *source_file_list = NULL; - if (source_files) - *source_files = NULL; - if (seq_points) - *seq_points = NULL; - if (n_seq_points) - *n_seq_points = 0; - - if (source_file_list) - *source_file_list = sfiles = g_ptr_array_new (); - if (source_files) - sindexes = g_ptr_array_new (); - - if (!method->token || table_info_get_rows (&tables [MONO_TABLE_METHODBODY]) == 0) - return; - - method_idx = mono_metadata_token_index (method->token); - - MonoTableInfo *methodbody_table = &tables [MONO_TABLE_METHODBODY]; - if (G_UNLIKELY (method_idx - 1 >= table_info_get_rows (methodbody_table))) { - char *method_name = mono_method_full_name (method, FALSE); - g_error ("Method idx %d is greater than number of rows (%d) in PPDB MethodDebugInformation table, for method %s in '%s'. Likely a malformed PDB file.", - method_idx - 1, table_info_get_rows (methodbody_table), method_name, image->name); - g_free (method_name); - } - mono_metadata_decode_row (methodbody_table, method_idx - 1, cols, MONO_METHODBODY_SIZE); - - docidx = cols [MONO_METHODBODY_DOCUMENT]; - - if (!cols [MONO_METHODBODY_SEQ_POINTS]) - return; - - ptr = mono_metadata_blob_heap (image, cols [MONO_METHODBODY_SEQ_POINTS]); - size = mono_metadata_decode_blob_size (ptr, &ptr); - end = ptr + size; + int iloffset = 0; + int start_line = 0; + int start_col = 0; + int delta_cols = 0; + gboolean first_non_hidden = TRUE; + int adv_line, adv_col; + int size = mono_metadata_decode_blob_size (ptr, &ptr); + const char* end = ptr + size; + MonoDebugSourceInfo *docinfo; + gboolean first = TRUE; sps = g_array_new (FALSE, TRUE, sizeof (MonoSymSeqPoint)); /* Header */ /* LocalSignature */ mono_metadata_decode_value (ptr, &ptr); - if (docidx == 0) + if (docidx == 0 && read_doc_value) docidx = mono_metadata_decode_value (ptr, &ptr); - docinfo = get_docinfo (ppdb, image, docidx); - - if (sfiles) - g_ptr_array_add (sfiles, docinfo); + if (sfiles && *sfiles) + { + docinfo = get_docinfo (ppdb, image, docidx); + g_ptr_array_add (*sfiles, docinfo); + } - if (source_file) + if (source_file && *source_file) *source_file = g_strdup (docinfo->source_file); iloffset = 0; start_line = 0; start_col = 0; while (ptr < end) { - delta_il = mono_metadata_decode_value (ptr, &ptr); - if (!first && delta_il == 0) { + int delta_il = mono_metadata_decode_value (ptr, &ptr); + if (!first && delta_il == 0 && read_doc_value) { /* subsequent-document-record */ docidx = mono_metadata_decode_value (ptr, &ptr); docinfo = get_docinfo (ppdb, image, docidx); - if (sfiles) - g_ptr_array_add (sfiles, docinfo); + if (sfiles && *sfiles) + { + g_ptr_array_add (*sfiles, docinfo); + } continue; } iloffset += delta_il; first = FALSE; - delta_lines = mono_metadata_decode_value (ptr, &ptr); + int delta_lines = mono_metadata_decode_value (ptr, &ptr); if (delta_lines == 0) delta_cols = mono_metadata_decode_value (ptr, &ptr); else @@ -574,8 +536,9 @@ mono_ppdb_get_seq_points (MonoDebugMethodInfo *minfo, char **source_file, GPtrAr sp.end_column = start_col + delta_cols; g_array_append_val (sps, sp); - if (source_files) - g_ptr_array_add (sindexes, GUINT_TO_POINTER (sfiles->len - 1)); + if (sindexes && *sindexes) { + g_ptr_array_add (*sindexes, GUINT_TO_POINTER ((*sfiles)->len - 1)); + } } if (n_seq_points) { @@ -584,15 +547,76 @@ mono_ppdb_get_seq_points (MonoDebugMethodInfo *minfo, char **source_file, GPtrAr *seq_points = g_new (MonoSymSeqPoint, sps->len); memcpy (*seq_points, sps->data, sps->len * sizeof (MonoSymSeqPoint)); } + int sps_len = sps->len; + g_array_free (sps, TRUE); + return sps_len; +} + +void +mono_ppdb_get_seq_points_enc (const char* ptr, MonoSymSeqPoint **seq_points, int *n_seq_points) +{ + mono_ppdb_get_seq_points_internal (ptr, seq_points, n_seq_points, 0, NULL, NULL, NULL, NULL, NULL, NULL, FALSE); +} + +void +mono_ppdb_get_seq_points (MonoDebugMethodInfo *minfo, char **source_file, GPtrArray **source_file_list, int **source_files, MonoSymSeqPoint **seq_points, int *n_seq_points) +{ + MonoPPDBFile *ppdb = minfo->handle->ppdb; + MonoImage *image = ppdb->image; + MonoMethod *method = minfo->method; + MonoTableInfo *tables = image->tables; + guint32 cols [MONO_METHODBODY_SIZE]; + const char *ptr; + int i, method_idx, docidx; + GPtrArray *sfiles = NULL; + GPtrArray *sindexes = NULL; + + if (source_file) + *source_file = NULL; + if (source_file_list) + *source_file_list = NULL; + if (source_files) + *source_files = NULL; + if (seq_points) + *seq_points = NULL; + if (n_seq_points) + *n_seq_points = 0; + + if (source_file_list) + *source_file_list = sfiles = g_ptr_array_new (); + if (source_files) + sindexes = g_ptr_array_new (); + + if (!method->token || table_info_get_rows (&tables [MONO_TABLE_METHODBODY]) == 0) + return; + + method_idx = mono_metadata_token_index (method->token); + + MonoTableInfo *methodbody_table = &tables [MONO_TABLE_METHODBODY]; + if (G_UNLIKELY (method_idx - 1 >= table_info_get_rows (methodbody_table))) { + char *method_name = mono_method_full_name (method, FALSE); + g_error ("Method idx %d is greater than number of rows (%d) in PPDB MethodDebugInformation table, for method %s in '%s'. Likely a malformed PDB file.", + method_idx - 1, table_info_get_rows (methodbody_table), method_name, image->name); + g_free (method_name); + } + mono_metadata_decode_row (methodbody_table, method_idx - 1, cols, MONO_METHODBODY_SIZE); + + docidx = cols [MONO_METHODBODY_DOCUMENT]; + + if (!cols [MONO_METHODBODY_SEQ_POINTS]) + return; + + ptr = mono_metadata_blob_heap (image, cols [MONO_METHODBODY_SEQ_POINTS]); + + int sps_len = mono_ppdb_get_seq_points_internal (ptr, seq_points, n_seq_points, docidx, image, ppdb, &sfiles, source_file, source_files, &sindexes, TRUE); if (source_files) { - *source_files = g_new (int, sps->len); - for (i = 0; i < sps->len; ++i) + *source_files = g_new (int, sps_len); + for (i = 0; i < sps_len; ++i) (*source_files)[i] = GPOINTER_TO_INT (g_ptr_array_index (sindexes, i)); g_ptr_array_free (sindexes, TRUE); } - g_array_free (sps, TRUE); } MonoDebugLocalsInfo* diff --git a/src/mono/mono/metadata/debug-mono-ppdb.h b/src/mono/mono/metadata/debug-mono-ppdb.h index c27a97069c97..e16317f9fc72 100644 --- a/src/mono/mono/metadata/debug-mono-ppdb.h +++ b/src/mono/mono/metadata/debug-mono-ppdb.h @@ -32,6 +32,9 @@ mono_ppdb_lookup_location (MonoDebugMethodInfo *minfo, uint32_t offset); void mono_ppdb_get_seq_points (MonoDebugMethodInfo *minfo, char **source_file, GPtrArray **source_file_list, int **source_files, MonoSymSeqPoint **seq_points, int *n_seq_points); +void +mono_ppdb_get_seq_points_enc (const char* ptr, MonoSymSeqPoint **seq_points, int *n_seq_points); + MonoDebugLocalsInfo* mono_ppdb_lookup_locals (MonoDebugMethodInfo *minfo); diff --git a/src/mono/mono/metadata/icall.c b/src/mono/mono/metadata/icall.c index 7c3f33fc7921..240479fb90a5 100644 --- a/src/mono/mono/metadata/icall.c +++ b/src/mono/mono/metadata/icall.c @@ -5782,9 +5782,9 @@ ves_icall_AssemblyExtensions_ApplyUpdate (MonoAssembly *assm, g_assert (dmeta_len >= 0); MonoImage *image_base = assm->image; g_assert (image_base); - // TODO: use dpdb_bytes - mono_image_load_enc_delta (image_base, dmeta_bytes, dmeta_len, dil_bytes, dil_len, error); + mono_image_load_enc_delta (image_base, dmeta_bytes, dmeta_len, dil_bytes, dil_len, dpdb_bytes, dpdb_len, error); + mono_error_set_pending_exception (error); } diff --git a/src/mono/mono/metadata/metadata-internals.h b/src/mono/mono/metadata/metadata-internals.h index 20f36c60e8f5..9a0d030a9f17 100644 --- a/src/mono/mono/metadata/metadata-internals.h +++ b/src/mono/mono/metadata/metadata-internals.h @@ -822,7 +822,7 @@ int mono_image_relative_delta_index (MonoImage *image_dmeta, int token); MONO_COMPONENT_API void -mono_image_load_enc_delta (MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, MonoError *error); +mono_image_load_enc_delta (MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, gconstpointer dpdb, uint32_t dpdb_len, MonoError *error); gboolean mono_image_load_cli_header (MonoImage *image, MonoCLIImageInfo *iinfo); diff --git a/src/mono/mono/metadata/metadata-update.c b/src/mono/mono/metadata/metadata-update.c index 8787b28bca65..71520b8aa1e6 100644 --- a/src/mono/mono/metadata/metadata-update.c +++ b/src/mono/mono/metadata/metadata-update.c @@ -73,9 +73,12 @@ mono_image_relative_delta_index (MonoImage *image_dmeta, int token) } void -mono_image_load_enc_delta (MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, MonoError *error) +mono_image_load_enc_delta (MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, gconstpointer dpdb, uint32_t dpdb_len, MonoError *error) { - mono_component_hot_reload ()->apply_changes (base_image, dmeta, dmeta_len, dil, dil_len, error); + mono_component_hot_reload ()->apply_changes (base_image, dmeta, dmeta_len, dil, dil_len, dpdb, dpdb_len, error); + if (is_ok (error)) { + mono_component_debugger ()->send_enc_delta (base_image, dmeta, dmeta_len, dpdb, dpdb_len); + } } static void @@ -108,6 +111,12 @@ mono_metadata_update_get_updated_method_rva (MonoImage *base_image, uint32_t idx return mono_component_hot_reload ()->get_updated_method_rva (base_image, idx); } +gpointer +mono_metadata_update_get_updated_method_ppdb (MonoImage *base_image, uint32_t idx) +{ + return mono_component_hot_reload ()->get_updated_method_ppdb (base_image, idx); +} + gboolean mono_metadata_update_table_bounds_check (MonoImage *base_image, int table_index, int token_index) { diff --git a/src/mono/mono/metadata/metadata-update.h b/src/mono/mono/metadata/metadata-update.h index 52bfa701e57f..15d0d51e10f4 100644 --- a/src/mono/mono/metadata/metadata-update.h +++ b/src/mono/mono/metadata/metadata-update.h @@ -48,6 +48,9 @@ mono_metadata_update_image_close_all (MonoImage *base_image); gpointer mono_metadata_update_get_updated_method_rva (MonoImage *base_image, uint32_t idx); +gpointer +mono_metadata_update_get_updated_method_ppdb (MonoImage *base_image, uint32_t idx); + gboolean mono_metadata_update_table_bounds_check (MonoImage *base_image, int table_index, int token_index); diff --git a/src/mono/mono/metadata/mono-debug.c b/src/mono/mono/metadata/mono-debug.c index bdef02c84f02..58ecee24d459 100644 --- a/src/mono/mono/metadata/mono-debug.c +++ b/src/mono/mono/metadata/mono-debug.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #if NO_UNALIGNED_ACCESS @@ -1115,7 +1116,12 @@ mono_debug_enabled (void) void mono_debug_get_seq_points (MonoDebugMethodInfo *minfo, char **source_file, GPtrArray **source_file_list, int **source_files, MonoSymSeqPoint **seq_points, int *n_seq_points) { - if (minfo->handle->ppdb) + MonoImage* img = m_class_get_image (minfo->method->klass); + if (img->has_updates) { + int idx = mono_metadata_token_index (minfo->method->token); + gpointer ptr = mono_metadata_update_get_updated_method_ppdb (img, idx); + mono_ppdb_get_seq_points_enc (ptr, seq_points, n_seq_points); + } else if (minfo->handle->ppdb) mono_ppdb_get_seq_points (minfo, source_file, source_file_list, source_files, seq_points, n_seq_points); else mono_debug_symfile_get_seq_points (minfo, source_file, source_file_list, source_files, seq_points, n_seq_points); diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index 0eed6c7146e0..e75fefe393c7 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -9758,8 +9758,7 @@ mono_interp_transform_method (InterpMethod *imethod, ThreadContext *context, Mon MonoJitMemoryManager *jit_mm = get_default_jit_mm (); jit_mm_lock (jit_mm); - if (!g_hash_table_lookup (jit_mm->seq_points, imethod->method)) - g_hash_table_insert (jit_mm->seq_points, imethod->method, imethod->jinfo->seq_points); + g_hash_table_replace (jit_mm->seq_points, imethod->method, imethod->jinfo->seq_points); jit_mm_unlock (jit_mm); // FIXME: Add a different callback ? diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 81c419e4c585..c1e8ce60bda6 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -62,12 +62,12 @@ internal class BreakpointRequest public int Line { get; private set; } public int Column { get; private set; } public string Condition { get; private set; } - public MethodInfo Method { get; private set; } + public MethodInfo Method { get; set; } private JObject request; public bool IsResolved => Assembly != null; - public List Locations { get; } = new List(); + public List Locations { get; set; } = new List(); public override string ToString() => $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}"; @@ -321,21 +321,26 @@ internal class MethodInfo public string Name { get; } public MethodDebugInformation DebugInformation; public MethodDefinitionHandle methodDefHandle; + private MetadataReader pdbMetadataReader; - public SourceLocation StartLocation { get; } - public SourceLocation EndLocation { get; } + public SourceLocation StartLocation { get; set;} + public SourceLocation EndLocation { get; set;} public AssemblyInfo Assembly { get; } public int Token { get; } + internal bool IsEnCMethod; + internal LocalScopeHandleCollection localScopes; public bool IsStatic() => (methodDef.Attributes & MethodAttributes.Static) != 0; - public MethodInfo(AssemblyInfo assembly, MethodDefinitionHandle methodDefHandle, int token, SourceFile source, TypeInfo type) + public MethodInfo(AssemblyInfo assembly, MethodDefinitionHandle methodDefHandle, int token, SourceFile source, TypeInfo type, MetadataReader asmMetadataReader, MetadataReader pdbMetadataReader) { this.Assembly = assembly; - this.methodDef = Assembly.asmMetadataReader.GetMethodDefinition(methodDefHandle); - this.DebugInformation = Assembly.pdbMetadataReader.GetMethodDebugInformation(methodDefHandle.ToDebugInformationHandle()); + this.methodDef = asmMetadataReader.GetMethodDefinition(methodDefHandle); + this.DebugInformation = pdbMetadataReader.GetMethodDebugInformation(methodDefHandle.ToDebugInformationHandle()); this.source = source; this.Token = token; this.methodDefHandle = methodDefHandle; - this.Name = Assembly.asmMetadataReader.GetString(methodDef.Name); + this.Name = asmMetadataReader.GetString(methodDef.Name); + this.pdbMetadataReader = pdbMetadataReader; + this.IsEnCMethod = false; if (!DebugInformation.SequencePointsBlob.IsNil) { var sps = DebugInformation.GetSequencePoints(); @@ -358,6 +363,37 @@ public MethodInfo(AssemblyInfo assembly, MethodDefinitionHandle methodDefHandle, StartLocation = new SourceLocation(this, start); EndLocation = new SourceLocation(this, end); } + localScopes = pdbMetadataReader.GetLocalScopes(methodDefHandle); + } + + public void UpdateEnC(MetadataReader asmMetadataReader, MetadataReader pdbMetadataReaderParm, int method_idx) + { + this.DebugInformation = pdbMetadataReaderParm.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(method_idx)); + this.pdbMetadataReader = pdbMetadataReaderParm; + this.IsEnCMethod = true; + if (!DebugInformation.SequencePointsBlob.IsNil) + { + var sps = DebugInformation.GetSequencePoints(); + SequencePoint start = sps.First(); + SequencePoint end = sps.First(); + + foreach (SequencePoint 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); + } + localScopes = pdbMetadataReader.GetLocalScopes(MetadataTokens.MethodDefinitionHandle(method_idx)); } public SourceLocation GetLocationByIl(int pos) @@ -394,18 +430,18 @@ public VarInfo[] GetLiveVarsAt(int offset) res.Add(new VarInfo(parameter, Assembly.asmMetadataReader)); } - var localScopes = Assembly.pdbMetadataReader.GetLocalScopes(methodDefHandle); + foreach (var localScopeHandle in localScopes) { - var localScope = Assembly.pdbMetadataReader.GetLocalScope(localScopeHandle); + var localScope = pdbMetadataReader.GetLocalScope(localScopeHandle); if (localScope.StartOffset <= offset && localScope.EndOffset > offset) { var localVariables = localScope.GetLocalVariables(); foreach (var localVariableHandle in localVariables) { - var localVariable = Assembly.pdbMetadataReader.GetLocalVariable(localVariableHandle); + var localVariable = pdbMetadataReader.GetLocalVariable(localVariableHandle); if (localVariable.Attributes != LocalVariableAttributes.DebuggerHidden) - res.Add(new VarInfo(localVariable, Assembly.pdbMetadataReader)); + res.Add(new VarInfo(localVariable, pdbMetadataReader)); } } } @@ -465,6 +501,8 @@ internal class AssemblyInfo internal string Url { get; } internal MetadataReader asmMetadataReader { get; } internal MetadataReader pdbMetadataReader { get; set; } + internal List enCMemoryStream = new List(); + internal List enCMetadataReader = new List(); internal PEReader peReader; internal MemoryStream asmStream; internal MemoryStream pdbStream; @@ -496,11 +534,39 @@ public unsafe AssemblyInfo(string url, byte[] assembly, byte[] pdb) Populate(); } + public bool EnC(byte[] meta, byte[] pdb) + { + var asmStream = new MemoryStream(meta); + MetadataReader asmMetadataReader = MetadataReaderProvider.FromMetadataStream(asmStream).GetMetadataReader(); + var pdbStream = new MemoryStream(pdb); + MetadataReader pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader(); + enCMemoryStream.Add(asmStream); + enCMemoryStream.Add(pdbStream); + enCMetadataReader.Add(asmMetadataReader); + enCMetadataReader.Add(pdbMetadataReader); + PopulateEnC(asmMetadataReader, pdbMetadataReader); + return true; + } + public AssemblyInfo(ILogger logger) { this.logger = logger; } + private void PopulateEnC(MetadataReader asmMetadataReaderParm, MetadataReader pdbMetadataReaderParm) + { + int i = 1; + foreach (EntityHandle encMapHandle in asmMetadataReaderParm.GetEditAndContinueMapEntries()) + { + if (encMapHandle.Kind == HandleKind.MethodDebugInformation) + { + var method = methods[asmMetadataReader.GetRowNumber(encMapHandle)]; + method.UpdateEnC(asmMetadataReaderParm, pdbMetadataReaderParm, i); + i++; + } + } + } + private void Populate() { var d2s = new Dictionary(); @@ -543,7 +609,7 @@ SourceFile FindSource(DocumentHandle doc, int rowid, string documentName) var document = pdbMetadataReader.GetDocument(methodDebugInformation.Document); var documentName = pdbMetadataReader.GetString(document.Name); SourceFile source = FindSource(methodDebugInformation.Document, asmMetadataReader.GetRowNumber(methodDebugInformation.Document), documentName); - var methodInfo = new MethodInfo(this, method, asmMetadataReader.GetRowNumber(method), source, typeInfo); + var methodInfo = new MethodInfo(this, method, asmMetadataReader.GetRowNumber(method), source, typeInfo, asmMetadataReader, pdbMetadataReader); methods[asmMetadataReader.GetRowNumber(method)] = methodInfo; if (source != null) @@ -605,6 +671,7 @@ private Uri GetSourceLinkUrl(string document) } public IEnumerable Sources => this.sources; + public Dictionary Methods => this.methods; public Dictionary TypesByName => this.typesByName; public int Id => id; @@ -828,6 +895,16 @@ private class DebugItem public Task Data { get; set; } } + public IEnumerable EnC(SessionId sessionId, AssemblyInfo asm, byte[] meta_data, byte[] pdb_data) + { + asm.EnC(meta_data, pdb_data); + foreach (var method in asm.Methods) + { + if (method.Value.IsEnCMethod) + yield return method.Value; + } + } + public IEnumerable Add(SessionId sessionId, byte[] assembly_data, byte[] pdb_data) { AssemblyInfo assembly = null; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index f45d31ca1c87..f14bc1e16a99 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -349,7 +349,7 @@ protected override async Task AcceptCommand(MessageId id, string method, J case "Debugger.removeBreakpoint": { - await RemoveBreakpoint(id, args, token); + await RemoveBreakpoint(id, args, false, token); break; } @@ -689,6 +689,56 @@ private async Task EvaluateCondition(SessionId sessionId, ExecutionContext } return false; } + + private async Task ProcessEnC(SessionId sessionId, ExecutionContext context, MonoBinaryReader ret_debugger_cmd_reader, CancellationToken token) + { + int moduleId = ret_debugger_cmd_reader.ReadInt32(); + int meta_size = ret_debugger_cmd_reader.ReadInt32(); + byte[] meta_buf = ret_debugger_cmd_reader.ReadBytes(meta_size); + int pdb_size = ret_debugger_cmd_reader.ReadInt32(); + byte[] pdb_buf = ret_debugger_cmd_reader.ReadBytes(pdb_size); + + var assembly_name = await sdbHelper.GetAssemblyNameFromModule(sessionId, moduleId, token); + DebugStore store = await LoadStore(sessionId, token); + AssemblyInfo asm = store.GetAssemblyByName(assembly_name); + foreach (var method in store.EnC(sessionId, asm, meta_buf, pdb_buf)) + await ResetBreakpoint(sessionId, method, token); + return true; + } + + private async Task SendBreakpointsOfMethodUpdated(SessionId sessionId, ExecutionContext context, MonoBinaryReader ret_debugger_cmd_reader, CancellationToken token) + { + var method_id = ret_debugger_cmd_reader.ReadInt32(); + var method_token = await sdbHelper.GetMethodToken(sessionId, method_id, token); + var assembly_id = await sdbHelper.GetAssemblyIdFromMethod(sessionId, method_id, token); + var assembly_name = await sdbHelper.GetAssemblyName(sessionId, assembly_id, token); + var method_name = await sdbHelper.GetMethodName(sessionId, method_id, token); + DebugStore store = await LoadStore(sessionId, token); + AssemblyInfo asm = store.GetAssemblyByName(assembly_name); + if (asm == null) + { + assembly_name = await sdbHelper.GetAssemblyNameFull(sessionId, assembly_id, token); + asm = store.GetAssemblyByName(assembly_name); + if (asm == null) + { + return true; + } + } + MethodInfo method = asm.GetMethodByToken(method_token); + if (method == null) + { + return true; + } + foreach (var req in context.BreakpointRequests.Values) + { + if (req.Method != null && req.Method.Assembly.Id == method.Assembly.Id && req.Method.Token == method.Token) + { + await SetBreakpoint(sessionId, context.store, req, true, token); + } + } + return true; + } + private async Task SendCallStack(SessionId sessionId, ExecutionContext context, string reason, int thread_id, Breakpoint bp, JObject data, IEnumerable orig_callframes, CancellationToken token) { var callFrames = new List(); @@ -844,6 +894,18 @@ private async Task OnReceiveDebuggerAgentEvent(SessionId sessionId, JObjec int thread_id = ret_debugger_cmd_reader.ReadInt32(); switch (event_kind) { + case EventKind.MethodUpdate: + { + var ret = await SendBreakpointsOfMethodUpdated(sessionId, context, ret_debugger_cmd_reader, token); + await SendCommand(sessionId, "Debugger.resume", new JObject(), token); + return ret; + } + case EventKind.EnC: + { + var ret = await ProcessEnC(sessionId, context, ret_debugger_cmd_reader, token); + await SendCommand(sessionId, "Debugger.resume", new JObject(), token); + return ret; + } case EventKind.Exception: { string reason = "exception"; @@ -1209,7 +1271,9 @@ private async Task RuntimeReady(SessionId sessionId, CancellationTok await sdbHelper.EnableExceptions(sessionId, "uncaught", token); await sdbHelper.SetProtocolVersion(sessionId, token); - await sdbHelper.EnableReceiveUserBreakRequest(sessionId, token); + await sdbHelper.EnableReceiveRequests(sessionId, EventKind.UserBreak, token); + await sdbHelper.EnableReceiveRequests(sessionId, EventKind.EnC, token); + await sdbHelper.EnableReceiveRequests(sessionId, EventKind.MethodUpdate, token); DebugStore store = await LoadStore(sessionId, token); @@ -1218,7 +1282,21 @@ private async Task RuntimeReady(SessionId sessionId, CancellationTok return store; } - private async Task RemoveBreakpoint(MessageId msg_id, JObject args, CancellationToken token) + private async Task ResetBreakpoint(SessionId msg_id, MethodInfo method, CancellationToken token) + { + ExecutionContext context = GetContext(msg_id); + foreach (var req in context.BreakpointRequests.Values) + { + if (req.Method != null) + { + if (req.Method.Assembly.Id == method.Assembly.Id && req.Method.Token == method.Token) { + await RemoveBreakpoint(msg_id, JObject.FromObject(new {breakpointId = req.Id}), true, token); + } + } + } + } + + private async Task RemoveBreakpoint(SessionId msg_id, JObject args, bool isEnCReset, CancellationToken token) { string bpid = args?["breakpointId"]?.Value(); @@ -1232,10 +1310,16 @@ private async Task RemoveBreakpoint(MessageId msg_id, JObject args, Cancellation if (breakpoint_removed) { bp.RemoteId = -1; - bp.State = BreakpointState.Disabled; + if (isEnCReset) + bp.State = BreakpointState.Pending; + else + bp.State = BreakpointState.Disabled; } } - context.BreakpointRequests.Remove(bpid); + if (!isEnCReset) + context.BreakpointRequests.Remove(bpid); + else + breakpointRequest.Locations = new List(); } private async Task SetBreakpoint(SessionId sessionId, DebugStore store, BreakpointRequest req, bool sendResolvedEvent, CancellationToken token) @@ -1263,6 +1347,7 @@ private async Task SetBreakpoint(SessionId sessionId, DebugStore store, Breakpoi foreach (IGrouping sourceId in locations) { SourceLocation loc = sourceId.First(); + req.Method = loc.CliLocation.Method; Breakpoint bp = await SetMonoBreakpoint(sessionId, req.Id, loc, req.Condition, token); // If we didn't successfully enable the breakpoint diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 48ed3a9992bb..42ad54e6e2c6 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -83,7 +83,9 @@ internal enum EventKind { KeepAlive = 14, UserBreak = 15, UserLog = 16, - Crash = 17 + Crash = 17, + EnC = 18, + MethodUpdate = 19 } internal enum ModifierKind { @@ -552,16 +554,18 @@ public async Task SetProtocolVersion(SessionId sessionId, CancellationToke var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdVM.SetProtocolVersion, command_params, token); return true; } - public async Task EnableReceiveUserBreakRequest(SessionId sessionId, CancellationToken token) + + public async Task EnableReceiveRequests(SessionId sessionId, EventKind event_kind, CancellationToken token) { var command_params = new MemoryStream(); var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write((byte)EventKind.UserBreak); + command_params_writer.Write((byte)event_kind); command_params_writer.Write((byte)SuspendPolicy.None); command_params_writer.Write((byte)0); var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Set, command_params, token); return true; } + internal async Task SendDebuggerAgentCommandInternal(SessionId sessionId, int command_set, int command, MemoryStream parms, CancellationToken token) { Result res = await proxy.SendMonoCommand(sessionId, MonoCommands.SendDebuggerAgentCommand(GetId(), command_set, command, Convert.ToBase64String(parms.ToArray())), token); @@ -652,6 +656,17 @@ public async Task GetAssemblyId(SessionId sessionId, string asm_name, Cance return ret_debugger_cmd_reader.ReadInt32(); } + public async Task GetAssemblyNameFromModule(SessionId sessionId, int moduleId, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(moduleId); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdModule.GetInfo, command_params, token); + ret_debugger_cmd_reader.ReadString(); + return ret_debugger_cmd_reader.ReadString(); + } + public async Task GetAssemblyName(SessionId sessionId, int assembly_id, CancellationToken token) { var command_params = new MemoryStream(); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs index a4f53aac02b3..77a8cb6c12e1 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs @@ -287,5 +287,63 @@ await LoadAssemblyDynamically( CheckNumber(locals, "b", 10); } + [Fact] + public async Task DebugHotReloadMethodChangedUserBreak() + { + var pause_location = await LoadAssemblyAndTestHotReload( + Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.dll"), + Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.pdb"), + Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"), + "MethodBody1", "StaticMethod1"); + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + CheckNumber(locals, "a", 10); + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 14, 8, "StaticMethod1"); + locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + CheckNumber(locals, "b", 15); + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 14, 8, "StaticMethod1"); + locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + CheckBool(locals, "c", true); + } + + [Fact] + public async Task DebugHotReloadMethodUnchanged() + { + var pause_location = await LoadAssemblyAndTestHotReload( + Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.dll"), + Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.pdb"), + Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"), + "MethodBody2", "StaticMethod1"); + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + CheckNumber(locals, "a", 10); + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 23, 8, "StaticMethod1"); + locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + CheckNumber(locals, "a", 10); + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 23, 8, "StaticMethod1"); + locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + CheckNumber(locals, "a", 10); + } + + [Fact] + public async Task DebugHotReloadMethodAddBreakpoint() + { + int line = 30; + await SetBreakpoint(".*/MethodBody1.cs$", line, 12, use_regex: true); + var pause_location = await LoadAssemblyAndTestHotReload( + Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.dll"), + Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.pdb"), + Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"), + "MethodBody3", "StaticMethod3"); + + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + CheckNumber(locals, "a", 10); + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 29, 12, "StaticMethod3"); + locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + CheckNumber(locals, "b", 15); + + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 29, 12, "StaticMethod3"); + locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + CheckBool(locals, "c", true); + } + } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index a6af28d8e459..b26624807150 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -1040,6 +1040,55 @@ internal async Task LoadAssemblyDynamicallyALCAndRunMethod(string asm_f await cli.SendCommand("Runtime.evaluate", run_method, token); return await insp.WaitFor(Inspector.PAUSE); } + + internal async Task LoadAssemblyAndTestHotReload(string asm_file, string pdb_file, string asm_file_hot_reload, string class_name, string method_name) + { + // Simulate loading an assembly into the framework + byte[] bytes = File.ReadAllBytes(asm_file); + string asm_base64 = Convert.ToBase64String(bytes); + bytes = File.ReadAllBytes(pdb_file); + string pdb_base64 = Convert.ToBase64String(bytes); + + bytes = File.ReadAllBytes($"{asm_file_hot_reload}.1.dmeta"); + string dmeta1 = Convert.ToBase64String(bytes); + + bytes = File.ReadAllBytes($"{asm_file_hot_reload}.1.dil"); + string dil1 = Convert.ToBase64String(bytes); + + bytes = File.ReadAllBytes($"{asm_file_hot_reload}.1.dpdb"); + string dpdb1 = Convert.ToBase64String(bytes); + + + bytes = File.ReadAllBytes($"{asm_file_hot_reload}.2.dmeta"); + string dmeta2 = Convert.ToBase64String(bytes); + + bytes = File.ReadAllBytes($"{asm_file_hot_reload}.2.dil"); + string dil2 = Convert.ToBase64String(bytes); + + bytes = File.ReadAllBytes($"{asm_file_hot_reload}.2.dpdb"); + string dpdb2 = Convert.ToBase64String(bytes); + + string expression = $"let asm_b64 = '{asm_base64}'; let pdb_b64 = '{pdb_base64}';"; + expression = $"{expression} let dmeta1 = '{dmeta1}'; let dil1 = '{dil1}'; let dpdb1 = '{dpdb1}';"; + expression = $"{expression} let dmeta2 = '{dmeta2}'; let dil2 = '{dil2}'; let dpdb2 = '{dpdb2}';"; + expression = $"{{ {expression} invoke_static_method('[debugger-test] TestHotReload:LoadLazyHotReload', asm_b64, pdb_b64, dmeta1, dil1, dpdb1, dmeta2, dil2, dpdb2); }}"; + var load_assemblies = JObject.FromObject(new + { + expression + }); + + Result load_assemblies_res = await cli.SendCommand("Runtime.evaluate", load_assemblies, token); + + Assert.True(load_assemblies_res.IsOk); + Thread.Sleep(1000); + var run_method = JObject.FromObject(new + { + expression = "window.setTimeout(function() { invoke_static_method('[debugger-test] TestHotReload:RunMethod', '" + class_name + "', '" + method_name + "'); }, 1);" + }); + + await cli.SendCommand("Runtime.evaluate", run_method, token); + return await insp.WaitFor(Inspector.PAUSE); + } } class DotnetObjectId diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/ApplyUpdateReferencedAssembly.csproj b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/ApplyUpdateReferencedAssembly.csproj new file mode 100644 index 000000000000..550971c8b6f3 --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/ApplyUpdateReferencedAssembly.csproj @@ -0,0 +1,31 @@ + + + true + deltascript.json + library + false + true + + false + true + + false + false + false + + + + + + + + + + + diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/MethodBody1.cs b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/MethodBody1.cs new file mode 100644 index 000000000000..b2b8f4d364c0 --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/MethodBody1.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System; + +namespace ApplyUpdateReferencedAssembly +{ + public class MethodBody1 { + public static string StaticMethod1 () { + Console.WriteLine("original"); + int a = 10; + Debugger.Break(); + return "OLD STRING"; + } + } + + public class MethodBody2 { + public static string StaticMethod1 () { + Console.WriteLine("original"); + int a = 10; + Debugger.Break(); + return "OLD STRING"; + } + } + + public class MethodBody3 { + public static string StaticMethod3 () { + int a = 10; + Console.WriteLine("original"); + return "OLD STRING"; + } + } +} diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/MethodBody1_v1.cs b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/MethodBody1_v1.cs new file mode 100644 index 000000000000..03a4d33b0646 --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/MethodBody1_v1.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System; + +namespace ApplyUpdateReferencedAssembly +{ + public class MethodBody1 { + public static string StaticMethod1 () { + Console.WriteLine("v1"); + double b = 15; + Debugger.Break(); + return "NEW STRING"; + } + } + + public class MethodBody2 { + public static string StaticMethod1 () { + Console.WriteLine("original"); + int a = 10; + Debugger.Break(); + return "OLD STRING"; + } + } + + public class MethodBody3 { + public static string StaticMethod3 () { + float b = 15; + Console.WriteLine("v1"); + return "NEW STRING"; + } + } +} diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/MethodBody1_v2.cs b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/MethodBody1_v2.cs new file mode 100644 index 000000000000..4cd88d660a3f --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/MethodBody1_v2.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; +using System; + +namespace ApplyUpdateReferencedAssembly +{ + public class MethodBody1 { + public static string StaticMethod1 () + { + Console.WriteLine("v2"); + bool c = true; + Debugger.Break(); + return "NEWEST STRING"; + } + } + + public class MethodBody2 { + public static string StaticMethod1 () { + Console.WriteLine("original"); + int a = 10; + Debugger.Break(); + return "OLD STRING"; + } + } + + public class MethodBody3 { + public static string StaticMethod3 () { + bool c = true; + Console.WriteLine("v2"); + return "NEWEST STRING"; + } + } +} diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/deltascript.json b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/deltascript.json new file mode 100644 index 000000000000..8e738364bc74 --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/deltascript.json @@ -0,0 +1,7 @@ +{ + "changes": [ + {"document": "MethodBody1.cs", "update": "MethodBody1_v1.cs"}, + {"document": "MethodBody1.cs", "update": "MethodBody1_v2.cs"}, + ] +} + 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 32177bbc0c5b..228d9d1ff62e 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs @@ -578,3 +578,78 @@ public static void RunMethodInALC(string type_name, string method_name) } } + public class TestHotReload { + static System.Reflection.Assembly loadedAssembly; + static byte[] dmeta_data1_bytes; + static byte[] dil_data1_bytes; + static byte[] dpdb_data1_bytes; + static byte[] dmeta_data2_bytes; + static byte[] dil_data2_bytes; + static byte[] dpdb_data2_bytes; + public static void LoadLazyHotReload(string asm_base64, string pdb_base64, string dmeta_data1, string dil_data1, string dpdb_data1, string dmeta_data2, string dil_data2, string dpdb_data2) + { + byte[] asm_bytes = Convert.FromBase64String(asm_base64); + byte[] pdb_bytes = Convert.FromBase64String(pdb_base64); + + dmeta_data1_bytes = Convert.FromBase64String(dmeta_data1); + dil_data1_bytes = Convert.FromBase64String(dil_data1); + dpdb_data1_bytes = Convert.FromBase64String(dpdb_data1); + + dmeta_data2_bytes = Convert.FromBase64String(dmeta_data2); + dil_data2_bytes = Convert.FromBase64String(dil_data2); + dpdb_data2_bytes = Convert.FromBase64String(dpdb_data2); + + + loadedAssembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(new System.IO.MemoryStream(asm_bytes), new System.IO.MemoryStream(pdb_bytes)); + Console.WriteLine($"Loaded - {loadedAssembly}"); + + } + public static void RunMethod(string className, string methodName) + { + var ty = typeof(System.Reflection.Metadata.AssemblyExtensions); + var mi = ty.GetMethod("GetApplyUpdateCapabilities", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static, Array.Empty()); + + if (mi == null) + return; + + var caps = mi.Invoke(null, null) as string; + + if (String.IsNullOrEmpty(caps)) + return; + + var myType = loadedAssembly.GetType($"ApplyUpdateReferencedAssembly.{className}"); + var myMethod = myType.GetMethod(methodName); + myMethod.Invoke(null, null); + + ApplyUpdate(loadedAssembly, 1); + + myType = loadedAssembly.GetType($"ApplyUpdateReferencedAssembly.{className}"); + myMethod = myType.GetMethod(methodName); + myMethod.Invoke(null, null); + + ApplyUpdate(loadedAssembly, 2); + + myType = loadedAssembly.GetType($"ApplyUpdateReferencedAssembly.{className}"); + myMethod = myType.GetMethod(methodName); + myMethod.Invoke(null, null); + } + + internal static void ApplyUpdate (System.Reflection.Assembly assm, int version) + { + string basename = assm.Location; + if (basename == "") + basename = assm.GetName().Name + ".dll"; + Console.Error.WriteLine($"Apply Delta Update for {basename}, revision {version}"); + + if (version == 1) + { + System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate(assm, dmeta_data1_bytes, dil_data1_bytes, dpdb_data1_bytes); + } + else if (version == 2) + { + System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate(assm, dmeta_data2_bytes, dil_data2_bytes, dpdb_data2_bytes); + } + + } + } + diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj index 648dbcc64fc6..797fa1675164 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj @@ -18,13 +18,15 @@ + $(AppDir) $(MonoProjectRoot)wasm\runtime-test.js - 1 + + -1 true @@ -38,6 +40,30 @@ + + + + + + + + + + + + + + + diff --git a/src/mono/wasm/debugger/tests/debugger-test/runtime-debugger.js b/src/mono/wasm/debugger/tests/debugger-test/runtime-debugger.js index 8fb1e86211af..2c24917f6203 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/runtime-debugger.js +++ b/src/mono/wasm/debugger/tests/debugger-test/runtime-debugger.js @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -var Module = { +var Module = { config: null, preInit: async function() { @@ -27,6 +27,10 @@ var Module = { MONO.mono_wasm_setenv ("MONO_LOG_LEVEL", "debug"); MONO.mono_wasm_setenv ("MONO_LOG_MASK", "all"); */ + + Module.config.environment_variables = { + "DOTNET_MODIFIABLE_ASSEMBLIES": "debug" + }; MONO.mono_load_runtime_and_bcl_args (Module.config) }, }; From d021b563e811b0685149c5a5ca7628569bef9f7f Mon Sep 17 00:00:00 2001 From: SingleAccretion <62474226+SingleAccretion@users.noreply.github.com> Date: Tue, 13 Jul 2021 03:38:46 +0300 Subject: [PATCH 039/133] Move the "do not zero-extend setcc" optimization to lower (#53778) * Strongly type StoreInd lowering * Improve clarity of code through the use of helpers * Move the "do not zero-extend setcc" to lowering It is XARCH-specific and moving it eliminates questionable code that is trying to compensate for CSE changing the store. * Delete now unnecessary copying of the relop type --- src/coreclr/jit/lower.cpp | 23 +++++++++++------------ src/coreclr/jit/lower.h | 6 +++--- src/coreclr/jit/lowerarmarch.cpp | 8 ++++---- src/coreclr/jit/lowerxarch.cpp | 22 +++++++++++++++------- src/coreclr/jit/morph.cpp | 18 ------------------ 5 files changed, 33 insertions(+), 44 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 9499ac5d8178..a5b0ba16fdc6 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -116,7 +116,7 @@ GenTree* Lowering::LowerNode(GenTree* node) break; case GT_STOREIND: - LowerStoreIndirCommon(node->AsIndir()); + LowerStoreIndirCommon(node->AsStoreInd()); break; case GT_ADD: @@ -3558,7 +3558,7 @@ void Lowering::LowerStoreSingleRegCallStruct(GenTreeBlk* store) { store->ChangeType(regType); store->SetOper(GT_STOREIND); - LowerStoreIndirCommon(store); + LowerStoreIndirCommon(store->AsStoreInd()); return; } else @@ -4100,7 +4100,7 @@ void Lowering::InsertPInvokeMethodProlog() // The init routine sets InlinedCallFrame's m_pNext, so we just set the thead's top-of-stack GenTree* frameUpd = CreateFrameLinkUpdate(PushFrame); firstBlockRange.InsertBefore(insertionPoint, LIR::SeqTree(comp, frameUpd)); - ContainCheckStoreIndir(frameUpd->AsIndir()); + ContainCheckStoreIndir(frameUpd->AsStoreInd()); DISPTREERANGE(firstBlockRange, frameUpd); } #endif // TARGET_64BIT @@ -4163,7 +4163,7 @@ void Lowering::InsertPInvokeMethodEpilog(BasicBlock* returnBB DEBUGARG(GenTree* { GenTree* frameUpd = CreateFrameLinkUpdate(PopFrame); returnBlockRange.InsertBefore(insertionPoint, LIR::SeqTree(comp, frameUpd)); - ContainCheckStoreIndir(frameUpd->AsIndir()); + ContainCheckStoreIndir(frameUpd->AsStoreInd()); } } @@ -4325,7 +4325,7 @@ void Lowering::InsertPInvokeCallProlog(GenTreeCall* call) // Stubs do this once per stub, not once per call. GenTree* frameUpd = CreateFrameLinkUpdate(PushFrame); BlockRange().InsertBefore(insertBefore, LIR::SeqTree(comp, frameUpd)); - ContainCheckStoreIndir(frameUpd->AsIndir()); + ContainCheckStoreIndir(frameUpd->AsStoreInd()); } #endif // TARGET_64BIT @@ -4335,7 +4335,7 @@ void Lowering::InsertPInvokeCallProlog(GenTreeCall* call) // [tcb + offsetOfGcState] = 0 GenTree* storeGCState = SetGCState(0); BlockRange().InsertBefore(insertBefore, LIR::SeqTree(comp, storeGCState)); - ContainCheckStoreIndir(storeGCState->AsIndir()); + ContainCheckStoreIndir(storeGCState->AsStoreInd()); // Indicate that codegen has switched this thread to preemptive GC. // This tree node doesn't generate any code, but impacts LSRA and gc reporting. @@ -4381,7 +4381,7 @@ void Lowering::InsertPInvokeCallEpilog(GenTreeCall* call) GenTree* tree = SetGCState(1); BlockRange().InsertBefore(insertionPoint, LIR::SeqTree(comp, tree)); - ContainCheckStoreIndir(tree->AsIndir()); + ContainCheckStoreIndir(tree->AsStoreInd()); tree = CreateReturnTrapSeq(); BlockRange().InsertBefore(insertionPoint, LIR::SeqTree(comp, tree)); @@ -4396,7 +4396,7 @@ void Lowering::InsertPInvokeCallEpilog(GenTreeCall* call) { tree = CreateFrameLinkUpdate(PopFrame); BlockRange().InsertBefore(insertionPoint, LIR::SeqTree(comp, tree)); - ContainCheckStoreIndir(tree->AsIndir()); + ContainCheckStoreIndir(tree->AsStoreInd()); } #else const CORINFO_EE_INFO::InlinedCallFrameInfo& callFrameInfo = comp->eeGetEEInfo()->inlinedCallFrameInfo; @@ -6421,7 +6421,7 @@ void Lowering::ContainCheckNode(GenTree* node) ContainCheckReturnTrap(node->AsOp()); break; case GT_STOREIND: - ContainCheckStoreIndir(node->AsIndir()); + ContainCheckStoreIndir(node->AsStoreInd()); break; case GT_IND: ContainCheckIndir(node->AsIndir()); @@ -6604,9 +6604,8 @@ void Lowering::ContainCheckBitCast(GenTree* node) // Arguments: // ind - the store indirection node we are lowering. // -void Lowering::LowerStoreIndirCommon(GenTreeIndir* ind) +void Lowering::LowerStoreIndirCommon(GenTreeStoreInd* ind) { - assert(ind->OperIs(GT_STOREIND)); assert(ind->TypeGet() != TYP_STRUCT); TryCreateAddrMode(ind->Addr(), true); if (!comp->codeGen->gcInfo.gcIsWriteBarrierStoreIndNode(ind)) @@ -6806,6 +6805,6 @@ bool Lowering::TryTransformStoreObjAsStoreInd(GenTreeBlk* blkNode) { assert(src->TypeIs(regType) || src->IsCnsIntOrI() || src->IsCall()); } - LowerStoreIndirCommon(blkNode); + LowerStoreIndirCommon(blkNode->AsStoreInd()); return true; } diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index 7f7c9d3760aa..a0d897e74da1 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -89,7 +89,7 @@ class Lowering final : public Phase void ContainCheckBitCast(GenTree* node); void ContainCheckCallOperands(GenTreeCall* call); void ContainCheckIndir(GenTreeIndir* indirNode); - void ContainCheckStoreIndir(GenTreeIndir* indirNode); + void ContainCheckStoreIndir(GenTreeStoreInd* indirNode); void ContainCheckMul(GenTreeOp* node); void ContainCheckShiftRotate(GenTreeOp* node); void ContainCheckStoreLoc(GenTreeLclVarCommon* storeLoc) const; @@ -292,9 +292,9 @@ class Lowering final : public Phase #endif // defined(TARGET_XARCH) // Per tree node member functions - void LowerStoreIndirCommon(GenTreeIndir* ind); + void LowerStoreIndirCommon(GenTreeStoreInd* ind); void LowerIndir(GenTreeIndir* ind); - void LowerStoreIndir(GenTreeIndir* node); + void LowerStoreIndir(GenTreeStoreInd* node); GenTree* LowerAdd(GenTreeOp* node); bool LowerUnsignedDivOrMod(GenTreeOp* divMod); GenTree* LowerConstIntDivOrMod(GenTree* node); diff --git a/src/coreclr/jit/lowerarmarch.cpp b/src/coreclr/jit/lowerarmarch.cpp index 7edf8c7103f1..134b77281f68 100644 --- a/src/coreclr/jit/lowerarmarch.cpp +++ b/src/coreclr/jit/lowerarmarch.cpp @@ -218,7 +218,7 @@ void Lowering::LowerStoreLoc(GenTreeLclVarCommon* storeLoc) // Return Value: // None. // -void Lowering::LowerStoreIndir(GenTreeIndir* node) +void Lowering::LowerStoreIndir(GenTreeStoreInd* node) { ContainCheckStoreIndir(node); } @@ -1376,11 +1376,11 @@ void Lowering::ContainCheckCallOperands(GenTreeCall* call) // Arguments: // node - pointer to the node // -void Lowering::ContainCheckStoreIndir(GenTreeIndir* node) +void Lowering::ContainCheckStoreIndir(GenTreeStoreInd* node) { #ifdef TARGET_ARM64 - GenTree* src = node->AsOp()->gtOp2; - if (!varTypeIsFloating(src->TypeGet()) && src->IsIntegralConst(0)) + GenTree* src = node->Data(); + if (src->IsIntegralConst(0)) { // an integer zero for 'src' can be contained. MakeSrcContained(node, src); diff --git a/src/coreclr/jit/lowerxarch.cpp b/src/coreclr/jit/lowerxarch.cpp index ed889f7f383b..43c0df620423 100644 --- a/src/coreclr/jit/lowerxarch.cpp +++ b/src/coreclr/jit/lowerxarch.cpp @@ -110,11 +110,11 @@ void Lowering::LowerStoreLoc(GenTreeLclVarCommon* storeLoc) // Return Value: // None. // -void Lowering::LowerStoreIndir(GenTreeIndir* node) +void Lowering::LowerStoreIndir(GenTreeStoreInd* node) { // Mark all GT_STOREIND nodes to indicate that it is not known // whether it represents a RMW memory op. - node->AsStoreInd()->SetRMWStatusDefault(); + node->SetRMWStatusDefault(); if (!varTypeIsFloating(node)) { @@ -130,10 +130,10 @@ void Lowering::LowerStoreIndir(GenTreeIndir* node) return; } } - else if (node->AsStoreInd()->Data()->OperIs(GT_CNS_DBL)) + else if (node->Data()->IsCnsFltOrDbl()) { // Optimize *x = DCON to *x = ICON which is slightly faster on xarch - GenTree* data = node->AsStoreInd()->Data(); + GenTree* data = node->Data(); double dblCns = data->AsDblCon()->gtDconVal; ssize_t intCns = 0; var_types type = TYP_UNKNOWN; @@ -162,6 +162,13 @@ void Lowering::LowerStoreIndir(GenTreeIndir* node) node->ChangeType(type); } } + + // Optimization: do not unnecessarily zero-extend the result of setcc. + if (varTypeIsByte(node) && (node->Data()->OperIsCompare() || node->Data()->OperIs(GT_SETCC))) + { + node->Data()->ChangeType(TYP_BYTE); + } + ContainCheckStoreIndir(node); } @@ -4588,17 +4595,18 @@ void Lowering::ContainCheckIndir(GenTreeIndir* node) // Arguments: // node - pointer to the node // -void Lowering::ContainCheckStoreIndir(GenTreeIndir* node) +void Lowering::ContainCheckStoreIndir(GenTreeStoreInd* node) { // If the source is a containable immediate, make it contained, unless it is // an int-size or larger store of zero to memory, because we can generate smaller code // by zeroing a register and then storing it. - GenTree* src = node->AsOp()->gtOp2; + GenTree* src = node->Data(); if (IsContainableImmed(node, src) && - (!src->IsIntegralConst(0) || varTypeIsSmall(node) || node->gtGetOp1()->OperGet() == GT_CLS_VAR_ADDR)) + (!src->IsIntegralConst(0) || varTypeIsSmall(node) || node->Addr()->OperIs(GT_CLS_VAR_ADDR))) { MakeSrcContained(node, src); } + ContainCheckIndir(node); } diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 68f31af76363..b6ee82d3b807 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -12087,23 +12087,8 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) tree->AsOp()->gtOp2 = op2 = op2->AsCast()->CastOp(); } } - else if (op2->OperIsCompare() && varTypeIsByte(effectiveOp1->TypeGet())) - { - /* We don't need to zero extend the setcc instruction */ - op2->gtType = TYP_BYTE; - } } - // If we introduced a CSE we may need to undo the optimization above - // (i.e. " op2->gtType = TYP_BYTE;" which depends upon op1 being a GT_IND of a byte type) - // When we introduce the CSE we remove the GT_IND and subsitute a GT_LCL_VAR in it place. - else if (op2->OperIsCompare() && (op2->gtType == TYP_BYTE) && (op1->gtOper == GT_LCL_VAR)) - { - unsigned varNum = op1->AsLclVarCommon()->GetLclNum(); - LclVarDsc* varDsc = &lvaTable[varNum]; - /* We again need to zero extend the setcc instruction */ - op2->gtType = varDsc->TypeGet(); - } fgAssignSetVarDef(tree); /* We can't CSE the LHS of an assignment */ @@ -12345,9 +12330,6 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) gtReverseCond(op1); } - /* Propagate gtType of tree into op1 in case it is TYP_BYTE for setcc optimization */ - op1->gtType = tree->gtType; - noway_assert((op1->gtFlags & GTF_RELOP_JMP_USED) == 0); op1->gtFlags |= tree->gtFlags & (GTF_RELOP_JMP_USED | GTF_RELOP_QMARK | GTF_DONT_CSE); From 2d5415894cea63bfb3da1f756b8d40105622f8ac Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 12 Jul 2021 21:40:24 -0500 Subject: [PATCH 040/133] [main] Update dependencies from 6 repositories (#55395) * Update dependencies from https://github.com/dotnet/runtime-assets build 20210708.1 System.ComponentModel.TypeConverter.TestData , System.Drawing.Common.TestData , System.IO.Compression.TestData , System.IO.Packaging.TestData , System.Net.TestData , System.Private.Runtime.UnicodeData , System.Runtime.Numerics.TestData , System.Runtime.TimeZoneData , System.Security.Cryptography.X509Certificates.TestData , System.Windows.Extensions.TestData From Version 6.0.0-beta.21356.1 -> To Version 6.0.0-beta.21358.1 * Update dependencies from https://github.com/mono/linker build 20210708.3 Microsoft.NET.ILLink.Tasks From Version 6.0.100-preview.6.21357.1 -> To Version 6.0.100-preview.6.21358.3 * Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-optimization build 20210709.4 optimization.linux-x64.MIBC.Runtime , optimization.windows_nt-x64.MIBC.Runtime , optimization.windows_nt-x86.MIBC.Runtime , optimization.PGO.CoreCLR From Version 1.0.0-prerelease.21357.3 -> To Version 1.0.0-prerelease.21359.4 * Update dependencies from https://github.com/dotnet/hotreload-utils build 20210709.1 Microsoft.DotNet.HotReload.Utils.Generator.BuildTool From Version 1.0.1-alpha.0.21355.1 -> To Version 1.0.1-alpha.0.21359.1 * Update dependencies from https://github.com/dotnet/hotreload-utils build 20210710.1 Microsoft.DotNet.HotReload.Utils.Generator.BuildTool From Version 1.0.1-alpha.0.21355.1 -> To Version 1.0.1-alpha.0.21360.1 * Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-optimization build 20210711.3 optimization.linux-x64.MIBC.Runtime , optimization.windows_nt-x64.MIBC.Runtime , optimization.windows_nt-x86.MIBC.Runtime , optimization.PGO.CoreCLR From Version 1.0.0-prerelease.21357.3 -> To Version 1.0.0-prerelease.21361.3 * Update dependencies from https://github.com/dotnet/runtime build 20210711.10 Microsoft.NETCore.DotNetHost , Microsoft.NETCore.DotNetHostPolicy , Microsoft.NETCore.ILAsm , runtime.native.System.IO.Ports , Microsoft.NET.Sdk.IL , System.Runtime.CompilerServices.Unsafe , System.Text.Json From Version 6.0.0-preview.7.21355.1 -> To Version 6.0.0-preview.7.21361.10 * Update dependencies from https://github.com/dotnet/hotreload-utils build 20210711.1 Microsoft.DotNet.HotReload.Utils.Generator.BuildTool From Version 1.0.1-alpha.0.21355.1 -> To Version 1.0.1-alpha.0.21361.1 * Update dependencies from https://github.com/dotnet/emsdk build 20210712.1 Microsoft.NET.Workload.Emscripten.Manifest-6.0.100 From Version 6.0.0-preview.7.21358.1 -> To Version 6.0.0-preview.7.21362.1 * Update dependencies from https://github.com/dotnet/emsdk build 20210712.2 Microsoft.NET.Workload.Emscripten.Manifest-6.0.100 From Version 6.0.0-preview.7.21358.1 -> To Version 6.0.0-preview.7.21362.2 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 96 ++++++++++++++++++++--------------------- eng/Versions.props | 44 +++++++++---------- global.json | 2 +- 3 files changed, 71 insertions(+), 71 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 2389066e4b33..73444fdb70f5 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -8,9 +8,9 @@ https://github.com/dotnet/msquic d7db669b70f4dd67ec001c192f9809c218cab88b - + https://github.com/dotnet/emsdk - 5c9145289bd4d4e14b18a544dda60a185f66f688 + 7e218d66bf9ec3ca4fc70c0b63e9a162f2e33451 @@ -82,41 +82,41 @@ https://github.com/microsoft/vstest 140434f7109d357d0158ade9e5164a4861513965 - + https://github.com/dotnet/runtime-assets - c6b17f3f85cb4ff078f7cd5264a9005f9b8c3334 + 3db3f0a34f73db72e5b918ad22e1fbe9f1c5c4da - + https://github.com/dotnet/runtime-assets - c6b17f3f85cb4ff078f7cd5264a9005f9b8c3334 + 3db3f0a34f73db72e5b918ad22e1fbe9f1c5c4da - + https://github.com/dotnet/runtime-assets - c6b17f3f85cb4ff078f7cd5264a9005f9b8c3334 + 3db3f0a34f73db72e5b918ad22e1fbe9f1c5c4da - + https://github.com/dotnet/runtime-assets - c6b17f3f85cb4ff078f7cd5264a9005f9b8c3334 + 3db3f0a34f73db72e5b918ad22e1fbe9f1c5c4da - + https://github.com/dotnet/runtime-assets - c6b17f3f85cb4ff078f7cd5264a9005f9b8c3334 + 3db3f0a34f73db72e5b918ad22e1fbe9f1c5c4da - + https://github.com/dotnet/runtime-assets - c6b17f3f85cb4ff078f7cd5264a9005f9b8c3334 + 3db3f0a34f73db72e5b918ad22e1fbe9f1c5c4da - + https://github.com/dotnet/runtime-assets - c6b17f3f85cb4ff078f7cd5264a9005f9b8c3334 + 3db3f0a34f73db72e5b918ad22e1fbe9f1c5c4da - + https://github.com/dotnet/runtime-assets - c6b17f3f85cb4ff078f7cd5264a9005f9b8c3334 + 3db3f0a34f73db72e5b918ad22e1fbe9f1c5c4da - + https://github.com/dotnet/runtime-assets - c6b17f3f85cb4ff078f7cd5264a9005f9b8c3334 + 3db3f0a34f73db72e5b918ad22e1fbe9f1c5c4da https://github.com/dotnet/llvm-project @@ -154,37 +154,37 @@ https://github.com/dotnet/runtime 38017c3935de95d0335bac04f4901ddfc2718656 - + https://github.com/dotnet/runtime - 5c340e9ade0baf7f3c0aa0a9128bf36b158fe7d6 + 98b7ed1a3b0543a31b5a0f9069cf44cb70c9230c - + https://github.com/dotnet/runtime - 5c340e9ade0baf7f3c0aa0a9128bf36b158fe7d6 + 98b7ed1a3b0543a31b5a0f9069cf44cb70c9230c - + https://github.com/dotnet/runtime - 5c340e9ade0baf7f3c0aa0a9128bf36b158fe7d6 + 98b7ed1a3b0543a31b5a0f9069cf44cb70c9230c - + https://github.com/dotnet/runtime - 5c340e9ade0baf7f3c0aa0a9128bf36b158fe7d6 + 98b7ed1a3b0543a31b5a0f9069cf44cb70c9230c - + https://github.com/dotnet/runtime - 5c340e9ade0baf7f3c0aa0a9128bf36b158fe7d6 + 98b7ed1a3b0543a31b5a0f9069cf44cb70c9230c - + https://github.com/dotnet/runtime - 5c340e9ade0baf7f3c0aa0a9128bf36b158fe7d6 + 98b7ed1a3b0543a31b5a0f9069cf44cb70c9230c - + https://github.com/dotnet/runtime - 5c340e9ade0baf7f3c0aa0a9128bf36b158fe7d6 + 98b7ed1a3b0543a31b5a0f9069cf44cb70c9230c - + https://github.com/mono/linker - 35a1c74d6a0dbd115bf079dc986cea59cdb01430 + b9501922637806f4135df09a9922d5540e203858 https://github.com/dotnet/xharness @@ -198,29 +198,29 @@ https://github.com/dotnet/arcade 286d98094b830b8dad769542b2669cb1b75f7097 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - a89f052e97fec59a2d0148c08d3b4801567ec200 + ae45cbdfa6d15fce7e3cf089462f0d2b55727273 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - a89f052e97fec59a2d0148c08d3b4801567ec200 + ae45cbdfa6d15fce7e3cf089462f0d2b55727273 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - a89f052e97fec59a2d0148c08d3b4801567ec200 + ae45cbdfa6d15fce7e3cf089462f0d2b55727273 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - a89f052e97fec59a2d0148c08d3b4801567ec200 + ae45cbdfa6d15fce7e3cf089462f0d2b55727273 - + https://github.com/dotnet/hotreload-utils - 3960ef9a8980181e840b5c1d64ed0b234711e850 + 640e908a67b5bc63fa615d31c7877e62c2b15062 - + https://github.com/dotnet/runtime-assets - c6b17f3f85cb4ff078f7cd5264a9005f9b8c3334 + 3db3f0a34f73db72e5b918ad22e1fbe9f1c5c4da https://github.com/dotnet/roslyn-analyzers diff --git a/eng/Versions.props b/eng/Versions.props index 7be0ee173f2f..24788db6f924 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -69,11 +69,11 @@ 6.0.0-preview.1.102 6.0.0-alpha.1.20612.4 - 6.0.0-preview.7.21355.1 - 6.0.0-preview.7.21355.1 + 6.0.0-preview.7.21361.10 + 6.0.0-preview.7.21361.10 3.1.0 - 6.0.0-preview.7.21355.1 + 6.0.0-preview.7.21361.10 5.0.0 4.3.0 @@ -107,27 +107,27 @@ 5.0.0 5.0.0 4.8.1 - 6.0.0-preview.7.21355.1 - 6.0.0-preview.7.21355.1 + 6.0.0-preview.7.21361.10 + 6.0.0-preview.7.21361.10 4.5.4 4.5.0 - 6.0.0-preview.7.21355.1 + 6.0.0-preview.7.21361.10 - 6.0.0-beta.21356.1 - 6.0.0-beta.21356.1 - 6.0.0-beta.21356.1 - 6.0.0-beta.21356.1 - 6.0.0-beta.21356.1 - 6.0.0-beta.21356.1 - 6.0.0-beta.21356.1 - 6.0.0-beta.21356.1 - 6.0.0-beta.21356.1 - 6.0.0-beta.21356.1 + 6.0.0-beta.21358.1 + 6.0.0-beta.21358.1 + 6.0.0-beta.21358.1 + 6.0.0-beta.21358.1 + 6.0.0-beta.21358.1 + 6.0.0-beta.21358.1 + 6.0.0-beta.21358.1 + 6.0.0-beta.21358.1 + 6.0.0-beta.21358.1 + 6.0.0-beta.21358.1 - 1.0.0-prerelease.21357.3 - 1.0.0-prerelease.21357.3 - 1.0.0-prerelease.21357.3 - 1.0.0-prerelease.21357.3 + 1.0.0-prerelease.21361.3 + 1.0.0-prerelease.21361.3 + 1.0.0-prerelease.21361.3 + 1.0.0-prerelease.21361.3 16.9.0-beta1.21055.5 2.0.0-beta1.20253.1 @@ -153,7 +153,7 @@ 16.9.0-preview-20201201-01 1.0.0-prerelease.21357.4 1.0.0-prerelease.21357.4 - 1.0.1-alpha.0.21355.1 + 1.0.1-alpha.0.21361.1 2.4.1 2.4.2 1.3.0 @@ -164,7 +164,7 @@ 5.0.0-preview-20201009.2 - 6.0.100-preview.6.21357.1 + 6.0.100-preview.6.21358.3 $(MicrosoftNETILLinkTasksVersion) 6.0.0-preview.7.21328.1 diff --git a/global.json b/global.json index 99807111962c..e9516bcfa8f5 100644 --- a/global.json +++ b/global.json @@ -19,6 +19,6 @@ "Microsoft.DotNet.SharedFramework.Sdk": "6.0.0-beta.21357.3", "Microsoft.Build.NoTargets": "3.0.4", "Microsoft.Build.Traversal": "3.0.23", - "Microsoft.NET.Sdk.IL": "6.0.0-preview.7.21355.1" + "Microsoft.NET.Sdk.IL": "6.0.0-preview.7.21361.10" } } From c7ebb65ac7bfd5958836073b52293a2b3dda7737 Mon Sep 17 00:00:00 2001 From: Manish Godse <61718172+mangod9@users.noreply.github.com> Date: Mon, 12 Jul 2021 19:48:55 -0700 Subject: [PATCH 041/133] switch to using CSTRMarshaller instead of AnsiBSTR (#55032) * switch to using CSTRMarshaller instead of AnsiBSTR * cleanup * Add CleanupManaged back Updated the cleanup logic to the one implemented in ILOptimizedAllocMarshaler::EmitClearNative * update MAX_LOCAL_BUFFER_LENGTH * CR feedback --- .../TypeSystem/Interop/IL/Marshaller.cs | 92 ++++++++++++++++++- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.cs b/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.cs index 46c53157b465..f78e4df8a29e 100644 --- a/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.cs +++ b/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.cs @@ -1581,6 +1581,12 @@ protected override void EmitCleanupManaged(ILCodeStream codeStream) class AnsiStringMarshaller : Marshaller { +#if READYTORUN + const int MAX_LOCAL_BUFFER_LENGTH = 260 + 1; // MAX_PATH + 1 + + private ILLocalVariable? _localBuffer = null; +#endif + internal override bool CleanupRequired { get @@ -1605,12 +1611,75 @@ protected override void TransformManagedToNative(ILCodeStream codeStream) #if READYTORUN var stringToAnsi = - Context.SystemModule.GetKnownType("System.StubHelpers", "AnsiBSTRMarshaler") + Context.SystemModule.GetKnownType("System.StubHelpers", "CSTRMarshaler") .GetKnownMethod("ConvertToNative", null); + + bool bPassByValueInOnly = In && !Out && !IsManagedByRef; + + if (bPassByValueInOnly) + { + var bufSize = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.Int32)); + _localBuffer = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.IntPtr)); + + // LocalBuffer = 0 + codeStream.Emit(ILOpcode.ldnull); + codeStream.EmitStLoc((ILLocalVariable)_localBuffer); + + var noOptimize = emitter.NewCodeLabel(); + + // if == NULL, goto NoOptimize + LoadManagedValue(codeStream); + codeStream.Emit(ILOpcode.brfalse, noOptimize); + + // String.Length + 2 + LoadManagedValue(codeStream); + var stringLen = + Context.GetWellKnownType(WellKnownType.String) + .GetKnownMethod("get_Length", null); + codeStream.Emit(ILOpcode.call, emitter.NewToken(stringLen)); + codeStream.EmitLdc(2); + codeStream.Emit(ILOpcode.add); + + // (String.Length + 2) * GetMaxDBCSCharByteSize() + codeStream.Emit(ILOpcode.ldsfld, emitter.NewToken(Context.SystemModule.GetKnownType( + "System.Runtime.InteropServices","Marshal") + .GetKnownField("SystemMaxDBCSCharSize"))); + codeStream.Emit(ILOpcode.mul_ovf); + + // BufSize = (String.Length + 2) * GetMaxDBCSCharByteSize() + codeStream.EmitStLoc(bufSize); + + // if (MAX_LOCAL_BUFFER_LENGTH < BufSize ) goto NoOptimize + codeStream.EmitLdc(MAX_LOCAL_BUFFER_LENGTH + 1); + codeStream.EmitLdLoc(bufSize); + codeStream.Emit(ILOpcode.clt); + codeStream.Emit(ILOpcode.brtrue, noOptimize); + + // LocalBuffer = localloc(BufSize); + codeStream.EmitLdLoc(bufSize); + codeStream.Emit(ILOpcode.localloc); + codeStream.EmitStLoc((ILLocalVariable)_localBuffer); + + // NoOptimize: + codeStream.EmitLabel(noOptimize); + } + int flags = (PInvokeFlags.BestFitMapping ? 0x1 : 0) | (PInvokeFlags.ThrowOnUnmappableChar ? 0x100 : 0); + + // CSTRMarshaler.ConvertToNative pManaged, dwAnsiMarshalFlags, pLocalBuffer codeStream.EmitLdc(flags); LoadManagedValue(codeStream); + + if (_localBuffer.HasValue) + { + codeStream.EmitLdLoc((ILLocalVariable)_localBuffer); + } + else + { + codeStream.Emit(ILOpcode.ldnull); + } + codeStream.Emit(ILOpcode.call, emitter.NewToken(stringToAnsi)); #else LoadManagedValue(codeStream); @@ -1631,7 +1700,7 @@ protected override void TransformNativeToManaged(ILCodeStream codeStream) #if READYTORUN var ansiToString = - Context.SystemModule.GetKnownType("System.StubHelpers", "AnsiBSTRMarshaler") + Context.SystemModule.GetKnownType("System.StubHelpers", "CSTRMarshaler") .GetKnownMethod("ConvertToManaged", null); #else var ansiToString = Context.GetHelperEntryPoint("InteropHelpers", "AnsiStringToString"); @@ -1645,11 +1714,28 @@ protected override void EmitCleanupManaged(ILCodeStream codeStream) { var emitter = _ilCodeStreams.Emitter; #if READYTORUN + var optimize = emitter.NewCodeLabel(); + MethodDesc clearNative = - Context.SystemModule.GetKnownType("System.StubHelpers", "AnsiBSTRMarshaler") + Context.SystemModule.GetKnownType("System.StubHelpers", "CSTRMarshaler") .GetKnownMethod("ClearNative", null); + + if (_localBuffer.HasValue) + { + // if (m_dwLocalBuffer) goto Optimize + codeStream.EmitLdLoc((ILLocalVariable)_localBuffer); + codeStream.Emit(ILOpcode.brtrue, optimize); + } + LoadNativeValue(codeStream); + // static void m_idClearNative(IntPtr ptr) codeStream.Emit(ILOpcode.call, emitter.NewToken(clearNative)); + + // Optimize: + if (_localBuffer != default) + { + codeStream.EmitLabel(optimize); + } #else var lNullCheck = emitter.NewCodeLabel(); From 66967685e97c48c416f1887530735925b2500c73 Mon Sep 17 00:00:00 2001 From: Juan Hoyos Date: Mon, 12 Jul 2021 20:05:15 -0700 Subject: [PATCH 042/133] Disable GetHostEntry tests on SLES. (#55543) --- .../tests/FunctionalTests/GetHostEntryTest.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs index 7a5d4c899e92..bfb8b48cc4e7 100644 --- a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs +++ b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs @@ -28,7 +28,9 @@ public async Task Dns_GetHostEntryAsync_IPAddress_Ok() // [ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)] PlatformDetection.IsNotOSX && // [ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - !PlatformDetection.IsiOS && !PlatformDetection.IstvOS && !PlatformDetection.IsMacCatalyst; + !PlatformDetection.IsiOS && !PlatformDetection.IstvOS && !PlatformDetection.IsMacCatalyst && + // [ActiveIssue("https://github.com/dotnet/runtime/issues/55271")] + !PlatformDetection.IsSLES; [ConditionalTheory(nameof(GetHostEntryWorks))] [InlineData("")] From 7b19ccefccb4d116a64bf09c9bb1db3dd1df35e8 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Mon, 12 Jul 2021 21:11:25 -0700 Subject: [PATCH 043/133] Enable property visibility tests in source-gen mode (#54526) * Enable property visibility tests in source-gen mode * Address feedback * Fix S.N.H.Json tests * Fix S.T.J tests * Fix S.N.H.J tests ii --- .../FunctionalTests/JsonContext/Person.cs | 12 + .../System.Text.Json/Common/JsonHelpers.cs | 28 + .../Common/ReflectionExtensions.cs | 17 + .../gen/ContextGenerationSpec.cs | 4 +- .../gen/JsonSourceGenerator.Emitter.cs | 387 ++- .../gen/JsonSourceGenerator.Parser.cs | 199 +- .../gen/JsonSourceGenerator.cs | 20 +- .../gen/PropertyGenerationSpec.cs | 14 +- .../gen/Reflection/AssemblyWrapper.cs | 2 +- .../gen/Reflection/ConstructorInfoWrapper.cs | 2 +- .../Reflection/CustomAttributeDataWrapper.cs | 2 +- .../gen/Reflection/FieldInfoWrapper.cs | 10 +- .../gen/Reflection/MemberInfoWrapper.cs | 2 +- .../Reflection/MetadataLoadContextInternal.cs | 2 +- .../gen/Reflection/MethodInfoWrapper.cs | 7 +- .../gen/Reflection/ParameterInfoWrapper.cs | 2 +- .../gen/Reflection/PropertyInfoWrapper.cs | 2 +- .../gen/Reflection/ReflectionExtensions.cs | 4 +- .../gen/Reflection/RoslynExtensions.cs | 2 +- .../gen/Reflection/TypeExtensions.cs | 2 +- .../gen/Reflection/TypeWrapper.cs | 46 +- .../System.Text.Json.SourceGeneration.csproj | 3 + .../gen/TypeGenerationSpec.cs | 89 +- .../System.Text.Json/ref/System.Text.Json.cs | 2 +- .../src/Resources/Strings.resx | 3 + .../src/System.Text.Json.csproj | 2 + .../src/System/Text/Json/JsonHelpers.cs | 19 - .../Metadata/JsonMetadataServices.cs | 17 +- .../Metadata/JsonPropertyInfo.cs | 25 +- .../Metadata/JsonPropertyInfoOfT.cs | 11 +- .../Metadata/JsonTypeInfo.Cache.cs | 41 +- .../Serialization/Metadata/JsonTypeInfo.cs | 115 +- .../Text/Json/ThrowHelper.Serialization.cs | 4 +- .../Common/JsonSerializerWrapperForString.cs | 27 + .../PropertyVisibilityTests.InitOnly.cs} | 21 +- ...pertyVisibilityTests.NonPublicAccessors.cs | 107 +- .../tests/Common/PropertyVisibilityTests.cs | 2632 +++++++++++++++++ .../tests/Common/SerializerTests.cs | 12 + .../TestClasses.ConcurrentCollections.cs | 0 .../TestClasses/TestClasses.Constructor.cs | 0 .../TestClasses.GenericCollections.cs | 0 .../TestClasses.ImmutableCollections.cs | 0 .../TestClasses.NonGenericCollections.cs | 0 .../TestClasses/TestClasses.Polymorphic.cs | 0 .../TestClasses.SimpleTestClass.cs | 0 .../TestClasses.SimpleTestClassWithFields.cs | 0 ...estClasses.SimpleTestClassWithNullables.cs | 0 .../TestClasses.SimpleTestClassWithObject.cs | 0 ...Classes.SimpleTestClassWithObjectArrays.cs | 0 ...Classes.SimpleTestClassWithSimpleObject.cs | 0 .../TestClasses.SimpleTestStruct.cs | 0 .../TestClasses.SimpleTestStructWithFields.cs | 0 .../TestClasses.ValueTypedMember.cs | 0 .../TestClasses/TestClasses.cs | 0 ...sonSerializerWrapperForString_SourceGen.cs | 102 + .../Serialization/PropertyVisibilityTests.cs | 424 +++ ...em.Text.Json.SourceGeneration.Tests.csproj | 29 + .../JsonSourceGeneratorTests.cs | 7 +- .../TypeWrapperTests.cs | 6 +- .../JsonSerializerWrapperForString.cs | 18 +- .../JsonSerializerWrapperForString_Dynamic.cs | 32 + .../MetadataTests/JsonContext/HighLowTemps.cs | 6 + .../JsonContext/WeatherForecastWithPOCOs.cs | 21 + .../MetadataTests.JsonMetadataServices.cs | 33 + .../MetadataTests.JsonSerializer.cs | 1 + .../Serialization/PropertyVisibilityTests.cs | 2547 +--------------- .../System.Text.Json.Tests.csproj | 42 +- 67 files changed, 4140 insertions(+), 3024 deletions(-) create mode 100644 src/libraries/System.Text.Json/Common/JsonHelpers.cs create mode 100644 src/libraries/System.Text.Json/Common/ReflectionExtensions.cs create mode 100644 src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapperForString.cs rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization/PropertyVisiblityTests.InitOnly.cs => Common/PropertyVisibilityTests.InitOnly.cs} (73%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/PropertyVisibilityTests.NonPublicAccessors.cs (66%) create mode 100644 src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs create mode 100644 src/libraries/System.Text.Json/tests/Common/SerializerTests.cs rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.ConcurrentCollections.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.Constructor.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.GenericCollections.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.ImmutableCollections.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.NonGenericCollections.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.Polymorphic.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.SimpleTestClass.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.SimpleTestClassWithFields.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.SimpleTestClassWithNullables.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.SimpleTestClassWithObject.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.SimpleTestClassWithObjectArrays.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.SimpleTestClassWithSimpleObject.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.SimpleTestStruct.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.SimpleTestStructWithFields.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.ValueTypedMember.cs (100%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/TestClasses/TestClasses.cs (100%) create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapperForString_SourceGen.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapperForString_Dynamic.cs diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs index 36785e95bd05..8049845e1a21 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs @@ -47,12 +47,15 @@ private static JsonPropertyInfo[] PersonPropInitFunc(JsonSerializerContext conte properties[0] = JsonMetadataServices.CreatePropertyInfo( options, isProperty: true, + isPublic: true, + isVirtual: false, declaringType: typeof(Person), propertyTypeInfo: jsonContext.Int32, converter: null, getter: static (obj) => { return ((Person)obj).Age; }, setter: static (obj, value) => { ((Person)obj).Age = value; }, ignoreCondition: default, + hasJsonInclude: false, numberHandling: default, propertyName: nameof(Tests.Person.Age), jsonPropertyName: null); @@ -60,12 +63,15 @@ private static JsonPropertyInfo[] PersonPropInitFunc(JsonSerializerContext conte properties[1] = JsonMetadataServices.CreatePropertyInfo( options, isProperty: true, + isPublic: true, + isVirtual: false, declaringType: typeof(Person), propertyTypeInfo: jsonContext.String, converter: null, getter: static (obj) => { return ((Person)obj).Name; }, setter: static (obj, value) => { ((Person)obj).Name = value; }, ignoreCondition: default, + hasJsonInclude: false, numberHandling: default, propertyName: nameof(Tests.Person.Name), jsonPropertyName: null); @@ -73,12 +79,15 @@ private static JsonPropertyInfo[] PersonPropInitFunc(JsonSerializerContext conte properties[2] = JsonMetadataServices.CreatePropertyInfo( options, isProperty: true, + isPublic: true, + isVirtual: false, declaringType: typeof(Person), propertyTypeInfo: jsonContext.Person, converter: null, getter: static (obj) => { return ((Person)obj).Parent; }, setter: static (obj, value) => { ((Person)obj).Parent = value; }, ignoreCondition: default, + hasJsonInclude: false, numberHandling: default, propertyName: nameof(Tests.Person.Parent), jsonPropertyName: null); @@ -86,12 +95,15 @@ private static JsonPropertyInfo[] PersonPropInitFunc(JsonSerializerContext conte properties[3] = JsonMetadataServices.CreatePropertyInfo( options, isProperty: true, + isPublic: true, + isVirtual: false, declaringType: typeof(Person), propertyTypeInfo: jsonContext.String, converter: null, getter: static (obj) => { return ((Person)obj).PlaceOfBirth; }, setter: static (obj, value) => { ((Person)obj).PlaceOfBirth = value; }, ignoreCondition: default, + hasJsonInclude: false, numberHandling: default, propertyName: nameof(Tests.Person.PlaceOfBirth), jsonPropertyName: null); diff --git a/src/libraries/System.Text.Json/Common/JsonHelpers.cs b/src/libraries/System.Text.Json/Common/JsonHelpers.cs new file mode 100644 index 000000000000..96a2872621f4 --- /dev/null +++ b/src/libraries/System.Text.Json/Common/JsonHelpers.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Text.Json +{ + internal static partial class JsonHelpers + { + /// + /// Emulates Dictionary.TryAdd on netstandard. + /// + public static bool TryAdd(this Dictionary dictionary, in TKey key, in TValue value) where TKey : notnull + { +#if NETSTANDARD2_0 || NETFRAMEWORK + if (!dictionary.ContainsKey(key)) + { + dictionary[key] = value; + return true; + } + + return false; +#else + return dictionary.TryAdd(key, value); +#endif + } + } +} diff --git a/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs b/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs new file mode 100644 index 000000000000..5d9ef545b8d4 --- /dev/null +++ b/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Reflection; + +namespace System.Text.Json.Reflection +{ + internal static partial class ReflectionExtensions + { + public static bool IsVirtual(this PropertyInfo? propertyInfo) + { + Debug.Assert(propertyInfo != null); + return propertyInfo != null && (propertyInfo.GetMethod?.IsVirtual == true || propertyInfo.SetMethod?.IsVirtual == true); + } + } +} diff --git a/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs index f4b27c2db118..8020f798086e 100644 --- a/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -using System.Text.Json.SourceGeneration.Reflection; +using System.Text.Json.Reflection; namespace System.Text.Json.SourceGeneration { @@ -19,6 +19,8 @@ internal sealed class ContextGenerationSpec public List RootSerializableTypes { get; } = new(); + public HashSet? NullableUnderlyingTypes { get; } = new(); + public List ContextClassDeclarationList { get; init; } /// diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 811a4ea8365e..cbab663971ee 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; @@ -35,8 +37,9 @@ private sealed partial class Emitter private const string ArrayTypeRef = "global::System.Array"; private const string InvalidOperationExceptionTypeRef = "global::System.InvalidOperationException"; private const string TypeTypeRef = "global::System.Type"; - private const string UnsafeTypeRef = "global::System.CompilerServices.Unsafe"; + private const string UnsafeTypeRef = "global::System.Runtime.CompilerServices.Unsafe"; private const string NullableTypeRef = "global::System.Nullable"; + private const string EqualityComparerTypeRef = "global::System.Collections.Generic.EqualityComparer"; private const string IListTypeRef = "global::System.Collections.Generic.IList"; private const string KeyValuePairTypeRef = "global::System.Collections.Generic.KeyValuePair"; private const string ListTypeRef = "global::System.Collections.Generic.List"; @@ -210,9 +213,9 @@ private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec) { source = GenerateForObject(typeGenerationSpec); - if (typeGenerationSpec.PropertiesMetadata != null) + if (typeGenerationSpec.PropertyGenSpecList != null) { - foreach (PropertyGenerationSpec metadata in typeGenerationSpec.PropertiesMetadata) + foreach (PropertyGenerationSpec metadata in typeGenerationSpec.PropertyGenSpecList) { GenerateTypeInfo(metadata.TypeGenerationSpec); } @@ -279,7 +282,7 @@ private string GenerateForTypeWithUnknownConverter(TypeGenerationSpec typeMetada }} // Allow nullable handling to forward to the underlying type's converter. - converter = {JsonMetadataServicesTypeRef}.GetNullableConverter<{typeCompilableName}>(({JsonConverterTypeRef}<{typeCompilableName}>)actualConverter); + converter = {JsonMetadataServicesTypeRef}.GetNullableConverter<{typeCompilableName}>(this.{typeFriendlyName}); }} else {{ @@ -471,7 +474,6 @@ private string GenerateFastPathFuncForDictionary( private string GenerateForObject(TypeGenerationSpec typeMetadata) { - string typeCompilableName = typeMetadata.TypeRef; string typeFriendlyName = typeMetadata.TypeInfoPropertyName; string createObjectFuncTypeArg = typeMetadata.ConstructionStrategy == ObjectConstructionStrategy.ParameterlessConstructor @@ -486,11 +488,9 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata) string? serializeFuncSource = null; string serializeFuncNamedArg; - List? properties = typeMetadata.PropertiesMetadata; - if (typeMetadata.GenerateMetadata) { - propMetadataInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata.IsValueType, propInitMethodName, properties); + propMetadataInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata.IsValueType, propInitMethodName, typeMetadata.PropertyGenSpecList!); propMetadataInitFuncNamedArg = $@"propInitFunc: {propInitMethodName}"; } else @@ -500,13 +500,7 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata) if (typeMetadata.GenerateSerializationLogic) { - serializeFuncSource = GenerateFastPathFuncForObject( - typeCompilableName, - serializeMethodName, - typeMetadata.CanBeNull, - typeMetadata.ImplementsIJsonOnSerialized, - typeMetadata.ImplementsIJsonOnSerializing, - properties); + serializeFuncSource = GenerateFastPathFuncForObject(typeMetadata, serializeMethodName); serializeFuncNamedArg = $@"serializeFunc: {serializeMethodName}"; } else @@ -514,14 +508,12 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata) serializeFuncNamedArg = @"serializeFunc: null"; } - string objectInfoInitSource = $@"{JsonTypeInfoTypeRef}<{typeCompilableName}> objectInfo = {JsonMetadataServicesTypeRef}.CreateObjectInfo<{typeCompilableName}>( + string objectInfoInitSource = $@"_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.CreateObjectInfo<{typeMetadata.TypeRef}>( {OptionsInstanceVariableName}, {createObjectFuncTypeArg}, {propMetadataInitFuncNamedArg}, {GetNumberHandlingAsStr(typeMetadata.NumberHandling)}, - {serializeFuncNamedArg}); - - _{typeFriendlyName} = objectInfo;"; + {serializeFuncNamedArg});"; string additionalSource; if (propMetadataInitFuncSource == null || serializeFuncSource == null) @@ -539,14 +531,16 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata) private string GeneratePropMetadataInitFunc( bool declaringTypeIsValueType, string propInitMethodName, - List? properties) + List properties) { const string PropVarName = "properties"; const string JsonContextVarName = "jsonContext"; + int propCount = properties.Count; + string propertyArrayInstantiationValue = properties == null ? $"{ArrayTypeRef}.Empty<{JsonPropertyInfoTypeRef}>()" - : $"new {JsonPropertyInfoTypeRef}[{properties.Count}]"; + : $"new {JsonPropertyInfoTypeRef}[{propCount}]"; string contextTypeRef = _currentContext.ContextTypeRef; @@ -562,72 +556,72 @@ private string GeneratePropMetadataInitFunc( {JsonPropertyInfoTypeRef}[] {PropVarName} = {propertyArrayInstantiationValue}; "); - if (properties != null) + for (int i = 0; i < propCount; i++) { - for (int i = 0; i < properties.Count; i++) - { - PropertyGenerationSpec memberMetadata = properties[i]; + PropertyGenerationSpec memberMetadata = properties[i]; - TypeGenerationSpec memberTypeMetadata = memberMetadata.TypeGenerationSpec; + TypeGenerationSpec memberTypeMetadata = memberMetadata.TypeGenerationSpec; - string clrPropertyName = memberMetadata.ClrName; + string clrPropertyName = memberMetadata.ClrName; - string declaringTypeCompilableName = memberMetadata.DeclaringTypeRef; + string declaringTypeCompilableName = memberMetadata.DeclaringTypeRef; - string memberTypeFriendlyName = memberTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen - ? "null" - : $"{JsonContextVarName}.{memberTypeMetadata.TypeInfoPropertyName}"; + string memberTypeFriendlyName = memberTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen + ? "null" + : $"{JsonContextVarName}.{memberTypeMetadata.TypeInfoPropertyName}"; - string typeTypeInfoNamedArg = $"propertyTypeInfo: {memberTypeFriendlyName}"; + string typeTypeInfoNamedArg = $"propertyTypeInfo: {memberTypeFriendlyName}"; - string jsonPropertyNameNamedArg = memberMetadata.JsonPropertyName != null - ? @$"jsonPropertyName: ""{memberMetadata.JsonPropertyName}""" - : "jsonPropertyName: null"; + string jsonPropertyNameNamedArg = memberMetadata.JsonPropertyName != null + ? @$"jsonPropertyName: ""{memberMetadata.JsonPropertyName}""" + : "jsonPropertyName: null"; - string getterNamedArg = memberMetadata.CanUseGetter - ? $"getter: static (obj) => {{ return (({declaringTypeCompilableName})obj).{clrPropertyName}; }}" - : "getter: null"; + string getterNamedArg = memberMetadata.CanUseGetter + ? $"getter: static (obj) => (({declaringTypeCompilableName})obj).{clrPropertyName}" + : "getter: null"; - string setterNamedArg; - if (memberMetadata.CanUseSetter) - { - string propMutation = declaringTypeIsValueType - ? @$"{{ {UnsafeTypeRef}.Unbox<{declaringTypeCompilableName}>(obj).{clrPropertyName} = value; }}" - : $@"{{ (({declaringTypeCompilableName})obj).{clrPropertyName} = value; }}"; + string setterNamedArg; + if (memberMetadata.CanUseSetter) + { + string propMutation = declaringTypeIsValueType + ? @$"{UnsafeTypeRef}.Unbox<{declaringTypeCompilableName}>(obj).{clrPropertyName} = value" + : $@"(({declaringTypeCompilableName})obj).{clrPropertyName} = value"; - setterNamedArg = $"setter: static (obj, value) => {propMutation}"; - } - else - { - setterNamedArg = "setter: null"; - } + setterNamedArg = $"setter: static (obj, value) => {propMutation}"; + } + else + { + setterNamedArg = "setter: null"; + } - JsonIgnoreCondition? ignoreCondition = memberMetadata.DefaultIgnoreCondition; - string ignoreConditionNamedArg = ignoreCondition.HasValue - ? $"ignoreCondition: JsonIgnoreCondition.{ignoreCondition.Value}" - : "ignoreCondition: default"; + JsonIgnoreCondition? ignoreCondition = memberMetadata.DefaultIgnoreCondition; + string ignoreConditionNamedArg = ignoreCondition.HasValue + ? $"ignoreCondition: {JsonIgnoreConditionTypeRef}.{ignoreCondition.Value}" + : "ignoreCondition: null"; - string converterNamedArg = memberMetadata.ConverterInstantiationLogic == null - ? "converter: null" - : $"converter: {memberMetadata.ConverterInstantiationLogic}"; + string converterNamedArg = memberMetadata.ConverterInstantiationLogic == null + ? "converter: null" + : $"converter: {memberMetadata.ConverterInstantiationLogic}"; - string memberTypeCompilableName = memberTypeMetadata.TypeRef; + string memberTypeCompilableName = memberTypeMetadata.TypeRef; - sb.Append($@" + sb.Append($@" {PropVarName}[{i}] = {JsonMetadataServicesTypeRef}.CreatePropertyInfo<{memberTypeCompilableName}>( options, - isProperty: {memberMetadata.IsProperty.ToString().ToLowerInvariant()}, + isProperty: {ToCSharpKeyword(memberMetadata.IsProperty)}, + isPublic: {ToCSharpKeyword(memberMetadata.IsPublic)}, + isVirtual: {ToCSharpKeyword(memberMetadata.IsVirtual)}, declaringType: typeof({memberMetadata.DeclaringTypeRef}), {typeTypeInfoNamedArg}, {converterNamedArg}, {getterNamedArg}, {setterNamedArg}, {ignoreConditionNamedArg}, + hasJsonInclude: {ToCSharpKeyword(memberMetadata.HasJsonInclude)}, numberHandling: {GetNumberHandlingAsStr(memberMetadata.NumberHandling)}, propertyName: ""{clrPropertyName}"", {jsonPropertyNameNamedArg}); "); - } } sb.Append(@$" @@ -637,24 +631,29 @@ private string GeneratePropMetadataInitFunc( return sb.ToString(); } - private string GenerateFastPathFuncForObject( - string typeInfoTypeRef, - string serializeMethodName, - bool canBeNull, - bool implementsIJsonOnSerialized, - bool implementsIJsonOnSerializing, - List? properties) + private string GenerateFastPathFuncForObject(TypeGenerationSpec typeGenSpec, string serializeMethodName) { JsonSourceGenerationOptionsAttribute options = _currentContext.GenerationOptions; + string typeRef = typeGenSpec.TypeRef; - // Add the property names to the context-wide cache; we'll generate the source to initialize them at the end of generation. - string[] runtimePropNames = GetRuntimePropNames(properties, options.PropertyNamingPolicy); - _currentContext.RuntimePropertyNames.UnionWith(runtimePropNames); + if (!typeGenSpec.TryFilterSerializableProps( + options, + out Dictionary? serializableProperties, + out bool castingRequiredForProps)) + { + string exceptionMessage = @$"""Invalid serializable-property configuration specified for type '{typeRef}'. For more information, use 'JsonSourceGenerationMode.Serialization'."""; + + return GenerateFastPathFuncForType( + serializeMethodName, + typeRef, + $@"throw new {InvalidOperationExceptionTypeRef}({exceptionMessage});", + canBeNull: false); // Skip null check since we want to throw an exception straightaway. + } StringBuilder sb = new(); - // Begin method definition - if (implementsIJsonOnSerializing) + // Begin method logic. + if (typeGenSpec.ImplementsIJsonOnSerializing) { sb.Append($@"(({IJsonOnSerializingFullName}){ValueVarName}).OnSerializing();"); sb.Append($@"{Environment.NewLine} "); @@ -662,98 +661,119 @@ private string GenerateFastPathFuncForObject( sb.Append($@"{WriterVarName}.WriteStartObject();"); - if (properties != null) + // Provide generation logic for each prop. + Debug.Assert(serializableProperties != null); + + foreach (PropertyGenerationSpec propertyGenSpec in serializableProperties.Values) { - // Provide generation logic for each prop. - for (int i = 0; i < properties.Count; i++) + if (!ShouldIncludePropertyForFastPath(propertyGenSpec, options)) { - PropertyGenerationSpec propertySpec = properties[i]; - TypeGenerationSpec propertyTypeSpec = propertySpec.TypeGenerationSpec; - - if (propertyTypeSpec.ClassType == ClassType.TypeUnsupportedBySourceGen) - { - continue; - } + continue; + } - if (propertySpec.IsReadOnly) - { - if (propertySpec.IsProperty) - { - if (options.IgnoreReadOnlyProperties) - { - continue; - } - } - else if (options.IgnoreReadOnlyFields) - { - continue; - } - } + TypeGenerationSpec propertyTypeSpec = propertyGenSpec.TypeGenerationSpec; - if (!propertySpec.IsProperty && !propertySpec.HasJsonInclude && !options.IncludeFields) - { - continue; - } + string runtimePropName = propertyGenSpec.RuntimePropertyName; - Type propertyType = propertyTypeSpec.Type; - string propName = $"{runtimePropNames[i]}PropName"; - string propValue = $"{ValueVarName}.{propertySpec.ClrName}"; - string methodArgs = $"{propName}, {propValue}"; + // Add the property names to the context-wide cache; we'll generate the source to initialize them at the end of generation. + _currentContext.RuntimePropertyNames.Add(runtimePropName); - string? methodToCall = GetWriterMethod(propertyType); + Type propertyType = propertyTypeSpec.Type; + string propName = $"{runtimePropName}PropName"; + string? objectRef = castingRequiredForProps ? $"(({propertyGenSpec.DeclaringTypeRef}){ValueVarName})" : ValueVarName; + string propValue = $"{objectRef}.{propertyGenSpec.ClrName}"; + string methodArgs = $"{propName}, {propValue}"; - if (propertyType == _generationSpec.CharType) - { - methodArgs = $"{methodArgs}.ToString()"; - } + string? methodToCall = GetWriterMethod(propertyType); - string serializationLogic; + if (propertyType == _generationSpec.CharType) + { + methodArgs = $"{methodArgs}.ToString()"; + } - if (methodToCall != null) - { - serializationLogic = $@" - {methodToCall}({methodArgs});"; - } - else - { - serializationLogic = $@" - {WriterVarName}.WritePropertyName({propName}); - {GetSerializeLogicForNonPrimitiveType(propertyTypeSpec.TypeInfoPropertyName, propValue, propertyTypeSpec.GenerateSerializationLogic)}"; - } + string serializationLogic; - JsonIgnoreCondition ignoreCondition = propertySpec.DefaultIgnoreCondition ?? options.DefaultIgnoreCondition; - DefaultCheckType defaultCheckType; - bool typeCanBeNull = propertyTypeSpec.CanBeNull; + if (methodToCall != null) + { + serializationLogic = $@" + {methodToCall}({methodArgs});"; + } + else + { + serializationLogic = $@" + {WriterVarName}.WritePropertyName({propName}); + {GetSerializeLogicForNonPrimitiveType(propertyTypeSpec.TypeInfoPropertyName, propValue, propertyTypeSpec.GenerateSerializationLogic)}"; + } - switch (ignoreCondition) - { - case JsonIgnoreCondition.WhenWritingNull: - defaultCheckType = typeCanBeNull ? DefaultCheckType.Null : DefaultCheckType.None; - break; - case JsonIgnoreCondition.WhenWritingDefault: - defaultCheckType = typeCanBeNull ? DefaultCheckType.Null : DefaultCheckType.Default; - break; - default: - defaultCheckType = DefaultCheckType.None; - break; - } + JsonIgnoreCondition ignoreCondition = propertyGenSpec.DefaultIgnoreCondition ?? options.DefaultIgnoreCondition; + DefaultCheckType defaultCheckType; + bool typeCanBeNull = propertyTypeSpec.CanBeNull; - sb.Append(WrapSerializationLogicInDefaultCheckIfRequired(serializationLogic, propValue, defaultCheckType)); + switch (ignoreCondition) + { + case JsonIgnoreCondition.WhenWritingNull: + defaultCheckType = typeCanBeNull ? DefaultCheckType.Null : DefaultCheckType.None; + break; + case JsonIgnoreCondition.WhenWritingDefault: + defaultCheckType = typeCanBeNull ? DefaultCheckType.Null : DefaultCheckType.Default; + break; + default: + defaultCheckType = DefaultCheckType.None; + break; } + + sb.Append(WrapSerializationLogicInDefaultCheckIfRequired(serializationLogic, propValue, propertyTypeSpec.TypeRef, defaultCheckType)); } - // End method definition + // End method logic. sb.Append($@" - {WriterVarName}.WriteEndObject();"); + {WriterVarName}.WriteEndObject();"); - if (implementsIJsonOnSerialized) + if (typeGenSpec.ImplementsIJsonOnSerialized) { sb.Append($@"{Environment.NewLine} "); sb.Append($@"(({IJsonOnSerializedFullName}){ValueVarName}).OnSerialized();"); }; - return GenerateFastPathFuncForType(serializeMethodName, typeInfoTypeRef, sb.ToString(), canBeNull); + return GenerateFastPathFuncForType(serializeMethodName, typeRef, sb.ToString(), typeGenSpec.CanBeNull); + } + + private static bool ShouldIncludePropertyForFastPath(PropertyGenerationSpec propertyGenSpec, JsonSourceGenerationOptionsAttribute options) + { + TypeGenerationSpec propertyTypeSpec = propertyGenSpec.TypeGenerationSpec; + + if (propertyTypeSpec.ClassType == ClassType.TypeUnsupportedBySourceGen || !propertyGenSpec.CanUseGetter) + { + return false; + } + + if (!propertyGenSpec.IsProperty && !propertyGenSpec.HasJsonInclude && !options.IncludeFields) + { + return false; + } + + if (propertyGenSpec.DefaultIgnoreCondition == JsonIgnoreCondition.Always) + { + return false; + } + + if (propertyGenSpec.IsReadOnly) + { + if (propertyGenSpec.IsProperty) + { + if (options.IgnoreReadOnlyProperties) + { + return false; + } + } + else if (options.IgnoreReadOnlyFields) + { + return false; + } + } + + return true; } private string? GetWriterMethod(Type type) @@ -829,62 +849,28 @@ private enum DefaultCheckType Default, } - private string WrapSerializationLogicInDefaultCheckIfRequired(string serializationLogic, string propValue, DefaultCheckType defaultCheckType) - { - if (defaultCheckType == DefaultCheckType.None) - { - return serializationLogic; - } - - string defaultLiteral = defaultCheckType == DefaultCheckType.Null ? "null" : "default"; - return $@" - if ({propValue} != {defaultLiteral}) - {{{serializationLogic} - }}"; - } - - private string[] GetRuntimePropNames(List? properties, JsonKnownNamingPolicy namingPolicy) + private string WrapSerializationLogicInDefaultCheckIfRequired(string serializationLogic, string propValue, string propTypeRef, DefaultCheckType defaultCheckType) { - if (properties == null) - { - return Array.Empty(); - } - - int propCount = properties.Count; - string[] runtimePropNames = new string[propCount]; + string comparisonLogic; - // Compute JsonEncodedText values to represent each property name. This gives the best throughput performance - for (int i = 0; i < propCount; i++) + switch (defaultCheckType) { - PropertyGenerationSpec propertySpec = properties[i]; - - string propName = DetermineRuntimePropName(propertySpec.ClrName, propertySpec.JsonPropertyName, namingPolicy); - Debug.Assert(propName != null); - - runtimePropNames[i] = propName; - } - - return runtimePropNames; - } - - private string DetermineRuntimePropName(string clrPropName, string? jsonPropName, JsonKnownNamingPolicy namingPolicy) - { - string runtimePropName; - - if (jsonPropName != null) - { - runtimePropName = jsonPropName; - } - else if (namingPolicy == JsonKnownNamingPolicy.CamelCase) - { - runtimePropName = JsonNamingPolicy.CamelCase.ConvertName(clrPropName); - } - else - { - runtimePropName = clrPropName; + case DefaultCheckType.None: + return serializationLogic; + case DefaultCheckType.Null: + comparisonLogic = $"{propValue} != null"; + break; + case DefaultCheckType.Default: + comparisonLogic = $"!{EqualityComparerTypeRef}<{propTypeRef}>.Default.Equals(default, {propValue})"; + break; + default: + throw new InvalidOperationException(); } - return runtimePropName; + return $@" + if ({comparisonLogic}) + {{{IndentSource(serializationLogic, numIndentations: 1)} + }}"; } private string GenerateForType(TypeGenerationSpec typeMetadata, string metadataInitSource, string? additionalSource = null) @@ -964,10 +950,10 @@ private string GetLogicForDefaultSerializerOptionsInit() private static {JsonSerializerOptionsTypeRef} {DefaultOptionsStaticVarName} {{ get; }} = new {JsonSerializerOptionsTypeRef}() {{ DefaultIgnoreCondition = {JsonIgnoreConditionTypeRef}.{options.DefaultIgnoreCondition}, - IgnoreReadOnlyFields = {options.IgnoreReadOnlyFields.ToString().ToLowerInvariant()}, - IgnoreReadOnlyProperties = {options.IgnoreReadOnlyProperties.ToString().ToLowerInvariant()}, - IncludeFields = {options.IncludeFields.ToString().ToLowerInvariant()}, - WriteIndented = {options.WriteIndented.ToString().ToLowerInvariant()},{namingPolicyInit} + IgnoreReadOnlyFields = {ToCSharpKeyword(options.IgnoreReadOnlyFields)}, + IgnoreReadOnlyProperties = {ToCSharpKeyword(options.IgnoreReadOnlyProperties)}, + IncludeFields = {ToCSharpKeyword(options.IncludeFields)}, + WriteIndented = {ToCSharpKeyword(options.WriteIndented)},{namingPolicyInit} }};"; } @@ -1013,8 +999,11 @@ private string GetGetTypeInfoImplementation() sb.Append(@$"public override {JsonTypeInfoTypeRef} GetTypeInfo({TypeTypeRef} type) {{"); + HashSet types = new(_currentContext.RootSerializableTypes); + types.UnionWith(_currentContext.NullableUnderlyingTypes); + // TODO (https://github.com/dotnet/runtime/issues/52218): Make this Dictionary-lookup-based if root-serializable type count > 64. - foreach (TypeGenerationSpec metadata in _currentContext.RootSerializableTypes) + foreach (TypeGenerationSpec metadata in types) { if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen) { @@ -1065,5 +1054,7 @@ private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling) private static string GetCreateValueInfoMethodRef(string typeCompilableName) => $"{CreateValueInfoMethodName}<{typeCompilableName}>"; } + + private static string ToCSharpKeyword(bool value) => value.ToString().ToLowerInvariant(); } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 70c8a412b6e5..cefb9de2d716 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -4,15 +4,15 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Reflection; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; +using System.Text.Json.Reflection; using System.Text.Json.Serialization; -using System.Text.Json.SourceGeneration.Reflection; -using System.Linq; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; namespace System.Text.Json.SourceGeneration { @@ -62,6 +62,10 @@ private sealed class Parser /// private readonly Dictionary _typeGenerationSpecCache = new(); + private readonly HashSet _nullableTypeGenerationSpecCache = new(); + + private JsonKnownNamingPolicy _currentContextNamingPolicy; + private static DiagnosticDescriptor ContextClassesMustBePartial { get; } = new DiagnosticDescriptor( id: "SYSLIB1032", title: new LocalizableResourceString(nameof(SR.ContextClassesMustBePartialTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), @@ -164,7 +168,10 @@ public Parser(in GeneratorExecutionContext executionContext) ContextClassDeclarationList = classDeclarationList }; - foreach(AttributeSyntax attribute in serializableAttributeList) + // Set the naming policy for the current context. + _currentContextNamingPolicy = contextGenSpec.GenerationOptions.PropertyNamingPolicy; + + foreach (AttributeSyntax attribute in serializableAttributeList) { TypeGenerationSpec? metadata = GetRootSerializableType(compilationSemanticModel, attribute, contextGenSpec.GenerationOptions.GenerationMode); if (metadata != null) @@ -178,11 +185,14 @@ public Parser(in GeneratorExecutionContext executionContext) continue; } + contextGenSpec.NullableUnderlyingTypes.UnionWith(_nullableTypeGenerationSpecCache); + contextGenSpecList ??= new List(); contextGenSpecList.Add(contextGenSpec); // Clear the cache of generated metadata between the processing of context classes. _typeGenerationSpecCache.Clear(); + _nullableTypeGenerationSpecCache.Clear(); } if (contextGenSpecList == null) @@ -231,7 +241,7 @@ private bool DerivesFromJsonSerializerContext( return match != null; } - private static bool TryGetClassDeclarationList(INamedTypeSymbol typeSymbol, [NotNullWhenAttribute(true)] out List? classDeclarationList) + private static bool TryGetClassDeclarationList(INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out List? classDeclarationList) { INamedTypeSymbol currentSymbol = typeSymbol; classDeclarationList = null; @@ -479,14 +489,14 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener } // Add metadata to cache now to prevent stack overflow when the same type is found somewhere else in the object graph. - typeMetadata = new(); + typeMetadata = new TypeGenerationSpec(); _typeGenerationSpecCache[type] = typeMetadata; ClassType classType; Type? collectionKeyType = null; Type? collectionValueType = null; - Type? nullableUnderlyingType = null; - List? propertiesMetadata = null; + TypeGenerationSpec? nullableUnderlyingTypeGenSpec = null; + List? propGenSpecList = null; CollectionType collectionType = CollectionType.NotApplicable; ObjectConstructionStrategy constructionStrategy = default; JsonNumberHandling? numberHandling = null; @@ -524,10 +534,12 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener { classType = ClassType.KnownType; } - else if (type.IsNullableValueType(_nullableOfTType, out nullableUnderlyingType)) + else if (type.IsNullableValueType(_nullableOfTType, out Type? nullableUnderlyingType)) { Debug.Assert(nullableUnderlyingType != null); classType = ClassType.Nullable; + nullableUnderlyingTypeGenSpec = GetOrAddTypeGenerationSpec(nullableUnderlyingType, generationMode); + _nullableTypeGenerationSpecCache.Add(nullableUnderlyingTypeGenSpec); } else if (type.IsEnum) { @@ -585,38 +597,40 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener implementsIJsonOnSerialized = interfaces.FirstOrDefault(interfaceName => interfaceName == IJsonOnSerializedFullName) != null; implementsIJsonOnSerializing = interfaces.FirstOrDefault(interfaceName => interfaceName == IJsonOnSerializingFullName) != null; + propGenSpecList = new List(); + Dictionary? ignoredMembers = null; + + const BindingFlags bindingFlags = + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.DeclaredOnly; + for (Type? currentType = type; currentType != null; currentType = currentType.BaseType) { - const BindingFlags bindingFlags = - BindingFlags.Instance | - BindingFlags.Public | - BindingFlags.NonPublic | - BindingFlags.DeclaredOnly; - foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags)) { - PropertyGenerationSpec metadata = GetPropertyGenerationSpec(propertyInfo, generationMode); + bool isVirtual = propertyInfo.IsVirtual(); - // Ignore indexers. - if (propertyInfo.GetIndexParameters().Length > 0) + if (propertyInfo.GetIndexParameters().Length > 0 || + PropertyIsOverridenAndIgnored(propertyInfo.Name, propertyInfo.PropertyType, isVirtual, ignoredMembers)) { continue; } - if (metadata.CanUseGetter || metadata.CanUseSetter) - { - (propertiesMetadata ??= new()).Add(metadata); - } + PropertyGenerationSpec spec = GetPropertyGenerationSpec(propertyInfo, isVirtual, generationMode); + CacheMember(spec, ref propGenSpecList, ref ignoredMembers); } foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags)) { - PropertyGenerationSpec metadata = GetPropertyGenerationSpec(fieldInfo, generationMode); - - if (metadata.CanUseGetter || metadata.CanUseSetter) + if (PropertyIsOverridenAndIgnored(fieldInfo.Name, fieldInfo.FieldType, currentMemberIsVirtual: false, ignoredMembers)) { - (propertiesMetadata ??= new()).Add(metadata); + continue; } + + PropertyGenerationSpec spec = GetPropertyGenerationSpec(fieldInfo, isVirtual: false, generationMode); + CacheMember(spec, ref propGenSpecList, ref ignoredMembers); } } } @@ -629,12 +643,12 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener classType, isValueType: type.IsValueType, numberHandling, - propertiesMetadata, + propGenSpecList, collectionType, collectionKeyTypeMetadata: collectionKeyType != null ? GetOrAddTypeGenerationSpec(collectionKeyType, generationMode) : null, collectionValueTypeMetadata: collectionValueType != null ? GetOrAddTypeGenerationSpec(collectionValueType, generationMode) : null, constructionStrategy, - nullableUnderlyingTypeMetadata: nullableUnderlyingType != null ? GetOrAddTypeGenerationSpec(nullableUnderlyingType, generationMode) : null, + nullableUnderlyingTypeMetadata: nullableUnderlyingTypeGenSpec, converterInstatiationLogic, implementsIJsonOnSerialized, implementsIJsonOnSerializing); @@ -642,7 +656,37 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener return typeMetadata; } - private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, JsonSourceGenerationMode generationMode) + private void CacheMember( + PropertyGenerationSpec propGenSpec, + ref List propGenSpecList, + ref Dictionary ignoredMembers) + { + propGenSpecList.Add(propGenSpec); + + if (propGenSpec.DefaultIgnoreCondition == JsonIgnoreCondition.Always) + { + ignoredMembers ??= new Dictionary(); + ignoredMembers.Add(propGenSpec.ClrName, propGenSpec); + } + } + + private static bool PropertyIsOverridenAndIgnored( + string currentMemberName, + Type currentMemberType, + bool currentMemberIsVirtual, + Dictionary? ignoredMembers) + { + if (ignoredMembers == null || !ignoredMembers.TryGetValue(currentMemberName, out PropertyGenerationSpec? ignoredMember)) + { + return false; + } + + return currentMemberType == ignoredMember.TypeGenerationSpec.Type && + currentMemberIsVirtual && + ignoredMember.IsVirtual; + } + + private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, bool isVirtual, JsonSourceGenerationMode generationMode) { IList attributeDataList = CustomAttributeData.GetCustomAttributes(memberInfo); @@ -709,8 +753,9 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, Type memberCLRType; bool isReadOnly; - bool canUseGetter; - bool canUseSetter; + bool isPublic = false; + bool canUseGetter = false; + bool canUseSetter = false; bool getterIsVirtual = false; bool setterIsVirtual = false; @@ -718,33 +763,75 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, { case PropertyInfo propertyInfo: { - MethodInfo setMethod = propertyInfo.SetMethod; memberCLRType = propertyInfo.PropertyType; - isReadOnly = setMethod == null; - canUseGetter = PropertyAccessorCanBeReferenced(propertyInfo.GetMethod, hasJsonInclude); - canUseSetter = PropertyAccessorCanBeReferenced(setMethod, hasJsonInclude) && !setMethod.IsInitOnly(); - getterIsVirtual = propertyInfo.GetMethod?.IsVirtual == true; - setterIsVirtual = propertyInfo.SetMethod?.IsVirtual == true; + + MethodInfo? getMethod = propertyInfo.GetMethod; + MethodInfo? setMethod = propertyInfo.SetMethod; + + if (getMethod != null) + { + if (getMethod.IsPublic) + { + isPublic = true; + canUseGetter = true; + } + else if (getMethod.IsAssembly) + { + canUseGetter = hasJsonInclude; + } + + getterIsVirtual = getMethod.IsVirtual; + } + + if (setMethod != null) + { + isReadOnly = false; + + if (setMethod.IsPublic) + { + isPublic = true; + canUseSetter = !setMethod.IsInitOnly(); + } + else if (setMethod.IsAssembly) + { + canUseSetter = hasJsonInclude && !setMethod.IsInitOnly(); + } + + setterIsVirtual = setMethod.IsVirtual; + } + else + { + isReadOnly = true; + } } break; case FieldInfo fieldInfo: { - Debug.Assert(fieldInfo.IsPublic); memberCLRType = fieldInfo.FieldType; + isPublic = fieldInfo.IsPublic; isReadOnly = fieldInfo.IsInitOnly; - canUseGetter = true; - canUseSetter = !isReadOnly; + + if (!fieldInfo.IsPrivate && !fieldInfo.IsFamily) + { + canUseGetter = true; + canUseSetter = !isReadOnly; + } } break; default: throw new InvalidOperationException(); } + string clrName = memberInfo.Name; + return new PropertyGenerationSpec { - ClrName = memberInfo.Name, + ClrName = clrName, IsProperty = memberInfo.MemberType == MemberTypes.Property, + IsPublic = isPublic, + IsVirtual = isVirtual, JsonPropertyName = jsonPropertyName, + RuntimePropertyName = DetermineRuntimePropName(clrName, jsonPropertyName, _currentContextNamingPolicy), IsReadOnly = isReadOnly, CanUseGetter = canUseGetter, CanUseSetter = canUseSetter, @@ -759,8 +846,8 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, }; } - private static bool PropertyAccessorCanBeReferenced(MethodInfo? memberAccessor, bool hasJsonInclude) => - (memberAccessor != null && !memberAccessor.IsPrivate) && (memberAccessor.IsPublic || hasJsonInclude); + private static bool PropertyAccessorCanBeReferenced(MethodInfo? accessor) + => accessor != null && (accessor.IsPublic || accessor.IsAssembly); private string? GetConverterInstantiationLogic(CustomAttributeData attributeData) { @@ -780,6 +867,26 @@ private static bool PropertyAccessorCanBeReferenced(MethodInfo? memberAccessor, return $"new {converterType.GetUniqueCompilableTypeName()}()"; } + private static string DetermineRuntimePropName(string clrPropName, string? jsonPropName, JsonKnownNamingPolicy namingPolicy) + { + string runtimePropName; + + if (jsonPropName != null) + { + runtimePropName = jsonPropName; + } + else if (namingPolicy == JsonKnownNamingPolicy.CamelCase) + { + runtimePropName = JsonNamingPolicy.CamelCase.ConvertName(clrPropName); + } + else + { + runtimePropName = clrPropName; + } + + return runtimePropName; + } + private void PopulateNumberTypes() { Debug.Assert(_numberTypes != null); diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs index 7e8c45ff4299..399b893e8e8e 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs @@ -5,7 +5,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Text.Json.SourceGeneration.Reflection; +using System.Text.Json.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -37,6 +37,12 @@ public void Initialize(GeneratorInitializationContext context) /// public void Execute(GeneratorExecutionContext executionContext) { +#if LAUNCH_DEBUGGER + if (!Diagnostics.Debugger.IsAttached) + { + Diagnostics.Debugger.Launch(); + } +#endif SyntaxReceiver receiver = (SyntaxReceiver)executionContext.SyntaxReceiver; List? contextClasses = receiver.ClassDeclarationSyntaxList; if (contextClasses == null) @@ -55,12 +61,6 @@ public void Execute(GeneratorExecutionContext executionContext) } } - /// - /// Helper for unit tests. - /// - public Dictionary? GetSerializableTypes() => _rootTypes?.ToDictionary(p => p.Type.FullName, p => p.Type); - private List? _rootTypes; - private sealed class SyntaxReceiver : ISyntaxReceiver { public List? ClassDeclarationSyntaxList { get; private set; } @@ -73,5 +73,11 @@ public void OnVisitSyntaxNode(SyntaxNode syntaxNode) } } } + + /// + /// Helper for unit tests. + /// + public Dictionary? GetSerializableTypes() => _rootTypes?.ToDictionary(p => p.Type.FullName, p => p.Type); + private List? _rootTypes; } } diff --git a/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs b/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs index 25ae73692ecb..5d52ae2e3553 100644 --- a/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs @@ -9,9 +9,6 @@ namespace System.Text.Json.SourceGeneration [DebuggerDisplay("Name={Name}, Type={TypeMetadata}")] internal sealed class PropertyGenerationSpec { - /// - /// The CLR name of the property. - /// public string ClrName { get; init; } /// @@ -19,11 +16,22 @@ internal sealed class PropertyGenerationSpec /// public bool IsProperty { get; init; } + public bool IsPublic { get; init; } + + public bool IsVirtual { get; init; } + /// /// The property name specified via JsonPropertyNameAttribute, if available. /// public string? JsonPropertyName { get; init; } + /// + /// The pre-determined JSON property name, accounting for + /// specified ahead-of-time via . + /// Only used in fast-path serialization logic. + /// + public string RuntimePropertyName { get; init; } + /// /// Whether the property has a set method. /// diff --git a/src/libraries/System.Text.Json/gen/Reflection/AssemblyWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/AssemblyWrapper.cs index d5ed28e4800a..a3282b698443 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/AssemblyWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/AssemblyWrapper.cs @@ -5,7 +5,7 @@ using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Text.Json.SourceGeneration.Reflection +namespace System.Text.Json.Reflection { internal class AssemblyWrapper : Assembly { diff --git a/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs index 979d32c8f7c9..42d167a46220 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs @@ -6,7 +6,7 @@ using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Text.Json.SourceGeneration.Reflection +namespace System.Text.Json.Reflection { internal class ConstructorInfoWrapper : ConstructorInfo { diff --git a/src/libraries/System.Text.Json/gen/Reflection/CustomAttributeDataWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/CustomAttributeDataWrapper.cs index 75b31db587a8..e518212ce65b 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/CustomAttributeDataWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/CustomAttributeDataWrapper.cs @@ -6,7 +6,7 @@ using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Text.Json.SourceGeneration.Reflection +namespace System.Text.Json.Reflection { internal class CustomAttributeDataWrapper : CustomAttributeData { diff --git a/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs index cf36ceacbeee..66e00c0de20b 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs @@ -6,7 +6,7 @@ using Microsoft.CodeAnalysis; using System.Globalization; -namespace System.Text.Json.SourceGeneration.Reflection +namespace System.Text.Json.Reflection { internal class FieldInfoWrapper : FieldInfo { @@ -33,6 +33,11 @@ public override FieldAttributes Attributes _attributes |= FieldAttributes.Static; } + if (_field.IsReadOnly) + { + _attributes |= FieldAttributes.InitOnly; + } + switch (_field.DeclaredAccessibility) { case Accessibility.Public: @@ -41,6 +46,9 @@ public override FieldAttributes Attributes case Accessibility.Private: _attributes |= FieldAttributes.Private; break; + case Accessibility.Protected: + _attributes |= FieldAttributes.Family; + break; } } diff --git a/src/libraries/System.Text.Json/gen/Reflection/MemberInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/MemberInfoWrapper.cs index 1b4de7811fd7..ccfd0fb7c6e4 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/MemberInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/MemberInfoWrapper.cs @@ -5,7 +5,7 @@ using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Text.Json.SourceGeneration.Reflection +namespace System.Text.Json.Reflection { internal class MemberInfoWrapper : MemberInfo { diff --git a/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs b/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs index 885885325fe5..d12f3f8ed60f 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs @@ -8,7 +8,7 @@ using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis; -namespace System.Text.Json.SourceGeneration.Reflection +namespace System.Text.Json.Reflection { internal class MetadataLoadContextInternal { diff --git a/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs index 44eecf0b5919..637483847e59 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs @@ -6,7 +6,7 @@ using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Text.Json.SourceGeneration.Reflection +namespace System.Text.Json.Reflection { internal class MethodInfoWrapper : MethodInfo { @@ -41,7 +41,7 @@ public override MethodAttributes Attributes _attributes |= MethodAttributes.Static; } - if (_method.IsVirtual) + if (_method.IsVirtual || _method.IsOverride) { _attributes |= MethodAttributes.Virtual; } @@ -54,6 +54,9 @@ public override MethodAttributes Attributes case Accessibility.Private: _attributes |= MethodAttributes.Private; break; + case Accessibility.Internal: + _attributes |= MethodAttributes.Assembly; + break; } } diff --git a/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs index b8891251c7e6..ad9091017728 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs @@ -5,7 +5,7 @@ using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Text.Json.SourceGeneration.Reflection +namespace System.Text.Json.Reflection { internal class ParameterInfoWrapper : ParameterInfo { diff --git a/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs index 294c6dd10591..bfb593f992fb 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs @@ -6,7 +6,7 @@ using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Text.Json.SourceGeneration.Reflection +namespace System.Text.Json.Reflection { internal class PropertyInfoWrapper : PropertyInfo { diff --git a/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs index 1c0619ddda58..599cae49aebd 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs @@ -5,9 +5,9 @@ using System.Linq; using System.Reflection; -namespace System.Text.Json.SourceGeneration.Reflection +namespace System.Text.Json.Reflection { - internal static class ReflectionExtensions + internal static partial class ReflectionExtensions { public static CustomAttributeData GetCustomAttributeData(this MemberInfo memberInfo, Type type) { diff --git a/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs index 610e7fe4eef6..3dd1e925e2f1 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs @@ -5,7 +5,7 @@ using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Text.Json.SourceGeneration.Reflection +namespace System.Text.Json.Reflection { internal static class RoslynExtensions { diff --git a/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs index 8ff487ab0c6e..6ad76f42b7b4 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs @@ -4,7 +4,7 @@ using System.Diagnostics; using System.Linq; -namespace System.Text.Json.SourceGeneration.Reflection +namespace System.Text.Json.Reflection { internal static class TypeExtensions { diff --git a/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs index 2ffc4725922a..abeed3dfaaf0 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs @@ -9,7 +9,7 @@ using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Text.Json.SourceGeneration.Reflection +namespace System.Text.Json.Reflection { internal class TypeWrapper : Type { @@ -231,18 +231,32 @@ public override FieldInfo GetField(string name, BindingFlags bindingAttr) public override FieldInfo[] GetFields(BindingFlags bindingAttr) { - var fields = new List(); + List fields = new(); + foreach (ISymbol item in _typeSymbol.GetMembers()) { - // Associated Symbol checks the field is not a backingfield. - if (item is IFieldSymbol field && field.AssociatedSymbol == null && !field.IsReadOnly) + if (item is IFieldSymbol fieldSymbol) { - if ((item.DeclaredAccessibility & Accessibility.Public) == Accessibility.Public) + // Skip if: + if ( + // this is a backing field + fieldSymbol.AssociatedSymbol != null || + // we want a static field and this is not static + (BindingFlags.Static & bindingAttr) != 0 && !fieldSymbol.IsStatic || + // we want an instance field and this is static or a constant + (BindingFlags.Instance & bindingAttr) != 0 && (fieldSymbol.IsStatic || fieldSymbol.IsConst)) + { + continue; + } + + if ((BindingFlags.Public & bindingAttr) != 0 && item.DeclaredAccessibility == Accessibility.Public || + (BindingFlags.NonPublic & bindingAttr) != 0) { - fields.Add(new FieldInfoWrapper(field, _metadataLoadContext)); + fields.Add(new FieldInfoWrapper(fieldSymbol, _metadataLoadContext)); } } } + return fields.ToArray(); } @@ -300,18 +314,28 @@ public override Type[] GetNestedTypes(BindingFlags bindingAttr) return nestedTypes.ToArray(); } - // TODO: make sure to use bindingAttr for correctness. Current implementation assumes public and non-static. public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) { - var properties = new List(); + List properties = new(); foreach (ISymbol item in _typeSymbol.GetMembers()) { - if (item is IPropertySymbol property) + if (item is IPropertySymbol propertySymbol) { - if ((item.DeclaredAccessibility & Accessibility.Public) == Accessibility.Public) + // Skip if: + if ( + // we want a static property and this is not static + (BindingFlags.Static & bindingAttr) != 0 && !propertySymbol.IsStatic || + // we want an instance property and this is static + (BindingFlags.Instance & bindingAttr) != 0 && propertySymbol.IsStatic) + { + continue; + } + + if ((BindingFlags.Public & bindingAttr) != 0 && item.DeclaredAccessibility == Accessibility.Public || + (BindingFlags.NonPublic & bindingAttr) != 0) { - properties.Add(new PropertyInfoWrapper(property, _metadataLoadContext)); + properties.Add(new PropertyInfoWrapper(propertySymbol, _metadataLoadContext)); } } } diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj index 3e449e71aab1..008a5b221895 100644 --- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj @@ -13,6 +13,7 @@ $(DefineConstants);BUILDING_SOURCE_GENERATOR + $(DefineConstants);LAUNCH_DEBUGGER @@ -27,12 +28,14 @@ + + diff --git a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs index 36a0c58a17a3..865134823f9a 100644 --- a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs @@ -3,8 +3,9 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Reflection; using System.Text.Json.Serialization; -using System.Text.Json.SourceGeneration.Reflection; namespace System.Text.Json.SourceGeneration { @@ -42,7 +43,7 @@ internal class TypeGenerationSpec public JsonNumberHandling? NumberHandling { get; private set; } - public List? PropertiesMetadata { get; private set; } + public List? PropertyGenSpecList { get; private set; } public CollectionType CollectionType { get; private set; } @@ -64,7 +65,7 @@ public void Initialize( ClassType classType, bool isValueType, JsonNumberHandling? numberHandling, - List? propertiesMetadata, + List? propertyGenSpecList, CollectionType collectionType, TypeGenerationSpec? collectionKeyTypeMetadata, TypeGenerationSpec? collectionValueTypeMetadata, @@ -82,7 +83,7 @@ public void Initialize( IsValueType = isValueType; CanBeNull = !isValueType || nullableUnderlyingTypeMetadata != null; NumberHandling = numberHandling; - PropertiesMetadata = propertiesMetadata; + PropertyGenSpecList = propertyGenSpecList; CollectionType = collectionType; CollectionKeyTypeMetadata = collectionKeyTypeMetadata; CollectionValueTypeMetadata = collectionValueTypeMetadata; @@ -93,6 +94,86 @@ public void Initialize( ImplementsIJsonOnSerializing = implementsIJsonOnSerializing; } + public bool TryFilterSerializableProps( + JsonSourceGenerationOptionsAttribute options, + [NotNullWhen(true)] out Dictionary? serializableProperties, + out bool castingRequiredForProps) + { + serializableProperties = new Dictionary(); + Dictionary? ignoredMembers = null; + + for (int i = 0; i < PropertyGenSpecList.Count; i++) + { + PropertyGenerationSpec propGenSpec = PropertyGenSpecList[i]; + bool hasJsonInclude = propGenSpec.HasJsonInclude; + JsonIgnoreCondition? ignoreCondition = propGenSpec.DefaultIgnoreCondition; + + if (ignoreCondition == JsonIgnoreCondition.WhenWritingNull && !propGenSpec.TypeGenerationSpec.CanBeNull) + { + goto ReturnFalse; + } + + if (!propGenSpec.IsPublic) + { + if (hasJsonInclude) + { + goto ReturnFalse; + } + + continue; + } + + if (!propGenSpec.IsProperty && !hasJsonInclude && !options.IncludeFields) + { + continue; + } + + string memberName = propGenSpec.ClrName!; + + // The JsonPropertyNameAttribute or naming policy resulted in a collision. + if (!serializableProperties.TryAdd(propGenSpec.RuntimePropertyName, propGenSpec)) + { + PropertyGenerationSpec other = serializableProperties[propGenSpec.RuntimePropertyName]!; + + if (other.DefaultIgnoreCondition == JsonIgnoreCondition.Always) + { + // Overwrite previously cached property since it has [JsonIgnore]. + serializableProperties[propGenSpec.RuntimePropertyName] = propGenSpec; + } + else if ( + // Does the current property have `JsonIgnoreAttribute`? + propGenSpec.DefaultIgnoreCondition != JsonIgnoreCondition.Always && + // Is the current property hidden by the previously cached property + // (with `new` keyword, or by overriding)? + other.ClrName != memberName && + // Was a property with the same CLR name was ignored? That property hid the current property, + // thus, if it was ignored, the current property should be ignored too. + ignoredMembers?.ContainsKey(memberName) != true) + { + // We throw if we have two public properties that have the same JSON property name, and neither have been ignored. + serializableProperties = null; + castingRequiredForProps = false; + return false; + } + // Ignore the current property. + } + + if (propGenSpec.DefaultIgnoreCondition == JsonIgnoreCondition.Always) + { + (ignoredMembers ??= new Dictionary()).Add(memberName, propGenSpec); + } + } + + Debug.Assert(PropertyGenSpecList.Count >= serializableProperties.Count); + castingRequiredForProps = PropertyGenSpecList.Count > serializableProperties.Count; + return true; + +ReturnFalse: + serializableProperties = null; + castingRequiredForProps = false; + return false; + } + private bool FastPathIsSupported() { if (ClassType == ClassType.Object) diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index 2d74d3535c3f..8e87154e8035 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -932,7 +932,7 @@ public static partial class JsonMetadataServices public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateDictionaryInfo(System.Text.Json.JsonSerializerOptions options, System.Func createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo keyInfo, System.Text.Json.Serialization.Metadata.JsonTypeInfo valueInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.Dictionary where TKey : notnull { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateListInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.List { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateObjectInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Func? propInitFunc, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where T : notnull { throw null; } - public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo(System.Text.Json.JsonSerializerOptions options, bool isProperty, System.Type declaringType, System.Text.Json.Serialization.Metadata.JsonTypeInfo propertyTypeInfo, System.Text.Json.Serialization.JsonConverter? converter, System.Func? getter, System.Action? setter, System.Text.Json.Serialization.JsonIgnoreCondition ignoreCondition, System.Text.Json.Serialization.JsonNumberHandling numberHandling, string propertyName, string? jsonPropertyName) { throw null; } + public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo(System.Text.Json.JsonSerializerOptions options, bool isProperty, bool isPublic, bool isVirtual, System.Type declaringType, System.Text.Json.Serialization.Metadata.JsonTypeInfo propertyTypeInfo, System.Text.Json.Serialization.JsonConverter? converter, System.Func? getter, System.Action? setter, System.Text.Json.Serialization.JsonIgnoreCondition? ignoreCondition, bool hasJsonInclude, System.Text.Json.Serialization.JsonNumberHandling? numberHandling, string propertyName, string? jsonPropertyName) { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateValueInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) { throw null; } public static System.Text.Json.Serialization.JsonConverter GetEnumConverter(System.Text.Json.JsonSerializerOptions options) where T : struct { throw null; } public static System.Text.Json.Serialization.JsonConverter GetNullableConverter(System.Text.Json.Serialization.Metadata.JsonTypeInfo underlyingTypeInfo) where T : struct { throw null; } diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index c650f6ebad8b..e289dfbb1ea6 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -608,4 +608,7 @@ To specify a serialization implementation for type '{0}'', context '{0}' must specify default options. + + A 'field' member cannot be 'virtual'. See arguments for the '{0}' and '{1}' parameters. + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 9d94ae9bdd4f..195c640c85aa 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -22,12 +22,14 @@ + + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs index 8dce0c594e1f..0bdd85880bd0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs @@ -4,7 +4,6 @@ using System.Buffers; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace System.Text.Json @@ -100,24 +99,6 @@ public static string Utf8GetString(ReadOnlySpan bytes) ); } - /// - /// Emulates Dictionary.TryAdd on netstandard. - /// - public static bool TryAdd(Dictionary dictionary, in TKey key, in TValue value) where TKey : notnull - { -#if NETSTANDARD2_0 || NETFRAMEWORK - if (!dictionary.ContainsKey(key)) - { - dictionary[key] = value; - return true; - } - - return false; -#else - return dictionary.TryAdd(key, value); -#endif - } - /// /// Emulates Dictionary(IEnumerable{KeyValuePair}) on netstandard. /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs index 5268573219dd..731869ec4a9b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs @@ -18,6 +18,8 @@ public static partial class JsonMetadataServices /// The type that the converter for the property returns or accepts when converting JSON data. /// The to initialize the metadata with. /// Whether the CLR member is a property or field. + /// Whether the CLR member is public. + /// Whether the CLR member is a virtual property. /// The declaring type of the property or field. /// The info for the property or field's type. /// A for the property or field, specified by . @@ -25,19 +27,23 @@ public static partial class JsonMetadataServices /// Provides a mechanism to set the property or field's value. /// Specifies a condition for the property to be ignored. /// If the property or field is a number, specifies how it should processed when serializing and deserializing. + /// Whether the property was annotated with . /// The CLR name of the property or field. /// The name to be used when processing the property or field, specified by . /// A instance intialized with the provided metadata. public static JsonPropertyInfo CreatePropertyInfo( JsonSerializerOptions options, bool isProperty, + bool isPublic, + bool isVirtual, Type declaringType, JsonTypeInfo propertyTypeInfo, JsonConverter? converter, Func? getter, Action? setter, - JsonIgnoreCondition ignoreCondition, - JsonNumberHandling numberHandling, + JsonIgnoreCondition? ignoreCondition, + bool hasJsonInclude, + JsonNumberHandling? numberHandling, string propertyName, string? jsonPropertyName) { @@ -70,16 +76,23 @@ public static JsonPropertyInfo CreatePropertyInfo( } } + if (!isProperty && isVirtual) + { + throw new InvalidOperationException(SR.Format(SR.FieldCannotBeVirtual, nameof(isProperty), nameof(isVirtual))); + } + JsonPropertyInfo jsonPropertyInfo = new JsonPropertyInfo(); jsonPropertyInfo.InitializeForSourceGen( options, isProperty, + isPublic, declaringType, propertyTypeInfo, converter, getter, setter, ignoreCondition, + hasJsonInclude, numberHandling, propertyName, jsonPropertyName); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs index 2a97240d67b9..95081b8519de 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs @@ -42,17 +42,19 @@ internal static JsonPropertyInfo GetPropertyPlaceholder() // Create a property that is ignored at run-time. It uses the same type (typeof(sbyte)) to help // prevent issues with unsupported types and helps ensure we don't accidently (de)serialize it. - internal static JsonPropertyInfo CreateIgnoredPropertyPlaceholder(MemberInfo memberInfo, JsonSerializerOptions options) + internal static JsonPropertyInfo CreateIgnoredPropertyPlaceholder(MemberInfo memberInfo, Type memberType, bool isVirtual, JsonSerializerOptions options) { JsonPropertyInfo jsonPropertyInfo = new JsonPropertyInfo(); + jsonPropertyInfo.Options = options; jsonPropertyInfo.MemberInfo = memberInfo; - jsonPropertyInfo.DeterminePropertyName(); jsonPropertyInfo.IsIgnored = true; + jsonPropertyInfo.DeclaredPropertyType = memberType; + jsonPropertyInfo.IsVirtual = isVirtual; + jsonPropertyInfo.DeterminePropertyName(); Debug.Assert(!jsonPropertyInfo.ShouldDeserialize); Debug.Assert(!jsonPropertyInfo.ShouldSerialize); - return jsonPropertyInfo; } @@ -81,6 +83,8 @@ private void DeterminePropertyName() { Debug.Assert(MemberInfo != null); + ClrName = MemberInfo.Name; + JsonPropertyNameAttribute? nameAttribute = GetAttribute(MemberInfo); if (nameAttribute != null) { @@ -305,6 +309,7 @@ internal virtual void Initialize( Type? runtimePropertyType, ConverterStrategy runtimeClassType, MemberInfo? memberInfo, + bool isVirtual, JsonConverter converter, JsonIgnoreCondition? ignoreCondition, JsonNumberHandling? parentTypeNumberHandling, @@ -312,12 +317,12 @@ internal virtual void Initialize( { Debug.Assert(converter != null); - ClrName = memberInfo?.Name; DeclaringType = parentClassType; DeclaredPropertyType = declaredPropertyType; RuntimePropertyType = runtimePropertyType; ConverterStrategy = runtimeClassType; MemberInfo = memberInfo; + IsVirtual = isVirtual; ConverterBase = converter; Options = options; } @@ -479,6 +484,16 @@ internal JsonTypeInfo RuntimeTypeInfo internal bool IsIgnored { get; set; } + /// + /// Relevant to source generated metadata: did the property have the ? + /// + internal bool SrcGen_HasJsonInclude { get; set; } + + /// + /// Relevant to source generated metadata: is the property public? + /// + internal bool SrcGen_IsPublic { get; set; } + internal JsonNumberHandling? NumberHandling { get; set; } // Whether the property type can be null. @@ -489,5 +504,7 @@ internal JsonTypeInfo RuntimeTypeInfo internal MemberTypes MemberType { get; set; } // TODO: with some refactoring, we should be able to remove this. internal string? ClrName { get; set; } + + internal bool IsVirtual { get; set; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs index 8e268c250994..dda0e7f12dd1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs @@ -37,6 +37,7 @@ internal override void Initialize( Type? runtimePropertyType, ConverterStrategy runtimeClassType, MemberInfo? memberInfo, + bool isVirtual, JsonConverter converter, JsonIgnoreCondition? ignoreCondition, JsonNumberHandling? parentTypeNumberHandling, @@ -48,6 +49,7 @@ internal override void Initialize( runtimePropertyType, runtimeClassType, memberInfo, + isVirtual, converter, ignoreCondition, parentTypeNumberHandling, @@ -116,13 +118,15 @@ internal override void Initialize( internal void InitializeForSourceGen( JsonSerializerOptions options, bool isProperty, + bool isPublic, Type declaringType, JsonTypeInfo typeInfo, JsonConverter converter, Func? getter, Action? setter, - JsonIgnoreCondition ignoreCondition, - JsonNumberHandling numberHandling, + JsonIgnoreCondition? ignoreCondition, + bool hasJsonInclude, + JsonNumberHandling? numberHandling, string propertyName, string? jsonPropertyName) { @@ -150,6 +154,9 @@ internal void InitializeForSourceGen( NameAsUtf8Bytes ??= Encoding.UTF8.GetBytes(NameAsString!); EscapedNameSection ??= JsonHelpers.GetEscapedPropertyNameSection(NameAsUtf8Bytes, Options.Encoder); + SrcGen_IsPublic = isPublic; + SrcGen_HasJsonInclude = hasJsonInclude; + if (ignoreCondition == JsonIgnoreCondition.Always) { IsIgnored = true; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs index 7b3369a90ee6..cb2545c0cc10 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs @@ -52,13 +52,14 @@ internal static JsonPropertyInfo AddProperty( MemberInfo memberInfo, Type memberType, Type parentClassType, + bool isVirtual, JsonNumberHandling? parentTypeNumberHandling, JsonSerializerOptions options) { JsonIgnoreCondition? ignoreCondition = JsonPropertyInfo.GetAttribute(memberInfo)?.Condition; if (ignoreCondition == JsonIgnoreCondition.Always) { - return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(memberInfo, options); + return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(memberInfo, memberType, isVirtual, options); } JsonConverter converter = GetConverter( @@ -73,6 +74,7 @@ internal static JsonPropertyInfo AddProperty( runtimePropertyType: runtimeType, memberInfo, parentClassType, + isVirtual, converter, options, parentTypeNumberHandling, @@ -84,6 +86,7 @@ internal static JsonPropertyInfo CreateProperty( Type? runtimePropertyType, MemberInfo? memberInfo, Type parentClassType, + bool isVirtual, JsonConverter converter, JsonSerializerOptions options, JsonNumberHandling? parentTypeNumberHandling = null, @@ -98,6 +101,7 @@ internal static JsonPropertyInfo CreateProperty( runtimePropertyType, runtimeClassType: converter.ConverterStrategy, memberInfo, + isVirtual, converter, ignoreCondition, parentTypeNumberHandling, @@ -108,7 +112,7 @@ internal static JsonPropertyInfo CreateProperty( /// /// Create a for a given Type. - /// See . + /// See . /// internal static JsonPropertyInfo CreatePropertyInfoForTypeInfo( Type declaredPropertyType, @@ -121,8 +125,9 @@ internal static JsonPropertyInfo CreatePropertyInfoForTypeInfo( declaredPropertyType: declaredPropertyType, runtimePropertyType: runtimePropertyType, memberInfo: null, // Not a real property so this is null. - parentClassType: JsonTypeInfo.ObjectType, // a dummy value (not used) - converter: converter, + parentClassType: ObjectType, // a dummy value (not used) + isVirtual: false, + converter, options, parentTypeNumberHandling: numberHandling); @@ -582,18 +587,34 @@ internal void InitializePropCache() } JsonPropertyInfo[] array = PropInitFunc(context); - var properties = new JsonPropertyDictionary(Options.PropertyNameCaseInsensitive, array.Length); + Dictionary? ignoredMembers = null; + JsonPropertyDictionary propertyCache = new(Options.PropertyNameCaseInsensitive, array.Length); + for (int i = 0; i < array.Length; i++) { - JsonPropertyInfo property = array[i]; - if (!properties.TryAdd(property.NameAsString, property)) + JsonPropertyInfo jsonPropertyInfo = array[i]; + bool hasJsonInclude = jsonPropertyInfo.SrcGen_HasJsonInclude; + + if (!jsonPropertyInfo.SrcGen_IsPublic) + { + if (hasJsonInclude) + { + ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(jsonPropertyInfo.ClrName!, jsonPropertyInfo.DeclaringType); + } + + continue; + } + + if (jsonPropertyInfo.MemberType == MemberTypes.Field && !hasJsonInclude && !Options.IncludeFields) { - ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, property); + continue; } + + CacheMember(jsonPropertyInfo, propertyCache, ref ignoredMembers); } - // Avoid threading issues by populating a local cache, and assigning it to the global cache after completion. - PropertyCache = properties; + // Avoid threading issues by populating a local cache and assigning it to the global cache after completion. + PropertyCache = propertyCache; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs index 53ed06d9c366..ba90400e2465 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; +using System.Text.Json.Reflection; namespace System.Text.Json.Serialization.Metadata { @@ -192,7 +193,7 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json CreateObject = Options.MemberAccessorStrategy.CreateConstructor(type); - Dictionary? ignoredMembers = null; + Dictionary? ignoredMembers = null; PropertyInfo[] properties = type.GetProperties(bindingFlags); @@ -208,8 +209,12 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json { foreach (PropertyInfo propertyInfo in properties) { + bool isVirtual = propertyInfo.IsVirtual(); + string propertyName = propertyInfo.Name; + // Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d. - if (propertyInfo.GetIndexParameters().Length > 0 || PropertyIsOverridenAndIgnored(propertyInfo, ignoredMembers)) + if (propertyInfo.GetIndexParameters().Length > 0 || + PropertyIsOverridenAndIgnored(propertyName, propertyInfo.PropertyType, isVirtual, ignoredMembers)) { continue; } @@ -222,6 +227,7 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json currentType, propertyInfo.PropertyType, propertyInfo, + isVirtual, typeNumberHandling, ref ignoredMembers); } @@ -229,7 +235,7 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json { if (JsonPropertyInfo.GetAttribute(propertyInfo) != null) { - ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyInfo, currentType); + ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyName, currentType); } // Non-public properties should not be included for (de)serialization. @@ -238,7 +244,9 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags)) { - if (PropertyIsOverridenAndIgnored(fieldInfo, ignoredMembers)) + string fieldName = fieldInfo.Name; + + if (PropertyIsOverridenAndIgnored(fieldName, fieldInfo.FieldType, currentMemberIsVirtual: false, ignoredMembers)) { continue; } @@ -253,6 +261,7 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json currentType, fieldInfo.FieldType, fieldInfo, + isVirtual: false, typeNumberHandling, ref ignoredMembers); } @@ -261,7 +270,7 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json { if (hasJsonInclude) { - ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(fieldInfo, currentType); + ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(fieldName, currentType); } // Non-public fields should not be included for (de)serialization. @@ -316,8 +325,9 @@ private void CacheMember( Type declaringType, Type memberType, MemberInfo memberInfo, + bool isVirtual, JsonNumberHandling? typeNumberHandling, - ref Dictionary? ignoredMembers) + ref Dictionary? ignoredMembers) { bool hasExtensionAttribute = memberInfo.GetCustomAttribute(typeof(JsonExtensionDataAttribute)) != null; if (hasExtensionAttribute && DataExtensionProperty != null) @@ -325,7 +335,7 @@ private void CacheMember( ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, typeof(JsonExtensionDataAttribute)); } - JsonPropertyInfo jsonPropertyInfo = AddProperty(memberInfo, memberType, declaringType, typeNumberHandling, Options); + JsonPropertyInfo jsonPropertyInfo = AddProperty(memberInfo, memberType, declaringType, isVirtual, typeNumberHandling, Options); Debug.Assert(jsonPropertyInfo.NameAsString != null); if (hasExtensionAttribute) @@ -336,38 +346,43 @@ private void CacheMember( } else { - string memberName = memberInfo.Name; + CacheMember(jsonPropertyInfo, PropertyCache, ref ignoredMembers); + } + } - // The JsonPropertyNameAttribute or naming policy resulted in a collision. - if (!PropertyCache!.TryAdd(jsonPropertyInfo.NameAsString, jsonPropertyInfo)) - { - JsonPropertyInfo other = PropertyCache[jsonPropertyInfo.NameAsString]!; + private void CacheMember(JsonPropertyInfo jsonPropertyInfo, JsonPropertyDictionary? propertyCache, ref Dictionary? ignoredMembers) + { + string memberName = jsonPropertyInfo.ClrName!; - if (other.IsIgnored) - { - // Overwrite previously cached property since it has [JsonIgnore]. - PropertyCache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo; - } - else if ( - // Does the current property have `JsonIgnoreAttribute`? - !jsonPropertyInfo.IsIgnored && - // Is the current property hidden by the previously cached property - // (with `new` keyword, or by overriding)? - other.MemberInfo!.Name != memberName && - // Was a property with the same CLR name was ignored? That property hid the current property, - // thus, if it was ignored, the current property should be ignored too. - ignoredMembers?.ContainsKey(memberName) != true) - { - // We throw if we have two public properties that have the same JSON property name, and neither have been ignored. - ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo); - } - // Ignore the current property. - } + // The JsonPropertyNameAttribute or naming policy resulted in a collision. + if (!propertyCache!.TryAdd(jsonPropertyInfo.NameAsString, jsonPropertyInfo)) + { + JsonPropertyInfo other = propertyCache[jsonPropertyInfo.NameAsString]!; - if (jsonPropertyInfo.IsIgnored) + if (other.IsIgnored) + { + // Overwrite previously cached property since it has [JsonIgnore]. + propertyCache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo; + } + else if ( + // Does the current property have `JsonIgnoreAttribute`? + !jsonPropertyInfo.IsIgnored && + // Is the current property hidden by the previously cached property + // (with `new` keyword, or by overriding)? + other.ClrName != memberName && + // Was a property with the same CLR name was ignored? That property hid the current property, + // thus, if it was ignored, the current property should be ignored too. + ignoredMembers?.ContainsKey(memberName) != true) { - (ignoredMembers ??= new Dictionary()).Add(memberName, memberInfo); + // We throw if we have two public properties that have the same JSON property name, and neither have been ignored. + ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo); } + // Ignore the current property. + } + + if (jsonPropertyInfo.IsIgnored) + { + (ignoredMembers ??= new Dictionary()).Add(memberName, jsonPropertyInfo); } } @@ -476,33 +491,21 @@ static Type GetMemberType(MemberInfo memberInfo) ParameterCache = parameterCache; ParameterCount = parameters.Length; } - private static bool PropertyIsOverridenAndIgnored(MemberInfo currentMember, Dictionary? ignoredMembers) + + private static bool PropertyIsOverridenAndIgnored( + string currentMemberName, + Type currentMemberType, + bool currentMemberIsVirtual, + Dictionary? ignoredMembers) { - if (ignoredMembers == null || !ignoredMembers.TryGetValue(currentMember.Name, out MemberInfo? ignoredProperty)) + if (ignoredMembers == null || !ignoredMembers.TryGetValue(currentMemberName, out JsonPropertyInfo? ignoredMember)) { return false; } - Debug.Assert(currentMember is PropertyInfo || currentMember is FieldInfo); - PropertyInfo? currentPropertyInfo = currentMember as PropertyInfo; - Type currentMemberType = currentPropertyInfo == null - ? Unsafe.As(currentMember).FieldType - : currentPropertyInfo.PropertyType; - - Debug.Assert(ignoredProperty is PropertyInfo || ignoredProperty is FieldInfo); - PropertyInfo? ignoredPropertyInfo = ignoredProperty as PropertyInfo; - Type ignoredPropertyType = ignoredPropertyInfo == null - ? Unsafe.As(ignoredProperty).FieldType - : ignoredPropertyInfo.PropertyType; - - return currentMemberType == ignoredPropertyType && - PropertyIsVirtual(currentPropertyInfo) && - PropertyIsVirtual(ignoredPropertyInfo); - } - - private static bool PropertyIsVirtual(PropertyInfo? propertyInfo) - { - return propertyInfo != null && (propertyInfo.GetMethod?.IsVirtual == true || propertyInfo.SetMethod?.IsVirtual == true); + return currentMemberType == ignoredMember.DeclaredPropertyType && + currentMemberIsVirtual && + ignoredMember.IsVirtual; } private void ValidateAndAssignDataExtensionProperty(JsonPropertyInfo jsonPropertyInfo) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index 7beb0612a4e1..42af83d147a5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -244,9 +244,9 @@ public static void ThrowInvalidOperationException_ExtensionDataCannotBindToCtorP [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(MemberInfo memberInfo, Type parentType) + public static void ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(string memberName, Type declaringType) { - throw new InvalidOperationException(SR.Format(SR.JsonIncludeOnNonPublicInvalid, memberInfo.Name, parentType)); + throw new InvalidOperationException(SR.Format(SR.JsonIncludeOnNonPublicInvalid, memberName, declaringType)); } [DoesNotReturn] diff --git a/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapperForString.cs b/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapperForString.cs new file mode 100644 index 000000000000..eb67440c0d18 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapperForString.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization.Metadata; +using System.Threading.Tasks; + +namespace System.Text.Json.Serialization.Tests +{ + public abstract partial class JsonSerializerWrapperForString + { + protected internal abstract Task SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null); + + protected internal abstract Task SerializeWrapper(T value, JsonSerializerOptions options = null); + + protected internal abstract Task SerializeWrapper(object value, Type inputType, JsonSerializerContext context); + + protected internal abstract Task SerializeWrapper(T value, JsonTypeInfo jsonTypeInfo); + + protected internal abstract Task DeserializeWrapper(string json, JsonSerializerOptions options = null); + + protected internal abstract Task DeserializeWrapper(string json, Type type, JsonSerializerOptions options = null); + + protected internal abstract Task DeserializeWrapper(string json, JsonTypeInfo jsonTypeInfo); + + protected internal abstract Task DeserializeWrapper(string json, Type type, JsonSerializerContext context); + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyVisiblityTests.InitOnly.cs b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs similarity index 73% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyVisiblityTests.InitOnly.cs rename to src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs index 48fe1e34c935..eda534737b0e 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyVisiblityTests.InitOnly.cs +++ b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs @@ -1,51 +1,52 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Threading.Tasks; using Xunit; namespace System.Text.Json.Serialization.Tests { - public static partial class PropertyVisibilityTests + public abstract partial class PropertyVisibilityTests { [Theory] [InlineData(typeof(ClassWithInitOnlyProperty))] [InlineData(typeof(StructWithInitOnlyProperty))] - public static void InitOnlyProperties_Work(Type type) + public virtual async Task InitOnlyProperties(Type type) { // Init-only property included by default. - object obj = JsonSerializer.Deserialize(@"{""MyInt"":1}", type); + object obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1}", type); Assert.Equal(1, (int)type.GetProperty("MyInt").GetValue(obj)); // Init-only properties can be serialized. - Assert.Equal(@"{""MyInt"":1}", JsonSerializer.Serialize(obj)); + Assert.Equal(@"{""MyInt"":1}", await JsonSerializerWrapperForString.SerializeWrapper(obj)); } [Theory] [InlineData(typeof(Class_PropertyWith_PrivateInitOnlySetter))] [InlineData(typeof(Class_PropertyWith_InternalInitOnlySetter))] [InlineData(typeof(Class_PropertyWith_ProtectedInitOnlySetter))] - public static void NonPublicInitOnlySetter_Without_JsonInclude_Fails(Type type) + public async Task NonPublicInitOnlySetter_Without_JsonInclude_Fails(Type type) { // Non-public init-only property setter ignored. - object obj = JsonSerializer.Deserialize(@"{""MyInt"":1}", type); + object obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1}", type); Assert.Equal(0, (int)type.GetProperty("MyInt").GetValue(obj)); // Public getter can be used for serialization. - Assert.Equal(@"{""MyInt"":0}", JsonSerializer.Serialize(obj)); + Assert.Equal(@"{""MyInt"":0}", await JsonSerializerWrapperForString.SerializeWrapper(obj, type)); } [Theory] [InlineData(typeof(Class_PropertyWith_PrivateInitOnlySetter_WithAttribute))] [InlineData(typeof(Class_PropertyWith_InternalInitOnlySetter_WithAttribute))] [InlineData(typeof(Class_PropertyWith_ProtectedInitOnlySetter_WithAttribute))] - public static void NonPublicInitOnlySetter_With_JsonInclude_Works(Type type) + public virtual async Task NonPublicInitOnlySetter_With_JsonInclude(Type type) { // Non-public init-only property setter included with [JsonInclude]. - object obj = JsonSerializer.Deserialize(@"{""MyInt"":1}", type); + object obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1}", type); Assert.Equal(1, (int)type.GetProperty("MyInt").GetValue(obj)); // Init-only properties can be serialized. - Assert.Equal(@"{""MyInt"":1}", JsonSerializer.Serialize(obj)); + Assert.Equal(@"{""MyInt"":1}", await JsonSerializerWrapperForString.SerializeWrapper(obj)); } public class ClassWithInitOnlyProperty diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyVisibilityTests.NonPublicAccessors.cs b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.NonPublicAccessors.cs similarity index 66% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyVisibilityTests.NonPublicAccessors.cs rename to src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.NonPublicAccessors.cs index 0dd3657e6f5c..0c8dc6a941c3 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyVisibilityTests.NonPublicAccessors.cs +++ b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.NonPublicAccessors.cs @@ -2,14 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Threading.Tasks; using Xunit; namespace System.Text.Json.Serialization.Tests { - public static partial class PropertyVisibilityTests + public abstract partial class PropertyVisibilityTests { [Fact] - public static void NonPublic_AccessorsNotSupported_WithoutAttribute() + public async Task NonPublic_AccessorsNotSupported_WithoutAttribute() { string json = @"{ ""MyInt"":1, @@ -18,20 +19,20 @@ public static void NonPublic_AccessorsNotSupported_WithoutAttribute() ""MyUri"":""https://microsoft.com"" }"; - var obj = JsonSerializer.Deserialize(json); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal(0, obj.MyInt); Assert.Null(obj.MyString); Assert.Equal(2f, obj.GetMyFloat); Assert.Equal(new Uri("https://microsoft.com"), obj.MyUri); - json = JsonSerializer.Serialize(obj); + json = await JsonSerializerWrapperForString.SerializeWrapper(obj); Assert.Contains(@"""MyInt"":0", json); Assert.Contains(@"""MyString"":null", json); Assert.DoesNotContain(@"""MyFloat"":", json); Assert.DoesNotContain(@"""MyUri"":", json); } - private class MyClass_WithNonPublicAccessors + public class MyClass_WithNonPublicAccessors { public int MyInt { get; private set; } public string MyString { get; internal set; } @@ -43,7 +44,7 @@ private class MyClass_WithNonPublicAccessors } [Fact] - public static void Honor_JsonSerializablePropertyAttribute_OnProperties() + public virtual async Task Honor_JsonSerializablePropertyAttribute_OnProperties() { string json = @"{ ""MyInt"":1, @@ -52,20 +53,20 @@ public static void Honor_JsonSerializablePropertyAttribute_OnProperties() ""MyUri"":""https://microsoft.com"" }"; - var obj = JsonSerializer.Deserialize(json); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal(1, obj.MyInt); Assert.Equal("Hello", obj.MyString); Assert.Equal(2f, obj.GetMyFloat); Assert.Equal(new Uri("https://microsoft.com"), obj.MyUri); - json = JsonSerializer.Serialize(obj); + json = await JsonSerializerWrapperForString.SerializeWrapper(obj); Assert.Contains(@"""MyInt"":1", json); Assert.Contains(@"""MyString"":""Hello""", json); Assert.Contains(@"""MyFloat"":2", json); Assert.Contains(@"""MyUri"":""https://microsoft.com""", json); } - private class MyClass_WithNonPublicAccessors_WithPropertyAttributes + public class MyClass_WithNonPublicAccessors_WithPropertyAttributes { [JsonInclude] public int MyInt { get; private set; } @@ -103,19 +104,23 @@ private class MyClass_WithNonPublicAccessors_WithPropertyAttributes_And_Property } [Fact] - public static void ExtensionDataCanHaveNonPublicSetter() +#if BUILDING_SOURCE_GENERATOR_TESTS + // Need support for extension data. + [ActiveIssue("https://github.com/dotnet/runtime/issues/45448")] +#endif + public async Task ExtensionDataCanHaveNonPublicSetter() { string json = @"{""Key"":""Value""}"; // Baseline - var obj1 = JsonSerializer.Deserialize(json); + var obj1 = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Null(obj1.ExtensionData); - Assert.Equal("{}", JsonSerializer.Serialize(obj1)); + Assert.Equal("{}", await JsonSerializerWrapperForString.SerializeWrapper(obj1)); // With attribute - var obj2 = JsonSerializer.Deserialize(json); + var obj2 = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal("Value", obj2.ExtensionData["Key"].GetString()); - Assert.Equal(json, JsonSerializer.Serialize(obj2)); + Assert.Equal(json, await JsonSerializerWrapperForString.SerializeWrapper(obj2)); } private class ClassWithExtensionData_NonPublicSetter @@ -138,7 +143,7 @@ private class ClassWithExtensionData_NonPublicGetter } [Fact] - public static void HonorCustomConverter() + public virtual async Task HonorCustomConverter_UsingPrivateSetter() { var options = new JsonSerializerOptions(); options.Converters.Add(new JsonStringEnumConverter()); @@ -146,17 +151,17 @@ public static void HonorCustomConverter() string json = @"{""MyEnum"":""AnotherValue"",""MyInt"":2}"; // Deserialization baseline, without enum converter, we get JsonException. - Assert.Throws(() => JsonSerializer.Deserialize(json)); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper(json)); - var obj = JsonSerializer.Deserialize(json, options); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); Assert.Equal(MySmallEnum.AnotherValue, obj.GetMyEnum); Assert.Equal(25, obj.MyInt); // ConverterForInt32 throws this exception. - Assert.Throws(() => JsonSerializer.Serialize(obj, options)); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(obj, options)); } - private struct StructWithPropertiesWithConverter + public struct StructWithPropertiesWithConverter { [JsonInclude] public MySmallEnum MyEnum { private get; set; } @@ -169,23 +174,23 @@ private struct StructWithPropertiesWithConverter internal MySmallEnum GetMyEnum => MyEnum; } - private enum MySmallEnum + public enum MySmallEnum { DefaultValue = 0, AnotherValue = 1 } [Fact] - public static void HonorCaseInsensitivity() + public async Task HonorCaseInsensitivity() { var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; string json = @"{""MYSTRING"":""Hello""}"; - Assert.Null(JsonSerializer.Deserialize(json).MyString); - Assert.Equal("Hello", JsonSerializer.Deserialize(json, options).MyString); + Assert.Null((await JsonSerializerWrapperForString.DeserializeWrapper(json)).MyString); + Assert.Equal("Hello", (await JsonSerializerWrapperForString.DeserializeWrapper(json, options)).MyString); } - private struct MyStruct_WithNonPublicAccessors_WithTypeAttribute + public struct MyStruct_WithNonPublicAccessors_WithTypeAttribute { [JsonInclude] public int MyInt { get; private set; } @@ -201,30 +206,30 @@ private struct MyStruct_WithNonPublicAccessors_WithTypeAttribute } [Fact] - public static void HonorNamingPolicy() + public async Task HonorNamingPolicy() { var options = new JsonSerializerOptions { PropertyNamingPolicy = new SimpleSnakeCasePolicy() }; string json = @"{""my_string"":""Hello""}"; - Assert.Null(JsonSerializer.Deserialize(json).MyString); - Assert.Equal("Hello", JsonSerializer.Deserialize(json, options).MyString); + Assert.Null((await JsonSerializerWrapperForString.DeserializeWrapper(json)).MyString); + Assert.Equal("Hello", (await JsonSerializerWrapperForString.DeserializeWrapper(json, options)).MyString); } [Fact] - public static void HonorJsonPropertyName() + public virtual async Task HonorJsonPropertyName() { string json = @"{""prop1"":1,""prop2"":2}"; - var obj = JsonSerializer.Deserialize(json); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal(MySmallEnum.AnotherValue, obj.GetMyEnum); Assert.Equal(2, obj.MyInt); - json = JsonSerializer.Serialize(obj); + json = await JsonSerializerWrapperForString.SerializeWrapper(obj); Assert.Contains(@"""prop1"":1", json); Assert.Contains(@"""prop2"":2", json); } - private struct StructWithPropertiesWithJsonPropertyName + public struct StructWithPropertiesWithJsonPropertyName { [JsonInclude] [JsonPropertyName("prop1")] @@ -239,9 +244,13 @@ private struct StructWithPropertiesWithJsonPropertyName } [Fact] - public static void Map_JsonSerializableProperties_ToCtorArgs() +#if BUILDING_SOURCE_GENERATOR_TESTS + // Needs support for parameterized ctors. + [ActiveIssue("https://github.com/dotnet/runtime/issues/45448")] +#endif + public async Task Map_JsonSerializableProperties_ToCtorArgs() { - var obj = JsonSerializer.Deserialize(@"{""X"":1,""Y"":2}"); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""X"":1,""Y"":2}"); Assert.Equal(1, obj.X); Assert.Equal(2, obj.GetY); } @@ -260,24 +269,24 @@ private struct PointWith_JsonSerializableProperties } [Fact] - public static void Public_And_NonPublicPropertyAccessors_PropertyAttributes() + public virtual async Task Public_And_NonPublicPropertyAccessors_PropertyAttributes() { string json = @"{""W"":1,""X"":2,""Y"":3,""Z"":4}"; - var obj = JsonSerializer.Deserialize(json); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal(1, obj.W); Assert.Equal(2, obj.X); Assert.Equal(3, obj.Y); Assert.Equal(4, obj.GetZ); - json = JsonSerializer.Serialize(obj); + json = await JsonSerializerWrapperForString.SerializeWrapper(obj); Assert.Contains(@"""W"":1", json); Assert.Contains(@"""X"":2", json); Assert.Contains(@"""Y"":3", json); Assert.Contains(@"""Z"":4", json); } - private class ClassWithMixedPropertyAccessors_PropertyAttributes + public class ClassWithMixedPropertyAccessors_PropertyAttributes { [JsonInclude] public int W { get; set; } @@ -301,40 +310,40 @@ private class ClassWithMixedPropertyAccessors_PropertyAttributes [InlineData(typeof(ClassWithPrivate_InitOnlyProperty_WithJsonIncludeProperty))] [InlineData(typeof(ClassWithInternal_InitOnlyProperty_WithJsonIncludeProperty))] [InlineData(typeof(ClassWithProtected_InitOnlyProperty_WithJsonIncludeProperty))] - public static void NonPublicProperty_WithJsonInclude_Invalid(Type type) + public virtual async Task NonPublicProperty_WithJsonInclude_Invalid(Type type) { - InvalidOperationException ex = Assert.Throws(() => JsonSerializer.Deserialize("", type)); + InvalidOperationException ex = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("{}", type)); string exAsStr = ex.ToString(); Assert.Contains("MyString", exAsStr); Assert.Contains(type.ToString(), exAsStr); Assert.Contains("JsonIncludeAttribute", exAsStr); - ex = Assert.Throws(() => JsonSerializer.Serialize(Activator.CreateInstance(type), type)); + ex = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(Activator.CreateInstance(type), type)); exAsStr = ex.ToString(); Assert.Contains("MyString", exAsStr); Assert.Contains(type.ToString(), exAsStr); Assert.Contains("JsonIncludeAttribute", exAsStr); } - private class ClassWithPrivateProperty_WithJsonIncludeProperty + public class ClassWithPrivateProperty_WithJsonIncludeProperty { [JsonInclude] private string MyString { get; set; } } - private class ClassWithInternalProperty_WithJsonIncludeProperty + public class ClassWithInternalProperty_WithJsonIncludeProperty { [JsonInclude] internal string MyString { get; } } - private class ClassWithProtectedProperty_WithJsonIncludeProperty + public class ClassWithProtectedProperty_WithJsonIncludeProperty { [JsonInclude] protected string MyString { get; private set; } } - private class ClassWithPrivateField_WithJsonIncludeProperty + public class ClassWithPrivateField_WithJsonIncludeProperty { [JsonInclude] private string MyString = null; @@ -342,31 +351,31 @@ private class ClassWithPrivateField_WithJsonIncludeProperty public override string ToString() => MyString; } - private class ClassWithInternalField_WithJsonIncludeProperty + public class ClassWithInternalField_WithJsonIncludeProperty { [JsonInclude] internal string MyString = null; } - private class ClassWithProtectedField_WithJsonIncludeProperty + public class ClassWithProtectedField_WithJsonIncludeProperty { [JsonInclude] protected string MyString = null; } - private class ClassWithPrivate_InitOnlyProperty_WithJsonIncludeProperty + public class ClassWithPrivate_InitOnlyProperty_WithJsonIncludeProperty { [JsonInclude] private string MyString { get; init; } } - private class ClassWithInternal_InitOnlyProperty_WithJsonIncludeProperty + public class ClassWithInternal_InitOnlyProperty_WithJsonIncludeProperty { [JsonInclude] internal string MyString { get; init; } } - private class ClassWithProtected_InitOnlyProperty_WithJsonIncludeProperty + public class ClassWithProtected_InitOnlyProperty_WithJsonIncludeProperty { [JsonInclude] protected string MyString { get; init; } diff --git a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs new file mode 100644 index 000000000000..32f19eb120c3 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs @@ -0,0 +1,2632 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Numerics; +using System.Threading.Tasks; +using Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public abstract partial class PropertyVisibilityTests : SerializerTests + { + public PropertyVisibilityTests(JsonSerializerWrapperForString serializerWrapper) : base(serializerWrapper) { } + + [Fact] + public async Task Serialize_NewSlotPublicField() + { + // Serialize + var obj = new ClassWithNewSlotField(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + + Assert.Equal(@"{""MyString"":""NewDefaultValue""}", json); + + // Deserialize + json = @"{""MyString"":""NewValue""}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal("NewValue", ((ClassWithNewSlotField)obj).MyString); + Assert.Equal("DefaultValue", ((ClassWithInternalField)obj).MyString); + } + + [Fact] + public async Task Serialize_NewSlotPublicProperty() + { + // Serialize + var obj = new ClassWithNewSlotProperty(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + + Assert.Equal(@"{""MyString"":""NewDefaultValue""}", json); + + // Deserialize + json = @"{""MyString"":""NewValue""}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal("NewValue", ((ClassWithNewSlotProperty)obj).MyString); + Assert.Equal("DefaultValue", ((ClassWithInternalProperty)obj).MyString); + } + + [Fact] + public async Task Serialize_BasePublicProperty_ConflictWithDerivedPrivate() + { + // Serialize + var obj = new ClassWithNewSlotInternalProperty(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + + Assert.Equal(@"{""MyString"":""DefaultValue""}", json); + + // Deserialize + json = @"{""MyString"":""NewValue""}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal("NewValue", ((ClassWithPublicProperty)obj).MyString); + Assert.Equal("NewDefaultValue", ((ClassWithNewSlotInternalProperty)obj).MyString); + } + + [Fact] + public async Task Serialize_PublicProperty_ConflictWithPrivateDueAttributes() + { + // Serialize + var obj = new ClassWithPropertyNamingConflict(); + + // Newtonsoft.Json throws JsonSerializationException here because + // non-public properties are included when [JsonProperty] is placed on them. + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + + Assert.Equal(@"{""MyString"":""DefaultValue""}", json); + + // Deserialize + json = @"{""MyString"":""NewValue""}"; + + // Newtonsoft.Json throws JsonSerializationException here because + // non-public properties are included when [JsonProperty] is placed on them. + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal("NewValue", obj.MyString); + Assert.Equal("ConflictingValue", obj.ConflictingString); + } + + [Fact] + public async Task Serialize_PublicProperty_ConflictWithPrivateDuePolicy() + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + // Serialize + var obj = new ClassWithPropertyPolicyConflict(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + + Assert.Equal(@"{""myString"":""DefaultValue""}", json); + + // Deserialize + json = @"{""myString"":""NewValue""}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + + Assert.Equal("NewValue", obj.MyString); + Assert.Equal("ConflictingValue", obj.myString); + } + + [Fact] + public async Task Serialize_NewSlotPublicProperty_ConflictWithBasePublicProperty() + { + // Serialize + var obj = new ClassWithNewSlotDecimalProperty(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + + Assert.Equal(@"{""MyNumeric"":1.5}", json); + + // Deserialize + json = @"{""MyNumeric"":2.5}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal(2.5M, obj.MyNumeric); + } + + [Fact] + public async Task Serialize_NewSlotPublicField_ConflictWithBasePublicProperty() + { + // Serialize + var obj = new ClassWithNewSlotDecimalField(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + + Assert.Equal(@"{""MyNumeric"":1.5}", json); + + // Deserialize + json = @"{""MyNumeric"":2.5}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal(2.5M, obj.MyNumeric); + } + + [Fact] + public async Task Serialize_NewSlotPublicField_SpecifiedJsonPropertyName() + { + // Serialize + var obj = new ClassWithNewSlotAttributedDecimalField(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + + Assert.Contains(@"""MyNewNumeric"":1.5", json); + Assert.Contains(@"""MyNumeric"":1", json); + + // Deserialize + json = @"{""MyNewNumeric"":2.5,""MyNumeric"":4}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal(4, ((ClassWithHiddenByNewSlotIntProperty)obj).MyNumeric); + Assert.Equal(2.5M, ((ClassWithNewSlotAttributedDecimalField)obj).MyNumeric); + } + + [Fact] + public async Task Serialize_NewSlotPublicProperty_SpecifiedJsonPropertyName() + { + // Serialize + var obj = new ClassWithNewSlotAttributedDecimalProperty(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + + Assert.Contains(@"""MyNewNumeric"":1.5", json); + Assert.Contains(@"""MyNumeric"":1", json); + + // Deserialize + json = @"{""MyNewNumeric"":2.5,""MyNumeric"":4}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal(4, ((ClassWithHiddenByNewSlotIntProperty)obj).MyNumeric); + Assert.Equal(2.5M, ((ClassWithNewSlotAttributedDecimalProperty)obj).MyNumeric); + } + + [Fact] + public async Task Ignore_NonPublicProperty() + { + // Serialize + var obj = new ClassWithInternalProperty(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + + Assert.Equal(@"{}", json); + + // Deserialize + json = @"{""MyString"":""NewValue""}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal("DefaultValue", obj.MyString); + } + + [Fact] + public async Task Ignore_NewSlotPublicFieldIgnored() + { + // Serialize + var obj = new ClassWithIgnoredNewSlotField(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + + Assert.Equal(@"{}", json); + + // Deserialize + json = @"{""MyString"":""NewValue""}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal("NewDefaultValue", ((ClassWithIgnoredNewSlotField)obj).MyString); + Assert.Equal("DefaultValue", ((ClassWithInternalField)obj).MyString); + } + + [Fact] + public async Task Ignore_NewSlotPublicPropertyIgnored() + { + // Serialize + var obj = new ClassWithIgnoredNewSlotProperty(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + + Assert.Equal(@"{}", json); + + // Deserialize + json = @"{""MyString"":""NewValue""}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal("NewDefaultValue", ((ClassWithIgnoredNewSlotProperty)obj).MyString); + Assert.Equal("DefaultValue", ((ClassWithInternalProperty)obj).MyString); + } + + [Fact] + public async Task Ignore_BasePublicPropertyIgnored_ConflictWithDerivedPrivate() + { + // Serialize + var obj = new ClassWithIgnoredPublicPropertyAndNewSlotPrivate(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + + Assert.Equal(@"{}", json); + + // Deserialize + json = @"{""MyString"":""NewValue""}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal("DefaultValue", ((ClassWithIgnoredPublicProperty)obj).MyString); + Assert.Equal("NewDefaultValue", ((ClassWithIgnoredPublicPropertyAndNewSlotPrivate)obj).MyString); + } + + [Fact] + public async Task Ignore_PublicProperty_ConflictWithPrivateDueAttributes() + { + // Serialize + var obj = new ClassWithIgnoredPropertyNamingConflictPrivate(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + + Assert.Equal(@"{}", json); + + // Newtonsoft.Json has the following output because non-public properties are included when [JsonProperty] is placed on them. + // {"MyString":"ConflictingValue"} + + // Deserialize + json = @"{""MyString"":""NewValue""}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal("DefaultValue", obj.MyString); + Assert.Equal("ConflictingValue", obj.ConflictingString); + + // The output for Newtonsoft.Json is: + // obj.ConflictingString = "NewValue" + // obj.MyString still equals "DefaultValue" + } + + [Fact] + public async Task Ignore_PublicProperty_ConflictWithPrivateDuePolicy() + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + // Serialize + var obj = new ClassWithIgnoredPropertyPolicyConflictPrivate(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + + Assert.Equal(@"{}", json); + + // Deserialize + json = @"{""myString"":""NewValue""}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + + Assert.Equal("DefaultValue", obj.MyString); + Assert.Equal("ConflictingValue", obj.myString); + } + + [Fact] + public async Task Ignore_PublicProperty_ConflictWithPublicDueAttributes() + { + // Serialize + var obj = new ClassWithIgnoredPropertyNamingConflictPublic(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + + Assert.Equal(@"{""MyString"":""ConflictingValue""}", json); + + // Deserialize + json = @"{""MyString"":""NewValue""}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal("DefaultValue", obj.MyString); + Assert.Equal("NewValue", obj.ConflictingString); + } + + [Fact] + public async Task Ignore_PublicProperty_ConflictWithPublicDuePolicy() + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + // Serialize + var obj = new ClassWithIgnoredPropertyPolicyConflictPublic(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + + Assert.Equal(@"{""myString"":""ConflictingValue""}", json); + + // Deserialize + json = @"{""myString"":""NewValue""}"; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + + Assert.Equal("DefaultValue", obj.MyString); + Assert.Equal("NewValue", obj.myString); + } + + [Fact] + public async Task Throw_PublicProperty_ConflictDueAttributes() + { + // Serialize + var obj = new ClassWithPropertyNamingConflictWhichThrows(); + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.SerializeWrapper(obj)); + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.DeserializeWrapper(json)); + } + + [Fact] + public async Task Throw_PublicPropertyAndField_ConflictDueAttributes() + { + // Serialize + var obj = new ClassWithPropertyFieldNamingConflictWhichThrows(); + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.SerializeWrapper(obj)); + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.DeserializeWrapper(json)); + } + + [Fact] + public async Task Throw_PublicProperty_ConflictDueAttributes_SingleInheritance() + { + // Serialize + var obj = new ClassInheritedWithPropertyNamingConflictWhichThrows(); + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.SerializeWrapper(obj)); + + // The output for Newtonsoft.Json is: + // {"MyString":"ConflictingValue"} + // Conflicts at different type-hierarchy levels that are not caused by + // deriving or the new keyword are allowed. Properties on more derived types win. + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.DeserializeWrapper(json)); + + // The output for Newtonsoft.Json is: + // obj.ConflictingString = "NewValue" + // obj.MyString still equals "DefaultValue" + } + + [Fact] + public async Task Throw_PublicPropertyAndField_ConflictDueAttributes_SingleInheritance() + { + // Serialize + var obj = new ClassInheritedWithPropertyFieldNamingConflictWhichThrows(); + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.SerializeWrapper(obj)); + + // The output for Newtonsoft.Json is: + // {"MyString":"ConflictingValue"} + // Conflicts at different type-hierarchy levels that are not caused by + // deriving or the new keyword are allowed. Properties on more derived types win. + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.DeserializeWrapper(json)); + + // The output for Newtonsoft.Json is: + // obj.ConflictingString = "NewValue" + // obj.MyString still equals "DefaultValue" + } + + [Fact] + public async Task Throw_PublicProperty_ConflictDueAttributes_DoubleInheritance() + { + // Serialize + var obj = new ClassTwiceInheritedWithPropertyNamingConflictWhichThrows(); + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.SerializeWrapper(obj)); + + // The output for Newtonsoft.Json is: + // {"MyString":"ConflictingValue"} + // Conflicts at different type-hierarchy levels that are not caused by + // deriving or the new keyword are allowed. Properties on more derived types win. + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.DeserializeWrapper(json)); + + // The output for Newtonsoft.Json is: + // obj.ConflictingString = "NewValue" + // obj.MyString still equals "DefaultValue" + } + + [Fact] + public async Task Throw_PublicPropertyAndField_ConflictDueAttributes_DoubleInheritance() + { + // Serialize + var obj = new ClassTwiceInheritedWithPropertyFieldNamingConflictWhichThrows(); + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.SerializeWrapper(obj)); + + // The output for Newtonsoft.Json is: + // {"MyString":"ConflictingValue"} + // Conflicts at different type-hierarchy levels that are not caused by + // deriving or the new keyword are allowed. Properties on more derived types win. + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.DeserializeWrapper(json)); + + // The output for Newtonsoft.Json is: + // obj.ConflictingString = "NewValue" + // obj.MyString still equals "DefaultValue" + } + + [Fact] + public async Task Throw_PublicProperty_ConflictDuePolicy() + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + // Serialize + var obj = new ClassWithPropertyPolicyConflictWhichThrows(); + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.SerializeWrapper(obj, options)); + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.DeserializeWrapper(json, options)); + } + + [Fact] + public async Task Throw_PublicPropertyAndField_ConflictDuePolicy() + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + // Serialize + var obj = new ClassWithPropertyFieldPolicyConflictWhichThrows(); + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.SerializeWrapper(obj, options)); + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.DeserializeWrapper(json, options)); + } + + [Fact] + public async Task Throw_PublicProperty_ConflictDuePolicy_SingleInheritance() + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + // Serialize + var obj = new ClassInheritedWithPropertyPolicyConflictWhichThrows(); + + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.SerializeWrapper(obj, options)); + + // The output for Newtonsoft.Json is: + // {"myString":"ConflictingValue"} + // Conflicts at different type-hierarchy levels that are not caused by + // deriving or the new keyword are allowed. Properties on more derived types win. + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.DeserializeWrapper(json, options)); + + // The output for Newtonsoft.Json is: + // obj.myString = "NewValue" + // obj.MyString still equals "DefaultValue" + } + + [Fact] + public async Task Throw_PublicPropertyAndField_ConflictDuePolicy_SingleInheritance() + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + // Serialize + var obj = new ClassInheritedWithPropertyFieldPolicyConflictWhichThrows(); + + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.SerializeWrapper(obj, options)); + + // The output for Newtonsoft.Json is: + // {"myString":"ConflictingValue"} + // Conflicts at different type-hierarchy levels that are not caused by + // deriving or the new keyword are allowed. Properties on more derived types win. + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.DeserializeWrapper(json, options)); + + // The output for Newtonsoft.Json is: + // obj.myString = "NewValue" + // obj.MyString still equals "DefaultValue" + } + + [Fact] + public async Task Throw_PublicProperty_ConflictDuePolicy_DobuleInheritance() + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + // Serialize + var obj = new ClassTwiceInheritedWithPropertyPolicyConflictWhichThrows(); + + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.SerializeWrapper(obj, options)); + + // The output for Newtonsoft.Json is: + // {"myString":"ConflictingValue"} + // Conflicts at different type-hierarchy levels that are not caused by + // deriving or the new keyword are allowed. Properties on more derived types win. + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.DeserializeWrapper(json, options)); + + // The output for Newtonsoft.Json is: + // obj.myString = "NewValue" + // obj.MyString still equals "DefaultValue" + } + + [Fact] + public async Task Throw_PublicPropertyAndField_ConflictDuePolicy_DobuleInheritance() + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + // Serialize + var obj = new ClassTwiceInheritedWithPropertyFieldPolicyConflictWhichThrows(); + + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.SerializeWrapper(obj, options)); + + // The output for Newtonsoft.Json is: + // {"myString":"ConflictingValue"} + // Conflicts at different type-hierarchy levels that are not caused by + // deriving or the new keyword are allowed. Properties on more derived types win. + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + + await Assert.ThrowsAsync( + async () => await JsonSerializerWrapperForString.DeserializeWrapper(json, options)); + + // The output for Newtonsoft.Json is: + // obj.myString = "NewValue" + // obj.MyString still equals "DefaultValue" + } + + [Fact] + public async Task HiddenPropertiesIgnored_WhenOverridesIgnored() + { + string serialized = await JsonSerializerWrapperForString.SerializeWrapper(new DerivedClass_With_IgnoredOverride()); + Assert.Equal(@"{}", serialized); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new DerivedClass_WithVisibleProperty_Of_DerivedClass_With_IgnoredOverride()); + Assert.Equal(@"{""MyProp"":false}", serialized); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new DerivedClass_With_IgnoredOverride_And_ConflictingPropertyName()); + Assert.Equal(@"{""MyProp"":null}", serialized); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new DerivedClass_With_Ignored_NewProperty()); + Assert.Equal(@"{""MyProp"":false}", serialized); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new DerivedClass_WithConflictingNewMember()); + Assert.Equal(@"{""MyProp"":false}", serialized); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new DerivedClass_WithConflictingNewMember_Of_DifferentType()); + Assert.Equal(@"{""MyProp"":0}", serialized); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new DerivedClass_With_Ignored_ConflictingNewMember()); + Assert.Equal(@"{""MyProp"":false}", serialized); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new DerivedClass_With_Ignored_ConflictingNewMember_Of_DifferentType()); + Assert.Equal(@"{""MyProp"":false}", serialized); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new DerivedClass_With_NewProperty_And_ConflictingPropertyName()); + Assert.Equal(@"{""MyProp"":null}", serialized); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new DerivedClass_With_Ignored_NewProperty_Of_DifferentType()); + Assert.Equal(@"{""MyProp"":false}", serialized); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new DerivedClass_With_Ignored_NewProperty_Of_DifferentType_And_ConflictingPropertyName()); + Assert.Equal(@"{""MyProp"":null}", serialized); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new FurtherDerivedClass_With_ConflictingPropertyName()); + Assert.Equal(@"{""MyProp"":null}", serialized); + + // Here we differ from Newtonsoft.Json, where the output would be + // {"MyProp":null} + // Conflicts at different type-hierarchy levels that are not caused by + // deriving or the new keyword are allowed. Properties on more derived types win. + // This is invalid in System.Text.Json. + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(new DerivedClass_WithConflictingPropertyName())); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new FurtherDerivedClass_With_IgnoredOverride()); + Assert.Equal(@"{""MyProp"":null}", serialized); + } + + public class ClassWithInternalField + { + internal string MyString = "DefaultValue"; + } + + public class ClassWithNewSlotField : ClassWithInternalField + { + [JsonInclude] + public new string MyString = "NewDefaultValue"; + } + + public class ClassWithInternalProperty + { + internal string MyString { get; set; } = "DefaultValue"; + } + + public class ClassWithNewSlotProperty : ClassWithInternalProperty + { + public new string MyString { get; set; } = "NewDefaultValue"; + } + + public class ClassWithPublicProperty + { + public string MyString { get; set; } = "DefaultValue"; + } + + public class ClassWithNewSlotInternalProperty : ClassWithPublicProperty + { + internal new string MyString { get; set; } = "NewDefaultValue"; + } + + public class ClassWithPropertyNamingConflict + { + public string MyString { get; set; } = "DefaultValue"; + + [JsonPropertyName(nameof(MyString))] + internal string ConflictingString { get; set; } = "ConflictingValue"; + } + + public class ClassWithPropertyNamingConflictWhichThrows + { + public string MyString { get; set; } = "DefaultValue"; + + [JsonPropertyName(nameof(MyString))] + public string ConflictingString { get; set; } = "ConflictingValue"; + } + + public class ClassWithPropertyFieldNamingConflictWhichThrows + { + public string MyString { get; set; } = "DefaultValue"; + + [JsonInclude] + [JsonPropertyName(nameof(MyString))] + public string ConflictingString = "ConflictingValue"; + } + + public class ClassInheritedWithPropertyNamingConflictWhichThrows : ClassWithPublicProperty + { + [JsonPropertyName(nameof(MyString))] + public string ConflictingString { get; set; } = "ConflictingValue"; + } + + public class ClassInheritedWithPropertyFieldNamingConflictWhichThrows : ClassWithPublicProperty + { + [JsonInclude] + [JsonPropertyName(nameof(MyString))] + public string ConflictingString = "ConflictingValue"; + } + + public class ClassTwiceInheritedWithPropertyNamingConflictWhichThrowsDummy : ClassWithPublicProperty + { + } + + public class ClassTwiceInheritedWithPropertyNamingConflictWhichThrows : ClassTwiceInheritedWithPropertyNamingConflictWhichThrowsDummy + { + [JsonPropertyName(nameof(MyString))] + public string ConflictingString { get; set; } = "ConflictingValue"; + } + + public class ClassTwiceInheritedWithPropertyFieldNamingConflictWhichThrows : ClassTwiceInheritedWithPropertyNamingConflictWhichThrowsDummy + { + [JsonInclude] + [JsonPropertyName(nameof(MyString))] + public string ConflictingString = "ConflictingValue"; + } + + public class ClassWithPropertyPolicyConflict + { + public string MyString { get; set; } = "DefaultValue"; + + internal string myString { get; set; } = "ConflictingValue"; + } + + public class ClassWithPropertyPolicyConflictWhichThrows + { + public string MyString { get; set; } = "DefaultValue"; + + public string myString { get; set; } = "ConflictingValue"; + } + + public class ClassWithPropertyFieldPolicyConflictWhichThrows + { + public string MyString { get; set; } = "DefaultValue"; + + [JsonInclude] + public string myString = "ConflictingValue"; + } + + public class ClassInheritedWithPropertyPolicyConflictWhichThrows : ClassWithPublicProperty + { + public string myString { get; set; } = "ConflictingValue"; + } + + public class ClassInheritedWithPropertyFieldPolicyConflictWhichThrows : ClassWithPublicProperty + { + [JsonInclude] + public string myString = "ConflictingValue"; + } + + public class ClassInheritedWithPropertyPolicyConflictWhichThrowsDummy : ClassWithPublicProperty + { + } + + public class ClassTwiceInheritedWithPropertyPolicyConflictWhichThrows : ClassInheritedWithPropertyPolicyConflictWhichThrowsDummy + { + public string myString { get; set; } = "ConflictingValue"; + } + + public class ClassTwiceInheritedWithPropertyFieldPolicyConflictWhichThrows : ClassInheritedWithPropertyPolicyConflictWhichThrowsDummy + { + [JsonInclude] + public string myString { get; set; } = "ConflictingValue"; + } + + public class ClassWithIgnoredNewSlotField : ClassWithInternalField + { + [JsonIgnore] + public new string MyString = "NewDefaultValue"; + } + + public class ClassWithIgnoredNewSlotProperty : ClassWithInternalProperty + { + [JsonIgnore] + public new string MyString { get; set; } = "NewDefaultValue"; + } + + public class ClassWithIgnoredPublicProperty + { + [JsonIgnore] + public string MyString { get; set; } = "DefaultValue"; + } + + public class ClassWithIgnoredPublicPropertyAndNewSlotPrivate : ClassWithIgnoredPublicProperty + { + internal new string MyString { get; set; } = "NewDefaultValue"; + } + + public class ClassWithIgnoredPropertyNamingConflictPrivate + { + [JsonIgnore] + public string MyString { get; set; } = "DefaultValue"; + + [JsonPropertyName(nameof(MyString))] + internal string ConflictingString { get; set; } = "ConflictingValue"; + } + + public class ClassWithIgnoredPropertyPolicyConflictPrivate + { + [JsonIgnore] + public string MyString { get; set; } = "DefaultValue"; + + internal string myString { get; set; } = "ConflictingValue"; + } + + public class ClassWithIgnoredPropertyNamingConflictPublic + { + [JsonIgnore] + public string MyString { get; set; } = "DefaultValue"; + + [JsonPropertyName(nameof(MyString))] + public string ConflictingString { get; set; } = "ConflictingValue"; + } + + public class ClassWithIgnoredPropertyPolicyConflictPublic + { + [JsonIgnore] + public string MyString { get; set; } = "DefaultValue"; + + public string myString { get; set; } = "ConflictingValue"; + } + + public class ClassWithHiddenByNewSlotIntProperty + { + public int MyNumeric { get; set; } = 1; + } + + public class ClassWithNewSlotDecimalField : ClassWithHiddenByNewSlotIntProperty + { + [JsonInclude] + public new decimal MyNumeric = 1.5M; + } + + public class ClassWithNewSlotDecimalProperty : ClassWithHiddenByNewSlotIntProperty + { + public new decimal MyNumeric { get; set; } = 1.5M; + } + + public class ClassWithNewSlotAttributedDecimalField : ClassWithHiddenByNewSlotIntProperty + { + [JsonInclude] + [JsonPropertyName("MyNewNumeric")] + public new decimal MyNumeric = 1.5M; + } + + public class ClassWithNewSlotAttributedDecimalProperty : ClassWithHiddenByNewSlotIntProperty + { + [JsonPropertyName("MyNewNumeric")] + public new decimal MyNumeric { get; set; } = 1.5M; + } + + public class Class_With_VirtualProperty + { + public virtual bool MyProp { get; set; } + } + + public class DerivedClass_With_IgnoredOverride : Class_With_VirtualProperty + { + [JsonIgnore] + public override bool MyProp { get; set; } + } + + public class DerivedClass_WithVisibleProperty_Of_DerivedClass_With_IgnoredOverride : DerivedClass_With_IgnoredOverride + { + public override bool MyProp { get; set; } + } + + public class DerivedClass_With_IgnoredOverride_And_ConflictingPropertyName : Class_With_VirtualProperty + { + [JsonPropertyName("MyProp")] + public string MyString { get; set; } + + [JsonIgnore] + public override bool MyProp { get; set; } + } + + public class Class_With_Property + { + public bool MyProp { get; set; } + } + + public class DerivedClass_With_Ignored_NewProperty : Class_With_Property + { + [JsonIgnore] + public new bool MyProp { get; set; } + } + + public class DerivedClass_With_NewProperty_And_ConflictingPropertyName : Class_With_Property + { + [JsonPropertyName("MyProp")] + public string MyString { get; set; } + + [JsonIgnore] + public new bool MyProp { get; set; } + } + + public class DerivedClass_With_Ignored_NewProperty_Of_DifferentType : Class_With_Property + { + [JsonIgnore] + public new int MyProp { get; set; } + } + + public class DerivedClass_With_Ignored_NewProperty_Of_DifferentType_And_ConflictingPropertyName : Class_With_Property + { + [JsonPropertyName("MyProp")] + public string MyString { get; set; } + + [JsonIgnore] + public new int MyProp { get; set; } + } + + public class DerivedClass_WithIgnoredOverride : Class_With_VirtualProperty + { + [JsonIgnore] + public override bool MyProp { get; set; } + } + + public class DerivedClass_WithConflictingNewMember : Class_With_VirtualProperty + { + public new bool MyProp { get; set; } + } + + public class DerivedClass_WithConflictingNewMember_Of_DifferentType : Class_With_VirtualProperty + { + public new int MyProp { get; set; } + } + + public class DerivedClass_With_Ignored_ConflictingNewMember : Class_With_VirtualProperty + { + [JsonIgnore] + public new bool MyProp { get; set; } + } + + public class DerivedClass_With_Ignored_ConflictingNewMember_Of_DifferentType : Class_With_VirtualProperty + { + [JsonIgnore] + public new int MyProp { get; set; } + } + + public class FurtherDerivedClass_With_ConflictingPropertyName : DerivedClass_WithIgnoredOverride + { + [JsonPropertyName("MyProp")] + public string MyString { get; set; } + } + + public class DerivedClass_WithConflictingPropertyName : Class_With_VirtualProperty + { + [JsonPropertyName("MyProp")] + public string MyString { get; set; } + } + + public class FurtherDerivedClass_With_IgnoredOverride : DerivedClass_WithConflictingPropertyName + { + [JsonIgnore] + public override bool MyProp { get; set; } + } + + [Fact] + public async Task IgnoreReadOnlyProperties() + { + var options = new JsonSerializerOptions(); + options.IgnoreReadOnlyProperties = true; + + var obj = new ClassWithNoSetter(); + + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + + // Collections are always serialized unless they have [JsonIgnore]. + Assert.Equal(@"{""MyInts"":[1,2]}", json); + } + + [Fact] + public async Task IgnoreReadOnlyFields() + { + var options = new JsonSerializerOptions(); + options.IncludeFields = true; + options.IgnoreReadOnlyFields = true; + + var obj = new ClassWithReadOnlyFields(); + + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + + // Collections are always serialized unless they have [JsonIgnore]. + Assert.Equal(@"{""MyInts"":[1,2]}", json); + } + + [Fact] + public async Task NoSetter() + { + var obj = new ClassWithNoSetter(); + + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Contains(@"""MyString"":""DefaultValue""", json); + Assert.Contains(@"""MyInts"":[1,2]", json); + + obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyString"":""IgnoreMe"",""MyInts"":[0]}"); + Assert.Equal("DefaultValue", obj.MyString); + Assert.Equal(2, obj.MyInts.Length); + } + + [Fact] + public async Task NoGetter() + { + ClassWithNoGetter objWithNoGetter = await JsonSerializerWrapperForString.DeserializeWrapper( + @"{""MyString"":""Hello"",""MyIntArray"":[0],""MyIntList"":[0]}"); + + Assert.Equal("Hello", objWithNoGetter.GetMyString()); + + // Currently we don't support setters without getters. + Assert.Equal(0, objWithNoGetter.GetMyIntArray().Length); + Assert.Equal(0, objWithNoGetter.GetMyIntList().Count); + } + + [Fact] + public async Task PrivateGetter() + { + var obj = new ClassWithPrivateSetterAndGetter(); + obj.SetMyString("Hello"); + + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Equal(@"{}", json); + } + + [Fact] + public async Task PrivateSetter() + { + string json = @"{""MyString"":""Hello""}"; + + ClassWithPrivateSetterAndGetter objCopy = await JsonSerializerWrapperForString.DeserializeWrapper(json); + Assert.Null(objCopy.GetMyString()); + } + + [Fact] + public async Task PrivateSetterPublicGetter() + { + // https://github.com/dotnet/runtime/issues/29503 + ClassWithPublicGetterAndPrivateSetter obj + = await JsonSerializerWrapperForString.DeserializeWrapper(@"{ ""Class"": {} }"); + + Assert.NotNull(obj); + Assert.Null(obj.Class); + } + + [Fact] + public async Task MissingObjectProperty() + { + ClassWithMissingObjectProperty obj + = await JsonSerializerWrapperForString.DeserializeWrapper(@"{ ""Object"": {} }"); + + Assert.Null(obj.Collection); + } + + [Fact] + public async Task MissingCollectionProperty() + { + ClassWithMissingCollectionProperty obj + = await JsonSerializerWrapperForString.DeserializeWrapper(@"{ ""Collection"": [] }"); + + Assert.Null(obj.Object); + } + + public class ClassWithPublicGetterAndPrivateSetter + { + public NestedClass Class { get; private set; } + } + + public class NestedClass + { + } + + [Fact] + public async Task JsonIgnoreAttribute() + { + var options = new JsonSerializerOptions { IncludeFields = true }; + + // Verify default state. + var obj = new ClassWithIgnoreAttributeProperty(); + Assert.Equal(@"MyString", obj.MyString); + Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore); + Assert.Equal(2, obj.MyStringsWithIgnore.Length); + Assert.Equal(1, obj.MyDictionaryWithIgnore["Key"]); + Assert.Equal(3.14M, obj.MyNumeric); + + // Verify serialize. + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + Assert.Contains(@"""MyString""", json); + Assert.DoesNotContain(@"MyStringWithIgnore", json); + Assert.DoesNotContain(@"MyStringsWithIgnore", json); + Assert.DoesNotContain(@"MyDictionaryWithIgnore", json); + Assert.DoesNotContain(@"MyNumeric", json); + + // Verify deserialize default. + obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{}", options); + Assert.Equal(@"MyString", obj.MyString); + Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore); + Assert.Equal(2, obj.MyStringsWithIgnore.Length); + Assert.Equal(1, obj.MyDictionaryWithIgnore["Key"]); + Assert.Equal(3.14M, obj.MyNumeric); + + // Verify deserialize ignores the json for MyStringWithIgnore and MyStringsWithIgnore. + obj = await JsonSerializerWrapperForString.DeserializeWrapper( + @"{""MyString"":""Hello"", ""MyStringWithIgnore"":""IgnoreMe"", ""MyStringsWithIgnore"":[""IgnoreMe""], ""MyDictionaryWithIgnore"":{""Key"":9}, ""MyNumeric"": 2.71828}", options); + Assert.Contains(@"Hello", obj.MyString); + Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore); + Assert.Equal(2, obj.MyStringsWithIgnore.Length); + Assert.Equal(1, obj.MyDictionaryWithIgnore["Key"]); + Assert.Equal(3.14M, obj.MyNumeric); + } + + [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + // Needs support for more collections. + [ActiveIssue("https://github.com/dotnet/runtime/issues/53393")] +#endif + public async Task JsonIgnoreAttribute_UnsupportedCollection() + { + string json = + @"{ + ""MyConcurrentDict"":{ + ""key"":""value"" + }, + ""MyIDict"":{ + ""key"":""value"" + }, + ""MyDict"":{ + ""key"":""value"" + } + }"; + string wrapperJson = + @"{ + ""MyClass"":{ + ""MyConcurrentDict"":{ + ""key"":""value"" + }, + ""MyIDict"":{ + ""key"":""value"" + }, + ""MyDict"":{ + ""key"":""value"" + } + } + }"; + + // Unsupported collections will throw on deserialize by default. + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper(json)); + + // Using new options instance to prevent using previously cached metadata. + JsonSerializerOptions options = new JsonSerializerOptions(); + + // Unsupported collections will throw on serialize by default. + // Only when the collection contains elements. + + var dictionary = new Dictionary(); + // Uri is an unsupported dictionary key. + dictionary.Add(new Uri("http://foo"), "bar"); + + var concurrentDictionary = new ConcurrentDictionary(dictionary); + + var instance = new ClassWithUnsupportedDictionary() + { + MyConcurrentDict = concurrentDictionary, + MyIDict = dictionary + }; + + var instanceWithIgnore = new ClassWithIgnoredUnsupportedDictionary + { + MyConcurrentDict = concurrentDictionary, + MyIDict = dictionary + }; + + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(instance, options)); + + // Unsupported collections will throw on deserialize by default if they contain elements. + options = new JsonSerializerOptions(); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper(wrapperJson, options)); + + options = new JsonSerializerOptions(); + // Unsupported collections will throw on serialize by default if they contain elements. + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(instance, options)); + + // When ignored, we can serialize and deserialize without exceptions. + options = new JsonSerializerOptions(); + + Assert.NotNull(await JsonSerializerWrapperForString.SerializeWrapper(instanceWithIgnore, options)); + + ClassWithIgnoredUnsupportedDictionary obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + Assert.Null(obj.MyDict); + + options = new JsonSerializerOptions(); + Assert.Equal("{}", await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithIgnoredUnsupportedDictionary())); + + options = new JsonSerializerOptions(); + WrapperForClassWithIgnoredUnsupportedDictionary wrapperObj = await JsonSerializerWrapperForString.DeserializeWrapper(wrapperJson, options); + Assert.Null(wrapperObj.MyClass.MyDict); + + options = new JsonSerializerOptions(); + Assert.Equal(@"{""MyClass"":{}}", await JsonSerializerWrapperForString.SerializeWrapper(new WrapperForClassWithIgnoredUnsupportedDictionary() + { + MyClass = new ClassWithIgnoredUnsupportedDictionary(), + }, options)); + } + + [Fact] + public async Task JsonIgnoreAttribute_UnsupportedBigInteger() + { + string json = @"{""MyBigInteger"":1}"; + string wrapperJson = @"{""MyClass"":{""MyBigInteger"":1}}"; + + // Unsupported types will throw by default. + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper(json)); + // Using new options instance to prevent using previously cached metadata. + JsonSerializerOptions options = new JsonSerializerOptions(); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper(wrapperJson, options)); + + // When ignored, we can serialize and deserialize without exceptions. + options = new JsonSerializerOptions(); + ClassWithIgnoredUnsupportedBigInteger obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + Assert.Null(obj.MyBigInteger); + + options = new JsonSerializerOptions(); + Assert.Equal("{}", await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithIgnoredUnsupportedBigInteger())); + + options = new JsonSerializerOptions(); + WrapperForClassWithIgnoredUnsupportedBigInteger wrapperObj = await JsonSerializerWrapperForString.DeserializeWrapper(wrapperJson, options); + Assert.Null(wrapperObj.MyClass.MyBigInteger); + + options = new JsonSerializerOptions(); + Assert.Equal(@"{""MyClass"":{}}", await JsonSerializerWrapperForString.SerializeWrapper(new WrapperForClassWithIgnoredUnsupportedBigInteger() + { + MyClass = new ClassWithIgnoredUnsupportedBigInteger(), + }, options)); + } + + public class ObjectDictWrapper : Dictionary { } + + public class ClassWithUnsupportedDictionary + { + public ConcurrentDictionary MyConcurrentDict { get; set; } + public IDictionary MyIDict { get; set; } + public ObjectDictWrapper MyDict { get; set; } + } + + public class WrapperForClassWithUnsupportedDictionary + { + public ClassWithUnsupportedDictionary MyClass { get; set; } = new ClassWithUnsupportedDictionary(); + } + + public class ClassWithIgnoredUnsupportedDictionary + { + [JsonIgnore] + public ConcurrentDictionary MyConcurrentDict { get; set; } + [JsonIgnore] + public IDictionary MyIDict { get; set; } + [JsonIgnore] + public ObjectDictWrapper MyDict { get; set; } + } + + public class WrapperForClassWithIgnoredUnsupportedDictionary + { + public ClassWithIgnoredUnsupportedDictionary MyClass { get; set; } + } + + public class ClassWithUnsupportedBigInteger + { + public BigInteger? MyBigInteger { get; set; } + } + + public class WrapperForClassWithUnsupportedBigInteger + { + public ClassWithUnsupportedBigInteger MyClass { get; set; } = new(); + } + + public class ClassWithIgnoredUnsupportedBigInteger + { + [JsonIgnore] + public BigInteger? MyBigInteger { get; set; } + } + + public class WrapperForClassWithIgnoredUnsupportedBigInteger + { + public ClassWithIgnoredUnsupportedBigInteger MyClass { get; set; } + } + + public class ClassWithMissingObjectProperty + { + public object[] Collection { get; set; } + } + + public class ClassWithMissingCollectionProperty + { + public object Object { get; set; } + } + + public class ClassWithPrivateSetterAndGetter + { + private string MyString { get; set; } + + public string GetMyString() + { + return MyString; + } + + public void SetMyString(string value) + { + MyString = value; + } + } + + public class ClassWithReadOnlyFields + { + public ClassWithReadOnlyFields() + { + MyString = "DefaultValue"; + MyInts = new int[] { 1, 2 }; + } + + public readonly string MyString; + public readonly int[] MyInts; + } + + public class ClassWithNoSetter + { + public ClassWithNoSetter() + { + MyString = "DefaultValue"; + MyInts = new int[] { 1, 2 }; + } + + public string MyString { get; } + public int[] MyInts { get; } + } + + public class ClassWithNoGetter + { + string _myString = ""; + int[] _myIntArray = new int[] { }; + List _myIntList = new List { }; + + public string MyString + { + set + { + _myString = value; + } + } + + public int[] MyIntArray + { + set + { + _myIntArray = value; + } + } + + public List MyList + { + set + { + _myIntList = value; + } + } + + public string GetMyString() + { + return _myString; + } + + public int[] GetMyIntArray() + { + return _myIntArray; + } + + public List GetMyIntList() + { + return _myIntList; + } + } + + public class ClassWithIgnoreAttributeProperty + { + public ClassWithIgnoreAttributeProperty() + { + MyDictionaryWithIgnore = new Dictionary { { "Key", 1 } }; + MyString = "MyString"; + MyStringWithIgnore = "MyStringWithIgnore"; + MyStringsWithIgnore = new string[] { "1", "2" }; + MyNumeric = 3.14M; + } + + [JsonIgnore] + public Dictionary MyDictionaryWithIgnore { get; set; } + + [JsonIgnore] + public string MyStringWithIgnore { get; set; } + + public string MyString { get; set; } + + [JsonIgnore] + public string[] MyStringsWithIgnore { get; set; } + + [JsonIgnore] + public decimal MyNumeric; + } + + public enum MyEnum + { + Case1 = 0, + Case2 = 1, + } + + public struct StructWithOverride + { + [JsonIgnore] + public MyEnum EnumValue { get; set; } + + [JsonPropertyName("EnumValue")] + public string EnumString + { + get => EnumValue.ToString(); + set + { + if (value == "Case1") + { + EnumValue = MyEnum.Case1; + } + else if (value == "Case2") + { + EnumValue = MyEnum.Case2; + } + else + { + throw new Exception("Unknown value!"); + } + } + } + } + + [Fact] + public async Task OverrideJsonIgnorePropertyUsingJsonPropertyName() + { + const string json = @"{""EnumValue"":""Case2""}"; + + StructWithOverride obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal(MyEnum.Case2, obj.EnumValue); + Assert.Equal("Case2", obj.EnumString); + + string jsonSerialized = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Equal(json, jsonSerialized); + } + + public struct ClassWithOverrideReversed + { + // Same as ClassWithOverride except the order of the properties is different, which should cause different reflection order. + [JsonPropertyName("EnumValue")] + public string EnumString + { + get => EnumValue.ToString(); + set + { + if (value == "Case1") + { + EnumValue = MyEnum.Case1; + } + if (value == "Case2") + { + EnumValue = MyEnum.Case2; + } + else + { + throw new Exception("Unknown value!"); + } + } + } + + [JsonIgnore] + public MyEnum EnumValue { get; set; } + } + + [Fact] + public async Task OverrideJsonIgnorePropertyUsingJsonPropertyNameReversed() + { + const string json = @"{""EnumValue"":""Case2""}"; + + ClassWithOverrideReversed obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + Assert.Equal(MyEnum.Case2, obj.EnumValue); + Assert.Equal("Case2", obj.EnumString); + + string jsonSerialized = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Equal(json, jsonSerialized); + } + + [Theory] + [InlineData(typeof(ClassWithProperty_IgnoreConditionAlways))] + [InlineData(typeof(ClassWithProperty_IgnoreConditionAlways_Ctor))] +#if BUILDING_SOURCE_GENERATOR_TESTS + // Need support for parameterized ctors. + [ActiveIssue("https://github.com/dotnet/runtime/issues/45448")] +#endif + public async Task JsonIgnoreConditionSetToAlwaysWorks(Type type) + { + string json = @"{""MyString"":""Random"",""MyDateTime"":""2020-03-23"",""MyInt"":4}"; + + object obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, type); + Assert.Equal("Random", (string)type.GetProperty("MyString").GetValue(obj)); + Assert.Equal(default, (DateTime)type.GetProperty("MyDateTime").GetValue(obj)); + Assert.Equal(4, (int)type.GetProperty("MyInt").GetValue(obj)); + + string serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Contains(@"""MyString"":""Random""", serialized); + Assert.Contains(@"""MyInt"":4", serialized); + Assert.DoesNotContain(@"""MyDateTime"":", serialized); + } + + public class ClassWithProperty_IgnoreConditionAlways + { + public string MyString { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.Always)] + public DateTime MyDateTime { get; set; } + public int MyInt { get; set; } + } + + private class ClassWithProperty_IgnoreConditionAlways_Ctor + { + public string MyString { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.Always)] + public DateTime MyDateTime { get; } + public int MyInt { get; } + + public ClassWithProperty_IgnoreConditionAlways_Ctor(DateTime myDateTime, int myInt) + { + MyDateTime = myDateTime; + MyInt = myInt; + } + } + + [Theory] + [MemberData(nameof(JsonIgnoreConditionWhenWritingDefault_ClassProperty_TestData))] +#if BUILDING_SOURCE_GENERATOR_TESTS + // Need support for parameterized ctors. + [ActiveIssue("https://github.com/dotnet/runtime/issues/45448")] +#endif + public async Task JsonIgnoreConditionWhenWritingDefault_ClassProperty(Type type, JsonSerializerOptions options) + { + // Property shouldn't be ignored if it isn't null. + string json = @"{""Int1"":1,""MyString"":""Random"",""Int2"":2}"; + + object obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, type, options); + Assert.Equal(1, (int)type.GetProperty("Int1").GetValue(obj)); + Assert.Equal("Random", (string)type.GetProperty("MyString").GetValue(obj)); + Assert.Equal(2, (int)type.GetProperty("Int2").GetValue(obj)); + + string serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + Assert.Contains(@"""Int1"":1", serialized); + Assert.Contains(@"""MyString"":""Random""", serialized); + Assert.Contains(@"""Int2"":2", serialized); + + // Property should be ignored when null. + json = @"{""Int1"":1,""MyString"":null,""Int2"":2}"; + + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, type, options); + Assert.Equal(1, (int)type.GetProperty("Int1").GetValue(obj)); + + if (options.IgnoreNullValues) + { + // Null values can be ignored on deserialization using IgnoreNullValues. + Assert.Equal("DefaultString", (string)type.GetProperty("MyString").GetValue(obj)); + } + else + { + Assert.Null((string)type.GetProperty("MyString").GetValue(obj)); + } + + Assert.Equal(2, (int)type.GetProperty("Int2").GetValue(obj)); + + // Set property to be ignored to null. + type.GetProperty("MyString").SetValue(obj, null); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + Assert.Contains(@"""Int1"":1", serialized); + Assert.Contains(@"""Int2"":2", serialized); + Assert.DoesNotContain(@"""MyString"":", serialized); + } + + public class ClassWithClassProperty_IgnoreConditionWhenWritingDefault + { + public int Int1 { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string MyString { get; set; } = "DefaultString"; + public int Int2 { get; set; } + } + + private class ClassWithClassProperty_IgnoreConditionWhenWritingDefault_Ctor + { + public int Int1 { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string MyString { get; set; } = "DefaultString"; + public int Int2 { get; set; } + + public ClassWithClassProperty_IgnoreConditionWhenWritingDefault_Ctor(string myString) + { + if (myString != null) + { + MyString = myString; + } + } + } + + public static IEnumerable JsonIgnoreConditionWhenWritingDefault_ClassProperty_TestData() + { + yield return new object[] { typeof(ClassWithClassProperty_IgnoreConditionWhenWritingDefault), new JsonSerializerOptions() }; + yield return new object[] { typeof(ClassWithClassProperty_IgnoreConditionWhenWritingDefault_Ctor), new JsonSerializerOptions { IgnoreNullValues = true } }; + } + + [Theory] + [MemberData(nameof(JsonIgnoreConditionWhenWritingDefault_StructProperty_TestData))] +#if BUILDING_SOURCE_GENERATOR_TESTS + // Need support for parameterized ctors. + [ActiveIssue("https://github.com/dotnet/runtime/issues/45448")] +#endif + public async Task JsonIgnoreConditionWhenWritingDefault_StructProperty(Type type, JsonSerializerOptions options) + { + // Property shouldn't be ignored if it isn't null. + string json = @"{""Int1"":1,""MyInt"":3,""Int2"":2}"; + + object obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, type, options); + Assert.Equal(1, (int)type.GetProperty("Int1").GetValue(obj)); + Assert.Equal(3, (int)type.GetProperty("MyInt").GetValue(obj)); + Assert.Equal(2, (int)type.GetProperty("Int2").GetValue(obj)); + + string serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + Assert.Contains(@"""Int1"":1", serialized); + Assert.Contains(@"""MyInt"":3", serialized); + Assert.Contains(@"""Int2"":2", serialized); + + // Null being assigned to non-nullable types is invalid. + json = @"{""Int1"":1,""MyInt"":null,""Int2"":2}"; + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper(json, type, options)); + } + + public class ClassWithStructProperty_IgnoreConditionWhenWritingDefault + { + public int Int1 { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int MyInt { get; set; } + public int Int2 { get; set; } + } + + private struct StructWithStructProperty_IgnoreConditionWhenWritingDefault_Ctor + { + public int Int1 { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int MyInt { get; } + public int Int2 { get; set; } + + [JsonConstructor] + public StructWithStructProperty_IgnoreConditionWhenWritingDefault_Ctor(int myInt) + { + Int1 = 0; + MyInt = myInt; + Int2 = 0; + } + } + + public static IEnumerable JsonIgnoreConditionWhenWritingDefault_StructProperty_TestData() + { + yield return new object[] { typeof(ClassWithStructProperty_IgnoreConditionWhenWritingDefault), new JsonSerializerOptions() }; + yield return new object[] { typeof(StructWithStructProperty_IgnoreConditionWhenWritingDefault_Ctor), new JsonSerializerOptions { IgnoreNullValues = true } }; + } + + [Theory] + [MemberData(nameof(JsonIgnoreConditionNever_TestData))] +#if BUILDING_SOURCE_GENERATOR_TESTS + // Need support for parameterized ctors. + [ActiveIssue("https://github.com/dotnet/runtime/issues/45448")] +#endif + public async Task JsonIgnoreConditionNever(Type type) + { + // Property should always be (de)serialized, even when null. + string json = @"{""Int1"":1,""MyString"":""Random"",""Int2"":2}"; + + object obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, type); + Assert.Equal(1, (int)type.GetProperty("Int1").GetValue(obj)); + Assert.Equal("Random", (string)type.GetProperty("MyString").GetValue(obj)); + Assert.Equal(2, (int)type.GetProperty("Int2").GetValue(obj)); + + string serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Contains(@"""Int1"":1", serialized); + Assert.Contains(@"""MyString"":""Random""", serialized); + Assert.Contains(@"""Int2"":2", serialized); + + // Property should always be (de)serialized, even when null. + json = @"{""Int1"":1,""MyString"":null,""Int2"":2}"; + + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, type); + Assert.Equal(1, (int)type.GetProperty("Int1").GetValue(obj)); + Assert.Null((string)type.GetProperty("MyString").GetValue(obj)); + Assert.Equal(2, (int)type.GetProperty("Int2").GetValue(obj)); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Contains(@"""Int1"":1", serialized); + Assert.Contains(@"""MyString"":null", serialized); + Assert.Contains(@"""Int2"":2", serialized); + } + + [Theory] + [MemberData(nameof(JsonIgnoreConditionNever_TestData))] +#if BUILDING_SOURCE_GENERATOR_TESTS + // Need support for parameterized ctors. + [ActiveIssue("https://github.com/dotnet/runtime/issues/45448")] +#endif + public async Task JsonIgnoreConditionNever_IgnoreNullValues_True(Type type) + { + // Property should always be (de)serialized. + string json = @"{""Int1"":1,""MyString"":""Random"",""Int2"":2}"; + var options = new JsonSerializerOptions { IgnoreNullValues = true }; + + object obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, type, options); + Assert.Equal(1, (int)type.GetProperty("Int1").GetValue(obj)); + Assert.Equal("Random", (string)type.GetProperty("MyString").GetValue(obj)); + Assert.Equal(2, (int)type.GetProperty("Int2").GetValue(obj)); + + string serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj, type, options); + Assert.Contains(@"""Int1"":1", serialized); + Assert.Contains(@"""MyString"":""Random""", serialized); + Assert.Contains(@"""Int2"":2", serialized); + + // Property should always be (de)serialized, even when null. + json = @"{""Int1"":1,""MyString"":null,""Int2"":2}"; + + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, type, options); + Assert.Equal(1, (int)type.GetProperty("Int1").GetValue(obj)); + Assert.Null((string)type.GetProperty("MyString").GetValue(obj)); + Assert.Equal(2, (int)type.GetProperty("Int2").GetValue(obj)); + + serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj, type, options); + Assert.Contains(@"""Int1"":1", serialized); + Assert.Contains(@"""MyString"":null", serialized); + Assert.Contains(@"""Int2"":2", serialized); + } + + public class ClassWithStructProperty_IgnoreConditionNever + { + public int Int1 { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public string MyString { get; set; } + public int Int2 { get; set; } + } + + public class ClassWithStructProperty_IgnoreConditionNever_Ctor + { + public int Int1 { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public string MyString { get; } + public int Int2 { get; set; } + + public ClassWithStructProperty_IgnoreConditionNever_Ctor(string myString) + { + MyString = myString; + } + } + + public static IEnumerable JsonIgnoreConditionNever_TestData() + { + yield return new object[] { typeof(ClassWithStructProperty_IgnoreConditionNever) }; + yield return new object[] { typeof(ClassWithStructProperty_IgnoreConditionNever_Ctor) }; + } + + [Fact] + public async Task JsonIgnoreCondition_LastOneWins() + { + string json = @"{""MyString"":""Random"",""MYSTRING"":null}"; + + var options = new JsonSerializerOptions + { + IgnoreNullValues = true, + PropertyNameCaseInsensitive = true + }; + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + + Assert.Null(obj.MyString); + } + + [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("https://github.com/dotnet/runtime/issues/53393")] +#endif + public async Task ClassWithComplexObjectsUsingIgnoreWhenWritingDefaultAttribute() + { + string json = @"{""Class"":{""MyInt16"":18}, ""Dictionary"":null}"; + + ClassUsingIgnoreWhenWritingDefaultAttribute obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + + // Class is deserialized. + Assert.NotNull(obj.Class); + Assert.Equal(18, obj.Class.MyInt16); + + // Dictionary is deserialized as JsonIgnoreCondition.WhenWritingDefault only applies to deserialization. + Assert.Null(obj.Dictionary); + + obj = new ClassUsingIgnoreWhenWritingDefaultAttribute(); + json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Equal(@"{""Dictionary"":{""Key"":""Value""}}", json); + } + + public class ClassUsingIgnoreWhenWritingDefaultAttribute + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public SimpleTestClass Class { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public Dictionary Dictionary { get; set; } = new Dictionary { ["Key"] = "Value" }; + } + + [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("https://github.com/dotnet/runtime/issues/53393")] +#endif + public async Task ClassWithComplexObjectUsingIgnoreNeverAttribute() + { + string json = @"{""Class"":null, ""Dictionary"":null}"; + var options = new JsonSerializerOptions { IgnoreNullValues = true }; + + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + + // Class is not deserialized because it is null in json. + Assert.NotNull(obj.Class); + Assert.Equal(18, obj.Class.MyInt16); + + // Dictionary is deserialized regardless of being null in json. + Assert.Null(obj.Dictionary); + + // Serialize when values are null. + obj = new ClassUsingIgnoreNeverAttribute(); + obj.Class = null; + obj.Dictionary = null; + + json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + + // Class is not included in json because it was null, Dictionary is included regardless of being null. + Assert.Equal(@"{""Dictionary"":null}", json); + } + + public class ClassUsingIgnoreNeverAttribute + { + public SimpleTestClass Class { get; set; } = new SimpleTestClass { MyInt16 = 18 }; + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public Dictionary Dictionary { get; set; } = new Dictionary { ["Key"] = "Value" }; + } + + [Fact] + public async Task IgnoreConditionNever_WinsOver_IgnoreReadOnlyProperties() + { + var options = new JsonSerializerOptions { IgnoreReadOnlyProperties = true }; + + // Baseline + string json = await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithReadOnlyStringProperty("Hello"), options); + Assert.Equal("{}", json); + + // With condition to never ignore + json = await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithReadOnlyStringProperty_IgnoreNever("Hello"), options); + Assert.Equal(@"{""MyString"":""Hello""}", json); + + json = await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithReadOnlyStringProperty_IgnoreNever(null), options); + Assert.Equal(@"{""MyString"":null}", json); + } + + [Fact] + public async Task IgnoreConditionWhenWritingDefault_WinsOver_IgnoreReadOnlyProperties() + { + var options = new JsonSerializerOptions { IgnoreReadOnlyProperties = true }; + + // Baseline + string json = await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithReadOnlyStringProperty("Hello"), options); + Assert.Equal("{}", json); + + // With condition to ignore when null + json = await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithReadOnlyStringProperty_IgnoreWhenWritingDefault("Hello"), options); + Assert.Equal(@"{""MyString"":""Hello""}", json); + + json = await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithReadOnlyStringProperty_IgnoreWhenWritingDefault(null), options); + Assert.Equal(@"{}", json); + } + + [Fact] + public async Task IgnoreConditionNever_WinsOver_IgnoreReadOnlyFields() + { + var options = new JsonSerializerOptions { IgnoreReadOnlyProperties = true }; + + // Baseline + string json = await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithReadOnlyStringField("Hello"), options); + Assert.Equal("{}", json); + + // With condition to never ignore + json = await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithReadOnlyStringField_IgnoreNever("Hello"), options); + Assert.Equal(@"{""MyString"":""Hello""}", json); + + json = await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithReadOnlyStringField_IgnoreNever(null), options); + Assert.Equal(@"{""MyString"":null}", json); + } + + [Fact] + public async Task IgnoreConditionWhenWritingDefault_WinsOver_IgnoreReadOnlyFields() + { + var options = new JsonSerializerOptions { IgnoreReadOnlyProperties = true }; + + // Baseline + string json = await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithReadOnlyStringField("Hello"), options); + Assert.Equal("{}", json); + + // With condition to ignore when null + json = await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithReadOnlyStringField_IgnoreWhenWritingDefault("Hello"), options); + Assert.Equal(@"{""MyString"":""Hello""}", json); + + json = await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithReadOnlyStringField_IgnoreWhenWritingDefault(null), options); + Assert.Equal(@"{}", json); + } + + public class ClassWithReadOnlyStringProperty + { + public string MyString { get; } + + public ClassWithReadOnlyStringProperty(string myString) => MyString = myString; + } + + public class ClassWithReadOnlyStringProperty_IgnoreNever + { + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public string MyString { get; } + + public ClassWithReadOnlyStringProperty_IgnoreNever(string myString) => MyString = myString; + } + + public class ClassWithReadOnlyStringProperty_IgnoreWhenWritingDefault + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string MyString { get; } + + public ClassWithReadOnlyStringProperty_IgnoreWhenWritingDefault(string myString) => MyString = myString; + } + + public class ClassWithReadOnlyStringField + { + public string MyString { get; } + + public ClassWithReadOnlyStringField(string myString) => MyString = myString; + } + + public class ClassWithReadOnlyStringField_IgnoreNever + { + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public string MyString { get; } + + public ClassWithReadOnlyStringField_IgnoreNever(string myString) => MyString = myString; + } + + public class ClassWithReadOnlyStringField_IgnoreWhenWritingDefault + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string MyString { get; } + + public ClassWithReadOnlyStringField_IgnoreWhenWritingDefault(string myString) => MyString = myString; + } + + [Fact] + public async Task NonPublicMembersAreNotIncluded() + { + Assert.Equal("{}", await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithNonPublicProperties())); + + string json = @"{""MyInt"":1,""MyString"":""Hello"",""MyFloat"":2,""MyDouble"":3}"; + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + Assert.Equal(0, obj.MyInt); + Assert.Null(obj.MyString); + Assert.Equal(0, obj.GetMyFloat); + Assert.Equal(0, obj.GetMyDouble); + } + + public class ClassWithNonPublicProperties + { + internal int MyInt { get; set; } + internal string MyString { get; private set; } + internal float MyFloat { private get; set; } + private double MyDouble { get; set; } + + internal float GetMyFloat => MyFloat; + internal double GetMyDouble => MyDouble; + } + + [Fact] + public async Task IgnoreCondition_WhenWritingDefault_Globally_Works() + { + // Baseline - default values written. + string expected = @"{""MyString"":null,""MyInt"":0,""MyPoint"":{""X"":0,""Y"":0}}"; + var obj = new ClassWithProps(); + JsonTestHelper.AssertJsonEqual(expected, await JsonSerializerWrapperForString.SerializeWrapper(obj)); + + // Default values ignored when specified. + Assert.Equal("{}", await JsonSerializerWrapperForString.SerializeWrapper(obj, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault })); + } + + public class ClassWithProps + { + public string MyString { get; set; } + public int MyInt { get; set; } + public Point_2D_Struct MyPoint { get; set; } + } + + [Fact] + public async Task IgnoreCondition_WhenWritingDefault_PerProperty_Works() + { + // Default values ignored when specified. + Assert.Equal(@"{""MyInt"":0}", await JsonSerializerWrapperForString.SerializeWrapper(new ClassWithPropsAndIgnoreAttributes())); + } + + public class ClassWithPropsAndIgnoreAttributes + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string MyString { get; set; } + public int MyInt { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public Point_2D_Struct MyPoint { get; set; } + } + + [Fact] + public async Task IgnoreCondition_WhenWritingDefault_DoesNotApplyToCollections() + { + var list = new List { false, true }; + + var options = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }; + Assert.Equal("[false,true]", await JsonSerializerWrapperForString.SerializeWrapper(list, options)); + } + + [Fact] + public async Task IgnoreCondition_WhenWritingDefault_DoesNotApplyToDeserialization() + { + // Baseline - null values are ignored on deserialization when using IgnoreNullValues (for compat with initial support). + string json = @"{""MyString"":null,""MyInt"":0,""MyPoint"":{""X"":0,""Y"":0}}"; + + var options = new JsonSerializerOptions { IgnoreNullValues = true }; + ClassWithInitializedProps obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + + Assert.Equal("Default", obj.MyString); + // Value types are not ignored. + Assert.Equal(0, obj.MyInt); + Assert.Equal(0, obj.MyPoint.X); + Assert.Equal(0, obj.MyPoint.X); + + // Test - default values (both null and default for value types) are not ignored when using + // JsonIgnoreCondition.WhenWritingDefault (as the option name implies) + options = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }; + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + Assert.Null(obj.MyString); + Assert.Equal(0, obj.MyInt); + Assert.Equal(0, obj.MyPoint.X); + Assert.Equal(0, obj.MyPoint.X); + } + + public class ClassWithInitializedProps + { + public string MyString { get; set; } = "Default"; + public int MyInt { get; set; } = -1; + public Point_2D_Struct MyPoint { get; set; } = new Point_2D_Struct(-1, -1); + } + + [Fact] + public async Task ValueType_Properties_NotIgnoredWhen_IgnoreNullValues_Active_ClassTest() + { + var options = new JsonSerializerOptions { IgnoreNullValues = true }; + + // Deserialization. + string json = @"{""MyString"":null,""MyInt"":0,""MyBool"":null,""MyPointClass"":null,""MyPointStruct"":{""X"":0,""Y"":0}}"; + + ClassWithValueAndReferenceTypes obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + + // Null values ignored for reference types/nullable value types. + Assert.Equal("Default", obj.MyString); + Assert.NotNull(obj.MyPointClass); + Assert.True(obj.MyBool); + + // Default values not ignored for value types. + Assert.Equal(0, obj.MyInt); + Assert.Equal(0, obj.MyPointStruct.X); + Assert.Equal(0, obj.MyPointStruct.Y); + + // Serialization. + + // Make all members their default CLR value. + obj.MyString = null; + obj.MyPointClass = null; + obj.MyBool = null; + + json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + + // Null values not serialized, default values for value types serialized. + JsonTestHelper.AssertJsonEqual(@"{""MyInt"":0,""MyPointStruct"":{""X"":0,""Y"":0}}", json); + } + + [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + // Need support for parameterized ctors. + [ActiveIssue("https://github.com/dotnet/runtime/issues/45448")] +#endif + public async Task ValueType_Properties_NotIgnoredWhen_IgnoreNullValues_Active_LargeStructTest() + { + var options = new JsonSerializerOptions { IgnoreNullValues = true }; + + // Deserialization. + string json = @"{""MyString"":null,""MyInt"":0,""MyBool"":null,""MyPointClass"":null,""MyPointStruct"":{""X"":0,""Y"":0}}"; + + LargeStructWithValueAndReferenceTypes obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + + // Null values ignored for reference types. + + Assert.Equal("Default", obj.MyString); + // No way to specify a non-constant default before construction with ctor, so this remains null. + Assert.Null(obj.MyPointClass); + Assert.True(obj.MyBool); + + // Default values not ignored for value types. + Assert.Equal(0, obj.MyInt); + Assert.Equal(0, obj.MyPointStruct.X); + Assert.Equal(0, obj.MyPointStruct.Y); + + // Serialization. + + // Make all members their default CLR value. + obj = new LargeStructWithValueAndReferenceTypes(null, new Point_2D_Struct(0, 0), null, 0, null); + + json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + + // Null values not serialized, default values for value types serialized. + JsonTestHelper.AssertJsonEqual(@"{""MyInt"":0,""MyPointStruct"":{""X"":0,""Y"":0}}", json); + } + + [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + // Need support for parameterized ctors. + [ActiveIssue("https://github.com/dotnet/runtime/issues/45448")] +#endif + public async Task ValueType_Properties_NotIgnoredWhen_IgnoreNullValues_Active_SmallStructTest() + { + var options = new JsonSerializerOptions { IgnoreNullValues = true }; + + // Deserialization. + string json = @"{""MyString"":null,""MyInt"":0,""MyBool"":null,""MyPointStruct"":{""X"":0,""Y"":0}}"; + + SmallStructWithValueAndReferenceTypes obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + + // Null values ignored for reference types. + Assert.Equal("Default", obj.MyString); + Assert.True(obj.MyBool); + + // Default values not ignored for value types. + Assert.Equal(0, obj.MyInt); + Assert.Equal(0, obj.MyPointStruct.X); + Assert.Equal(0, obj.MyPointStruct.Y); + + // Serialization. + + // Make all members their default CLR value. + obj = new SmallStructWithValueAndReferenceTypes(new Point_2D_Struct(0, 0), null, 0, null); + + json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + + // Null values not serialized, default values for value types serialized. + JsonTestHelper.AssertJsonEqual(@"{""MyInt"":0,""MyPointStruct"":{""X"":0,""Y"":0}}", json); + } + + public class ClassWithValueAndReferenceTypes + { + public string MyString { get; set; } = "Default"; + public int MyInt { get; set; } = -1; + public bool? MyBool { get; set; } = true; + public PointClass MyPointClass { get; set; } = new PointClass(); + public Point_2D_Struct MyPointStruct { get; set; } = new Point_2D_Struct(1, 2); + } + + public struct LargeStructWithValueAndReferenceTypes + { + public string MyString { get; } + public int MyInt { get; set; } + public bool? MyBool { get; set; } + public PointClass MyPointClass { get; set; } + public Point_2D_Struct MyPointStruct { get; set; } + + [JsonConstructor] + public LargeStructWithValueAndReferenceTypes( + PointClass myPointClass, + Point_2D_Struct myPointStruct, + string myString = "Default", + int myInt = -1, + bool? myBool = true) + { + MyString = myString; + MyInt = myInt; + MyBool = myBool; + MyPointClass = myPointClass; + MyPointStruct = myPointStruct; + } + } + + private struct SmallStructWithValueAndReferenceTypes + { + public string MyString { get; } + public int MyInt { get; set; } + public bool? MyBool { get; set; } + public Point_2D_Struct MyPointStruct { get; set; } + + [JsonConstructor] + public SmallStructWithValueAndReferenceTypes( + Point_2D_Struct myPointStruct, + string myString = "Default", + int myInt = -1, + bool? myBool = true) + { + MyString = myString; + MyInt = myInt; + MyBool = myBool; + MyPointStruct = myPointStruct; + } + } + + public class PointClass { } + + [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + // Need support for parameterized ctors. + [ActiveIssue("https://github.com/dotnet/runtime/issues/45448")] +#endif + public async Task Ignore_WhenWritingNull_Globally() + { + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + IncludeFields = true + }; + + string json = @"{ +""MyPointClass2_IgnoredWhenWritingNull"":{}, +""MyString1_IgnoredWhenWritingNull"":""Default"", +""MyNullableBool1_IgnoredWhenWritingNull"":null, +""MyInt2"":0, +""MyPointStruct2"":{""X"":1,""Y"":2}, +""MyInt1"":1, +""MyString2_IgnoredWhenWritingNull"":null, +""MyPointClass1_IgnoredWhenWritingNull"":null, +""MyNullableBool2_IgnoredWhenWritingNull"":true, +""MyPointStruct1"":{""X"":0,""Y"":0} +}"; + + // All members should correspond to JSON contents, as ignore doesn't apply to deserialization. + ClassWithThingsToIgnore obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + Assert.NotNull(obj.MyPointClass2_IgnoredWhenWritingNull); + Assert.Equal("Default", obj.MyString1_IgnoredWhenWritingNull); + Assert.Null(obj.MyNullableBool1_IgnoredWhenWritingNull); + Assert.Equal(0, obj.MyInt2); + Assert.Equal(1, obj.MyPointStruct2.X); + Assert.Equal(2, obj.MyPointStruct2.Y); + Assert.Equal(1, obj.MyInt1); + Assert.Null(obj.MyString2_IgnoredWhenWritingNull); + Assert.Null(obj.MyPointClass1_IgnoredWhenWritingNull); + Assert.True(obj.MyNullableBool2_IgnoredWhenWritingNull); + Assert.Equal(0, obj.MyPointStruct1.X); + Assert.Equal(0, obj.MyPointStruct1.Y); + + // Ignore null as appropriate during serialization. + string expectedJson = @"{ +""MyPointClass2_IgnoredWhenWritingNull"":{}, +""MyString1_IgnoredWhenWritingNull"":""Default"", +""MyInt2"":0, +""MyPointStruct2"":{""X"":1,""Y"":2}, +""MyInt1"":1, +""MyNullableBool2_IgnoredWhenWritingNull"":true, +""MyPointStruct1"":{""X"":0,""Y"":0} +}"; + JsonTestHelper.AssertJsonEqual(expectedJson, await JsonSerializerWrapperForString.SerializeWrapper(obj, options)); + } + + public class ClassWithThingsToIgnore + { + public string MyString1_IgnoredWhenWritingNull { get; set; } + + public string MyString2_IgnoredWhenWritingNull; + + public int MyInt1; + + public int MyInt2 { get; set; } + + public bool? MyNullableBool1_IgnoredWhenWritingNull { get; set; } + + public bool? MyNullableBool2_IgnoredWhenWritingNull; + + public PointClass MyPointClass1_IgnoredWhenWritingNull; + + public PointClass MyPointClass2_IgnoredWhenWritingNull { get; set; } + + public Point_2D_Struct_WithAttribute MyPointStruct1; + + public Point_2D_Struct_WithAttribute MyPointStruct2 { get; set; } + } + + [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + // Need support for parameterized ctors. + [ActiveIssue("https://github.com/dotnet/runtime/issues/45448")] +#endif + public async Task Ignore_WhenWritingNull_PerProperty() + { + var options = new JsonSerializerOptions + { + IncludeFields = true + }; + + string json = @"{ +""MyPointClass2_IgnoredWhenWritingNull"":{}, +""MyString1_IgnoredWhenWritingNull"":""Default"", +""MyNullableBool1_IgnoredWhenWritingNull"":null, +""MyInt2"":0, +""MyPointStruct2"":{""X"":1,""Y"":2}, +""MyInt1"":1, +""MyString2_IgnoredWhenWritingNull"":null, +""MyPointClass1_IgnoredWhenWritingNull"":null, +""MyNullableBool2_IgnoredWhenWritingNull"":true, +""MyPointStruct1"":{""X"":0,""Y"":0} +}"; + + // All members should correspond to JSON contents, as ignore doesn't apply to deserialization. + ClassWithThingsToIgnore_PerProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + Assert.NotNull(obj.MyPointClass2_IgnoredWhenWritingNull); + Assert.Equal("Default", obj.MyString1_IgnoredWhenWritingNull); + Assert.Null(obj.MyNullableBool1_IgnoredWhenWritingNull); + Assert.Equal(0, obj.MyInt2); + Assert.Equal(1, obj.MyPointStruct2.X); + Assert.Equal(2, obj.MyPointStruct2.Y); + Assert.Equal(1, obj.MyInt1); + Assert.Null(obj.MyString2_IgnoredWhenWritingNull); + Assert.Null(obj.MyPointClass1_IgnoredWhenWritingNull); + Assert.True(obj.MyNullableBool2_IgnoredWhenWritingNull); + Assert.Equal(0, obj.MyPointStruct1.X); + Assert.Equal(0, obj.MyPointStruct1.Y); + + // Ignore null as appropriate during serialization. + string expectedJson = @"{ +""MyPointClass2_IgnoredWhenWritingNull"":{}, +""MyString1_IgnoredWhenWritingNull"":""Default"", +""MyInt2"":0, +""MyPointStruct2"":{""X"":1,""Y"":2}, +""MyInt1"":1, +""MyNullableBool2_IgnoredWhenWritingNull"":true, +""MyPointStruct1"":{""X"":0,""Y"":0} +}"; + JsonTestHelper.AssertJsonEqual(expectedJson, await JsonSerializerWrapperForString.SerializeWrapper(obj, options)); + } + + public class ClassWithThingsToIgnore_PerProperty + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string MyString1_IgnoredWhenWritingNull { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string MyString2_IgnoredWhenWritingNull; + + public int MyInt1; + + public int MyInt2 { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? MyNullableBool1_IgnoredWhenWritingNull { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? MyNullableBool2_IgnoredWhenWritingNull; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public PointClass MyPointClass1_IgnoredWhenWritingNull; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public PointClass MyPointClass2_IgnoredWhenWritingNull { get; set; } + + public Point_2D_Struct_WithAttribute MyPointStruct1; + + public Point_2D_Struct_WithAttribute MyPointStruct2 { get; set; } + } + + [Theory] + [InlineData(typeof(ClassWithBadIgnoreAttribute))] + [InlineData(typeof(StructWithBadIgnoreAttribute))] + public virtual async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail(Type type) + { + InvalidOperationException ex = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("{}", type)); + string exAsStr = ex.ToString(); + Assert.Contains("JsonIgnoreCondition.WhenWritingNull", exAsStr); + Assert.Contains("MyBadMember", exAsStr); + Assert.Contains(type.ToString(), exAsStr); + Assert.Contains("JsonIgnoreCondition.WhenWritingDefault", exAsStr); + + ex = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(Activator.CreateInstance(type), type)); + exAsStr = ex.ToString(); + Assert.Contains("JsonIgnoreCondition.WhenWritingNull", exAsStr); + Assert.Contains("MyBadMember", exAsStr); + Assert.Contains(type.ToString(), exAsStr); + Assert.Contains("JsonIgnoreCondition.WhenWritingDefault", exAsStr); + } + + [Theory] + [InlineData(typeof(ClassWithBadIgnoreAttribute))] + [InlineData(typeof(StructWithBadIgnoreAttribute))] + public virtual async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail_EmptyJson(Type type) + { + InvalidOperationException ex = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("", type)); + string exAsStr = ex.ToString(); + Assert.Contains("JsonIgnoreCondition.WhenWritingNull", exAsStr); + Assert.Contains("MyBadMember", exAsStr); + Assert.Contains(type.ToString(), exAsStr); + Assert.Contains("JsonIgnoreCondition.WhenWritingDefault", exAsStr); + + ex = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(Activator.CreateInstance(type))); + exAsStr = ex.ToString(); + Assert.Contains("JsonIgnoreCondition.WhenWritingNull", exAsStr); + Assert.Contains("MyBadMember", exAsStr); + Assert.Contains(type.ToString(), exAsStr); + Assert.Contains("JsonIgnoreCondition.WhenWritingDefault", exAsStr); + } + + public class ClassWithBadIgnoreAttribute + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int MyBadMember { get; set; } + } + + public struct StructWithBadIgnoreAttribute + { + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Point_2D_Struct MyBadMember { get; set; } + } + + public interface IUseCustomConverter { } + + [JsonConverter(typeof(MyCustomConverter))] + public struct MyValueTypeWithProperties : IUseCustomConverter + { + public int PrimitiveValue { get; set; } + public object RefValue { get; set; } + } + + public class MyCustomConverter : JsonConverter + { + public override bool CanConvert(Type typeToConvert) + { + return typeof(IUseCustomConverter).IsAssignableFrom(typeToConvert); + } + + public override IUseCustomConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + throw new NotImplementedException(); + + public override void Write(Utf8JsonWriter writer, IUseCustomConverter value, JsonSerializerOptions options) + { + MyValueTypeWithProperties obj = (MyValueTypeWithProperties)value; + writer.WriteNumberValue(obj.PrimitiveValue + 100); + // Ignore obj.RefValue + } + } + + public class MyClassWithValueType + { + public MyClassWithValueType() { } + + public MyValueTypeWithProperties Value { get; set; } + } + + [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + // Needs bug fixes to custom converter handling. + [ActiveIssue("https://github.com/dotnet/runtime/issues/45448")] +#endif + public async Task JsonIgnoreCondition_WhenWritingDefault_OnValueTypeWithCustomConverter() + { + var obj = new MyClassWithValueType(); + + // Baseline without custom options. + Assert.True(EqualityComparer.Default.Equals(default, obj.Value)); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Equal("{\"Value\":100}", json); + + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault + }; + + // Verify ignored. + Assert.True(EqualityComparer.Default.Equals(default, obj.Value)); + json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + Assert.Equal("{}", json); + + // Change a primitive value so it's no longer a default value. + obj.Value = new MyValueTypeWithProperties { PrimitiveValue = 1 }; + Assert.False(EqualityComparer.Default.Equals(default, obj.Value)); + json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + Assert.Equal("{\"Value\":101}", json); + + // Change reference value so it's no longer a default value. + obj.Value = new MyValueTypeWithProperties { RefValue = 1 }; + Assert.False(EqualityComparer.Default.Equals(default, obj.Value)); + json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + Assert.Equal("{\"Value\":100}", json); + } + + [Fact] + public async Task JsonIgnoreCondition_ConverterCalledOnDeserialize() + { + // Verify converter is called. + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("{}")); + + var options = new JsonSerializerOptions + { + IgnoreNullValues = true + }; + + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("{}", options)); + } + + [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + // Needs bug fixes to custom converter handling. + [ActiveIssue("https://github.com/dotnet/runtime/issues/45448")] +#endif + public async Task JsonIgnoreCondition_WhenWritingNull_OnValueTypeWithCustomConverter() + { + string json; + var obj = new MyClassWithValueType(); + + // Baseline without custom options. + Assert.True(EqualityComparer.Default.Equals(default, obj.Value)); + json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Equal("{\"Value\":100}", json); + + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + + // Verify not ignored; MyValueTypeWithProperties is not null. + Assert.True(EqualityComparer.Default.Equals(default, obj.Value)); + json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + Assert.Equal("{\"Value\":100}", json); + } + + [Fact] + public async Task JsonIgnoreCondition_WhenWritingDefault_OnRootTypes() + { + string json; + int i = 0; + object obj = null; + + // Baseline without custom options. + json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Equal("null", json); + + json = await JsonSerializerWrapperForString.SerializeWrapper(i); + Assert.Equal("0", json); + + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault + }; + + // We don't ignore when applied to root types; only properties. + + json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + Assert.Equal("null", json); + + json = await JsonSerializerWrapperForString.SerializeWrapper(i, options); + Assert.Equal("0", json); + } + + public struct MyValueTypeWithBoxedPrimitive + { + public object BoxedPrimitive { get; set; } + } + + [Fact] + public async Task JsonIgnoreCondition_WhenWritingDefault_OnBoxedPrimitive() + { + string json; + + MyValueTypeWithBoxedPrimitive obj = new MyValueTypeWithBoxedPrimitive { BoxedPrimitive = 0 }; + + // Baseline without custom options. + json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Equal("{\"BoxedPrimitive\":0}", json); + + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault + }; + + // No check if the boxed object's value type is a default value (0 in this case). + json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + Assert.Equal("{\"BoxedPrimitive\":0}", json); + + obj = new MyValueTypeWithBoxedPrimitive(); + json = await JsonSerializerWrapperForString.SerializeWrapper(obj, options); + Assert.Equal("{}", json); + } + + public class MyClassWithValueTypeInterfaceProperty + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public IInterface MyProp { get; set; } + + public interface IInterface { } + public struct MyStruct : IInterface { } + } + + [Fact] + public async Task JsonIgnoreCondition_WhenWritingDefault_OnInterface() + { + // MyProp should be ignored due to [JsonIgnore]. + var obj = new MyClassWithValueTypeInterfaceProperty(); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Equal("{}", json); + + // No check if the interface property's value type is a default value. + obj = new MyClassWithValueTypeInterfaceProperty { MyProp = new MyClassWithValueTypeInterfaceProperty.MyStruct() }; + json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Equal("{\"MyProp\":{}}", json); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs b/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs new file mode 100644 index 000000000000..c7198b1e3069 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Text.Json.Serialization.Tests +{ + public abstract class SerializerTests + { + protected JsonSerializerWrapperForString JsonSerializerWrapperForString { get; } + + protected SerializerTests(JsonSerializerWrapperForString serializerWrapper) => JsonSerializerWrapperForString = serializerWrapper; + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.ConcurrentCollections.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.ConcurrentCollections.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.ConcurrentCollections.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.ConcurrentCollections.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.Constructor.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.Constructor.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.GenericCollections.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.GenericCollections.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.GenericCollections.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.GenericCollections.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.ImmutableCollections.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.ImmutableCollections.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.ImmutableCollections.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.ImmutableCollections.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.NonGenericCollections.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.NonGenericCollections.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.NonGenericCollections.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.NonGenericCollections.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.Polymorphic.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Polymorphic.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.Polymorphic.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Polymorphic.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestClass.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestClass.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestClass.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestClass.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestClassWithFields.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestClassWithFields.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestClassWithFields.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestClassWithFields.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestClassWithNullables.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestClassWithNullables.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestClassWithNullables.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestClassWithNullables.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestClassWithObject.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestClassWithObject.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestClassWithObject.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestClassWithObject.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestClassWithObjectArrays.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestClassWithObjectArrays.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestClassWithObjectArrays.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestClassWithObjectArrays.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestClassWithSimpleObject.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestClassWithSimpleObject.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestClassWithSimpleObject.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestClassWithSimpleObject.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestStruct.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestStruct.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestStruct.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestStruct.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestStructWithFields.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestStructWithFields.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.SimpleTestStructWithFields.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.SimpleTestStructWithFields.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.ValueTypedMember.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.ValueTypedMember.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.ValueTypedMember.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.ValueTypedMember.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TestClasses/TestClasses.cs rename to src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapperForString_SourceGen.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapperForString_SourceGen.cs new file mode 100644 index 000000000000..dfb968371a22 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapperForString_SourceGen.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using System.Text.Json.Serialization.Tests; +using System.Threading.Tasks; + +namespace System.Text.Json.SourceGeneration.Tests +{ + internal sealed class JsonSerializerWrapperForString_SourceGen : JsonSerializerWrapperForString + { + private readonly JsonSerializerContext _defaultContext; + private readonly Func _customContextCreator; + + public JsonSerializerWrapperForString_SourceGen(JsonSerializerContext defaultContext, Func customContextCreator) + { + _defaultContext = defaultContext ?? throw new ArgumentNullException(nameof(defaultContext)); + _customContextCreator = customContextCreator ?? throw new ArgumentNullException(nameof(defaultContext)); + } + + protected internal override Task SerializeWrapper(object value, Type type, JsonSerializerOptions? options = null) + { + if (options != null) + { + return Task.FromResult(Serialize(value, type, options)); + } + + return Task.FromResult(JsonSerializer.Serialize(value, type, _defaultContext)); + } + + private string Serialize(object value, Type type, JsonSerializerOptions options) + { + JsonSerializerContext context = _customContextCreator(new JsonSerializerOptions(options)); + return JsonSerializer.Serialize(value, type, context); + } + + protected internal override Task SerializeWrapper(T value, JsonSerializerOptions? options = null) + { + if (options != null) + { + return Task.FromResult(Serialize(value, options)); + } + + JsonTypeInfo typeInfo = (JsonTypeInfo)_defaultContext.GetTypeInfo(typeof(T)); + return Task.FromResult(JsonSerializer.Serialize(value, typeInfo)); + } + + private string Serialize(T value, JsonSerializerOptions options) + { + JsonSerializerContext context = _customContextCreator(new JsonSerializerOptions(options)); + JsonTypeInfo typeInfo = (JsonTypeInfo)context.GetTypeInfo(typeof(T)); + return JsonSerializer.Serialize(value, typeInfo); + } + + protected internal override Task SerializeWrapper(object value, Type inputType, JsonSerializerContext context) + => throw new NotImplementedException(); + + protected internal override Task SerializeWrapper(T value, JsonTypeInfo jsonTypeInfo) + => throw new NotImplementedException(); + + protected internal override Task DeserializeWrapper(string json, JsonSerializerOptions? options = null) + { + if (options != null) + { + return Task.FromResult(Deserialize(json, options)); + } + + JsonTypeInfo typeInfo = (JsonTypeInfo)_defaultContext.GetTypeInfo(typeof(T)); + return Task.FromResult(JsonSerializer.Deserialize(json, typeInfo)); + } + + private T Deserialize(string json, JsonSerializerOptions options) + { + JsonSerializerContext context = _customContextCreator(new JsonSerializerOptions(options)); + JsonTypeInfo typeInfo = (JsonTypeInfo)context.GetTypeInfo(typeof(T)); + return JsonSerializer.Deserialize(json, typeInfo); + } + + protected internal override Task DeserializeWrapper(string json, Type type, JsonSerializerOptions? options = null) + { + if (options != null) + { + return Task.FromResult(Deserialize(json, type, options)); + } + + return Task.FromResult(JsonSerializer.Deserialize(json, type, _defaultContext)); + } + + private object Deserialize(string json, Type type, JsonSerializerOptions options) + { + JsonSerializerContext context = _customContextCreator(new JsonSerializerOptions(options)); + return JsonSerializer.Deserialize(json, type, context); + } + + protected internal override Task DeserializeWrapper(string json, JsonTypeInfo jsonTypeInfo) + => throw new NotImplementedException(); + + protected internal override Task DeserializeWrapper(string json, Type type, JsonSerializerContext context) + => throw new NotImplementedException(); + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs new file mode 100644 index 000000000000..6ebb90f030c6 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs @@ -0,0 +1,424 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Tests; +using System.Threading.Tasks; +using Xunit; + +namespace System.Text.Json.SourceGeneration.Tests +{ + public partial class PropertyVisibilityTests_Metadata : PropertyVisibilityTests + { + public PropertyVisibilityTests_Metadata() + : this(new JsonSerializerWrapperForString_SourceGen(PropertyVisibilityTestsContext_Metadata.Default, (options) => new PropertyVisibilityTestsContext_Metadata(options))) + { + } + + protected PropertyVisibilityTests_Metadata(JsonSerializerWrapperForString serializerWrapper) + : base(serializerWrapper) + { + } + + [Theory] + [InlineData(typeof(ClassWithBadIgnoreAttribute))] + [InlineData(typeof(StructWithBadIgnoreAttribute))] + public override async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail_EmptyJson(Type type) + { + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("", type)); + + InvalidOperationException ioe = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(Activator.CreateInstance(type), type)); + string exAsStr = ioe.ToString(); + Assert.Contains("JsonIgnoreCondition.WhenWritingNull", exAsStr); + Assert.Contains("MyBadMember", exAsStr); + Assert.Contains(type.ToString(), exAsStr); + Assert.Contains("JsonIgnoreCondition.WhenWritingDefault", exAsStr); + } + + [Fact] + public override async Task Honor_JsonSerializablePropertyAttribute_OnProperties() + { + string json = @"{ + ""MyInt"":1, + ""MyString"":""Hello"", + ""MyFloat"":2, + ""MyUri"":""https://microsoft.com"" + }"; + + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + Assert.Equal(0, obj.MyInt); // Source gen can't use private setter + Assert.Equal("Hello", obj.MyString); + Assert.Equal(2f, obj.GetMyFloat); + Assert.Equal(new Uri("https://microsoft.com"), obj.MyUri); + + json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Contains(@"""MyInt"":0", json); + Assert.Contains(@"""MyString"":""Hello""", json); + Assert.DoesNotContain(@"""MyFloat"":", json); // Source gen can't use private setter + Assert.Contains(@"""MyUri"":""https://microsoft.com""", json); + } + + [Theory] + [InlineData(typeof(ClassWithInitOnlyProperty))] + [InlineData(typeof(StructWithInitOnlyProperty))] + public override async Task InitOnlyProperties(Type type) + { + // Init-only setters cannot be referenced as get/set helpers in generated code. + object obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1}", type); + Assert.Equal(0, (int)type.GetProperty("MyInt").GetValue(obj)); + + // Init-only properties can be serialized. + Assert.Equal(@"{""MyInt"":0}", await JsonSerializerWrapperForString.SerializeWrapper(obj, type)); + } + + [Theory] + [InlineData(typeof(Class_PropertyWith_PrivateInitOnlySetter_WithAttribute))] + [InlineData(typeof(Class_PropertyWith_InternalInitOnlySetter_WithAttribute))] + [InlineData(typeof(Class_PropertyWith_ProtectedInitOnlySetter_WithAttribute))] + public override async Task NonPublicInitOnlySetter_With_JsonInclude(Type type) + { + // Init-only setters cannot be referenced as get/set helpers in generated code. + object obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1}", type); + Assert.Equal(0, (int)type.GetProperty("MyInt").GetValue(obj)); + + // Init-only properties can be serialized. + Assert.Equal(@"{""MyInt"":0}", await JsonSerializerWrapperForString.SerializeWrapper(obj, type)); + } + + [Fact] + public override async Task HonorCustomConverter_UsingPrivateSetter() + { + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + + string json = @"{""MyEnum"":""AnotherValue"",""MyInt"":2}"; + + // Deserialization baseline, without enum converter, we get JsonException. + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper(json)); + + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); + Assert.Equal(MySmallEnum.AnotherValue, obj.GetMyEnum); + Assert.Equal(0, obj.MyInt); // Private setter can't be used with source-gen. + + // ConverterForInt32 throws this exception. + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(obj, options)); + } + + [Fact] + public override async Task Public_And_NonPublicPropertyAccessors_PropertyAttributes() + { + string json = @"{""W"":1,""X"":2,""Y"":3,""Z"":4}"; + + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + Assert.Equal(1, obj.W); + Assert.Equal(2, obj.X); + Assert.Equal(3, obj.Y); + Assert.Equal(4, obj.GetZ); + + json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.Contains(@"""W"":1", json); + Assert.Contains(@"""X"":2", json); + Assert.Contains(@"""Y"":3", json); + Assert.DoesNotContain(@"""Z"":", json); // Private setter cannot be used with source gen. + } + + [Fact] + public override async Task HonorJsonPropertyName() + { + string json = @"{""prop1"":1,""prop2"":2}"; + + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); + Assert.Equal(MySmallEnum.AnotherValue, obj.GetMyEnum); + Assert.Equal(0, obj.MyInt); // Private setter cannot be used with source gen. + + json = await JsonSerializerWrapperForString.SerializeWrapper(obj); + Assert.DoesNotContain(@"""prop1"":", json); // Private getter cannot be used with source gen. + Assert.Contains(@"""prop2"":0", json); + } + + [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(ClassWithNewSlotField))] + [JsonSerializable(typeof(int))] + [JsonSerializable(typeof(object))] + [JsonSerializable(typeof(ClassWithInternalField))] + [JsonSerializable(typeof(ClassWithNewSlotDecimalField))] + [JsonSerializable(typeof(ClassWithNewSlotAttributedDecimalField))] + [JsonSerializable(typeof(ClassWithIgnoredPropertyPolicyConflictPrivate))] + [JsonSerializable(typeof(ClassWithMissingCollectionProperty))] + [JsonSerializable(typeof(ClassWithClassProperty_IgnoreConditionWhenWritingDefault))] + [JsonSerializable(typeof(ClassWithNoSetter))] + [JsonSerializable(typeof(ClassWithInternalProperty))] + [JsonSerializable(typeof(ClassWithPropertyNamingConflict))] + [JsonSerializable(typeof(ClassWithStructProperty_IgnoreConditionWhenWritingDefault))] + [JsonSerializable(typeof(ClassWithMissingObjectProperty))] + [JsonSerializable(typeof(ClassWithInitOnlyProperty))] + [JsonSerializable(typeof(StructWithInitOnlyProperty))] + [JsonSerializable(typeof(MyClassWithValueTypeInterfaceProperty))] + [JsonSerializable(typeof(ClassWithNonPublicProperties))] + [JsonSerializable(typeof(ClassWithProperty_IgnoreConditionAlways))] + [JsonSerializable(typeof(ClassWithBadIgnoreAttribute))] + [JsonSerializable(typeof(StructWithBadIgnoreAttribute))] + [JsonSerializable(typeof(Class_PropertyWith_InternalInitOnlySetter))] + [JsonSerializable(typeof(Class_PropertyWith_ProtectedInitOnlySetter))] + [JsonSerializable(typeof(ClassWithIgnoredPublicPropertyAndNewSlotPrivate))] + [JsonSerializable(typeof(ClassWithIgnoredPropertyPolicyConflictPublic))] + [JsonSerializable(typeof(ClassWithIgnoredPropertyNamingConflictPrivate))] + [JsonSerializable(typeof(ClassWithIgnoredNewSlotProperty))] + [JsonSerializable(typeof(ClassWithPublicGetterAndPrivateSetter))] + [JsonSerializable(typeof(ClassWithInitializedProps))] + [JsonSerializable(typeof(ClassWithNewSlotInternalProperty))] + [JsonSerializable(typeof(ClassWithPropertyPolicyConflict))] + [JsonSerializable(typeof(ClassWithPrivateSetterAndGetter))] + [JsonSerializable(typeof(ClassWithIgnoreAttributeProperty))] + [JsonSerializable(typeof(ClassWithIgnoredNewSlotField))] + [JsonSerializable(typeof(MyStruct_WithNonPublicAccessors_WithTypeAttribute))] + [JsonSerializable(typeof(ClassWithReadOnlyFields))] + [JsonSerializable(typeof(MyValueTypeWithBoxedPrimitive))] + [JsonSerializable(typeof(int))] + [JsonSerializable(typeof(ClassWithNoGetter))] + [JsonSerializable(typeof(ClassWithPropsAndIgnoreAttributes))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(MyValueTypeWithProperties))] + [JsonSerializable(typeof(ClassInheritedWithPropertyPolicyConflictWhichThrows))] + [JsonSerializable(typeof(ClassInheritedWithPropertyFieldPolicyConflictWhichThrows))] + [JsonSerializable(typeof(ClassWithPropertyFieldPolicyConflictWhichThrows))] + [JsonSerializable(typeof(ClassWithOverrideReversed))] + [JsonSerializable(typeof(ClassWithReadOnlyStringProperty))] + [JsonSerializable(typeof(ClassWithReadOnlyStringProperty_IgnoreNever))] + [JsonSerializable(typeof(ClassWithProps))] + [JsonSerializable(typeof(ClassWithStructProperty_IgnoreConditionNever))] + [JsonSerializable(typeof(ClassWithStructProperty_IgnoreConditionNever_Ctor))] + [JsonSerializable(typeof(ClassWithPropertyFieldNamingConflictWhichThrows))] + [JsonSerializable(typeof(ClassWithBadIgnoreAttribute))] + [JsonSerializable(typeof(StructWithBadIgnoreAttribute))] + [JsonSerializable(typeof(ClassWithPropertyNamingConflictWhichThrows))] + [JsonSerializable(typeof(ClassWithReadOnlyStringField))] + [JsonSerializable(typeof(ClassWithReadOnlyStringField_IgnoreWhenWritingDefault))] + [JsonSerializable(typeof(ClassWithReadOnlyStringField_IgnoreNever))] + [JsonSerializable(typeof(ClassInheritedWithPropertyFieldNamingConflictWhichThrows))] + [JsonSerializable(typeof(ClassTwiceInheritedWithPropertyFieldNamingConflictWhichThrows))] + [JsonSerializable(typeof(ClassWithPrivateProperty_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithInternalProperty_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithProtectedProperty_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithPrivateField_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithInternalField_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithProtectedField_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithPrivate_InitOnlyProperty_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithInternal_InitOnlyProperty_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithProtected_InitOnlyProperty_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithPublicProperty))] + [JsonSerializable(typeof(ClassInheritedWithPropertyNamingConflictWhichThrows))] + [JsonSerializable(typeof(StructWithOverride))] + [JsonSerializable(typeof(ClassTwiceInheritedWithPropertyFieldPolicyConflictWhichThrows))] + [JsonSerializable(typeof(ClassTwiceInheritedWithPropertyNamingConflictWhichThrows))] + [JsonSerializable(typeof(MyClass_WithNonPublicAccessors_WithPropertyAttributes))] + [JsonSerializable(typeof(Class_PropertyWith_PrivateInitOnlySetter))] + [JsonSerializable(typeof(Class_PropertyWith_InternalInitOnlySetter))] + [JsonSerializable(typeof(Class_PropertyWith_ProtectedInitOnlySetter))] + [JsonSerializable(typeof(Class_PropertyWith_PrivateInitOnlySetter_WithAttribute))] + [JsonSerializable(typeof(Class_PropertyWith_InternalInitOnlySetter_WithAttribute))] + [JsonSerializable(typeof(Class_PropertyWith_ProtectedInitOnlySetter_WithAttribute))] + [JsonSerializable(typeof(DerivedClass_With_IgnoredOverride))] + [JsonSerializable(typeof(DerivedClass_WithVisibleProperty_Of_DerivedClass_With_IgnoredOverride))] + [JsonSerializable(typeof(DerivedClass_With_IgnoredOverride_And_ConflictingPropertyName))] + [JsonSerializable(typeof(DerivedClass_With_Ignored_NewProperty))] + [JsonSerializable(typeof(DerivedClass_WithConflictingNewMember))] + [JsonSerializable(typeof(DerivedClass_WithConflictingNewMember_Of_DifferentType))] + [JsonSerializable(typeof(DerivedClass_With_Ignored_ConflictingNewMember))] + [JsonSerializable(typeof(DerivedClass_With_Ignored_ConflictingNewMember_Of_DifferentType))] + [JsonSerializable(typeof(DerivedClass_With_NewProperty_And_ConflictingPropertyName))] + [JsonSerializable(typeof(DerivedClass_With_Ignored_NewProperty_Of_DifferentType))] + [JsonSerializable(typeof(DerivedClass_With_Ignored_NewProperty_Of_DifferentType_And_ConflictingPropertyName))] + [JsonSerializable(typeof(FurtherDerivedClass_With_ConflictingPropertyName))] + [JsonSerializable(typeof(DerivedClass_WithConflictingPropertyName))] + [JsonSerializable(typeof(FurtherDerivedClass_With_IgnoredOverride))] + [JsonSerializable(typeof(ClassWithIgnoredPropertyNamingConflictPublic))] + [JsonSerializable(typeof(MyClassWithValueType))] + [JsonSerializable(typeof(StructWithPropertiesWithConverter))] + [JsonSerializable(typeof(ClassWithNewSlotProperty))] + [JsonSerializable(typeof(ClassWithNewSlotAttributedDecimalProperty))] + [JsonSerializable(typeof(ClassWithNewSlotDecimalProperty))] + [JsonSerializable(typeof(LargeStructWithValueAndReferenceTypes))] + [JsonSerializable(typeof(ClassWithUnsupportedBigInteger))] + [JsonSerializable(typeof(WrapperForClassWithUnsupportedBigInteger))] + [JsonSerializable(typeof(ClassWithIgnoredUnsupportedBigInteger))] + [JsonSerializable(typeof(WrapperForClassWithIgnoredUnsupportedBigInteger))] + [JsonSerializable(typeof(ClassWithThingsToIgnore))] + [JsonSerializable(typeof(ClassWithMixedPropertyAccessors_PropertyAttributes))] + [JsonSerializable(typeof(ClassWithPropertyPolicyConflictWhichThrows))] + [JsonSerializable(typeof(ClassTwiceInheritedWithPropertyPolicyConflictWhichThrows))] + [JsonSerializable(typeof(MyClass_WithNonPublicAccessors))] + [JsonSerializable(typeof(ClassWithThingsToIgnore_PerProperty))] + [JsonSerializable(typeof(StructWithPropertiesWithJsonPropertyName))] + [JsonSerializable(typeof(ClassWithValueAndReferenceTypes))] + [JsonSerializable(typeof(ClassWithReadOnlyStringProperty_IgnoreWhenWritingDefault))] + internal sealed partial class PropertyVisibilityTestsContext_Metadata : JsonSerializerContext + { + } + } + + public partial class PropertyVisibilityTests_Default : PropertyVisibilityTests_Metadata + //public partial class PropertyVisibilityTests_Default : PropertyVisibilityTests + { + public PropertyVisibilityTests_Default() + : base(new JsonSerializerWrapperForString_SourceGen(PropertyVisibilityTestsContext_Default.Default, (options) => new PropertyVisibilityTestsContext_Default(options))) + { + } + + [Theory] + [InlineData(typeof(ClassWithPrivateProperty_WithJsonIncludeProperty))] + [InlineData(typeof(ClassWithInternalProperty_WithJsonIncludeProperty))] + [InlineData(typeof(ClassWithProtectedProperty_WithJsonIncludeProperty))] + [InlineData(typeof(ClassWithPrivateField_WithJsonIncludeProperty))] + [InlineData(typeof(ClassWithInternalField_WithJsonIncludeProperty))] + [InlineData(typeof(ClassWithProtectedField_WithJsonIncludeProperty))] + [InlineData(typeof(ClassWithPrivate_InitOnlyProperty_WithJsonIncludeProperty))] + [InlineData(typeof(ClassWithInternal_InitOnlyProperty_WithJsonIncludeProperty))] + [InlineData(typeof(ClassWithProtected_InitOnlyProperty_WithJsonIncludeProperty))] + public override async Task NonPublicProperty_WithJsonInclude_Invalid(Type type) + { + // Exception messages direct users to use JsonSourceGenerationMode.Metadata to see a more detailed error. + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("{}", type)); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(Activator.CreateInstance(type), type)); + } + + [Theory] + [InlineData(typeof(ClassWithBadIgnoreAttribute))] + [InlineData(typeof(StructWithBadIgnoreAttribute))] + public override async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail(Type type) + { + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("{}", type)); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(Activator.CreateInstance(type), type)); + } + + [Theory] + [InlineData(typeof(ClassWithBadIgnoreAttribute))] + [InlineData(typeof(StructWithBadIgnoreAttribute))] + public override async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail_EmptyJson(Type type) + { + // Since this code goes down fast-path, there's no warm up and we hit the reader exception about having no tokens. + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper("", type)); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(Activator.CreateInstance(type), type)); + } + + [JsonSerializable(typeof(ClassWithNewSlotField))] + [JsonSerializable(typeof(int))] + [JsonSerializable(typeof(object))] + [JsonSerializable(typeof(ClassWithInternalField))] + [JsonSerializable(typeof(ClassWithNewSlotDecimalField))] + [JsonSerializable(typeof(ClassWithNewSlotAttributedDecimalField))] + [JsonSerializable(typeof(ClassWithIgnoredPropertyPolicyConflictPrivate))] + [JsonSerializable(typeof(ClassWithMissingCollectionProperty))] + [JsonSerializable(typeof(ClassWithClassProperty_IgnoreConditionWhenWritingDefault))] + [JsonSerializable(typeof(ClassWithNoSetter))] + [JsonSerializable(typeof(ClassWithInternalProperty))] + [JsonSerializable(typeof(ClassWithPropertyNamingConflict))] + [JsonSerializable(typeof(ClassWithStructProperty_IgnoreConditionWhenWritingDefault))] + [JsonSerializable(typeof(ClassWithMissingObjectProperty))] + [JsonSerializable(typeof(ClassWithInitOnlyProperty))] + [JsonSerializable(typeof(StructWithInitOnlyProperty))] + [JsonSerializable(typeof(MyClassWithValueTypeInterfaceProperty))] + [JsonSerializable(typeof(ClassWithNonPublicProperties))] + [JsonSerializable(typeof(ClassWithProperty_IgnoreConditionAlways))] + [JsonSerializable(typeof(ClassWithBadIgnoreAttribute))] + [JsonSerializable(typeof(StructWithBadIgnoreAttribute))] + [JsonSerializable(typeof(Class_PropertyWith_InternalInitOnlySetter))] + [JsonSerializable(typeof(Class_PropertyWith_ProtectedInitOnlySetter))] + [JsonSerializable(typeof(ClassWithIgnoredPublicPropertyAndNewSlotPrivate))] + [JsonSerializable(typeof(ClassWithIgnoredPropertyPolicyConflictPublic))] + [JsonSerializable(typeof(ClassWithIgnoredPropertyNamingConflictPrivate))] + [JsonSerializable(typeof(ClassWithIgnoredNewSlotProperty))] + [JsonSerializable(typeof(ClassWithPublicGetterAndPrivateSetter))] + [JsonSerializable(typeof(ClassWithInitializedProps))] + [JsonSerializable(typeof(ClassWithNewSlotInternalProperty))] + [JsonSerializable(typeof(ClassWithPropertyPolicyConflict))] + [JsonSerializable(typeof(ClassWithPrivateSetterAndGetter))] + [JsonSerializable(typeof(ClassWithIgnoreAttributeProperty))] + [JsonSerializable(typeof(ClassWithIgnoredNewSlotField))] + [JsonSerializable(typeof(MyStruct_WithNonPublicAccessors_WithTypeAttribute))] + [JsonSerializable(typeof(ClassWithReadOnlyFields))] + [JsonSerializable(typeof(MyValueTypeWithBoxedPrimitive))] + [JsonSerializable(typeof(int))] + [JsonSerializable(typeof(ClassWithNoGetter))] + [JsonSerializable(typeof(ClassWithPropsAndIgnoreAttributes))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(MyValueTypeWithProperties))] + [JsonSerializable(typeof(ClassInheritedWithPropertyPolicyConflictWhichThrows))] + [JsonSerializable(typeof(ClassInheritedWithPropertyFieldPolicyConflictWhichThrows))] + [JsonSerializable(typeof(ClassWithPropertyFieldPolicyConflictWhichThrows))] + [JsonSerializable(typeof(ClassWithOverrideReversed))] + [JsonSerializable(typeof(ClassWithReadOnlyStringProperty))] + [JsonSerializable(typeof(ClassWithReadOnlyStringProperty_IgnoreNever))] + [JsonSerializable(typeof(ClassWithProps))] + [JsonSerializable(typeof(ClassWithStructProperty_IgnoreConditionNever))] + [JsonSerializable(typeof(ClassWithStructProperty_IgnoreConditionNever_Ctor))] + [JsonSerializable(typeof(ClassWithPropertyFieldNamingConflictWhichThrows))] + [JsonSerializable(typeof(ClassWithBadIgnoreAttribute))] + [JsonSerializable(typeof(StructWithBadIgnoreAttribute))] + [JsonSerializable(typeof(ClassWithPropertyNamingConflictWhichThrows))] + [JsonSerializable(typeof(ClassWithReadOnlyStringField))] + [JsonSerializable(typeof(ClassWithReadOnlyStringField_IgnoreWhenWritingDefault))] + [JsonSerializable(typeof(ClassWithReadOnlyStringField_IgnoreNever))] + [JsonSerializable(typeof(ClassInheritedWithPropertyFieldNamingConflictWhichThrows))] + [JsonSerializable(typeof(ClassTwiceInheritedWithPropertyFieldNamingConflictWhichThrows))] + [JsonSerializable(typeof(ClassWithPrivateProperty_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithInternalProperty_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithProtectedProperty_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithPrivateField_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithInternalField_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithProtectedField_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithPrivate_InitOnlyProperty_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithInternal_InitOnlyProperty_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithProtected_InitOnlyProperty_WithJsonIncludeProperty))] + [JsonSerializable(typeof(ClassWithPublicProperty))] + [JsonSerializable(typeof(ClassInheritedWithPropertyNamingConflictWhichThrows))] + [JsonSerializable(typeof(StructWithOverride))] + [JsonSerializable(typeof(ClassTwiceInheritedWithPropertyFieldPolicyConflictWhichThrows))] + [JsonSerializable(typeof(ClassTwiceInheritedWithPropertyNamingConflictWhichThrows))] + [JsonSerializable(typeof(MyClass_WithNonPublicAccessors_WithPropertyAttributes))] + [JsonSerializable(typeof(Class_PropertyWith_PrivateInitOnlySetter))] + [JsonSerializable(typeof(Class_PropertyWith_InternalInitOnlySetter))] + [JsonSerializable(typeof(Class_PropertyWith_ProtectedInitOnlySetter))] + [JsonSerializable(typeof(Class_PropertyWith_PrivateInitOnlySetter_WithAttribute))] + [JsonSerializable(typeof(Class_PropertyWith_InternalInitOnlySetter_WithAttribute))] + [JsonSerializable(typeof(Class_PropertyWith_ProtectedInitOnlySetter_WithAttribute))] + [JsonSerializable(typeof(DerivedClass_With_IgnoredOverride))] + [JsonSerializable(typeof(DerivedClass_WithVisibleProperty_Of_DerivedClass_With_IgnoredOverride))] + [JsonSerializable(typeof(DerivedClass_With_IgnoredOverride_And_ConflictingPropertyName))] + [JsonSerializable(typeof(DerivedClass_With_Ignored_NewProperty))] + [JsonSerializable(typeof(DerivedClass_WithConflictingNewMember))] + [JsonSerializable(typeof(DerivedClass_WithConflictingNewMember_Of_DifferentType))] + [JsonSerializable(typeof(DerivedClass_With_Ignored_ConflictingNewMember))] + [JsonSerializable(typeof(DerivedClass_With_Ignored_ConflictingNewMember_Of_DifferentType))] + [JsonSerializable(typeof(DerivedClass_With_NewProperty_And_ConflictingPropertyName))] + [JsonSerializable(typeof(DerivedClass_With_Ignored_NewProperty_Of_DifferentType))] + [JsonSerializable(typeof(DerivedClass_With_Ignored_NewProperty_Of_DifferentType_And_ConflictingPropertyName))] + [JsonSerializable(typeof(FurtherDerivedClass_With_ConflictingPropertyName))] + [JsonSerializable(typeof(DerivedClass_WithConflictingPropertyName))] + [JsonSerializable(typeof(FurtherDerivedClass_With_IgnoredOverride))] + [JsonSerializable(typeof(ClassWithIgnoredPropertyNamingConflictPublic))] + [JsonSerializable(typeof(MyClassWithValueType))] + [JsonSerializable(typeof(StructWithPropertiesWithConverter))] + [JsonSerializable(typeof(ClassWithNewSlotProperty))] + [JsonSerializable(typeof(ClassWithNewSlotAttributedDecimalProperty))] + [JsonSerializable(typeof(ClassWithNewSlotDecimalProperty))] + [JsonSerializable(typeof(LargeStructWithValueAndReferenceTypes))] + [JsonSerializable(typeof(ClassWithUnsupportedBigInteger))] + [JsonSerializable(typeof(WrapperForClassWithUnsupportedBigInteger))] + [JsonSerializable(typeof(ClassWithIgnoredUnsupportedBigInteger))] + [JsonSerializable(typeof(WrapperForClassWithIgnoredUnsupportedBigInteger))] + [JsonSerializable(typeof(ClassWithThingsToIgnore))] + [JsonSerializable(typeof(ClassWithMixedPropertyAccessors_PropertyAttributes))] + [JsonSerializable(typeof(ClassWithPropertyPolicyConflictWhichThrows))] + [JsonSerializable(typeof(ClassTwiceInheritedWithPropertyPolicyConflictWhichThrows))] + [JsonSerializable(typeof(MyClass_WithNonPublicAccessors))] + [JsonSerializable(typeof(ClassWithThingsToIgnore_PerProperty))] + [JsonSerializable(typeof(StructWithPropertiesWithJsonPropertyName))] + [JsonSerializable(typeof(ClassWithValueAndReferenceTypes))] + [JsonSerializable(typeof(ClassWithReadOnlyStringProperty_IgnoreWhenWritingDefault))] + internal sealed partial class PropertyVisibilityTestsContext_Default : JsonSerializerContext + { + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj index a88c018a1e75..2520aa52a8b1 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj @@ -2,6 +2,12 @@ $(NetCoreAppCurrent);$(NetFrameworkCurrent) true + + $(NoWarn);SYSLIB0020 + + + + $(DefineConstants);BUILDING_SOURCE_GENERATOR_TESTS @@ -9,15 +15,38 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs index 654b41310202..d654c01bd67d 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Reflection; using Microsoft.CodeAnalysis; using Xunit; @@ -395,8 +396,10 @@ private void CheckCompilationDiagnosticsErrors(ImmutableArray diagno private void CheckFieldsPropertiesMethods(Type type, string[] expectedFields, string[] expectedProperties, string[] expectedMethods) { - string[] receivedFields = type.GetFields().Select(field => field.Name).OrderBy(s => s).ToArray(); - string[] receivedProperties = type.GetProperties().Select(property => property.Name).OrderBy(s => s).ToArray(); + BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance; + + string[] receivedFields = type.GetFields(bindingFlags).Select(field => field.Name).OrderBy(s => s).ToArray(); + string[] receivedProperties = type.GetProperties(bindingFlags).Select(property => property.Name).OrderBy(s => s).ToArray(); string[] receivedMethods = type.GetMethods().Select(method => method.Name).OrderBy(s => s).ToArray(); Assert.Equal(expectedFields, receivedFields); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.cs index afcd88a5867e..24a86137bc17 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.cs @@ -158,8 +158,10 @@ public void MySecondMethod() { } receivedMethodsWithAttributeNames ); + BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance; + // Check for FieldInfoWrapper attribute usage. - (string, string[])[] receivedFieldsWithAttributeNames = foundType.GetFields().Select(field => (field.Name, field.GetCustomAttributesData().Cast().Select(attributeData => attributeData.AttributeType.Name).ToArray())).Where(x => x.Item2.Any()).ToArray(); + (string, string[])[] receivedFieldsWithAttributeNames = foundType.GetFields(bindingFlags).Select(field => (field.Name, field.GetCustomAttributesData().Cast().Select(attributeData => attributeData.AttributeType.Name).ToArray())).Where(x => x.Item2.Any()).ToArray(); Assert.Equal( new (string, string[])[] { ("PublicDouble", new string[] { "JsonIncludeAttribute" }), @@ -169,7 +171,7 @@ public void MySecondMethod() { } ); // Check for PropertyInfoWrapper attribute usage. - (string, string[])[] receivedPropertyWithAttributeNames = foundType.GetProperties().Select(property => (property.Name, property.GetCustomAttributesData().Cast().Select(attributeData => attributeData.AttributeType.Name).ToArray())).Where(x => x.Item2.Any()).ToArray(); + (string, string[])[] receivedPropertyWithAttributeNames = foundType.GetProperties(bindingFlags).Select(property => (property.Name, property.GetCustomAttributesData().Cast().Select(attributeData => attributeData.AttributeType.Name).ToArray())).Where(x => x.Item2.Any()).ToArray(); Assert.Equal( new (string, string[])[] { ("PublicPropertyInt", new string[] { "JsonPropertyNameAttribute" }), diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapperForString.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapperForString.cs index b01738c7a962..6b691004df94 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapperForString.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapperForString.cs @@ -11,7 +11,7 @@ namespace System.Text.Json.Serialization.Tests /// /// Base class for wrapping string-based JsonSerializer methods which allows tests to run under different configurations. /// - public abstract class JsonSerializerWrapperForString + public abstract partial class JsonSerializerWrapperForString { private static readonly JsonSerializerOptions _optionsWithSmallBuffer = new JsonSerializerOptions { DefaultBufferSize = 1 }; @@ -22,22 +22,6 @@ public abstract class JsonSerializerWrapperForString public static JsonSerializerWrapperForString SyncStreamSerializer => new SyncStreamSerializerWrapper(); public static JsonSerializerWrapperForString ReaderWriterSerializer => new ReaderWriterSerializerWrapper(); - protected internal abstract Task SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null); - - protected internal abstract Task SerializeWrapper(T value, JsonSerializerOptions options = null); - - protected internal abstract Task SerializeWrapper(object value, Type inputType, JsonSerializerContext context); - - protected internal abstract Task SerializeWrapper(T value, JsonTypeInfo jsonTypeInfo); - - protected internal abstract Task DeserializeWrapper(string json, JsonSerializerOptions options = null); - - protected internal abstract Task DeserializeWrapper(string json, Type type, JsonSerializerOptions options = null); - - protected internal abstract Task DeserializeWrapper(string json, JsonTypeInfo jsonTypeInfo); - - protected internal abstract Task DeserializeWrapper(string json, Type type, JsonSerializerContext context); - private class SpanSerializerWrapper : JsonSerializerWrapperForString { protected internal override Task SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapperForString_Dynamic.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapperForString_Dynamic.cs new file mode 100644 index 000000000000..aaee9f03d81c --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapperForString_Dynamic.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization.Metadata; +using System.Threading.Tasks; + +namespace System.Text.Json.Serialization.Tests +{ + internal sealed class JsonSerializerWrapperForString_Dynamic + : JsonSerializerWrapperForString + { + protected internal override Task DeserializeWrapper(string json, JsonSerializerOptions options = null) + => Task.FromResult(JsonSerializer.Deserialize(json, options)); + + protected internal override Task DeserializeWrapper(string json, Type type, JsonSerializerOptions options = null) + => Task.FromResult(JsonSerializer.Deserialize(json, type, options)); + + protected internal override Task DeserializeWrapper(string json, JsonTypeInfo jsonTypeInfo) => throw new NotImplementedException(); + + protected internal override Task DeserializeWrapper(string json, Type type, JsonSerializerContext context) => throw new NotImplementedException(); + + protected internal override Task SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null) + => Task.FromResult(JsonSerializer.Serialize(value, inputType, options)); + + protected internal override Task SerializeWrapper(T value, JsonSerializerOptions options = null) + => Task.FromResult(JsonSerializer.Serialize(value, options)); + + protected internal override Task SerializeWrapper(object value, Type inputType, JsonSerializerContext context) => throw new NotImplementedException(); + + protected internal override Task SerializeWrapper(T value, JsonTypeInfo jsonTypeInfo) => throw new NotImplementedException(); + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/HighLowTemps.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/HighLowTemps.cs index 39899a5b91e5..c61356029789 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/HighLowTemps.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/HighLowTemps.cs @@ -47,6 +47,8 @@ private static JsonPropertyInfo[] HighLowTempsPropInitFunc(JsonSerializerContext properties[0] = JsonMetadataServices.CreatePropertyInfo( options, isProperty: true, + isPublic: true, + isVirtual: false, declaringType: typeof(HighLowTemps), propertyTypeInfo: jsonContext.Int32, converter: null, @@ -54,12 +56,15 @@ private static JsonPropertyInfo[] HighLowTempsPropInitFunc(JsonSerializerContext setter: static (obj, value) => { ((HighLowTemps)obj).High = value; }, ignoreCondition: default, numberHandling: default, + hasJsonInclude: false, propertyName: nameof(Serialization.HighLowTemps.High), jsonPropertyName: null); properties[1] = JsonMetadataServices.CreatePropertyInfo( options, isProperty: true, + isPublic: true, + isVirtual: false, declaringType: typeof(HighLowTemps), propertyTypeInfo: jsonContext.Int32, converter: null, @@ -67,6 +72,7 @@ private static JsonPropertyInfo[] HighLowTempsPropInitFunc(JsonSerializerContext setter: static (obj, value) => { ((HighLowTemps)obj).Low = value; }, ignoreCondition: default, numberHandling: default, + hasJsonInclude: false, propertyName: nameof(Serialization.HighLowTemps.Low), jsonPropertyName: null); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/WeatherForecastWithPOCOs.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/WeatherForecastWithPOCOs.cs index f75200bf8b55..7d3f84451000 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/WeatherForecastWithPOCOs.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/WeatherForecastWithPOCOs.cs @@ -47,6 +47,8 @@ private static JsonPropertyInfo[] WeatherForecastWithPOCOsPropInitFunc(JsonSeria properties[0] = JsonMetadataServices.CreatePropertyInfo( options, isProperty: true, + isPublic: true, + isVirtual: false, declaringType: typeof(WeatherForecastWithPOCOs), propertyTypeInfo: jsonContext.DateTimeOffset, converter: null, @@ -54,12 +56,15 @@ private static JsonPropertyInfo[] WeatherForecastWithPOCOsPropInitFunc(JsonSeria setter: static (obj, value) => { ((WeatherForecastWithPOCOs)obj).Date = value; }, ignoreCondition: default, numberHandling: default, + hasJsonInclude: false, propertyName: nameof(Serialization.WeatherForecastWithPOCOs.Date), jsonPropertyName: null); properties[1] = JsonMetadataServices.CreatePropertyInfo( options, isProperty: true, + isPublic: true, + isVirtual: false, declaringType: typeof(WeatherForecastWithPOCOs), propertyTypeInfo: jsonContext.Int32, converter: null, @@ -67,12 +72,15 @@ private static JsonPropertyInfo[] WeatherForecastWithPOCOsPropInitFunc(JsonSeria setter: static (obj, value) => { ((WeatherForecastWithPOCOs)obj).TemperatureCelsius = value; }, ignoreCondition: default, numberHandling: default, + hasJsonInclude: false, propertyName: nameof(Serialization.WeatherForecastWithPOCOs.TemperatureCelsius), jsonPropertyName: null); properties[2] = JsonMetadataServices.CreatePropertyInfo( options, isProperty: true, + isPublic: true, + isVirtual: false, declaringType: typeof(WeatherForecastWithPOCOs), propertyTypeInfo: jsonContext.String, converter: null, @@ -80,12 +88,15 @@ private static JsonPropertyInfo[] WeatherForecastWithPOCOsPropInitFunc(JsonSeria setter: static (obj, value) => { ((WeatherForecastWithPOCOs)obj).Summary = value; }, ignoreCondition: default, numberHandling: default, + hasJsonInclude: false, propertyName: nameof(Serialization.WeatherForecastWithPOCOs.Summary), jsonPropertyName: null); properties[3] = JsonMetadataServices.CreatePropertyInfo( options, isProperty: true, + isPublic: true, + isVirtual: false, declaringType: typeof(WeatherForecastWithPOCOs), propertyTypeInfo: jsonContext.ListSystemDateTimeOffset, converter: null, @@ -93,12 +104,15 @@ private static JsonPropertyInfo[] WeatherForecastWithPOCOsPropInitFunc(JsonSeria setter: static (obj, value) => { ((WeatherForecastWithPOCOs)obj).DatesAvailable = value; }, ignoreCondition: default, numberHandling: default, + hasJsonInclude: false, propertyName: nameof(Serialization.WeatherForecastWithPOCOs.DatesAvailable), jsonPropertyName: null); properties[4] = JsonMetadataServices.CreatePropertyInfo( options, isProperty: true, + isPublic: true, + isVirtual: false, declaringType: typeof(WeatherForecastWithPOCOs), propertyTypeInfo: jsonContext.Dictionary, converter: null, @@ -106,12 +120,15 @@ private static JsonPropertyInfo[] WeatherForecastWithPOCOsPropInitFunc(JsonSeria setter: static (obj, value) => { ((WeatherForecastWithPOCOs)obj).TemperatureRanges = value; }, ignoreCondition: default, numberHandling: default, + hasJsonInclude: false, propertyName: nameof(Serialization.WeatherForecastWithPOCOs.TemperatureRanges), jsonPropertyName: null); properties[5] = JsonMetadataServices.CreatePropertyInfo( options, isProperty: true, + isPublic: true, + isVirtual: false, declaringType: typeof(WeatherForecastWithPOCOs), propertyTypeInfo: jsonContext.StringArray, converter: null, @@ -119,12 +136,15 @@ private static JsonPropertyInfo[] WeatherForecastWithPOCOsPropInitFunc(JsonSeria setter: static (obj, value) => { ((WeatherForecastWithPOCOs)obj).SummaryWords = value; }, ignoreCondition: default, numberHandling: default, + hasJsonInclude: false, propertyName: nameof(Serialization.WeatherForecastWithPOCOs.SummaryWords), jsonPropertyName: null); properties[6] = JsonMetadataServices.CreatePropertyInfo( options, isProperty: false, + isPublic: true, + isVirtual: false, declaringType: typeof(WeatherForecastWithPOCOs), propertyTypeInfo: jsonContext.String, converter: null, @@ -132,6 +152,7 @@ private static JsonPropertyInfo[] WeatherForecastWithPOCOsPropInitFunc(JsonSeria setter: static (obj, value) => { ((WeatherForecastWithPOCOs)obj).SummaryField = value; }, ignoreCondition: default, numberHandling: default, + hasJsonInclude: false, propertyName: nameof(Serialization.WeatherForecastWithPOCOs.SummaryField), jsonPropertyName: null); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonMetadataServices.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonMetadataServices.cs index 227aab7bf3ab..a583b3a121b8 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonMetadataServices.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonMetadataServices.cs @@ -21,6 +21,8 @@ public void CreatePropertyInfo() ArgumentNullException ane = Assert.Throws(() => JsonMetadataServices.CreatePropertyInfo( options: null, isProperty: true, + isPublic: false, + isVirtual: false, declaringType: typeof(Point), propertyTypeInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), converter: null, @@ -28,6 +30,7 @@ public void CreatePropertyInfo() setter: null, ignoreCondition: default, numberHandling: default, + hasJsonInclude: false, propertyName: "MyInt", jsonPropertyName: null)); Assert.Contains("options", ane.ToString()); @@ -36,6 +39,8 @@ public void CreatePropertyInfo() ane = Assert.Throws(() => JsonMetadataServices.CreatePropertyInfo( options: options, isProperty: true, + isPublic: false, + isVirtual: false, declaringType: null, propertyTypeInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), converter: null, @@ -43,6 +48,7 @@ public void CreatePropertyInfo() setter: null, ignoreCondition: default, numberHandling: default, + hasJsonInclude: false, propertyName: "MyInt", jsonPropertyName: null)); Assert.Contains("declaringType", ane.ToString()); @@ -51,6 +57,8 @@ public void CreatePropertyInfo() ane = Assert.Throws(() => JsonMetadataServices.CreatePropertyInfo( options: options, isProperty: true, + isPublic: false, + isVirtual: false, declaringType: typeof(Point), propertyTypeInfo: null, converter: null, @@ -58,6 +66,7 @@ public void CreatePropertyInfo() setter: null, ignoreCondition: default, numberHandling: default, + hasJsonInclude: false, propertyName: "MyInt", jsonPropertyName: null)); Assert.Contains("propertyTypeInfo", ane.ToString()); @@ -66,6 +75,8 @@ public void CreatePropertyInfo() ane = Assert.Throws(() => JsonMetadataServices.CreatePropertyInfo( options: options, isProperty: true, + isPublic: false, + isVirtual: false, declaringType: typeof(Point), propertyTypeInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), converter: null, @@ -73,6 +84,7 @@ public void CreatePropertyInfo() setter: null, ignoreCondition: default, numberHandling: default, + hasJsonInclude: false, propertyName: null, jsonPropertyName: null)); Assert.Contains("propertyName", ane.ToString()); @@ -81,6 +93,8 @@ public void CreatePropertyInfo() InvalidOperationException ioe = Assert.Throws(() => JsonMetadataServices.CreatePropertyInfo( options: options, isProperty: true, + isPublic: false, + isVirtual: false, declaringType: typeof(Point), // Converter invalid because you'd need to create with JsonMetadataServices.CreatePropertyInfo instead. propertyTypeInfo: JsonMetadataServices.CreateValueInfo(options, new DerivedClassConverter()), @@ -89,12 +103,31 @@ public void CreatePropertyInfo() setter: null, ignoreCondition: default, numberHandling: default, + hasJsonInclude: false, propertyName: "MyProp", jsonPropertyName: null)); string ioeAsStr = ioe.ToString(); Assert.Contains("Point.MyProp", ioeAsStr); Assert.Contains("MyClass", ioeAsStr); + // Fields cannot be virtual. + ioe = Assert.Throws(() => JsonMetadataServices.CreatePropertyInfo( + options: options, + isProperty: false, + isPublic: false, + isVirtual: true, + declaringType: typeof(Point), + propertyTypeInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), + converter: null, + getter: null, + setter: null, + ignoreCondition: default, + numberHandling: default, + hasJsonInclude: false, + propertyName: "X", + jsonPropertyName: null)); + Assert.Contains("field", ioe.ToString()); + // Source generator tests verify that generated metadata is actually valid. } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonSerializer.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonSerializer.cs index b4db06777a31..5e02a3b5d6b1 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonSerializer.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonSerializer.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; using Xunit; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyVisibilityTests.cs index f164592f4fb3..85a36e92b9b1 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyVisibilityTests.cs @@ -1,2555 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; -using System.Collections.Concurrent; -using System.Numerics; using Xunit; namespace System.Text.Json.Serialization.Tests { - public static partial class PropertyVisibilityTests + public sealed partial class PropertyVisibilityTestsDynamic : PropertyVisibilityTests { - [Fact] - public static void Serialize_NewSlotPublicField() - { - // Serialize - var obj = new ClassWithNewSlotField(); - string json = JsonSerializer.Serialize(obj); - - Assert.Equal(@"{""MyString"":""NewDefaultValue""}", json); - - // Deserialize - json = @"{""MyString"":""NewValue""}"; - obj = JsonSerializer.Deserialize(json); - - Assert.Equal("NewValue", ((ClassWithNewSlotField)obj).MyString); - Assert.Equal("DefaultValue", ((ClassWithInternalField)obj).MyString); - } - - [Fact] - public static void Serialize_NewSlotPublicProperty() - { - // Serialize - var obj = new ClassWithNewSlotProperty(); - string json = JsonSerializer.Serialize(obj); - - Assert.Equal(@"{""MyString"":""NewDefaultValue""}", json); - - // Deserialize - json = @"{""MyString"":""NewValue""}"; - obj = JsonSerializer.Deserialize(json); - - Assert.Equal("NewValue", ((ClassWithNewSlotProperty)obj).MyString); - Assert.Equal("DefaultValue", ((ClassWithInternalProperty)obj).MyString); - } - - [Fact] - public static void Serialize_BasePublicProperty_ConflictWithDerivedPrivate() - { - // Serialize - var obj = new ClassWithNewSlotInternalProperty(); - string json = JsonSerializer.Serialize(obj); - - Assert.Equal(@"{""MyString"":""DefaultValue""}", json); - - // Deserialize - json = @"{""MyString"":""NewValue""}"; - obj = JsonSerializer.Deserialize(json); - - Assert.Equal("NewValue", ((ClassWithPublicProperty)obj).MyString); - Assert.Equal("NewDefaultValue", ((ClassWithNewSlotInternalProperty)obj).MyString); - } - - [Fact] - public static void Serialize_PublicProperty_ConflictWithPrivateDueAttributes() - { - // Serialize - var obj = new ClassWithPropertyNamingConflict(); - - // Newtonsoft.Json throws JsonSerializationException here because - // non-public properties are included when [JsonProperty] is placed on them. - string json = JsonSerializer.Serialize(obj); - - Assert.Equal(@"{""MyString"":""DefaultValue""}", json); - - // Deserialize - json = @"{""MyString"":""NewValue""}"; - - // Newtonsoft.Json throws JsonSerializationException here because - // non-public properties are included when [JsonProperty] is placed on them. - obj = JsonSerializer.Deserialize(json); - - Assert.Equal("NewValue", obj.MyString); - Assert.Equal("ConflictingValue", obj.ConflictingString); - } - - [Fact] - public static void Serialize_PublicProperty_ConflictWithPrivateDuePolicy() - { - var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - - // Serialize - var obj = new ClassWithPropertyPolicyConflict(); - string json = JsonSerializer.Serialize(obj, options); - - Assert.Equal(@"{""myString"":""DefaultValue""}", json); - - // Deserialize - json = @"{""myString"":""NewValue""}"; - obj = JsonSerializer.Deserialize(json, options); - - Assert.Equal("NewValue", obj.MyString); - Assert.Equal("ConflictingValue", obj.myString); - } - - [Fact] - public static void Serialize_NewSlotPublicProperty_ConflictWithBasePublicProperty() - { - // Serialize - var obj = new ClassWithNewSlotDecimalProperty(); - string json = JsonSerializer.Serialize(obj); - - Assert.Equal(@"{""MyNumeric"":1.5}", json); - - // Deserialize - json = @"{""MyNumeric"":2.5}"; - obj = JsonSerializer.Deserialize(json); - - Assert.Equal(2.5M, obj.MyNumeric); - } - - [Fact] - public static void Serialize_NewSlotPublicField_ConflictWithBasePublicProperty() - { - // Serialize - var obj = new ClassWithNewSlotDecimalField(); - string json = JsonSerializer.Serialize(obj); - - Assert.Equal(@"{""MyNumeric"":1.5}", json); - - // Deserialize - json = @"{""MyNumeric"":2.5}"; - obj = JsonSerializer.Deserialize(json); - - Assert.Equal(2.5M, obj.MyNumeric); - } - - [Fact] - public static void Serialize_NewSlotPublicField_SpecifiedJsonPropertyName() - { - // Serialize - var obj = new ClassWithNewSlotAttributedDecimalField(); - string json = JsonSerializer.Serialize(obj); - - Assert.Contains(@"""MyNewNumeric"":1.5", json); - Assert.Contains(@"""MyNumeric"":1", json); - - // Deserialize - json = @"{""MyNewNumeric"":2.5,""MyNumeric"":4}"; - obj = JsonSerializer.Deserialize(json); - - Assert.Equal(4, ((ClassWithHiddenByNewSlotIntProperty)obj).MyNumeric); - Assert.Equal(2.5M, ((ClassWithNewSlotAttributedDecimalField)obj).MyNumeric); - } - - [Fact] - public static void Serialize_NewSlotPublicProperty_SpecifiedJsonPropertyName() - { - // Serialize - var obj = new ClassWithNewSlotAttributedDecimalProperty(); - string json = JsonSerializer.Serialize(obj); - - Assert.Contains(@"""MyNewNumeric"":1.5", json); - Assert.Contains(@"""MyNumeric"":1", json); - - // Deserialize - json = @"{""MyNewNumeric"":2.5,""MyNumeric"":4}"; - obj = JsonSerializer.Deserialize(json); - - Assert.Equal(4, ((ClassWithHiddenByNewSlotIntProperty)obj).MyNumeric); - Assert.Equal(2.5M, ((ClassWithNewSlotAttributedDecimalProperty)obj).MyNumeric); - } - - [Fact] - public static void Ignore_NonPublicProperty() - { - // Serialize - var obj = new ClassWithInternalProperty(); - string json = JsonSerializer.Serialize(obj); - - Assert.Equal(@"{}", json); - - // Deserialize - json = @"{""MyString"":""NewValue""}"; - obj = JsonSerializer.Deserialize(json); - - Assert.Equal("DefaultValue", obj.MyString); - } - - [Fact] - public static void Ignore_NewSlotPublicFieldIgnored() - { - // Serialize - var obj = new ClassWithIgnoredNewSlotField(); - string json = JsonSerializer.Serialize(obj); - - Assert.Equal(@"{}", json); - - // Deserialize - json = @"{""MyString"":""NewValue""}"; - obj = JsonSerializer.Deserialize(json); - - Assert.Equal("NewDefaultValue", ((ClassWithIgnoredNewSlotField)obj).MyString); - Assert.Equal("DefaultValue", ((ClassWithInternalField)obj).MyString); - } - - [Fact] - public static void Ignore_NewSlotPublicPropertyIgnored() - { - // Serialize - var obj = new ClassWithIgnoredNewSlotProperty(); - string json = JsonSerializer.Serialize(obj); - - Assert.Equal(@"{}", json); - - // Deserialize - json = @"{""MyString"":""NewValue""}"; - obj = JsonSerializer.Deserialize(json); - - Assert.Equal("NewDefaultValue", ((ClassWithIgnoredNewSlotProperty)obj).MyString); - Assert.Equal("DefaultValue", ((ClassWithInternalProperty)obj).MyString); - } - - [Fact] - public static void Ignore_BasePublicPropertyIgnored_ConflictWithDerivedPrivate() - { - // Serialize - var obj = new ClassWithIgnoredPublicPropertyAndNewSlotPrivate(); - string json = JsonSerializer.Serialize(obj); - - Assert.Equal(@"{}", json); - - // Deserialize - json = @"{""MyString"":""NewValue""}"; - obj = JsonSerializer.Deserialize(json); - - Assert.Equal("DefaultValue", ((ClassWithIgnoredPublicProperty)obj).MyString); - Assert.Equal("NewDefaultValue", ((ClassWithIgnoredPublicPropertyAndNewSlotPrivate)obj).MyString); - } - - [Fact] - public static void Ignore_PublicProperty_ConflictWithPrivateDueAttributes() - { - // Serialize - var obj = new ClassWithIgnoredPropertyNamingConflictPrivate(); - string json = JsonSerializer.Serialize(obj); - - Assert.Equal(@"{}", json); - - // Newtonsoft.Json has the following output because non-public properties are included when [JsonProperty] is placed on them. - // {"MyString":"ConflictingValue"} - - // Deserialize - json = @"{""MyString"":""NewValue""}"; - obj = JsonSerializer.Deserialize(json); - - Assert.Equal("DefaultValue", obj.MyString); - Assert.Equal("ConflictingValue", obj.ConflictingString); - - // The output for Newtonsoft.Json is: - // obj.ConflictingString = "NewValue" - // obj.MyString still equals "DefaultValue" - } - - [Fact] - public static void Ignore_PublicProperty_ConflictWithPrivateDuePolicy() - { - var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - - // Serialize - var obj = new ClassWithIgnoredPropertyPolicyConflictPrivate(); - string json = JsonSerializer.Serialize(obj, options); - - Assert.Equal(@"{}", json); - - // Deserialize - json = @"{""myString"":""NewValue""}"; - obj = JsonSerializer.Deserialize(json, options); - - Assert.Equal("DefaultValue", obj.MyString); - Assert.Equal("ConflictingValue", obj.myString); - } - - [Fact] - public static void Ignore_PublicProperty_ConflictWithPublicDueAttributes() - { - // Serialize - var obj = new ClassWithIgnoredPropertyNamingConflictPublic(); - string json = JsonSerializer.Serialize(obj); - - Assert.Equal(@"{""MyString"":""ConflictingValue""}", json); - - // Deserialize - json = @"{""MyString"":""NewValue""}"; - obj = JsonSerializer.Deserialize(json); - - Assert.Equal("DefaultValue", obj.MyString); - Assert.Equal("NewValue", obj.ConflictingString); - } - - [Fact] - public static void Ignore_PublicProperty_ConflictWithPublicDuePolicy() - { - var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - - // Serialize - var obj = new ClassWithIgnoredPropertyPolicyConflictPublic(); - string json = JsonSerializer.Serialize(obj, options); - - Assert.Equal(@"{""myString"":""ConflictingValue""}", json); - - // Deserialize - json = @"{""myString"":""NewValue""}"; - obj = JsonSerializer.Deserialize(json, options); - - Assert.Equal("DefaultValue", obj.MyString); - Assert.Equal("NewValue", obj.myString); - } - - [Fact] - public static void Throw_PublicProperty_ConflictDueAttributes() - { - // Serialize - var obj = new ClassWithPropertyNamingConflictWhichThrows(); - Assert.Throws( - () => JsonSerializer.Serialize(obj)); - - // Deserialize - string json = @"{""MyString"":""NewValue""}"; - Assert.Throws( - () => JsonSerializer.Deserialize(json)); - } - - [Fact] - public static void Throw_PublicPropertyAndField_ConflictDueAttributes() - { - // Serialize - var obj = new ClassWithPropertyFieldNamingConflictWhichThrows(); - Assert.Throws( - () => JsonSerializer.Serialize(obj)); - - // Deserialize - string json = @"{""MyString"":""NewValue""}"; - Assert.Throws( - () => JsonSerializer.Deserialize(json)); - } - - [Fact] - public static void Throw_PublicProperty_ConflictDueAttributes_SingleInheritance() - { - // Serialize - var obj = new ClassInheritedWithPropertyNamingConflictWhichThrows(); - Assert.Throws( - () => JsonSerializer.Serialize(obj)); - - // The output for Newtonsoft.Json is: - // {"MyString":"ConflictingValue"} - // Conflicts at different type-hierarchy levels that are not caused by - // deriving or the new keyword are allowed. Properties on more derived types win. - - // Deserialize - string json = @"{""MyString"":""NewValue""}"; - Assert.Throws( - () => JsonSerializer.Deserialize(json)); - - // The output for Newtonsoft.Json is: - // obj.ConflictingString = "NewValue" - // obj.MyString still equals "DefaultValue" - } - - [Fact] - public static void Throw_PublicPropertyAndField_ConflictDueAttributes_SingleInheritance() - { - // Serialize - var obj = new ClassInheritedWithPropertyFieldNamingConflictWhichThrows(); - Assert.Throws( - () => JsonSerializer.Serialize(obj)); - - // The output for Newtonsoft.Json is: - // {"MyString":"ConflictingValue"} - // Conflicts at different type-hierarchy levels that are not caused by - // deriving or the new keyword are allowed. Properties on more derived types win. - - // Deserialize - string json = @"{""MyString"":""NewValue""}"; - Assert.Throws( - () => JsonSerializer.Deserialize(json)); - - // The output for Newtonsoft.Json is: - // obj.ConflictingString = "NewValue" - // obj.MyString still equals "DefaultValue" - } - - [Fact] - public static void Throw_PublicProperty_ConflictDueAttributes_DoubleInheritance() - { - // Serialize - var obj = new ClassTwiceInheritedWithPropertyNamingConflictWhichThrows(); - Assert.Throws( - () => JsonSerializer.Serialize(obj)); - - // The output for Newtonsoft.Json is: - // {"MyString":"ConflictingValue"} - // Conflicts at different type-hierarchy levels that are not caused by - // deriving or the new keyword are allowed. Properties on more derived types win. - - // Deserialize - string json = @"{""MyString"":""NewValue""}"; - - Assert.Throws( - () => JsonSerializer.Deserialize(json)); - - // The output for Newtonsoft.Json is: - // obj.ConflictingString = "NewValue" - // obj.MyString still equals "DefaultValue" - } - - [Fact] - public static void Throw_PublicPropertyAndField_ConflictDueAttributes_DoubleInheritance() - { - // Serialize - var obj = new ClassTwiceInheritedWithPropertyFieldNamingConflictWhichThrows(); - Assert.Throws( - () => JsonSerializer.Serialize(obj)); - - // The output for Newtonsoft.Json is: - // {"MyString":"ConflictingValue"} - // Conflicts at different type-hierarchy levels that are not caused by - // deriving or the new keyword are allowed. Properties on more derived types win. - - // Deserialize - string json = @"{""MyString"":""NewValue""}"; - - Assert.Throws( - () => JsonSerializer.Deserialize(json)); - - // The output for Newtonsoft.Json is: - // obj.ConflictingString = "NewValue" - // obj.MyString still equals "DefaultValue" - } - - [Fact] - public static void Throw_PublicProperty_ConflictDuePolicy() - { - var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - - // Serialize - var obj = new ClassWithPropertyPolicyConflictWhichThrows(); - Assert.Throws( - () => JsonSerializer.Serialize(obj, options)); - - // Deserialize - string json = @"{""MyString"":""NewValue""}"; - Assert.Throws( - () => JsonSerializer.Deserialize(json, options)); - } - - [Fact] - public static void Throw_PublicPropertyAndField_ConflictDuePolicy() - { - var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - - // Serialize - var obj = new ClassWithPropertyFieldPolicyConflictWhichThrows(); - Assert.Throws( - () => JsonSerializer.Serialize(obj, options)); - - // Deserialize - string json = @"{""MyString"":""NewValue""}"; - Assert.Throws( - () => JsonSerializer.Deserialize(json, options)); - } - - [Fact] - public static void Throw_PublicProperty_ConflictDuePolicy_SingleInheritance() - { - var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - - // Serialize - var obj = new ClassInheritedWithPropertyPolicyConflictWhichThrows(); - - Assert.Throws( - () => JsonSerializer.Serialize(obj, options)); - - // The output for Newtonsoft.Json is: - // {"myString":"ConflictingValue"} - // Conflicts at different type-hierarchy levels that are not caused by - // deriving or the new keyword are allowed. Properties on more derived types win. - - // Deserialize - string json = @"{""MyString"":""NewValue""}"; - Assert.Throws( - () => JsonSerializer.Deserialize(json, options)); - - // The output for Newtonsoft.Json is: - // obj.myString = "NewValue" - // obj.MyString still equals "DefaultValue" - } - - [Fact] - public static void Throw_PublicPropertyAndField_ConflictDuePolicy_SingleInheritance() - { - var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - - // Serialize - var obj = new ClassInheritedWithPropertyFieldPolicyConflictWhichThrows(); - - Assert.Throws( - () => JsonSerializer.Serialize(obj, options)); - - // The output for Newtonsoft.Json is: - // {"myString":"ConflictingValue"} - // Conflicts at different type-hierarchy levels that are not caused by - // deriving or the new keyword are allowed. Properties on more derived types win. - - // Deserialize - string json = @"{""MyString"":""NewValue""}"; - Assert.Throws( - () => JsonSerializer.Deserialize(json, options)); - - // The output for Newtonsoft.Json is: - // obj.myString = "NewValue" - // obj.MyString still equals "DefaultValue" - } - - [Fact] - public static void Throw_PublicProperty_ConflictDuePolicy_DobuleInheritance() - { - var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - - // Serialize - var obj = new ClassTwiceInheritedWithPropertyPolicyConflictWhichThrows(); - - Assert.Throws( - () => JsonSerializer.Serialize(obj, options)); - - // The output for Newtonsoft.Json is: - // {"myString":"ConflictingValue"} - // Conflicts at different type-hierarchy levels that are not caused by - // deriving or the new keyword are allowed. Properties on more derived types win. - - // Deserialize - string json = @"{""MyString"":""NewValue""}"; - - Assert.Throws( - () => JsonSerializer.Deserialize(json, options)); - - // The output for Newtonsoft.Json is: - // obj.myString = "NewValue" - // obj.MyString still equals "DefaultValue" - } - - [Fact] - public static void Throw_PublicPropertyAndField_ConflictDuePolicy_DobuleInheritance() - { - var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - - // Serialize - var obj = new ClassTwiceInheritedWithPropertyFieldPolicyConflictWhichThrows(); - - Assert.Throws( - () => JsonSerializer.Serialize(obj, options)); - - // The output for Newtonsoft.Json is: - // {"myString":"ConflictingValue"} - // Conflicts at different type-hierarchy levels that are not caused by - // deriving or the new keyword are allowed. Properties on more derived types win. - - // Deserialize - string json = @"{""MyString"":""NewValue""}"; - - Assert.Throws( - () => JsonSerializer.Deserialize(json, options)); - - // The output for Newtonsoft.Json is: - // obj.myString = "NewValue" - // obj.MyString still equals "DefaultValue" - } - - [Fact] - public static void HiddenPropertiesIgnored_WhenOverridesIgnored() - { - string serialized = JsonSerializer.Serialize(new DerivedClass_With_IgnoredOverride()); - Assert.Equal(@"{}", serialized); - - serialized = JsonSerializer.Serialize(new DerivedClass_WithVisibleProperty_Of_DerivedClass_With_IgnoredOverride()); - Assert.Equal(@"{""MyProp"":false}", serialized); - - serialized = JsonSerializer.Serialize(new DerivedClass_With_IgnoredOverride_And_ConflictingPropertyName()); - Assert.Equal(@"{""MyProp"":null}", serialized); - - serialized = JsonSerializer.Serialize(new DerivedClass_With_Ignored_NewProperty()); - Assert.Equal(@"{""MyProp"":false}", serialized); - - serialized = JsonSerializer.Serialize(new DerivedClass_WithConflictingNewMember()); - Assert.Equal(@"{""MyProp"":false}", serialized); - - serialized = JsonSerializer.Serialize(new DerivedClass_WithConflictingNewMember_Of_DifferentType()); - Assert.Equal(@"{""MyProp"":0}", serialized); - - serialized = JsonSerializer.Serialize(new DerivedClass_With_Ignored_ConflictingNewMember()); - Assert.Equal(@"{""MyProp"":false}", serialized); - - serialized = JsonSerializer.Serialize(new DerivedClass_With_Ignored_ConflictingNewMember_Of_DifferentType()); - Assert.Equal(@"{""MyProp"":false}", serialized); - - serialized = JsonSerializer.Serialize(new DerivedClass_With_NewProperty_And_ConflictingPropertyName()); - Assert.Equal(@"{""MyProp"":null}", serialized); - - serialized = JsonSerializer.Serialize(new DerivedClass_With_Ignored_NewProperty_Of_DifferentType()); - Assert.Equal(@"{""MyProp"":false}", serialized); - - serialized = JsonSerializer.Serialize(new DerivedClass_With_Ignored_NewProperty_Of_DifferentType_And_ConflictingPropertyName()); - Assert.Equal(@"{""MyProp"":null}", serialized); - - serialized = JsonSerializer.Serialize(new FurtherDerivedClass_With_ConflictingPropertyName()); - Assert.Equal(@"{""MyProp"":null}", serialized); - - // Here we differ from Newtonsoft.Json, where the output would be - // {"MyProp":null} - // Conflicts at different type-hierarchy levels that are not caused by - // deriving or the new keyword are allowed. Properties on more derived types win. - // This is invalid in System.Text.Json. - Assert.Throws(() => JsonSerializer.Serialize(new DerivedClass_WithConflictingPropertyName())); - - serialized = JsonSerializer.Serialize(new FurtherDerivedClass_With_IgnoredOverride()); - Assert.Equal(@"{""MyProp"":null}", serialized); - } - - public class ClassWithInternalField - { - internal string MyString = "DefaultValue"; - } - - public class ClassWithNewSlotField : ClassWithInternalField - { - [JsonInclude] - public new string MyString = "NewDefaultValue"; - } - - public class ClassWithInternalProperty - { - internal string MyString { get; set; } = "DefaultValue"; - } - - public class ClassWithNewSlotProperty : ClassWithInternalProperty - { - public new string MyString { get; set; } = "NewDefaultValue"; - } - - public class ClassWithPublicProperty - { - public string MyString { get; set; } = "DefaultValue"; - } - - public class ClassWithNewSlotInternalProperty : ClassWithPublicProperty - { - internal new string MyString { get; set; } = "NewDefaultValue"; - } - - public class ClassWithPropertyNamingConflict - { - public string MyString { get; set; } = "DefaultValue"; - - [JsonPropertyName(nameof(MyString))] - internal string ConflictingString { get; set; } = "ConflictingValue"; - } - - public class ClassWithPropertyNamingConflictWhichThrows - { - public string MyString { get; set; } = "DefaultValue"; - - [JsonPropertyName(nameof(MyString))] - public string ConflictingString { get; set; } = "ConflictingValue"; - } - - public class ClassWithPropertyFieldNamingConflictWhichThrows - { - public string MyString { get; set; } = "DefaultValue"; - - [JsonInclude] - [JsonPropertyName(nameof(MyString))] - public string ConflictingString = "ConflictingValue"; - } - - public class ClassInheritedWithPropertyNamingConflictWhichThrows : ClassWithPublicProperty - { - [JsonPropertyName(nameof(MyString))] - public string ConflictingString { get; set; } = "ConflictingValue"; - } - - public class ClassInheritedWithPropertyFieldNamingConflictWhichThrows : ClassWithPublicProperty - { - [JsonInclude] - [JsonPropertyName(nameof(MyString))] - public string ConflictingString = "ConflictingValue"; - } - - public class ClassTwiceInheritedWithPropertyNamingConflictWhichThrowsDummy : ClassWithPublicProperty - { - } - - public class ClassTwiceInheritedWithPropertyNamingConflictWhichThrows : ClassTwiceInheritedWithPropertyNamingConflictWhichThrowsDummy - { - [JsonPropertyName(nameof(MyString))] - public string ConflictingString { get; set; } = "ConflictingValue"; - } - - public class ClassTwiceInheritedWithPropertyFieldNamingConflictWhichThrows : ClassTwiceInheritedWithPropertyNamingConflictWhichThrowsDummy - { - [JsonInclude] - [JsonPropertyName(nameof(MyString))] - public string ConflictingString = "ConflictingValue"; - } - - public class ClassWithPropertyPolicyConflict - { - public string MyString { get; set; } = "DefaultValue"; - - internal string myString { get; set; } = "ConflictingValue"; - } - - public class ClassWithPropertyPolicyConflictWhichThrows - { - public string MyString { get; set; } = "DefaultValue"; - - public string myString { get; set; } = "ConflictingValue"; - } - - public class ClassWithPropertyFieldPolicyConflictWhichThrows - { - public string MyString { get; set; } = "DefaultValue"; - - [JsonInclude] - public string myString = "ConflictingValue"; - } - - public class ClassInheritedWithPropertyPolicyConflictWhichThrows : ClassWithPublicProperty - { - public string myString { get; set; } = "ConflictingValue"; - } - - public class ClassInheritedWithPropertyFieldPolicyConflictWhichThrows : ClassWithPublicProperty - { - [JsonInclude] - public string myString = "ConflictingValue"; - } - - public class ClassInheritedWithPropertyPolicyConflictWhichThrowsDummy : ClassWithPublicProperty - { - } - - public class ClassTwiceInheritedWithPropertyPolicyConflictWhichThrows : ClassInheritedWithPropertyPolicyConflictWhichThrowsDummy - { - public string myString { get; set; } = "ConflictingValue"; - } - - public class ClassTwiceInheritedWithPropertyFieldPolicyConflictWhichThrows : ClassInheritedWithPropertyPolicyConflictWhichThrowsDummy - { - [JsonInclude] - public string myString { get; set; } = "ConflictingValue"; - } - - public class ClassWithIgnoredNewSlotField : ClassWithInternalField - { - [JsonIgnore] - public new string MyString = "NewDefaultValue"; - } - - public class ClassWithIgnoredNewSlotProperty : ClassWithInternalProperty - { - [JsonIgnore] - public new string MyString { get; set; } = "NewDefaultValue"; - } - - public class ClassWithIgnoredPublicProperty - { - [JsonIgnore] - public string MyString { get; set; } = "DefaultValue"; - } - - public class ClassWithIgnoredPublicPropertyAndNewSlotPrivate : ClassWithIgnoredPublicProperty - { - internal new string MyString { get; set; } = "NewDefaultValue"; - } - - public class ClassWithIgnoredPropertyNamingConflictPrivate - { - [JsonIgnore] - public string MyString { get; set; } = "DefaultValue"; - - [JsonPropertyName(nameof(MyString))] - internal string ConflictingString { get; set; } = "ConflictingValue"; - } - - public class ClassWithIgnoredPropertyPolicyConflictPrivate - { - [JsonIgnore] - public string MyString { get; set; } = "DefaultValue"; - - internal string myString { get; set; } = "ConflictingValue"; - } - - public class ClassWithIgnoredPropertyNamingConflictPublic - { - [JsonIgnore] - public string MyString { get; set; } = "DefaultValue"; - - [JsonPropertyName(nameof(MyString))] - public string ConflictingString { get; set; } = "ConflictingValue"; - } - - public class ClassWithIgnoredPropertyPolicyConflictPublic - { - [JsonIgnore] - public string MyString { get; set; } = "DefaultValue"; - - public string myString { get; set; } = "ConflictingValue"; - } - - public class ClassWithHiddenByNewSlotIntProperty - { - public int MyNumeric { get; set; } = 1; - } - - public class ClassWithNewSlotDecimalField : ClassWithHiddenByNewSlotIntProperty - { - [JsonInclude] - public new decimal MyNumeric = 1.5M; - } - - public class ClassWithNewSlotDecimalProperty : ClassWithHiddenByNewSlotIntProperty - { - public new decimal MyNumeric { get; set; } = 1.5M; - } - - public class ClassWithNewSlotAttributedDecimalField : ClassWithHiddenByNewSlotIntProperty - { - [JsonInclude] - [JsonPropertyName("MyNewNumeric")] - public new decimal MyNumeric = 1.5M; - } - - public class ClassWithNewSlotAttributedDecimalProperty : ClassWithHiddenByNewSlotIntProperty - { - [JsonPropertyName("MyNewNumeric")] - public new decimal MyNumeric { get; set; } = 1.5M; - } - - private class Class_With_VirtualProperty - { - public virtual bool MyProp { get; set; } - } - - private class DerivedClass_With_IgnoredOverride : Class_With_VirtualProperty - { - [JsonIgnore] - public override bool MyProp { get; set; } - } - - private class DerivedClass_WithVisibleProperty_Of_DerivedClass_With_IgnoredOverride : DerivedClass_With_IgnoredOverride - { - public override bool MyProp { get; set; } - } - - private class DerivedClass_With_IgnoredOverride_And_ConflictingPropertyName : Class_With_VirtualProperty - { - [JsonPropertyName("MyProp")] - public string MyString { get; set; } - - [JsonIgnore] - public override bool MyProp { get; set; } - } - - private class Class_With_Property - { - public bool MyProp { get; set; } - } - - private class DerivedClass_With_Ignored_NewProperty : Class_With_Property - { - [JsonIgnore] - public new bool MyProp { get; set; } - } - - private class DerivedClass_With_NewProperty_And_ConflictingPropertyName : Class_With_Property - { - [JsonPropertyName("MyProp")] - public string MyString { get; set; } - - [JsonIgnore] - public new bool MyProp { get; set; } - } - - private class DerivedClass_With_Ignored_NewProperty_Of_DifferentType : Class_With_Property - { - [JsonIgnore] - public new int MyProp { get; set; } - } - - private class DerivedClass_With_Ignored_NewProperty_Of_DifferentType_And_ConflictingPropertyName : Class_With_Property - { - [JsonPropertyName("MyProp")] - public string MyString { get; set; } - - [JsonIgnore] - public new int MyProp { get; set; } - } - - private class DerivedClass_WithIgnoredOverride : Class_With_VirtualProperty - { - [JsonIgnore] - public override bool MyProp { get; set; } - } - - private class DerivedClass_WithConflictingNewMember : Class_With_VirtualProperty - { - public new bool MyProp { get; set; } - } - - private class DerivedClass_WithConflictingNewMember_Of_DifferentType : Class_With_VirtualProperty - { - public new int MyProp { get; set; } - } - - private class DerivedClass_With_Ignored_ConflictingNewMember : Class_With_VirtualProperty - { - [JsonIgnore] - public new bool MyProp { get; set; } - } - - private class DerivedClass_With_Ignored_ConflictingNewMember_Of_DifferentType : Class_With_VirtualProperty - { - [JsonIgnore] - public new int MyProp { get; set; } - } - - private class FurtherDerivedClass_With_ConflictingPropertyName : DerivedClass_WithIgnoredOverride - { - [JsonPropertyName("MyProp")] - public string MyString { get; set; } - } - - private class DerivedClass_WithConflictingPropertyName : Class_With_VirtualProperty - { - [JsonPropertyName("MyProp")] - public string MyString { get; set; } - } - - private class FurtherDerivedClass_With_IgnoredOverride : DerivedClass_WithConflictingPropertyName - { - [JsonIgnore] - public override bool MyProp { get; set; } - } - - [Fact] - public static void IgnoreReadOnlyProperties() - { - var options = new JsonSerializerOptions(); - options.IgnoreReadOnlyProperties = true; - - var obj = new ClassWithNoSetter(); - - string json = JsonSerializer.Serialize(obj, options); - - // Collections are always serialized unless they have [JsonIgnore]. - Assert.Equal(@"{""MyInts"":[1,2]}", json); - } - - [Fact] - public static void IgnoreReadOnlyFields() - { - var options = new JsonSerializerOptions(); - options.IncludeFields = true; - options.IgnoreReadOnlyFields = true; - - var obj = new ClassWithReadOnlyFields(); - - string json = JsonSerializer.Serialize(obj, options); - - // Collections are always serialized unless they have [JsonIgnore]. - Assert.Equal(@"{""MyInts"":[1,2]}", json); - } - - [Fact] - public static void NoSetter() - { - var obj = new ClassWithNoSetter(); - - string json = JsonSerializer.Serialize(obj); - Assert.Contains(@"""MyString"":""DefaultValue""", json); - Assert.Contains(@"""MyInts"":[1,2]", json); - - obj = JsonSerializer.Deserialize(@"{""MyString"":""IgnoreMe"",""MyInts"":[0]}"); - Assert.Equal("DefaultValue", obj.MyString); - Assert.Equal(2, obj.MyInts.Length); - } - - [Fact] - public static void NoGetter() - { - ClassWithNoGetter objWithNoGetter = JsonSerializer.Deserialize( - @"{""MyString"":""Hello"",""MyIntArray"":[0],""MyIntList"":[0]}"); - - Assert.Equal("Hello", objWithNoGetter.GetMyString()); - - // Currently we don't support setters without getters. - Assert.Equal(0, objWithNoGetter.GetMyIntArray().Length); - Assert.Equal(0, objWithNoGetter.GetMyIntList().Count); - } - - [Fact] - public static void PrivateGetter() - { - var obj = new ClassWithPrivateSetterAndGetter(); - obj.SetMyString("Hello"); - - string json = JsonSerializer.Serialize(obj); - Assert.Equal(@"{}", json); - } - - [Fact] - public static void PrivateSetter() - { - string json = @"{""MyString"":""Hello""}"; - - ClassWithPrivateSetterAndGetter objCopy = JsonSerializer.Deserialize(json); - Assert.Null(objCopy.GetMyString()); - } - - [Fact] - public static void PrivateSetterPublicGetter() - { - // https://github.com/dotnet/runtime/issues/29503 - ClassWithPublicGetterAndPrivateSetter obj - = JsonSerializer.Deserialize(@"{ ""Class"": {} }"); - - Assert.NotNull(obj); - Assert.Null(obj.Class); - } - - [Fact] - public static void MissingObjectProperty() - { - ClassWithMissingObjectProperty obj - = JsonSerializer.Deserialize(@"{ ""Object"": {} }"); - - Assert.Null(obj.Collection); - } - - [Fact] - public static void MissingCollectionProperty() - { - ClassWithMissingCollectionProperty obj - = JsonSerializer.Deserialize(@"{ ""Collection"": [] }"); - - Assert.Null(obj.Object); - } - - private class ClassWithPublicGetterAndPrivateSetter - { - public NestedClass Class { get; private set; } - } - - private class NestedClass - { - } - - [Fact] - public static void JsonIgnoreAttribute() - { - var options = new JsonSerializerOptions { IncludeFields = true }; - - // Verify default state. - var obj = new ClassWithIgnoreAttributeProperty(); - Assert.Equal(@"MyString", obj.MyString); - Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore); - Assert.Equal(2, obj.MyStringsWithIgnore.Length); - Assert.Equal(1, obj.MyDictionaryWithIgnore["Key"]); - Assert.Equal(3.14M, obj.MyNumeric); - - // Verify serialize. - string json = JsonSerializer.Serialize(obj, options); - Assert.Contains(@"""MyString""", json); - Assert.DoesNotContain(@"MyStringWithIgnore", json); - Assert.DoesNotContain(@"MyStringsWithIgnore", json); - Assert.DoesNotContain(@"MyDictionaryWithIgnore", json); - Assert.DoesNotContain(@"MyNumeric", json); - - // Verify deserialize default. - obj = JsonSerializer.Deserialize(@"{}", options); - Assert.Equal(@"MyString", obj.MyString); - Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore); - Assert.Equal(2, obj.MyStringsWithIgnore.Length); - Assert.Equal(1, obj.MyDictionaryWithIgnore["Key"]); - Assert.Equal(3.14M, obj.MyNumeric); - - // Verify deserialize ignores the json for MyStringWithIgnore and MyStringsWithIgnore. - obj = JsonSerializer.Deserialize( - @"{""MyString"":""Hello"", ""MyStringWithIgnore"":""IgnoreMe"", ""MyStringsWithIgnore"":[""IgnoreMe""], ""MyDictionaryWithIgnore"":{""Key"":9}, ""MyNumeric"": 2.71828}", options); - Assert.Contains(@"Hello", obj.MyString); - Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore); - Assert.Equal(2, obj.MyStringsWithIgnore.Length); - Assert.Equal(1, obj.MyDictionaryWithIgnore["Key"]); - Assert.Equal(3.14M, obj.MyNumeric); - } - - [Fact] - public static void JsonIgnoreAttribute_UnsupportedCollection() - { - string json = - @"{ - ""MyConcurrentDict"":{ - ""key"":""value"" - }, - ""MyIDict"":{ - ""key"":""value"" - }, - ""MyDict"":{ - ""key"":""value"" - } - }"; - string wrapperJson = - @"{ - ""MyClass"":{ - ""MyConcurrentDict"":{ - ""key"":""value"" - }, - ""MyIDict"":{ - ""key"":""value"" - }, - ""MyDict"":{ - ""key"":""value"" - } - } - }"; - - // Unsupported collections will throw on deserialize by default. - Assert.Throws(() => JsonSerializer.Deserialize(json)); - - // Using new options instance to prevent using previously cached metadata. - JsonSerializerOptions options = new JsonSerializerOptions(); - - // Unsupported collections will throw on serialize by default. - // Only when the collection contains elements. - - var dictionary = new Dictionary(); - // Uri is an unsupported dictionary key. - dictionary.Add(new Uri("http://foo"), "bar"); - - var concurrentDictionary = new ConcurrentDictionary(dictionary); - - var instance = new ClassWithUnsupportedDictionary() - { - MyConcurrentDict = concurrentDictionary, - MyIDict = dictionary - }; - - var instanceWithIgnore = new ClassWithIgnoredUnsupportedDictionary - { - MyConcurrentDict = concurrentDictionary, - MyIDict = dictionary - }; - - Assert.Throws(() => JsonSerializer.Serialize(instance, options)); - - // Unsupported collections will throw on deserialize by default if they contain elements. - options = new JsonSerializerOptions(); - Assert.Throws(() => JsonSerializer.Deserialize(wrapperJson, options)); - - options = new JsonSerializerOptions(); - // Unsupported collections will throw on serialize by default if they contain elements. - Assert.Throws(() => JsonSerializer.Serialize(instance, options)); - - // When ignored, we can serialize and deserialize without exceptions. - options = new JsonSerializerOptions(); - - Assert.NotNull(JsonSerializer.Serialize(instanceWithIgnore, options)); - - ClassWithIgnoredUnsupportedDictionary obj = JsonSerializer.Deserialize(json, options); - Assert.Null(obj.MyDict); - - options = new JsonSerializerOptions(); - Assert.Equal("{}", JsonSerializer.Serialize(new ClassWithIgnoredUnsupportedDictionary())); - - options = new JsonSerializerOptions(); - WrapperForClassWithIgnoredUnsupportedDictionary wrapperObj = JsonSerializer.Deserialize(wrapperJson, options); - Assert.Null(wrapperObj.MyClass.MyDict); - - options = new JsonSerializerOptions(); - Assert.Equal(@"{""MyClass"":{}}", JsonSerializer.Serialize(new WrapperForClassWithIgnoredUnsupportedDictionary() - { - MyClass = new ClassWithIgnoredUnsupportedDictionary(), - }, options)); - } - - [Fact] - public static void JsonIgnoreAttribute_UnsupportedBigInteger() - { - string json = @"{""MyBigInteger"":1}"; - string wrapperJson = @"{""MyClass"":{""MyBigInteger"":1}}"; - - // Unsupported types will throw by default. - Assert.Throws(() => JsonSerializer.Deserialize(json)); - // Using new options instance to prevent using previously cached metadata. - JsonSerializerOptions options = new JsonSerializerOptions(); - Assert.Throws(() => JsonSerializer.Deserialize(wrapperJson, options)); - - // When ignored, we can serialize and deserialize without exceptions. - options = new JsonSerializerOptions(); - ClassWithIgnoredUnsupportedBigInteger obj = JsonSerializer.Deserialize(json, options); - Assert.Null(obj.MyBigInteger); - - options = new JsonSerializerOptions(); - Assert.Equal("{}", JsonSerializer.Serialize(new ClassWithIgnoredUnsupportedBigInteger())); - - options = new JsonSerializerOptions(); - WrapperForClassWithIgnoredUnsupportedBigInteger wrapperObj = JsonSerializer.Deserialize(wrapperJson, options); - Assert.Null(wrapperObj.MyClass.MyBigInteger); - - options = new JsonSerializerOptions(); - Assert.Equal(@"{""MyClass"":{}}", JsonSerializer.Serialize(new WrapperForClassWithIgnoredUnsupportedBigInteger() - { - MyClass = new ClassWithIgnoredUnsupportedBigInteger(), - }, options)); - } - - public class ObjectDictWrapper : Dictionary { } - - public class ClassWithUnsupportedDictionary - { - public ConcurrentDictionary MyConcurrentDict { get; set; } - public IDictionary MyIDict { get; set; } - public ObjectDictWrapper MyDict { get; set; } - } - - public class WrapperForClassWithUnsupportedDictionary - { - public ClassWithUnsupportedDictionary MyClass { get; set; } = new ClassWithUnsupportedDictionary(); - } - - public class ClassWithIgnoredUnsupportedDictionary - { - [JsonIgnore] - public ConcurrentDictionary MyConcurrentDict { get; set; } - [JsonIgnore] - public IDictionary MyIDict { get; set; } - [JsonIgnore] - public ObjectDictWrapper MyDict { get; set; } - } - - public class WrapperForClassWithIgnoredUnsupportedDictionary - { - public ClassWithIgnoredUnsupportedDictionary MyClass { get; set; } - } - - public class ClassWithUnsupportedBigInteger - { - public BigInteger? MyBigInteger { get; set; } - } - - public class WrapperForClassWithUnsupportedBigInteger - { - public ClassWithUnsupportedBigInteger MyClass { get; set; } = new ClassWithUnsupportedBigInteger(); - } - - public class ClassWithIgnoredUnsupportedBigInteger - { - [JsonIgnore] - public BigInteger? MyBigInteger { get; set; } - } - - public class WrapperForClassWithIgnoredUnsupportedBigInteger - { - public ClassWithIgnoredUnsupportedBigInteger MyClass { get; set; } - } - - public class ClassWithMissingObjectProperty - { - public object[] Collection { get; set; } - } - - public class ClassWithMissingCollectionProperty - { - public object Object { get; set; } - } - - public class ClassWithPrivateSetterAndGetter - { - private string MyString { get; set; } - - public string GetMyString() - { - return MyString; - } - - public void SetMyString(string value) - { - MyString = value; - } - } - - public class ClassWithReadOnlyFields - { - public ClassWithReadOnlyFields() - { - MyString = "DefaultValue"; - MyInts = new int[] { 1, 2 }; - } - - public readonly string MyString; - public readonly int[] MyInts; - } - - public class ClassWithNoSetter - { - public ClassWithNoSetter() - { - MyString = "DefaultValue"; - MyInts = new int[] { 1, 2 }; - } - - public string MyString { get; } - public int[] MyInts { get; } - } - - public class ClassWithNoGetter - { - string _myString = ""; - int[] _myIntArray = new int[] { }; - List _myIntList = new List { }; - - public string MyString - { - set - { - _myString = value; - } - } - - public int[] MyIntArray - { - set - { - _myIntArray = value; - } - } - - public List MyList - { - set - { - _myIntList = value; - } - } - - public string GetMyString() - { - return _myString; - } - - public int[] GetMyIntArray() - { - return _myIntArray; - } - - public List GetMyIntList() - { - return _myIntList; - } - } - - public class ClassWithIgnoreAttributeProperty - { - public ClassWithIgnoreAttributeProperty() - { - MyDictionaryWithIgnore = new Dictionary { { "Key", 1 } }; - MyString = "MyString"; - MyStringWithIgnore = "MyStringWithIgnore"; - MyStringsWithIgnore = new string[] { "1", "2" }; - MyNumeric = 3.14M; - } - - [JsonIgnore] - public Dictionary MyDictionaryWithIgnore { get; set; } - - [JsonIgnore] - public string MyStringWithIgnore { get; set; } - - public string MyString { get; set; } - - [JsonIgnore] - public string[] MyStringsWithIgnore { get; set; } - - [JsonIgnore] - public decimal MyNumeric; - } - - private enum MyEnum - { - Case1 = 0, - Case2 = 1, - } - - private struct StructWithOverride - { - [JsonIgnore] - public MyEnum EnumValue { get; set; } - - [JsonPropertyName("EnumValue")] - public string EnumString - { - get => EnumValue.ToString(); - set - { - if (value == "Case1") - { - EnumValue = MyEnum.Case1; - } - else if (value == "Case2") - { - EnumValue = MyEnum.Case2; - } - else - { - throw new Exception("Unknown value!"); - } - } - } - } - - [Fact] - public static void OverrideJsonIgnorePropertyUsingJsonPropertyName() - { - const string json = @"{""EnumValue"":""Case2""}"; - - StructWithOverride obj = JsonSerializer.Deserialize(json); - - Assert.Equal(MyEnum.Case2, obj.EnumValue); - Assert.Equal("Case2", obj.EnumString); - - string jsonSerialized = JsonSerializer.Serialize(obj); - Assert.Equal(json, jsonSerialized); - } - - private struct ClassWithOverrideReversed - { - // Same as ClassWithOverride except the order of the properties is different, which should cause different reflection order. - [JsonPropertyName("EnumValue")] - public string EnumString - { - get => EnumValue.ToString(); - set - { - if (value == "Case1") - { - EnumValue = MyEnum.Case1; - } - if (value == "Case2") - { - EnumValue = MyEnum.Case2; - } - else - { - throw new Exception("Unknown value!"); - } - } - } - - [JsonIgnore] - public MyEnum EnumValue { get; set; } - } - - [Fact] - public static void OverrideJsonIgnorePropertyUsingJsonPropertyNameReversed() - { - const string json = @"{""EnumValue"":""Case2""}"; - - ClassWithOverrideReversed obj = JsonSerializer.Deserialize(json); - - Assert.Equal(MyEnum.Case2, obj.EnumValue); - Assert.Equal("Case2", obj.EnumString); - - string jsonSerialized = JsonSerializer.Serialize(obj); - Assert.Equal(json, jsonSerialized); - } - - [Theory] - [InlineData(typeof(ClassWithProperty_IgnoreConditionAlways))] - [InlineData(typeof(ClassWithProperty_IgnoreConditionAlways_Ctor))] - public static void JsonIgnoreConditionSetToAlwaysWorks(Type type) - { - string json = @"{""MyString"":""Random"",""MyDateTime"":""2020-03-23"",""MyInt"":4}"; - - object obj = JsonSerializer.Deserialize(json, type); - Assert.Equal("Random", (string)type.GetProperty("MyString").GetValue(obj)); - Assert.Equal(default, (DateTime)type.GetProperty("MyDateTime").GetValue(obj)); - Assert.Equal(4, (int)type.GetProperty("MyInt").GetValue(obj)); - - string serialized = JsonSerializer.Serialize(obj); - Assert.Contains(@"""MyString"":""Random""", serialized); - Assert.Contains(@"""MyInt"":4", serialized); - Assert.DoesNotContain(@"""MyDateTime"":", serialized); - } - - private class ClassWithProperty_IgnoreConditionAlways - { - public string MyString { get; set; } - [JsonIgnore(Condition = JsonIgnoreCondition.Always)] - public DateTime MyDateTime { get; set; } - public int MyInt { get; set; } - } - - private class ClassWithProperty_IgnoreConditionAlways_Ctor - { - public string MyString { get; set; } - [JsonIgnore(Condition = JsonIgnoreCondition.Always)] - public DateTime MyDateTime { get; } - public int MyInt { get; } - - public ClassWithProperty_IgnoreConditionAlways_Ctor(DateTime myDateTime, int myInt) - { - MyDateTime = myDateTime; - MyInt = myInt; - } - } - - [Theory] - [MemberData(nameof(JsonIgnoreConditionWhenWritingDefault_ClassProperty_TestData))] - public static void JsonIgnoreConditionWhenWritingDefault_ClassProperty(Type type, JsonSerializerOptions options) - { - // Property shouldn't be ignored if it isn't null. - string json = @"{""Int1"":1,""MyString"":""Random"",""Int2"":2}"; - - object obj = JsonSerializer.Deserialize(json, type, options); - Assert.Equal(1, (int)type.GetProperty("Int1").GetValue(obj)); - Assert.Equal("Random", (string)type.GetProperty("MyString").GetValue(obj)); - Assert.Equal(2, (int)type.GetProperty("Int2").GetValue(obj)); - - string serialized = JsonSerializer.Serialize(obj, options); - Assert.Contains(@"""Int1"":1", serialized); - Assert.Contains(@"""MyString"":""Random""", serialized); - Assert.Contains(@"""Int2"":2", serialized); - - // Property should be ignored when null. - json = @"{""Int1"":1,""MyString"":null,""Int2"":2}"; - - obj = JsonSerializer.Deserialize(json, type, options); - Assert.Equal(1, (int)type.GetProperty("Int1").GetValue(obj)); - - if (options.IgnoreNullValues) - { - // Null values can be ignored on deserialization using IgnoreNullValues. - Assert.Equal("DefaultString", (string)type.GetProperty("MyString").GetValue(obj)); - } - else - { - Assert.Null((string)type.GetProperty("MyString").GetValue(obj)); - } - - Assert.Equal(2, (int)type.GetProperty("Int2").GetValue(obj)); - - // Set property to be ignored to null. - type.GetProperty("MyString").SetValue(obj, null); - - serialized = JsonSerializer.Serialize(obj, options); - Assert.Contains(@"""Int1"":1", serialized); - Assert.Contains(@"""Int2"":2", serialized); - Assert.DoesNotContain(@"""MyString"":", serialized); - } - - private class ClassWithClassProperty_IgnoreConditionWhenWritingDefault - { - public int Int1 { get; set; } - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public string MyString { get; set; } = "DefaultString"; - public int Int2 { get; set; } - } - - private class ClassWithClassProperty_IgnoreConditionWhenWritingDefault_Ctor - { - public int Int1 { get; set; } - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public string MyString { get; set; } = "DefaultString"; - public int Int2 { get; set; } - - public ClassWithClassProperty_IgnoreConditionWhenWritingDefault_Ctor(string myString) - { - if (myString != null) - { - MyString = myString; - } - } - } - - public static IEnumerable JsonIgnoreConditionWhenWritingDefault_ClassProperty_TestData() - { - yield return new object[] { typeof(ClassWithClassProperty_IgnoreConditionWhenWritingDefault), new JsonSerializerOptions() }; - yield return new object[] { typeof(ClassWithClassProperty_IgnoreConditionWhenWritingDefault_Ctor), new JsonSerializerOptions { IgnoreNullValues = true } }; - } - - [Theory] - [MemberData(nameof(JsonIgnoreConditionWhenWritingDefault_StructProperty_TestData))] - public static void JsonIgnoreConditionWhenWritingDefault_StructProperty(Type type, JsonSerializerOptions options) - { - // Property shouldn't be ignored if it isn't null. - string json = @"{""Int1"":1,""MyInt"":3,""Int2"":2}"; - - object obj = JsonSerializer.Deserialize(json, type, options); - Assert.Equal(1, (int)type.GetProperty("Int1").GetValue(obj)); - Assert.Equal(3, (int)type.GetProperty("MyInt").GetValue(obj)); - Assert.Equal(2, (int)type.GetProperty("Int2").GetValue(obj)); - - string serialized = JsonSerializer.Serialize(obj, options); - Assert.Contains(@"""Int1"":1", serialized); - Assert.Contains(@"""MyInt"":3", serialized); - Assert.Contains(@"""Int2"":2", serialized); - - // Null being assigned to non-nullable types is invalid. - json = @"{""Int1"":1,""MyInt"":null,""Int2"":2}"; - Assert.Throws(() => JsonSerializer.Deserialize(json, type, options)); - } - - private class ClassWithStructProperty_IgnoreConditionWhenWritingDefault - { - public int Int1 { get; set; } - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public int MyInt { get; set; } - public int Int2 { get; set; } - } - - private struct StructWithStructProperty_IgnoreConditionWhenWritingDefault_Ctor - { - public int Int1 { get; set; } - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public int MyInt { get; } - public int Int2 { get; set; } - - [JsonConstructor] - public StructWithStructProperty_IgnoreConditionWhenWritingDefault_Ctor(int myInt) - { - Int1 = 0; - MyInt = myInt; - Int2 = 0; - } - } - - public static IEnumerable JsonIgnoreConditionWhenWritingDefault_StructProperty_TestData() - { - yield return new object[] { typeof(ClassWithStructProperty_IgnoreConditionWhenWritingDefault), new JsonSerializerOptions() }; - yield return new object[] { typeof(StructWithStructProperty_IgnoreConditionWhenWritingDefault_Ctor), new JsonSerializerOptions { IgnoreNullValues = true } }; - } - - [Theory] - [MemberData(nameof(JsonIgnoreConditionNever_TestData))] - public static void JsonIgnoreConditionNever(Type type) - { - // Property should always be (de)serialized, even when null. - string json = @"{""Int1"":1,""MyString"":""Random"",""Int2"":2}"; - - object obj = JsonSerializer.Deserialize(json, type); - Assert.Equal(1, (int)type.GetProperty("Int1").GetValue(obj)); - Assert.Equal("Random", (string)type.GetProperty("MyString").GetValue(obj)); - Assert.Equal(2, (int)type.GetProperty("Int2").GetValue(obj)); - - string serialized = JsonSerializer.Serialize(obj); - Assert.Contains(@"""Int1"":1", serialized); - Assert.Contains(@"""MyString"":""Random""", serialized); - Assert.Contains(@"""Int2"":2", serialized); - - // Property should always be (de)serialized, even when null. - json = @"{""Int1"":1,""MyString"":null,""Int2"":2}"; - - obj = JsonSerializer.Deserialize(json, type); - Assert.Equal(1, (int)type.GetProperty("Int1").GetValue(obj)); - Assert.Null((string)type.GetProperty("MyString").GetValue(obj)); - Assert.Equal(2, (int)type.GetProperty("Int2").GetValue(obj)); - - serialized = JsonSerializer.Serialize(obj); - Assert.Contains(@"""Int1"":1", serialized); - Assert.Contains(@"""MyString"":null", serialized); - Assert.Contains(@"""Int2"":2", serialized); - } - - [Theory] - [MemberData(nameof(JsonIgnoreConditionNever_TestData))] - public static void JsonIgnoreConditionNever_IgnoreNullValues_True(Type type) - { - // Property should always be (de)serialized. - string json = @"{""Int1"":1,""MyString"":""Random"",""Int2"":2}"; - var options = new JsonSerializerOptions { IgnoreNullValues = true }; - - object obj = JsonSerializer.Deserialize(json, type, options); - Assert.Equal(1, (int)type.GetProperty("Int1").GetValue(obj)); - Assert.Equal("Random", (string)type.GetProperty("MyString").GetValue(obj)); - Assert.Equal(2, (int)type.GetProperty("Int2").GetValue(obj)); - - string serialized = JsonSerializer.Serialize(obj, options); - Assert.Contains(@"""Int1"":1", serialized); - Assert.Contains(@"""MyString"":""Random""", serialized); - Assert.Contains(@"""Int2"":2", serialized); - - // Property should always be (de)serialized, even when null. - json = @"{""Int1"":1,""MyString"":null,""Int2"":2}"; - - obj = JsonSerializer.Deserialize(json, type, options); - Assert.Equal(1, (int)type.GetProperty("Int1").GetValue(obj)); - Assert.Null((string)type.GetProperty("MyString").GetValue(obj)); - Assert.Equal(2, (int)type.GetProperty("Int2").GetValue(obj)); - - serialized = JsonSerializer.Serialize(obj, options); - Assert.Contains(@"""Int1"":1", serialized); - Assert.Contains(@"""MyString"":null", serialized); - Assert.Contains(@"""Int2"":2", serialized); - } - - private class ClassWithStructProperty_IgnoreConditionNever - { - public int Int1 { get; set; } - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public string MyString { get; set; } - public int Int2 { get; set; } - } - - private class ClassWithStructProperty_IgnoreConditionNever_Ctor - { - public int Int1 { get; set; } - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public string MyString { get; } - public int Int2 { get; set; } - - public ClassWithStructProperty_IgnoreConditionNever_Ctor(string myString) - { - MyString = myString; - } - } - - public static IEnumerable JsonIgnoreConditionNever_TestData() - { - yield return new object[] { typeof(ClassWithStructProperty_IgnoreConditionNever) }; - yield return new object[] { typeof(ClassWithStructProperty_IgnoreConditionNever_Ctor) }; - } - - [Fact] - public static void JsonIgnoreCondition_LastOneWins() - { - string json = @"{""MyString"":""Random"",""MYSTRING"":null}"; - - var options = new JsonSerializerOptions - { - IgnoreNullValues = true, - PropertyNameCaseInsensitive = true - }; - var obj = JsonSerializer.Deserialize(json, options); - - Assert.Null(obj.MyString); - } - - [Fact] - public static void ClassWithComplexObjectsUsingIgnoreWhenWritingDefaultAttribute() - { - string json = @"{""Class"":{""MyInt16"":18}, ""Dictionary"":null}"; - - ClassUsingIgnoreWhenWritingDefaultAttribute obj = JsonSerializer.Deserialize(json); - - // Class is deserialized. - Assert.NotNull(obj.Class); - Assert.Equal(18, obj.Class.MyInt16); - - // Dictionary is deserialized as JsonIgnoreCondition.WhenWritingDefault only applies to deserialization. - Assert.Null(obj.Dictionary); - - obj = new ClassUsingIgnoreWhenWritingDefaultAttribute(); - json = JsonSerializer.Serialize(obj); - Assert.Equal(@"{""Dictionary"":{""Key"":""Value""}}", json); - } - - public class ClassUsingIgnoreWhenWritingDefaultAttribute - { - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public SimpleTestClass Class { get; set; } - - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public Dictionary Dictionary { get; set; } = new Dictionary { ["Key"] = "Value" }; - } - - [Fact] - public static void ClassWithComplexObjectUsingIgnoreNeverAttribute() - { - string json = @"{""Class"":null, ""Dictionary"":null}"; - var options = new JsonSerializerOptions { IgnoreNullValues = true }; - - var obj = JsonSerializer.Deserialize(json, options); - - // Class is not deserialized because it is null in json. - Assert.NotNull(obj.Class); - Assert.Equal(18, obj.Class.MyInt16); - - // Dictionary is deserialized regardless of being null in json. - Assert.Null(obj.Dictionary); - - // Serialize when values are null. - obj = new ClassUsingIgnoreNeverAttribute(); - obj.Class = null; - obj.Dictionary = null; - - json = JsonSerializer.Serialize(obj, options); - - // Class is not included in json because it was null, Dictionary is included regardless of being null. - Assert.Equal(@"{""Dictionary"":null}", json); - } - - public class ClassUsingIgnoreNeverAttribute - { - public SimpleTestClass Class { get; set; } = new SimpleTestClass { MyInt16 = 18 }; - - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public Dictionary Dictionary { get; set; } = new Dictionary { ["Key"] = "Value" }; - } - - [Fact] - public static void IgnoreConditionNever_WinsOver_IgnoreReadOnlyProperties() - { - var options = new JsonSerializerOptions { IgnoreReadOnlyProperties = true }; - - // Baseline - string json = JsonSerializer.Serialize(new ClassWithReadOnlyStringProperty("Hello"), options); - Assert.Equal("{}", json); - - // With condition to never ignore - json = JsonSerializer.Serialize(new ClassWithReadOnlyStringProperty_IgnoreNever("Hello"), options); - Assert.Equal(@"{""MyString"":""Hello""}", json); - - json = JsonSerializer.Serialize(new ClassWithReadOnlyStringProperty_IgnoreNever(null), options); - Assert.Equal(@"{""MyString"":null}", json); - } - - [Fact] - public static void IgnoreConditionWhenWritingDefault_WinsOver_IgnoreReadOnlyProperties() - { - var options = new JsonSerializerOptions { IgnoreReadOnlyProperties = true }; - - // Baseline - string json = JsonSerializer.Serialize(new ClassWithReadOnlyStringProperty("Hello"), options); - Assert.Equal("{}", json); - - // With condition to ignore when null - json = JsonSerializer.Serialize(new ClassWithReadOnlyStringProperty_IgnoreWhenWritingDefault("Hello"), options); - Assert.Equal(@"{""MyString"":""Hello""}", json); - - json = JsonSerializer.Serialize(new ClassWithReadOnlyStringProperty_IgnoreWhenWritingDefault(null), options); - Assert.Equal(@"{}", json); - } - - [Fact] - public static void IgnoreConditionNever_WinsOver_IgnoreReadOnlyFields() - { - var options = new JsonSerializerOptions { IgnoreReadOnlyProperties = true }; - - // Baseline - string json = JsonSerializer.Serialize(new ClassWithReadOnlyStringField("Hello"), options); - Assert.Equal("{}", json); - - // With condition to never ignore - json = JsonSerializer.Serialize(new ClassWithReadOnlyStringField_IgnoreNever("Hello"), options); - Assert.Equal(@"{""MyString"":""Hello""}", json); - - json = JsonSerializer.Serialize(new ClassWithReadOnlyStringField_IgnoreNever(null), options); - Assert.Equal(@"{""MyString"":null}", json); - } - - [Fact] - public static void IgnoreConditionWhenWritingDefault_WinsOver_IgnoreReadOnlyFields() - { - var options = new JsonSerializerOptions { IgnoreReadOnlyProperties = true }; - - // Baseline - string json = JsonSerializer.Serialize(new ClassWithReadOnlyStringField("Hello"), options); - Assert.Equal("{}", json); - - // With condition to ignore when null - json = JsonSerializer.Serialize(new ClassWithReadOnlyStringField_IgnoreWhenWritingDefault("Hello"), options); - Assert.Equal(@"{""MyString"":""Hello""}", json); - - json = JsonSerializer.Serialize(new ClassWithReadOnlyStringField_IgnoreWhenWritingDefault(null), options); - Assert.Equal(@"{}", json); - } - - private class ClassWithReadOnlyStringProperty - { - public string MyString { get; } - - public ClassWithReadOnlyStringProperty(string myString) => MyString = myString; - } - - private class ClassWithReadOnlyStringProperty_IgnoreNever - { - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public string MyString { get; } - - public ClassWithReadOnlyStringProperty_IgnoreNever(string myString) => MyString = myString; - } - - private class ClassWithReadOnlyStringProperty_IgnoreWhenWritingDefault - { - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public string MyString { get; } - - public ClassWithReadOnlyStringProperty_IgnoreWhenWritingDefault(string myString) => MyString = myString; - } - - private class ClassWithReadOnlyStringField - { - public string MyString { get; } - - public ClassWithReadOnlyStringField(string myString) => MyString = myString; - } - - private class ClassWithReadOnlyStringField_IgnoreNever - { - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public string MyString { get; } - - public ClassWithReadOnlyStringField_IgnoreNever(string myString) => MyString = myString; - } - - private class ClassWithReadOnlyStringField_IgnoreWhenWritingDefault - { - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public string MyString { get; } - - public ClassWithReadOnlyStringField_IgnoreWhenWritingDefault(string myString) => MyString = myString; - } - - [Fact] - public static void NonPublicMembersAreNotIncluded() - { - Assert.Equal("{}", JsonSerializer.Serialize(new ClassWithNonPublicProperties())); - - string json = @"{""MyInt"":1,""MyString"":""Hello"",""MyFloat"":2,""MyDouble"":3}"; - var obj = JsonSerializer.Deserialize(json); - Assert.Equal(0, obj.MyInt); - Assert.Null(obj.MyString); - Assert.Equal(0, obj.GetMyFloat); - Assert.Equal(0, obj.GetMyDouble); - } - - private class ClassWithNonPublicProperties - { - internal int MyInt { get; set; } - internal string MyString { get; private set; } - internal float MyFloat { private get; set; } - private double MyDouble { get; set; } - - internal float GetMyFloat => MyFloat; - internal double GetMyDouble => MyDouble; - } - - [Fact] - public static void IgnoreCondition_WhenWritingDefault_Globally_Works() - { - // Baseline - default values written. - string expected = @"{""MyString"":null,""MyInt"":0,""MyPoint"":{""X"":0,""Y"":0}}"; - var obj = new ClassWithProps(); - JsonTestHelper.AssertJsonEqual(expected, JsonSerializer.Serialize(obj)); - - // Default values ignored when specified. - Assert.Equal("{}", JsonSerializer.Serialize(obj, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault })); - } - - private class ClassWithProps - { - public string MyString { get; set; } - public int MyInt { get; set; } - public Point_2D_Struct MyPoint { get; set; } - } - - [Fact] - public static void IgnoreCondition_WhenWritingDefault_PerProperty_Works() - { - // Default values ignored when specified. - Assert.Equal(@"{""MyInt"":0}", JsonSerializer.Serialize(new ClassWithPropsAndIgnoreAttributes())); - } - - private class ClassWithPropsAndIgnoreAttributes - { - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public string MyString { get; set; } - public int MyInt { get; set; } - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public Point_2D_Struct MyPoint { get; set; } - } - - [Fact] - public static void IgnoreCondition_WhenWritingDefault_DoesNotApplyToCollections() - { - var list = new List { false, true }; - - var options = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }; - Assert.Equal("[false,true]", JsonSerializer.Serialize(list, options)); - } - - [Fact] - public static void IgnoreCondition_WhenWritingDefault_DoesNotApplyToDeserialization() - { - // Baseline - null values are ignored on deserialization when using IgnoreNullValues (for compat with initial support). - string json = @"{""MyString"":null,""MyInt"":0,""MyPoint"":{""X"":0,""Y"":0}}"; - - var options = new JsonSerializerOptions { IgnoreNullValues = true }; - ClassWithInitializedProps obj = JsonSerializer.Deserialize(json, options); - - Assert.Equal("Default", obj.MyString); - // Value types are not ignored. - Assert.Equal(0, obj.MyInt); - Assert.Equal(0, obj.MyPoint.X); - Assert.Equal(0, obj.MyPoint.X); - - // Test - default values (both null and default for value types) are not ignored when using - // JsonIgnoreCondition.WhenWritingDefault (as the option name implies) - options = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }; - obj = JsonSerializer.Deserialize(json, options); - Assert.Null(obj.MyString); - Assert.Equal(0, obj.MyInt); - Assert.Equal(0, obj.MyPoint.X); - Assert.Equal(0, obj.MyPoint.X); - } - - private class ClassWithInitializedProps - { - public string MyString { get; set; } = "Default"; - public int MyInt { get; set; } = -1; - public Point_2D_Struct MyPoint { get; set; } = new Point_2D_Struct(-1, -1); - } - - [Fact] - public static void ValueType_Properties_NotIgnoredWhen_IgnoreNullValues_Active_ClassTest() - { - var options = new JsonSerializerOptions { IgnoreNullValues = true }; - - // Deserialization. - string json = @"{""MyString"":null,""MyInt"":0,""MyBool"":null,""MyPointClass"":null,""MyPointStruct"":{""X"":0,""Y"":0}}"; - - ClassWithValueAndReferenceTypes obj = JsonSerializer.Deserialize(json, options); - - // Null values ignored for reference types/nullable value types. - Assert.Equal("Default", obj.MyString); - Assert.NotNull(obj.MyPointClass); - Assert.True(obj.MyBool); - - // Default values not ignored for value types. - Assert.Equal(0, obj.MyInt); - Assert.Equal(0, obj.MyPointStruct.X); - Assert.Equal(0, obj.MyPointStruct.Y); - - // Serialization. - - // Make all members their default CLR value. - obj.MyString = null; - obj.MyPointClass = null; - obj.MyBool = null; - - json = JsonSerializer.Serialize(obj, options); - - // Null values not serialized, default values for value types serialized. - JsonTestHelper.AssertJsonEqual(@"{""MyInt"":0,""MyPointStruct"":{""X"":0,""Y"":0}}", json); - } - - [Fact] - public static void ValueType_Properties_NotIgnoredWhen_IgnoreNullValues_Active_LargeStructTest() - { - var options = new JsonSerializerOptions { IgnoreNullValues = true }; - - // Deserialization. - string json = @"{""MyString"":null,""MyInt"":0,""MyBool"":null,""MyPointClass"":null,""MyPointStruct"":{""X"":0,""Y"":0}}"; - - LargeStructWithValueAndReferenceTypes obj = JsonSerializer.Deserialize(json, options); - - // Null values ignored for reference types. - - Assert.Equal("Default", obj.MyString); - // No way to specify a non-constant default before construction with ctor, so this remains null. - Assert.Null(obj.MyPointClass); - Assert.True(obj.MyBool); - - // Default values not ignored for value types. - Assert.Equal(0, obj.MyInt); - Assert.Equal(0, obj.MyPointStruct.X); - Assert.Equal(0, obj.MyPointStruct.Y); - - // Serialization. - - // Make all members their default CLR value. - obj = new LargeStructWithValueAndReferenceTypes(null, new Point_2D_Struct(0, 0), null, 0, null); - - json = JsonSerializer.Serialize(obj, options); - - // Null values not serialized, default values for value types serialized. - JsonTestHelper.AssertJsonEqual(@"{""MyInt"":0,""MyPointStruct"":{""X"":0,""Y"":0}}", json); - } - - [Fact] - public static void ValueType_Properties_NotIgnoredWhen_IgnoreNullValues_Active_SmallStructTest() - { - var options = new JsonSerializerOptions { IgnoreNullValues = true }; - - // Deserialization. - string json = @"{""MyString"":null,""MyInt"":0,""MyBool"":null,""MyPointStruct"":{""X"":0,""Y"":0}}"; - - SmallStructWithValueAndReferenceTypes obj = JsonSerializer.Deserialize(json, options); - - // Null values ignored for reference types. - Assert.Equal("Default", obj.MyString); - Assert.True(obj.MyBool); - - // Default values not ignored for value types. - Assert.Equal(0, obj.MyInt); - Assert.Equal(0, obj.MyPointStruct.X); - Assert.Equal(0, obj.MyPointStruct.Y); - - // Serialization. - - // Make all members their default CLR value. - obj = new SmallStructWithValueAndReferenceTypes(new Point_2D_Struct(0, 0), null, 0, null); - - json = JsonSerializer.Serialize(obj, options); - - // Null values not serialized, default values for value types serialized. - JsonTestHelper.AssertJsonEqual(@"{""MyInt"":0,""MyPointStruct"":{""X"":0,""Y"":0}}", json); - } - - private class ClassWithValueAndReferenceTypes - { - public string MyString { get; set; } = "Default"; - public int MyInt { get; set; } = -1; - public bool? MyBool { get; set; } = true; - public PointClass MyPointClass { get; set; } = new PointClass(); - public Point_2D_Struct MyPointStruct { get; set; } = new Point_2D_Struct(1, 2); - } - - private struct LargeStructWithValueAndReferenceTypes - { - public string MyString { get; } - public int MyInt { get; set; } - public bool? MyBool { get; set; } - public PointClass MyPointClass { get; set; } - public Point_2D_Struct MyPointStruct { get; set; } - - [JsonConstructor] - public LargeStructWithValueAndReferenceTypes( - PointClass myPointClass, - Point_2D_Struct myPointStruct, - string myString = "Default", - int myInt = -1, - bool? myBool = true) - { - MyString = myString; - MyInt = myInt; - MyBool = myBool; - MyPointClass = myPointClass; - MyPointStruct = myPointStruct; - } - } - - private struct SmallStructWithValueAndReferenceTypes - { - public string MyString { get; } - public int MyInt { get; set; } - public bool? MyBool { get; set; } - public Point_2D_Struct MyPointStruct { get; set; } - - [JsonConstructor] - public SmallStructWithValueAndReferenceTypes( - Point_2D_Struct myPointStruct, - string myString = "Default", - int myInt = -1, - bool? myBool = true) - { - MyString = myString; - MyInt = myInt; - MyBool = myBool; - MyPointStruct = myPointStruct; - } - } - - public class PointClass { } - - [Fact] - public static void Ignore_WhenWritingNull_Globally() - { - var options = new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - IncludeFields = true - }; - - string json = @"{ -""MyPointClass2_IgnoredWhenWritingNull"":{}, -""MyString1_IgnoredWhenWritingNull"":""Default"", -""MyNullableBool1_IgnoredWhenWritingNull"":null, -""MyInt2"":0, -""MyPointStruct2"":{""X"":1,""Y"":2}, -""MyInt1"":1, -""MyString2_IgnoredWhenWritingNull"":null, -""MyPointClass1_IgnoredWhenWritingNull"":null, -""MyNullableBool2_IgnoredWhenWritingNull"":true, -""MyPointStruct1"":{""X"":0,""Y"":0} -}"; - - // All members should correspond to JSON contents, as ignore doesn't apply to deserialization. - ClassWithThingsToIgnore obj = JsonSerializer.Deserialize(json, options); - Assert.NotNull(obj.MyPointClass2_IgnoredWhenWritingNull); - Assert.Equal("Default", obj.MyString1_IgnoredWhenWritingNull); - Assert.Null(obj.MyNullableBool1_IgnoredWhenWritingNull); - Assert.Equal(0, obj.MyInt2); - Assert.Equal(1, obj.MyPointStruct2.X); - Assert.Equal(2, obj.MyPointStruct2.Y); - Assert.Equal(1, obj.MyInt1); - Assert.Null(obj.MyString2_IgnoredWhenWritingNull); - Assert.Null(obj.MyPointClass1_IgnoredWhenWritingNull); - Assert.True(obj.MyNullableBool2_IgnoredWhenWritingNull); - Assert.Equal(0, obj.MyPointStruct1.X); - Assert.Equal(0, obj.MyPointStruct1.Y); - - // Ignore null as appropriate during serialization. - string expectedJson = @"{ -""MyPointClass2_IgnoredWhenWritingNull"":{}, -""MyString1_IgnoredWhenWritingNull"":""Default"", -""MyInt2"":0, -""MyPointStruct2"":{""X"":1,""Y"":2}, -""MyInt1"":1, -""MyNullableBool2_IgnoredWhenWritingNull"":true, -""MyPointStruct1"":{""X"":0,""Y"":0} -}"; - JsonTestHelper.AssertJsonEqual(expectedJson, JsonSerializer.Serialize(obj, options)); - } - - public class ClassWithThingsToIgnore - { - public string MyString1_IgnoredWhenWritingNull { get; set; } - - public string MyString2_IgnoredWhenWritingNull; - - public int MyInt1; - - public int MyInt2 { get; set; } - - public bool? MyNullableBool1_IgnoredWhenWritingNull { get; set; } - - public bool? MyNullableBool2_IgnoredWhenWritingNull; - - public PointClass MyPointClass1_IgnoredWhenWritingNull; - - public PointClass MyPointClass2_IgnoredWhenWritingNull { get; set; } - - public Point_2D_Struct_WithAttribute MyPointStruct1; - - public Point_2D_Struct_WithAttribute MyPointStruct2 { get; set; } - } - - [Fact] - public static void Ignore_WhenWritingNull_PerProperty() - { - var options = new JsonSerializerOptions - { - IncludeFields = true - }; - - string json = @"{ -""MyPointClass2_IgnoredWhenWritingNull"":{}, -""MyString1_IgnoredWhenWritingNull"":""Default"", -""MyNullableBool1_IgnoredWhenWritingNull"":null, -""MyInt2"":0, -""MyPointStruct2"":{""X"":1,""Y"":2}, -""MyInt1"":1, -""MyString2_IgnoredWhenWritingNull"":null, -""MyPointClass1_IgnoredWhenWritingNull"":null, -""MyNullableBool2_IgnoredWhenWritingNull"":true, -""MyPointStruct1"":{""X"":0,""Y"":0} -}"; - - // All members should correspond to JSON contents, as ignore doesn't apply to deserialization. - ClassWithThingsToIgnore_PerProperty obj = JsonSerializer.Deserialize(json, options); - Assert.NotNull(obj.MyPointClass2_IgnoredWhenWritingNull); - Assert.Equal("Default", obj.MyString1_IgnoredWhenWritingNull); - Assert.Null(obj.MyNullableBool1_IgnoredWhenWritingNull); - Assert.Equal(0, obj.MyInt2); - Assert.Equal(1, obj.MyPointStruct2.X); - Assert.Equal(2, obj.MyPointStruct2.Y); - Assert.Equal(1, obj.MyInt1); - Assert.Null(obj.MyString2_IgnoredWhenWritingNull); - Assert.Null(obj.MyPointClass1_IgnoredWhenWritingNull); - Assert.True(obj.MyNullableBool2_IgnoredWhenWritingNull); - Assert.Equal(0, obj.MyPointStruct1.X); - Assert.Equal(0, obj.MyPointStruct1.Y); - - // Ignore null as appropriate during serialization. - string expectedJson = @"{ -""MyPointClass2_IgnoredWhenWritingNull"":{}, -""MyString1_IgnoredWhenWritingNull"":""Default"", -""MyInt2"":0, -""MyPointStruct2"":{""X"":1,""Y"":2}, -""MyInt1"":1, -""MyNullableBool2_IgnoredWhenWritingNull"":true, -""MyPointStruct1"":{""X"":0,""Y"":0} -}"; - JsonTestHelper.AssertJsonEqual(expectedJson, JsonSerializer.Serialize(obj, options)); - } - - public class ClassWithThingsToIgnore_PerProperty - { - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string MyString1_IgnoredWhenWritingNull { get; set; } - - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string MyString2_IgnoredWhenWritingNull; - - public int MyInt1; - - public int MyInt2 { get; set; } - - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public bool? MyNullableBool1_IgnoredWhenWritingNull { get; set; } - - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public bool? MyNullableBool2_IgnoredWhenWritingNull; - - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public PointClass MyPointClass1_IgnoredWhenWritingNull; - - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public PointClass MyPointClass2_IgnoredWhenWritingNull { get; set; } - - public Point_2D_Struct_WithAttribute MyPointStruct1; - - public Point_2D_Struct_WithAttribute MyPointStruct2 { get; set; } - } - - [Theory] - [InlineData(typeof(ClassWithBadIgnoreAttribute))] - [InlineData(typeof(StructWithBadIgnoreAttribute))] - public static void JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail(Type type) - { - InvalidOperationException ex = Assert.Throws(() => JsonSerializer.Deserialize("", type)); - string exAsStr = ex.ToString(); - Assert.Contains("JsonIgnoreCondition.WhenWritingNull", exAsStr); - Assert.Contains("MyBadMember", exAsStr); - Assert.Contains(type.ToString(), exAsStr); - Assert.Contains("JsonIgnoreCondition.WhenWritingDefault", exAsStr); - - ex = Assert.Throws(() => JsonSerializer.Serialize(Activator.CreateInstance(type))); - exAsStr = ex.ToString(); - Assert.Contains("JsonIgnoreCondition.WhenWritingNull", exAsStr); - Assert.Contains("MyBadMember", exAsStr); - Assert.Contains(type.ToString(), exAsStr); - Assert.Contains("JsonIgnoreCondition.WhenWritingDefault", exAsStr); - } - - private class ClassWithBadIgnoreAttribute - { - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public int MyBadMember { get; set; } - } - - private struct StructWithBadIgnoreAttribute - { - [JsonInclude] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public Point_2D_Struct MyBadMember { get; set; } - } - - private interface IUseCustomConverter { } - - [JsonConverter(typeof(MyCustomConverter))] - private struct MyValueTypeWithProperties : IUseCustomConverter - { - public int PrimitiveValue { get; set; } - public object RefValue { get; set; } - } - - private class MyCustomConverter : JsonConverter - { - public override bool CanConvert(Type typeToConvert) - { - return typeof(IUseCustomConverter).IsAssignableFrom(typeToConvert); - } - - public override IUseCustomConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => - throw new NotImplementedException(); - - public override void Write(Utf8JsonWriter writer, IUseCustomConverter value, JsonSerializerOptions options) - { - MyValueTypeWithProperties obj = (MyValueTypeWithProperties)value; - writer.WriteNumberValue(obj.PrimitiveValue + 100); - // Ignore obj.RefValue - } - } - - private class MyClassWithValueType - { - public MyClassWithValueType() { } - - public MyValueTypeWithProperties Value { get; set; } - } - - [Fact] - public static void JsonIgnoreCondition_WhenWritingDefault_OnValueTypeWithCustomConverter() - { - var obj = new MyClassWithValueType(); - - // Baseline without custom options. - Assert.True(EqualityComparer.Default.Equals(default, obj.Value)); - string json = JsonSerializer.Serialize(obj); - Assert.Equal("{\"Value\":100}", json); - - var options = new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault - }; - - // Verify ignored. - Assert.True(EqualityComparer.Default.Equals(default, obj.Value)); - json = JsonSerializer.Serialize(obj, options); - Assert.Equal("{}", json); - - // Change a primitive value so it's no longer a default value. - obj.Value = new MyValueTypeWithProperties { PrimitiveValue = 1 }; - Assert.False(EqualityComparer.Default.Equals(default, obj.Value)); - json = JsonSerializer.Serialize(obj, options); - Assert.Equal("{\"Value\":101}", json); - - // Change reference value so it's no longer a default value. - obj.Value = new MyValueTypeWithProperties { RefValue = 1 }; - Assert.False(EqualityComparer.Default.Equals(default, obj.Value)); - json = JsonSerializer.Serialize(obj, options); - Assert.Equal("{\"Value\":100}", json); - } - - [Fact] - public static void JsonIgnoreCondition_ConverterCalledOnDeserialize() - { - // Verify converter is called. - Assert.Throws(() => JsonSerializer.Deserialize("{}")); - - var options = new JsonSerializerOptions - { - IgnoreNullValues = true - }; - - Assert.Throws(() => JsonSerializer.Deserialize("{}", options)); - } - - [Fact] - public static void JsonIgnoreCondition_WhenWritingNull_OnValueTypeWithCustomConverter() - { - string json; - var obj = new MyClassWithValueType(); - - // Baseline without custom options. - Assert.True(EqualityComparer.Default.Equals(default, obj.Value)); - json = JsonSerializer.Serialize(obj); - Assert.Equal("{\"Value\":100}", json); - - var options = new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - // Verify not ignored; MyValueTypeWithProperties is not null. - Assert.True(EqualityComparer.Default.Equals(default, obj.Value)); - json = JsonSerializer.Serialize(obj, options); - Assert.Equal("{\"Value\":100}", json); - } - - [Fact] - public static void JsonIgnoreCondition_WhenWritingDefault_OnRootTypes() - { - string json; - int i = 0; - object obj = null; - - // Baseline without custom options. - json = JsonSerializer.Serialize(obj); - Assert.Equal("null", json); - - json = JsonSerializer.Serialize(i); - Assert.Equal("0", json); - - var options = new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault - }; - - // We don't ignore when applied to root types; only properties. - - json = JsonSerializer.Serialize(obj, options); - Assert.Equal("null", json); - - json = JsonSerializer.Serialize(i, options); - Assert.Equal("0", json); - } - - private struct MyValueTypeWithBoxedPrimitive - { - public object BoxedPrimitive { get; set; } - } - - [Fact] - public static void JsonIgnoreCondition_WhenWritingDefault_OnBoxedPrimitive() - { - string json; - - MyValueTypeWithBoxedPrimitive obj = new MyValueTypeWithBoxedPrimitive { BoxedPrimitive = 0 }; - - // Baseline without custom options. - json = JsonSerializer.Serialize(obj); - Assert.Equal("{\"BoxedPrimitive\":0}", json); - - var options = new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault - }; - - // No check if the boxed object's value type is a default value (0 in this case). - json = JsonSerializer.Serialize(obj, options); - Assert.Equal("{\"BoxedPrimitive\":0}", json); - - obj = new MyValueTypeWithBoxedPrimitive(); - json = JsonSerializer.Serialize(obj, options); - Assert.Equal("{}", json); - } - - private class MyClassWithValueTypeInterfaceProperty - { - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public IInterface MyProp { get; set; } - - public interface IInterface { } - public struct MyStruct : IInterface { } - } - - [Fact] - public static void JsonIgnoreCondition_WhenWritingDefault_OnInterface() - { - // MyProp should be ignored due to [JsonIgnore]. - var obj = new MyClassWithValueTypeInterfaceProperty(); - string json = JsonSerializer.Serialize(obj); - Assert.Equal("{}", json); - - // No check if the interface property's value type is a default value. - obj = new MyClassWithValueTypeInterfaceProperty { MyProp = new MyClassWithValueTypeInterfaceProperty.MyStruct() }; - json = JsonSerializer.Serialize(obj); - Assert.Equal("{\"MyProp\":{}}", json); - } + public PropertyVisibilityTestsDynamic() : base(new JsonSerializerWrapperForString_Dynamic()) { } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj index 3ba9831cb6db..62b646631196 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj @@ -1,4 +1,4 @@ - + $(NetCoreAppCurrent);net461 true @@ -16,7 +16,28 @@ + + + + + + + + + + + + + + + + + + + + + @@ -118,6 +139,7 @@ + @@ -144,8 +166,6 @@ - - @@ -160,22 +180,6 @@ - - - - - - - - - - - - - - - - From 92ed4005e70b37d6d0093d3e68da51f3749f15be Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Mon, 12 Jul 2021 23:18:32 -0700 Subject: [PATCH 044/133] Enreg structs x86 windows. (#55535) * Mark more cases as DoNotEnreg before CSE. There are CSE metrics that take into account how many potential enreg locals do we have. * enable for x86. --- src/coreclr/jit/gentree.h | 1 + src/coreclr/jit/jitconfigvalues.h | 2 +- src/coreclr/jit/lclvars.cpp | 4 ++++ src/coreclr/jit/lsra.cpp | 17 ++++++++++++++--- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 4aeeeb9e4863..4b7374573ef7 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -6801,6 +6801,7 @@ struct GenTreeCopyOrReload : public GenTreeUnOp GenTreeCopyOrReload(genTreeOps oper, var_types type, GenTree* op1) : GenTreeUnOp(oper, type, op1) { + assert(type != TYP_STRUCT || op1->IsMultiRegNode()); SetRegNum(REG_NA); ClearOtherRegs(); } diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index b14597a87029..3220193c662a 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -555,7 +555,7 @@ CONFIG_INTEGER(JitSaveFpLrWithCalleeSavedRegisters, W("JitSaveFpLrWithCalleeSave #endif // defined(TARGET_ARM64) #endif // DEBUG -#if defined(TARGET_AMD64) && defined(TARGET_WINDOWS) +#if defined(TARGET_WINDOWS) && defined(TARGET_XARCH) CONFIG_INTEGER(JitEnregStructLocals, W("JitEnregStructLocals"), 1) // Allow to enregister locals with struct type. #else CONFIG_INTEGER(JitEnregStructLocals, W("JitEnregStructLocals"), 0) // Don't allow to enregister locals with struct type diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 33c5abaa30df..75bcbfafbf29 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -3497,6 +3497,10 @@ void Compiler::lvaSortByRefCount() { lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_IsStruct)); } + else if (!varDsc->IsEnregisterableType()) + { + lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_IsStruct)); + } } if (varDsc->lvIsStructField && (lvaGetParentPromotionType(lclNum) != PROMOTION_TYPE_INDEPENDENT)) { diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index 4bbb877fda8c..f382b1dcf463 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -6180,10 +6180,21 @@ void LinearScan::insertCopyOrReload(BasicBlock* block, GenTree* tree, unsigned m } else { - // Create the new node, with "tree" as its only child. - var_types treeType = tree->TypeGet(); + var_types regType = tree->TypeGet(); + if ((regType == TYP_STRUCT) && !tree->IsMultiRegNode()) + { + assert(compiler->compEnregStructLocals()); + assert(tree->IsLocal()); + const GenTreeLclVarCommon* lcl = tree->AsLclVarCommon(); + const LclVarDsc* varDsc = compiler->lvaGetDesc(lcl); + // We create struct copies with a primitive type so we don't bother copy node with parsing structHndl. + // Note that for multiReg node we keep each regType in the tree and don't need this. + regType = varDsc->GetRegisterType(lcl); + assert(regType != TYP_UNDEF); + } - GenTreeCopyOrReload* newNode = new (compiler, oper) GenTreeCopyOrReload(oper, treeType, tree); + // Create the new node, with "tree" as its only child. + GenTreeCopyOrReload* newNode = new (compiler, oper) GenTreeCopyOrReload(oper, regType, tree); assert(refPosition->registerAssignment != RBM_NONE); SetLsraAdded(newNode); newNode->SetRegNumByIdx(refPosition->assignedReg(), multiRegIdx); From 88112bcd9792266ea6895a944f90f61d8d037555 Mon Sep 17 00:00:00 2001 From: Steve Molloy Date: Tue, 13 Jul 2021 00:18:13 -0700 Subject: [PATCH 045/133] Handle nullable primitives before passing them to WritePrimitive which expects non-null values. (#54800) --- .../ReflectionXmlSerializationWriter.cs | 11 ++++++++ .../XmlSerializerTests.RuntimeOnly.cs | 26 +++++++++++++++++++ .../tests/SerializationTypes.RuntimeOnly.cs | 7 +++++ 3 files changed, 44 insertions(+) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationWriter.cs index 3ed294b0ac69..d138146708e5 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationWriter.cs @@ -397,6 +397,17 @@ private void WriteElement(object? o, ElementAccessor element, bool writeAccessor { WriteQualifiedNameElement(name, ns!, element.Default, (XmlQualifiedName)o!, element.IsNullable, mapping.IsSoap, mapping); } + else if (o == null && element.IsNullable) + { + if (mapping.IsSoap) + { + WriteNullTagEncoded(element.Name, ns); + } + else + { + WriteNullTagLiteral(element.Name, ns); + } + } else { WritePrimitiveMethodRequirement suffixNullable = mapping.IsSoap ? WritePrimitiveMethodRequirement.Encoded : WritePrimitiveMethodRequirement.None; diff --git a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs index 182fb0a3d717..f83571e7ebf3 100644 --- a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs +++ b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs @@ -42,6 +42,19 @@ public static void Xml_ByteArrayAsRoot() Assert.Equal(x, y); } + [Fact] + public static void Xml_ByteArrayNull() + { + Assert.Null(SerializeAndDeserialize(null, +@" +")); + byte[] x = new byte[] { 1, 2 }; + byte[] y = SerializeAndDeserialize(x, +@" +AQI="); + Assert.Equal(x, y); + } + [Fact] public static void Xml_CharAsRoot() { @@ -1279,6 +1292,19 @@ public static void XML_TypeWithByteArrayAsXmlAttribute() Assert.True(Enumerable.SequenceEqual(value.XmlAttributeForms, actual.XmlAttributeForms)); } + [Fact] + public static void XML_TypeWithNullableByteArray() + { + var value = new TypeWithNullableByteArray(); // XmlAttributeForms == null + + var actual = SerializeAndDeserialize(value, + "\r\n\r\n \r\n"); + + Assert.NotNull(actual); + Assert.Null(value.XmlAttributeForms); + Assert.Null(actual.XmlAttributeForms); + } + [Fact] public static void XML_TypeWithByteArrayArrayAsXmlAttribute() { diff --git a/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.RuntimeOnly.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.RuntimeOnly.cs index 2d414e857024..70e080bae6e0 100644 --- a/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.RuntimeOnly.cs +++ b/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.RuntimeOnly.cs @@ -1987,6 +1987,13 @@ public class TypeWithByteArrayAsXmlAttribute public byte[] XmlAttributeForms; } + [XmlType(TypeName = "MyXmlType")] + public class TypeWithNullableByteArray + { + [XmlElement(DataType = "base64Binary", IsNullable = true)] + public byte[] XmlAttributeForms { get; set; } + } + [XmlType(TypeName = "MyXmlType")] public class TypeWithByteArrayArrayAsXmlAttribute { From 1749deb1cb51412c8f19a0246ac4d9d17c2b1a6b Mon Sep 17 00:00:00 2001 From: Steve Molloy Date: Tue, 13 Jul 2021 00:18:55 -0700 Subject: [PATCH 046/133] Use scopes to resolve array naming issue. (#55451) * Use scope to avoid using duplicate names for arrays in ILGen serializer. * Remove comment. --- .../XmlSerializationWriterILGen.cs | 6 +++ .../tests/XmlSerializer/XmlSerializerTests.cs | 38 +++++++++++++++++++ .../tests/SerializationTypes.cs | 30 ++++++++++++++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriterILGen.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriterILGen.cs index 46eda857550c..f348fdfa9538 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriterILGen.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriterILGen.cs @@ -1176,6 +1176,7 @@ private void WriteMember(SourceInfo source, AttributeAccessor attribute, TypeDes string aiVar = "ai" + memberTypeDesc.Name; string iVar = "i"; string fullTypeName = memberTypeDesc.CSharpName; + ilg.EnterScope(); WriteArrayLocalDecl(fullTypeName, aVar, source, memberTypeDesc); if (memberTypeDesc.IsNullable) { @@ -1361,6 +1362,8 @@ private void WriteMember(SourceInfo source, AttributeAccessor attribute, TypeDes { ilg.EndIf(); } + + ilg.ExitScope(); } else { @@ -1435,6 +1438,7 @@ private void WriteArray(SourceInfo source, string? choiceSource, ElementAccessor if (elements.Length == 0 && text == null) return; string arrayTypeName = arrayTypeDesc.CSharpName; string aName = "a" + arrayTypeDesc.Name; + ilg.EnterScope(); WriteArrayLocalDecl(arrayTypeName, aName, source, arrayTypeDesc); LocalBuilder aLoc = ilg.GetLocal(aName); if (arrayTypeDesc.IsNullable) @@ -1486,6 +1490,8 @@ private void WriteArray(SourceInfo source, string? choiceSource, ElementAccessor { ilg.EndIf(); } + + ilg.ExitScope(); } [RequiresUnreferencedCode("calls WriteElements")] diff --git a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs index 364c4f880082..e648a51d5c6e 100644 --- a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs +++ b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs @@ -69,6 +69,44 @@ public static void Xml_TypeWithDateTimePropertyAsXmlTime() } } + [Fact] + public static void Xml_NamespaceTypeNameClashTest() + { + var serializer = new XmlSerializer(typeof(NamespaceTypeNameClashContainer)); + + Assert.NotNull(serializer); + + var root = new NamespaceTypeNameClashContainer + { + A = new[] { new SerializationTypes.TypeNameClashA.TypeNameClash { Name = "N1" }, new SerializationTypes.TypeNameClashA.TypeNameClash { Name = "N2" } }, + B = new[] { new SerializationTypes.TypeNameClashB.TypeNameClash { Name = "N3" } } + }; + + var xml = @" + + + N1 + + + N2 + + + N3 + + "; + + var actualRoot = SerializeAndDeserialize(root, xml); + + Assert.NotNull(actualRoot); + Assert.NotNull(actualRoot.A); + Assert.NotNull(actualRoot.B); + Assert.Equal(root.A.Length, actualRoot.A.Length); + Assert.Equal(root.B.Length, actualRoot.B.Length); + Assert.Equal(root.A[0].Name, actualRoot.A[0].Name); + Assert.Equal(root.A[1].Name, actualRoot.A[1].Name); + Assert.Equal(root.B[0].Name, actualRoot.B[0].Name); + } + [Fact] public static void Xml_ArrayAsGetSet() { diff --git a/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.cs index 12ba77621b4a..6c8da649d56e 100644 --- a/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.cs +++ b/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.cs @@ -850,6 +850,35 @@ public class TypeWithKnownTypesOfCollectionsWithConflictingXmlName public object Value2 = new SimpleType[1]; } + + namespace TypeNameClashA + { + [System.Xml.Serialization.XmlType("TypeClashA")] + public class TypeNameClash + { + public string Name { get; set; } + } + } + + namespace TypeNameClashB + { + [System.Xml.Serialization.XmlType("TypeClashB")] + public class TypeNameClash + { + public string Name { get; set; } + } + } + + [System.Xml.Serialization.XmlRootAttribute("Root")] + [System.Xml.Serialization.XmlType("ContainerType")] + public class NamespaceTypeNameClashContainer + { + [System.Xml.Serialization.XmlElementAttribute("A")] + public TypeNameClashA.TypeNameClash[] A { get; set; } + + [System.Xml.Serialization.XmlElementAttribute("B")] + public TypeNameClashB.TypeNameClash[] B { get; set; } + } } public class TypeWithXmlElementProperty @@ -921,7 +950,6 @@ public class TypeWithXmlNodeArrayProperty public XmlNode[] CDATA { get; set; } } - public class Animal { public int Age; From c8b3051399fdf2d6d904be66e859df7135bf9afd Mon Sep 17 00:00:00 2001 From: Steve Molloy Date: Tue, 13 Jul 2021 00:27:06 -0700 Subject: [PATCH 047/133] Keep looking up derived chain for Property.Get if it doesn't exist. (#52509) --- .../ReflectionXmlSerializationReader.cs | 2 +- .../ReflectionXmlSerializationWriter.cs | 66 ++++++++++++++++++- .../src/System/Xml/Serialization/Types.cs | 5 +- .../XmlSerializerTests.RuntimeOnly.cs | 7 +- .../tests/DataContractSerializer.cs | 2 +- .../tests/SerializationTypes.RuntimeOnly.cs | 4 ++ 6 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationReader.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationReader.cs index 4e7b4e8e6e92..eca311b4d31d 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationReader.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationReader.cs @@ -637,7 +637,7 @@ private static ReflectionXmlSerializationReaderHelper.SetMemberValueDelegate Get (Type, string) typeMemberNameTuple = (o.GetType(), memberName); if (!s_setMemberValueDelegateCache.TryGetValue(typeMemberNameTuple, out ReflectionXmlSerializationReaderHelper.SetMemberValueDelegate? result)) { - MemberInfo memberInfo = ReflectionXmlSerializationHelper.GetMember(o.GetType(), memberName); + MemberInfo memberInfo = ReflectionXmlSerializationHelper.GetEffectiveSetInfo(o.GetType(), memberName); Debug.Assert(memberInfo != null, "memberInfo could not be retrieved"); Type memberType; if (memberInfo is PropertyInfo propInfo) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationWriter.cs index d138146708e5..c40076aa8386 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationWriter.cs @@ -660,7 +660,7 @@ private void WriteStructMethod(StructMapping mapping, string n, string? ns, obje [RequiresUnreferencedCode("Calls GetType on object")] private object? GetMemberValue(object o, string memberName) { - MemberInfo memberInfo = ReflectionXmlSerializationHelper.GetMember(o.GetType(), memberName); + MemberInfo memberInfo = ReflectionXmlSerializationHelper.GetEffectiveGetInfo(o.GetType(), memberName); object? memberValue = GetMemberValue(o, memberInfo); return memberValue; } @@ -1367,7 +1367,7 @@ private enum WritePrimitiveMethodRequirement internal static class ReflectionXmlSerializationHelper { [RequiresUnreferencedCode("Reflects over base members")] - public static MemberInfo GetMember(Type declaringType, string memberName) + public static MemberInfo? GetMember(Type declaringType, string memberName, bool throwOnNotFound) { MemberInfo[] memberInfos = declaringType.GetMember(memberName); if (memberInfos == null || memberInfos.Length == 0) @@ -1388,7 +1388,11 @@ public static MemberInfo GetMember(Type declaringType, string memberName) if (!foundMatchedMember) { - throw new InvalidOperationException(SR.Format(SR.XmlInternalErrorDetails, $"Could not find member named {memberName} of type {declaringType}")); + if (throwOnNotFound) + { + throw new InvalidOperationException(SR.Format(SR.XmlInternalErrorDetails, $"Could not find member named {memberName} of type {declaringType}")); + } + return null; } declaringType = currentType!; @@ -1409,5 +1413,61 @@ public static MemberInfo GetMember(Type declaringType, string memberName) return memberInfo; } + + [RequiresUnreferencedCode(XmlSerializer.TrimSerializationWarning)] + public static MemberInfo GetEffectiveGetInfo(Type declaringType, string memberName) + { + MemberInfo memberInfo = GetMember(declaringType, memberName, true)!; + + // For properties, we might have a PropertyInfo that does not have a valid + // getter at this level of inheritance. If that's the case, we need to look + // up the chain to find the right PropertyInfo for the getter. + if (memberInfo is PropertyInfo propInfo && propInfo.GetMethod == null) + { + var parent = declaringType.BaseType; + + while (parent != null) + { + var mi = GetMember(parent, memberName, false); + + if (mi is PropertyInfo pi && pi.GetMethod != null && pi.PropertyType == propInfo.PropertyType) + { + return pi; + } + + parent = parent.BaseType; + } + } + + return memberInfo; + } + + [RequiresUnreferencedCode(XmlSerializer.TrimSerializationWarning)] + public static MemberInfo GetEffectiveSetInfo(Type declaringType, string memberName) + { + MemberInfo memberInfo = GetMember(declaringType, memberName, true)!; + + // For properties, we might have a PropertyInfo that does not have a valid + // setter at this level of inheritance. If that's the case, we need to look + // up the chain to find the right PropertyInfo for the setter. + if (memberInfo is PropertyInfo propInfo && propInfo.SetMethod == null) + { + var parent = declaringType.BaseType; + + while (parent != null) + { + var mi = GetMember(parent, memberName, false); + + if (mi is PropertyInfo pi && pi.SetMethod != null && pi.PropertyType == propInfo.PropertyType) + { + return pi; + } + + parent = parent.BaseType; + } + } + + return memberInfo; + } } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/Types.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/Types.cs index 84a3d736550f..1971544633bf 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/Types.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/Types.cs @@ -1219,7 +1219,10 @@ private static bool ShouldBeReplaced( replacedInfo = info; if (replacedInfo != memberInfoToBeReplaced) { - if (!info.GetMethod!.IsPublic + // The property name is a match. It might be an override, or + // it might be hiding. Either way, check to see if the derived + // property has a getter that is useable for serialization. + if (info.GetMethod != null && !info.GetMethod!.IsPublic && memberInfoToBeReplaced is PropertyInfo && ((PropertyInfo)memberInfoToBeReplaced).GetMethod!.IsPublic ) diff --git a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs index f83571e7ebf3..167e812048c2 100644 --- a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs +++ b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs @@ -2836,13 +2836,14 @@ public static void DerivedTypeWithDifferentOverrides() [Fact] public static void DerivedTypeWithDifferentOverrides2() { - DerivedTypeWithDifferentOverrides2 value = new DerivedTypeWithDifferentOverrides2() { Name1 = "Name1", Name2 = "Name2", Name3 = "Name3", Name4 = "Name4", Name5 = "Name5", Name6 = "Name6" }; + DerivedTypeWithDifferentOverrides2 value = new DerivedTypeWithDifferentOverrides2() { Name1 = "Name1", Name2 = "Name2", Name3 = "Name3", Name4 = "Name4", Name5 = "Name5", Name6 = "Name6", Name7 = "Name7" }; ((DerivedTypeWithDifferentOverrides)value).Name5 = "MidLevelName5"; ((DerivedTypeWithDifferentOverrides)value).Name4 = "MidLevelName4"; ((SerializationTypes.BaseType)value).Name4 = "BaseLevelName4"; ((DerivedTypeWithDifferentOverrides)value).Name6 = "MidLevelName6"; ((SerializationTypes.BaseType)value).Name6 = "BaseLevelName6"; - DerivedTypeWithDifferentOverrides2 actual = SerializeAndDeserialize(value, @"Name1Name2Name3BaseLevelName4MidLevelName5BaseLevelName6"); + ((DerivedTypeWithDifferentOverrides)value).Name7 = "MidLevelName7"; + DerivedTypeWithDifferentOverrides2 actual = SerializeAndDeserialize(value, @"Name1Name2Name3BaseLevelName4MidLevelName5BaseLevelName6MidLevelName7"); Assert.Equal(value.Name1, actual.Name1); Assert.Equal(value.Name2, actual.Name2); Assert.Equal(value.Name3, actual.Name3); @@ -2855,6 +2856,8 @@ public static void DerivedTypeWithDifferentOverrides2() Assert.Null(actual.Name6); Assert.Equal(((DerivedTypeWithDifferentOverrides)actual).Name6, ((SerializationTypes.BaseType)actual).Name6); Assert.Equal(((SerializationTypes.BaseType)actual).Name6, ((SerializationTypes.BaseType)actual).Name6); + Assert.Equal(((DerivedTypeWithDifferentOverrides)actual).Name7, ((SerializationTypes.BaseType)actual).Name7); + Assert.Equal(actual.Name7, ((SerializationTypes.BaseType)actual).Name7); } [Fact] diff --git a/src/libraries/System.Runtime.Serialization.Xml/tests/DataContractSerializer.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/DataContractSerializer.cs index 2a39358aa09b..038b9130d4dc 100644 --- a/src/libraries/System.Runtime.Serialization.Xml/tests/DataContractSerializer.cs +++ b/src/libraries/System.Runtime.Serialization.Xml/tests/DataContractSerializer.cs @@ -1084,7 +1084,7 @@ public static void DCS_WithListOfXElement() public static void DCS_DerivedTypeWithDifferentOverrides() { var x = new DerivedTypeWithDifferentOverrides() { Name1 = "Name1", Name2 = "Name2", Name3 = "Name3", Name4 = "Name4", Name5 = "Name5" }; - var y = DataContractSerializerHelper.SerializeAndDeserialize(x, @"Name1Name2Name3Name5"); + var y = DataContractSerializerHelper.SerializeAndDeserialize(x, @"Name1Name2Name3Name5"); Assert.Equal(x.Name1, y.Name1); Assert.Equal(x.Name2, y.Name2); diff --git a/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.RuntimeOnly.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.RuntimeOnly.cs index 70e080bae6e0..de485ec10b57 100644 --- a/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.RuntimeOnly.cs +++ b/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.RuntimeOnly.cs @@ -837,6 +837,8 @@ public class BaseType public string @Name5 { get; set; } public virtual string Name6 { get; set; } + + public virtual string Name7 { get; set; } } public class DerivedTypeWithDifferentOverrides : BaseType @@ -852,6 +854,8 @@ public class DerivedTypeWithDifferentOverrides : BaseType public new string Name5 { get; set; } public override string Name6 { get; set; } + + public override string Name7 { set { base.Name7 = value; } } } public class DerivedTypeWithDifferentOverrides2 : DerivedTypeWithDifferentOverrides From 2d596ecea22934ecfc18346a40903cf035a0353b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Tue, 13 Jul 2021 00:50:00 -0700 Subject: [PATCH 048/133] Change server paths to use LOCALHOST (#55523) --- .../tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs index 6f9a1ace4004..0682f8ce16e5 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs @@ -512,7 +512,7 @@ internal static IEnumerable PathToTargetData // Extended DOS yield return Path.Combine(@"\\?\", Path.GetTempPath(), "foo"); // UNC - yield return @"\\SERVER\share\path"; + yield return @"\\LOCALHOST\share\path"; } else { From fe44d588f241c5b72b3b56768a0dbbb1bea027a7 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Tue, 13 Jul 2021 10:05:43 +0200 Subject: [PATCH 049/133] Last set of ILLink annotations related to System.Data.Common (#55507) * Last set of ILLink annotations related to System.Data.Common * apply feedback --- .../ref/System.Data.Common.cs | 2 + .../ILLink.Suppressions.LibraryBuild.xml | 47 +++++++++++++++++++ .../src/ILLink/ILLink.Suppressions.xml | 41 ---------------- .../src/System/Data/DataSet.cs | 24 ++++++++-- .../src/System/Data/DataTable.cs | 30 ++++++++---- 5 files changed, 91 insertions(+), 53 deletions(-) create mode 100644 src/libraries/System.Data.Common/src/ILLink/ILLink.Suppressions.LibraryBuild.xml delete mode 100644 src/libraries/System.Data.Common/src/ILLink/ILLink.Suppressions.xml diff --git a/src/libraries/System.Data.Common/ref/System.Data.Common.cs b/src/libraries/System.Data.Common/ref/System.Data.Common.cs index 38a37217d2fa..7bcd618b806d 100644 --- a/src/libraries/System.Data.Common/ref/System.Data.Common.cs +++ b/src/libraries/System.Data.Common/ref/System.Data.Common.cs @@ -753,6 +753,7 @@ public void EndLoadData() { } public System.Data.DataRow[] GetErrors() { throw null; } public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } protected virtual System.Type GetRowType() { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Members from serialized types may be trimmed if not referenced directly.")] protected virtual System.Xml.Schema.XmlSchema? GetSchema() { throw null; } public void ImportRow(System.Data.DataRow? row) { } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Members from types used in the expression column to be trimmed if not referenced directly.")] @@ -796,6 +797,7 @@ public void ReadXmlSchema(System.IO.TextReader? reader) { } public void ReadXmlSchema(string fileName) { } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Members from serialized types may be trimmed if not referenced directly.")] public void ReadXmlSchema(System.Xml.XmlReader? reader) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Members from serialized types may be trimmed if not referenced directly.")] protected virtual void ReadXmlSerializable(System.Xml.XmlReader? reader) { } public void RejectChanges() { } public virtual void Reset() { } diff --git a/src/libraries/System.Data.Common/src/ILLink/ILLink.Suppressions.LibraryBuild.xml b/src/libraries/System.Data.Common/src/ILLink/ILLink.Suppressions.LibraryBuild.xml new file mode 100644 index 000000000000..fff5aa12ed91 --- /dev/null +++ b/src/libraries/System.Data.Common/src/ILLink/ILLink.Suppressions.LibraryBuild.xml @@ -0,0 +1,47 @@ + + + + + ILLink + IL2026 + member + M:System.Data.DataSet.System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader) + DataSet.ReadXml is not trimming safe but developers cannot use it directly and have to go through the IXmlSerializable interface. Neither marking the interface nor constructors of DataSet as unsafe is a good solution so we rely on the fact that this method will be trimmed and warning will be shown only when both interface and DataSet is used in the same app which will reduce chance of seeing false positive warning. + + + ILLink + IL2026 + member + M:System.Data.DataSet.System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter) + DataSet.WriteXml is not trimming safe but developers cannot use it directly and have to go through the IXmlSerializable interface. Neither marking the interface nor constructors of DataSet as unsafe is a good solution so we rely on the fact that this method will be trimmed and warning will be shown only when both interface and DataSet is used in the same app which will reduce chance of seeing false positive warning. + + + ILLink + IL2026 + member + M:System.Data.DataSet.System.Xml.Serialization.IXmlSerializable.GetSchema() + DataSet.GetSchema is not trimming safe when used in derived types but developers cannot use it directly and have to go through the IXmlSerializable interface. Neither marking the interface nor constructors of DataSet as unsafe is a good solution so we rely on the fact that this method will be trimmed and warning will be shown only when both interface and DataSet is used in the same app which will reduce chance of seeing false positive warning. + + + ILLink + IL2026 + member + M:System.Data.DataTable.System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader) + DataTable.ReadXml is not trimming safe but developers cannot use it directly and have to go through the IXmlSerializable interface. Neither marking the interface nor constructors of DataTable as unsafe is a good solution so we rely on the fact that this method will be trimmed and warning will be shown only when both interface and DataTable is used in the same app which will reduce chance of seeing false positive warning. + + + ILLink + IL2026 + member + M:System.Data.DataTable.System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter) + DataTable.WriteXml is not trimming safe but developers cannot use it directly and have to go through the IXmlSerializable interface. Neither marking the interface nor constructors of DataTable as unsafe is a good solution so we rely on the fact that this method will be trimmed and warning will be shown only when both interface and DataTable is used in the same app which will reduce chance of seeing false positive warning. + + + ILLink + IL2026 + member + M:System.Data.DataTable.System.Xml.Serialization.IXmlSerializable.GetSchema() + DataTable.GetSchema is not trimming safe when used in derived types but developers cannot use it directly and have to go through the IXmlSerializable interface. Neither marking the interface nor constructors of DataTable as unsafe is a good solution so we rely on the fact that this method will be trimmed and warning will be shown only when both interface and DataTable is used in the same app which will reduce chance of seeing false positive warning. + + + diff --git a/src/libraries/System.Data.Common/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.Data.Common/src/ILLink/ILLink.Suppressions.xml deleted file mode 100644 index 393abfc15c49..000000000000 --- a/src/libraries/System.Data.Common/src/ILLink/ILLink.Suppressions.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - ILLink - IL2026 - member - M:System.Data.DataSet.System.Xml.Serialization.IXmlSerializable.GetSchema() - - - ILLink - IL2026 - member - M:System.Data.DataSet.System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader) - - - ILLink - IL2026 - member - M:System.Data.DataSet.System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter) - - - ILLink - IL2026 - member - M:System.Data.DataTable.GetSchema() - - - ILLink - IL2026 - member - M:System.Data.DataTable.ReadXml(System.Xml.XmlReader,System.Data.XmlReadMode,System.Boolean) - - - ILLink - IL2026 - member - M:System.Data.DataTable.WriteXmlCore(System.Xml.XmlWriter) - - - diff --git a/src/libraries/System.Data.Common/src/System/Data/DataSet.cs b/src/libraries/System.Data.Common/src/System/Data/DataSet.cs index 33c265766cee..6974aafce602 100644 --- a/src/libraries/System.Data.Common/src/System/Data/DataSet.cs +++ b/src/libraries/System.Data.Common/src/System/Data/DataSet.cs @@ -3444,7 +3444,6 @@ public static XmlSchemaComplexType GetDataSetSchema(XmlSchemaSet? schemaSet) private static bool PublishLegacyWSDL() => false; -#pragma warning disable 8632 XmlSchema? IXmlSerializable.GetSchema() { if (GetType() == typeof(DataSet)) @@ -3457,12 +3456,18 @@ public static XmlSchemaComplexType GetDataSetSchema(XmlSchemaSet? schemaSet) XmlWriter writer = new XmlTextWriter(stream, null); if (writer != null) { - (new XmlTreeGen(SchemaFormat.WebService)).Save(this, writer); + WriteXmlSchema(this, writer); } stream.Position = 0; return XmlSchema.Read(new XmlTextReader(stream), null); } + [RequiresUnreferencedCode("DataSet.GetSchema uses TypeDescriptor and XmlSerialization underneath which are not trimming safe. Members from serialized types may be trimmed if not referenced directly.")] + private static void WriteXmlSchema(DataSet ds, XmlWriter writer) + { + (new XmlTreeGen(SchemaFormat.WebService)).Save(ds, writer); + } + void IXmlSerializable.ReadXml(XmlReader reader) { bool fNormalization = true; @@ -3483,7 +3488,7 @@ void IXmlSerializable.ReadXml(XmlReader reader) } } - ReadXmlSerializable(reader); + ReadXmlSerializableInternal(reader); if (xmlTextParser != null) { @@ -3495,12 +3500,23 @@ void IXmlSerializable.ReadXml(XmlReader reader) } } + [RequiresUnreferencedCode("DataSet.ReadXml uses XmlSerialization underneath which is not trimming safe. Members from serialized types may be trimmed if not referenced directly.")] + private void ReadXmlSerializableInternal(XmlReader reader) + { + ReadXmlSerializable(reader); + } + void IXmlSerializable.WriteXml(XmlWriter writer) + { + WriteXmlInternal(writer); + } + + [RequiresUnreferencedCode("DataSet.WriteXml uses XmlSerialization underneath which is not trimming safe. Members from serialized types may be trimmed if not referenced directly.")] + private void WriteXmlInternal(XmlWriter writer) { WriteXmlSchema(writer, SchemaFormat.WebService, null); WriteXml(writer, XmlWriteMode.DiffGram); } -#pragma warning restore 8632 [RequiresUnreferencedCode("Using LoadOption may cause members from types used in the expression column to be trimmed if not referenced directly.")] public virtual void Load(IDataReader reader, LoadOption loadOption, FillErrorEventHandler? errorHandler, params DataTable[] tables) diff --git a/src/libraries/System.Data.Common/src/System/Data/DataTable.cs b/src/libraries/System.Data.Common/src/System/Data/DataTable.cs index bd8257122e23..22ace6fce640 100644 --- a/src/libraries/System.Data.Common/src/System/Data/DataTable.cs +++ b/src/libraries/System.Data.Common/src/System/Data/DataTable.cs @@ -5955,6 +5955,7 @@ internal XmlReadMode ReadXml(XmlReader? reader, bool denyResolving) } } + [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)] internal XmlReadMode ReadXml(XmlReader? reader, XmlReadMode mode, bool denyResolving) { IDisposable? restrictedScope = null; @@ -6685,8 +6686,11 @@ public static XmlSchemaComplexType GetDataTableSchema(XmlSchemaSet? schemaSet) return type; } - XmlSchema? IXmlSerializable.GetSchema() => GetSchema(); + XmlSchema? IXmlSerializable.GetSchema() => GetXmlSchema(); + [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2046:UnrecognizedReflectionPattern", + Justification = "https://github.com/mono/linker/issues/1187 Trimmer thinks this implements IXmlSerializable.GetSchema() and warns about not matching attributes.")] protected virtual XmlSchema? GetSchema() { if (GetType() == typeof(DataTable)) @@ -6704,7 +6708,12 @@ public static XmlSchemaComplexType GetDataTableSchema(XmlSchemaSet? schemaSet) return XmlSchema.Read(new XmlTextReader(stream), null); } -#pragma warning disable 8632 + [RequiresUnreferencedCode("DataTable.GetSchema uses TypeDescriptor and XmlSerialization underneath which are not trimming safe. Members from serialized types may be trimmed if not referenced directly.")] + private XmlSchema? GetXmlSchema() + { + return GetSchema(); + } + void IXmlSerializable.ReadXml(XmlReader reader) { IXmlTextParser? textReader = reader as IXmlTextParser; @@ -6714,7 +6723,7 @@ void IXmlSerializable.ReadXml(XmlReader reader) fNormalization = textReader.Normalized; textReader.Normalized = false; } - ReadXmlSerializable(reader); + ReadXmlSerializableInternal(reader); if (textReader != null) { @@ -6722,20 +6731,25 @@ void IXmlSerializable.ReadXml(XmlReader reader) } } + [RequiresUnreferencedCode("DataTable.ReadXml uses XmlSerialization underneath which is not trimming safe. Members from serialized types may be trimmed if not referenced directly.")] + private void ReadXmlSerializableInternal(XmlReader reader) + { + ReadXmlSerializable(reader); + } + void IXmlSerializable.WriteXml(XmlWriter writer) { - WriteXmlCore(writer); + WriteXmlInternal(writer); } -#pragma warning restore 8632 - // This method exists so that suppression can be placed on `IXmlSerializable.WriteXml(XmlWriter writer)` - private void WriteXmlCore(XmlWriter writer) + [RequiresUnreferencedCode("DataTable.WriteXml uses XmlSerialization underneath which is not trimming safe. Members from serialized types may be trimmed if not referenced directly.")] + private void WriteXmlInternal(XmlWriter writer) { WriteXmlSchema(writer, false); WriteXml(writer, XmlWriteMode.DiffGram, false); } -#pragma warning restore 8632 + [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)] protected virtual void ReadXmlSerializable(XmlReader? reader) => ReadXml(reader, XmlReadMode.DiffGram, true); // RowDiffIdUsageSection & DSRowDiffIdUsageSection Usage: From b744e1f0bdd6aedf9908ff53acf0ff163be6d20f Mon Sep 17 00:00:00 2001 From: SingleAccretion <62474226+SingleAccretion@users.noreply.github.com> Date: Tue, 13 Jul 2021 11:35:10 +0300 Subject: [PATCH 050/133] Fix bad folding (#54722) * Always sign-extend from int32 in gtFoldExprConst GT_CNS_INT of TYP_INT on 64 hosts has an implicit contract: the value returned by IconValue() must "fit" into 32 bit signed integer, i. e. "static_cast(IconValue()) == IconValue()". gtFoldExprConst was failing to uphold this contract when the target was 32 bit and the host was 64 bit. Fix this by always truncating before calling SetIconValue(). * Add a simple test that reproduces bad codegen --- src/coreclr/jit/gentree.cpp | 12 +++------ .../folding_extends_int32_on_64_bit_hosts.cs | 27 +++++++++++++++++++ ...lding_extends_int32_on_64_bit_hosts.csproj | 10 +++++++ 3 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 src/tests/JIT/Directed/ConstantFolding/folding_extends_int32_on_64_bit_hosts.cs create mode 100644 src/tests/JIT/Directed/ConstantFolding/folding_extends_int32_on_64_bit_hosts.csproj diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 277cd53e1c10..7f16e6cd4004 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -14856,19 +14856,15 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) JITDUMP("\nFolding operator with constant nodes into a constant:\n"); DISPTREE(tree); -#ifdef TARGET_64BIT - // Some operations are performed as 64 bit instead of 32 bit so the upper 32 bits - // need to be discarded. Since constant values are stored as ssize_t and the node - // has TYP_INT the result needs to be sign extended rather than zero extended. - i1 = INT32(i1); -#endif // TARGET_64BIT - // Also all conditional folding jumps here since the node hanging from // GT_JTRUE has to be a GT_CNS_INT - value 0 or 1. tree->ChangeOperConst(GT_CNS_INT); tree->ChangeType(TYP_INT); - tree->AsIntCon()->SetIconValue(i1); + // Some operations are performed as 64 bit instead of 32 bit so the upper 32 bits + // need to be discarded. Since constant values are stored as ssize_t and the node + // has TYP_INT the result needs to be sign extended rather than zero extended. + tree->AsIntCon()->SetIconValue(static_cast(i1)); tree->AsIntCon()->gtFieldSeq = fieldSeq; if (vnStore != nullptr) { diff --git a/src/tests/JIT/Directed/ConstantFolding/folding_extends_int32_on_64_bit_hosts.cs b/src/tests/JIT/Directed/ConstantFolding/folding_extends_int32_on_64_bit_hosts.cs new file mode 100644 index 000000000000..08d7e7a684f7 --- /dev/null +++ b/src/tests/JIT/Directed/ConstantFolding/folding_extends_int32_on_64_bit_hosts.cs @@ -0,0 +1,27 @@ +public class FoldingExtendsInt32On64BitHostsTest +{ + // On 64 bit hosts, 32 bit constants are stored as 64 bit signed values. + // gtFoldExpr failed to properly truncate the folded value to 32 bits when + // the host was 64 bit and the target - 32 bit. Thus local assertion prop + // got the "poisoned" value, which lead to silent bad codegen. + + public static int Main() + { + var r1 = 31; + // "Poisoned" value. + var s1 = 0b11 << r1; + + if (s1 == 0b11 << 31) + { + return 100; + } + + // Just so that Roslyn actually uses locals. + Use(s1); + Use(r1); + + return -1; + } + + private static void Use(int a) { } +} diff --git a/src/tests/JIT/Directed/ConstantFolding/folding_extends_int32_on_64_bit_hosts.csproj b/src/tests/JIT/Directed/ConstantFolding/folding_extends_int32_on_64_bit_hosts.csproj new file mode 100644 index 000000000000..edc51be9ca25 --- /dev/null +++ b/src/tests/JIT/Directed/ConstantFolding/folding_extends_int32_on_64_bit_hosts.csproj @@ -0,0 +1,10 @@ + + + Exe + True + None + + + + + From 086079a776c6c4d957efa0449f50d6fa5624d724 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Tue, 13 Jul 2021 01:37:09 -0700 Subject: [PATCH 051/133] Allow multiple private key references in Unix PFXes Windows has a complicated state for when a PFX contains two certificates that link to the same private key: * EphemeralKeySet: The PFX load fails. * PersistKeySet: Things probably work. * (normal): "It's complicated". When the Unix PFX loader was written it was based on the EphemeralKeySet behavior, because that's what the tests used (to avoid disk penalties and problems). Trying to maintain a balance between Herculean efforts of bug-for-bug compatibility and OS variability, this change takes a simpler approach: * EphemeralKeySet: The PFX load fails, like it will on Windows. * Otherwise: Let it work (but always with cloned keys, so some of the subtle Windows undesirable states are lost). --- .../Pal.Android/AndroidCertificatePal.cs | 7 +- .../Cryptography/Pal.Android/StorePal.cs | 11 +- .../Pal.OSX/AppleCertificatePal.Pkcs12.cs | 2 +- .../Internal/Cryptography/Pal.OSX/StorePal.cs | 5 +- .../Pal.Unix/OpenSslX509CertificateReader.cs | 5 +- .../Cryptography/Pal.Unix/PkcsFormatReader.cs | 38 ++- .../Cryptography/Pal.Unix/StorePal.cs | 15 +- .../Cryptography/Pal.Unix/UnixPkcs12Reader.cs | 28 +- .../AppleCertificatePal.ImportExport.cs | 4 +- .../Pal.iOS/AppleCertificatePal.Pkcs12.cs | 5 +- .../Internal/Cryptography/Pal.iOS/StorePal.cs | 3 +- .../tests/PfxFormatTests.cs | 251 ++++++++++++++++-- .../tests/PfxFormatTests_Collection.cs | 15 +- .../tests/PfxFormatTests_SingleCert.cs | 50 +++- 14 files changed, 376 insertions(+), 63 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/AndroidCertificatePal.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/AndroidCertificatePal.cs index 658c2da9e1a7..2a987506f120 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/AndroidCertificatePal.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/AndroidCertificatePal.cs @@ -52,6 +52,7 @@ public static ICertificatePal FromBlob(ReadOnlySpan rawData, SafePasswordH { Debug.Assert(password != null); + bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet); X509ContentType contentType = X509Certificate2.GetCertContentType(rawData); switch (contentType) @@ -62,7 +63,7 @@ public static ICertificatePal FromBlob(ReadOnlySpan rawData, SafePasswordH // We don't support determining this on Android right now, so we throw. throw new CryptographicException(SR.Cryptography_X509_PKCS7_NoSigner); case X509ContentType.Pkcs12: - return ReadPkcs12(rawData, password); + return ReadPkcs12(rawData, password, ephemeralSpecified); case X509ContentType.Cert: default: { @@ -104,11 +105,11 @@ ref MemoryMarshal.GetReference(rawData), return true; } - private static ICertificatePal ReadPkcs12(ReadOnlySpan rawData, SafePasswordHandle password) + private static ICertificatePal ReadPkcs12(ReadOnlySpan rawData, SafePasswordHandle password, bool ephemeralSpecified) { using (var reader = new AndroidPkcs12Reader(rawData)) { - reader.Decrypt(password); + reader.Decrypt(password, ephemeralSpecified); UnixPkcs12Reader.CertAndKey certAndKey = reader.GetSingleCert(); AndroidCertificatePal pal = (AndroidCertificatePal)certAndKey.Cert!; diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/StorePal.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/StorePal.cs index 09ec3ce53895..6187979d905f 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/StorePal.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Android/StorePal.cs @@ -23,9 +23,11 @@ public static ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle Debug.Assert(password != null); X509ContentType contentType = X509Certificate2.GetCertContentType(rawData); + bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet); + if (contentType == X509ContentType.Pkcs12) { - ICertificatePal[] certPals = ReadPkcs12Collection(rawData, password); + ICertificatePal[] certPals = ReadPkcs12Collection(rawData, password, ephemeralSpecified); return new AndroidCertLoader(certPals); } else @@ -108,11 +110,14 @@ public static IStorePal FromSystemStore(string storeName, StoreLocation storeLoc throw new CryptographicException(message, new PlatformNotSupportedException(message)); } - private static ICertificatePal[] ReadPkcs12Collection(ReadOnlySpan rawData, SafePasswordHandle password) + private static ICertificatePal[] ReadPkcs12Collection( + ReadOnlySpan rawData, + SafePasswordHandle password, + bool ephemeralSpecified) { using (var reader = new AndroidPkcs12Reader(rawData)) { - reader.Decrypt(password); + reader.Decrypt(password, ephemeralSpecified); ICertificatePal[] certs = new ICertificatePal[reader.GetCertCount()]; int idx = 0; diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Pkcs12.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Pkcs12.cs index 4febd7080f08..1eaa3f8323ad 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Pkcs12.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Pkcs12.cs @@ -19,7 +19,7 @@ private static AppleCertificatePal ImportPkcs12( { using (ApplePkcs12Reader reader = new ApplePkcs12Reader(rawData)) { - reader.Decrypt(password); + reader.Decrypt(password, ephemeralSpecified: false); UnixPkcs12Reader.CertAndKey certAndKey = reader.GetSingleCert(); AppleCertificatePal pal = (AppleCertificatePal)certAndKey.Cert!; diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.cs index 86bbebc594d4..e7cbefeb5b4d 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.cs @@ -47,7 +47,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle ? Interop.AppleCrypto.SecKeychainCopyDefault() : Interop.AppleCrypto.CreateTemporaryKeychain(); - return ImportPkcs12(rawData, password, exportable, keychain); + return ImportPkcs12(rawData, password, exportable, ephemeralSpecified: false, keychain); } SafeCFArrayHandle certs = Interop.AppleCrypto.X509ImportCollection( @@ -64,13 +64,14 @@ private static ILoaderPal ImportPkcs12( ReadOnlySpan rawData, SafePasswordHandle password, bool exportable, + bool ephemeralSpecified, SafeKeychainHandle keychain) { ApplePkcs12Reader reader = new ApplePkcs12Reader(rawData); try { - reader.Decrypt(password); + reader.Decrypt(password, ephemeralSpecified); return new ApplePkcs12CertLoader(reader, keychain, password, exportable); } catch diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509CertificateReader.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509CertificateReader.cs index e5561d9279c6..942c6251f6cb 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509CertificateReader.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509CertificateReader.cs @@ -49,12 +49,13 @@ public static ICertificatePal FromBlob(ReadOnlySpan rawData, SafePasswordH ICertificatePal? cert; Exception? openSslException; + bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet); if (TryReadX509Der(rawData, out cert) || TryReadX509Pem(rawData, out cert) || PkcsFormatReader.TryReadPkcs7Der(rawData, out cert) || PkcsFormatReader.TryReadPkcs7Pem(rawData, out cert) || - PkcsFormatReader.TryReadPkcs12(rawData, password, out cert, out openSslException)) + PkcsFormatReader.TryReadPkcs12(rawData, password, ephemeralSpecified, out cert, out openSslException)) { if (cert == null) { @@ -73,6 +74,7 @@ public static ICertificatePal FromBlob(ReadOnlySpan rawData, SafePasswordH public static ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) { ICertificatePal? pal; + bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet); // If we can't open the file, fail right away. using (SafeBioHandle fileBio = Interop.Crypto.BioNewFile(fileName, "rb")) @@ -87,6 +89,7 @@ public static ICertificatePal FromFile(string fileName, SafePasswordHandle passw PkcsFormatReader.TryReadPkcs12( File.ReadAllBytes(fileName), password, + ephemeralSpecified, out pal, out Exception? exception); diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/PkcsFormatReader.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/PkcsFormatReader.cs index 31a65038d598..f73b59373233 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/PkcsFormatReader.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/PkcsFormatReader.cs @@ -253,24 +253,49 @@ private static bool TryReadPkcs7( return true; } - internal static bool TryReadPkcs12(ReadOnlySpan rawData, SafePasswordHandle password, [NotNullWhen(true)] out ICertificatePal? certPal, out Exception? openSslException) + internal static bool TryReadPkcs12( + ReadOnlySpan rawData, + SafePasswordHandle password, + bool ephemeralSpecified, + [NotNullWhen(true)] out ICertificatePal? certPal, + out Exception? openSslException) { List? ignored; - return TryReadPkcs12(rawData, password, true, out certPal!, out ignored, out openSslException); + return TryReadPkcs12( + rawData, + password, + single: true, + ephemeralSpecified, + out certPal!, + out ignored, + out openSslException); } - internal static bool TryReadPkcs12(ReadOnlySpan rawData, SafePasswordHandle password, [NotNullWhen(true)] out List? certPals, out Exception? openSslException) + internal static bool TryReadPkcs12( + ReadOnlySpan rawData, + SafePasswordHandle password, + bool ephemeralSpecified, + [NotNullWhen(true)] out List? certPals, + out Exception? openSslException) { ICertificatePal? ignored; - return TryReadPkcs12(rawData, password, false, out ignored, out certPals!, out openSslException); + return TryReadPkcs12( + rawData, + password, + single: false, + ephemeralSpecified, + out ignored, + out certPals!, + out openSslException); } private static bool TryReadPkcs12( ReadOnlySpan rawData, SafePasswordHandle password, bool single, + bool ephemeralSpecified, out ICertificatePal? readPal, out List? readCerts, out Exception? openSslException) @@ -287,7 +312,7 @@ private static bool TryReadPkcs12( using (pfx) { - return TryReadPkcs12(pfx, password, single, out readPal, out readCerts); + return TryReadPkcs12(pfx, password, single, ephemeralSpecified, out readPal, out readCerts); } } @@ -295,10 +320,11 @@ private static bool TryReadPkcs12( OpenSslPkcs12Reader pfx, SafePasswordHandle password, bool single, + bool ephemeralSpecified, out ICertificatePal? readPal, out List? readCerts) { - pfx.Decrypt(password); + pfx.Decrypt(password, ephemeralSpecified); if (single) { diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/StorePal.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/StorePal.cs index 749beed10edc..bef72007c383 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/StorePal.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/StorePal.cs @@ -23,6 +23,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle Debug.Assert(password != null); ICertificatePal? singleCert; + bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet); if (OpenSslX509CertificateReader.TryReadX509Der(rawData, out singleCert) || OpenSslX509CertificateReader.TryReadX509Pem(rawData, out singleCert)) @@ -39,7 +40,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle if (PkcsFormatReader.TryReadPkcs7Der(rawData, out certPals) || PkcsFormatReader.TryReadPkcs7Pem(rawData, out certPals) || - PkcsFormatReader.TryReadPkcs12(rawData, password, out certPals, out openSslException)) + PkcsFormatReader.TryReadPkcs12(rawData, password, ephemeralSpecified, out certPals, out openSslException)) { Debug.Assert(certPals != null); @@ -52,15 +53,21 @@ public static ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle public static ILoaderPal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) { + bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet); + using (SafeBioHandle bio = Interop.Crypto.BioNewFile(fileName, "rb")) { Interop.Crypto.CheckValidOpenSslHandle(bio); - return FromBio(fileName, bio, password); + return FromBio(fileName, bio, password, ephemeralSpecified); } } - private static ILoaderPal FromBio(string fileName, SafeBioHandle bio, SafePasswordHandle password) + private static ILoaderPal FromBio( + string fileName, + SafeBioHandle bio, + SafePasswordHandle password, + bool ephemeralSpecified) { int bioPosition = Interop.Crypto.BioTell(bio); Debug.Assert(bioPosition >= 0); @@ -104,7 +111,7 @@ private static ILoaderPal FromBio(string fileName, SafeBioHandle bio, SafePasswo // Capture the exception so in case of failure, the call to BioSeek does not override it. Exception? openSslException; byte[] data = File.ReadAllBytes(fileName); - if (PkcsFormatReader.TryReadPkcs12(data, password, out certPals, out openSslException)) + if (PkcsFormatReader.TryReadPkcs12(data, password, ephemeralSpecified, out certPals, out openSslException)) { return ListToLoaderPal(certPals); } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixPkcs12Reader.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixPkcs12Reader.cs index 5d4783dbc29e..bf6cb9ce630d 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixPkcs12Reader.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixPkcs12Reader.cs @@ -26,6 +26,7 @@ internal abstract class UnixPkcs12Reader : IDisposable private CertAndKey[]? _certs; private int _certCount; private PointerMemoryManager? _tmpManager; + private bool _allowDoubleBind; protected abstract ICertificatePalCore ReadX509Der(ReadOnlyMemory data); protected abstract AsymmetricAlgorithm LoadKey(ReadOnlyMemory safeBagBagValue); @@ -180,11 +181,13 @@ private static void ReturnRentedContentInfos(ContentInfoAsn[] rentedContents) ArrayPool.Shared.Return(rentedContents, clearArray: true); } - public void Decrypt(SafePasswordHandle password) + public void Decrypt(SafePasswordHandle password, bool ephemeralSpecified) { ReadOnlyMemory authSafeContents = Helpers.DecodeOctetStringAsMemory(_pfxAsn.AuthSafe.Content); + _allowDoubleBind = !ephemeralSpecified; + bool hasRef = false; password.DangerousAddRef(ref hasRef); @@ -314,6 +317,7 @@ private void Decrypt(ReadOnlySpan password, ReadOnlyMemory authSafeC ExtractPrivateKeys(password, keyBags, keyBagIdx, keys, publicKeyInfos); BuildCertsWithKeys( + password, certBags, certBagAttrs, certs, @@ -497,6 +501,7 @@ private void ExtractPrivateKeys( } private void BuildCertsWithKeys( + ReadOnlySpan password, CertBagAsn[] certBags, AttributeAsn[]?[] certBagAttrs, CertAndKey[] certs, @@ -550,13 +555,26 @@ private void BuildCertsWithKeys( if (matchingKeyIdx != -1) { + // Windows compat: + // If the PFX is loaded with EphemeralKeySet, don't allow double-bind. + // Otherwise, reload the key so a second instance is bound (avoiding one + // cert Dispose removing the key of another). if (keys[matchingKeyIdx] == null) { - throw new CryptographicException(SR.Cryptography_Pfx_BadKeyReference); + if (_allowDoubleBind) + { + certs[certBagIdx].Key = LoadKey(keyBags[matchingKeyIdx], password); + } + else + { + throw new CryptographicException(SR.Cryptography_Pfx_BadKeyReference); + } + } + else + { + certs[certBagIdx].Key = keys[matchingKeyIdx]; + keys[matchingKeyIdx] = null; } - - certs[certBagIdx].Key = keys[matchingKeyIdx]; - keys[matchingKeyIdx] = null; } } } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.ImportExport.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.ImportExport.cs index dfdb5b554f4a..ad4baa38cc3f 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.ImportExport.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.ImportExport.cs @@ -103,6 +103,8 @@ internal static ICertificatePal FromDerBlob( { Debug.Assert(password != null); + bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet); + if (contentType == X509ContentType.Pkcs7) { throw new CryptographicException( @@ -116,7 +118,7 @@ internal static ICertificatePal FromDerBlob( // We ignore keyStorageFlags which is tracked in https://github.com/dotnet/runtime/issues/52434. // The keys are always imported as ephemeral and never persisted. Exportability is ignored for // the moment and it needs to be investigated how to map it to iOS keychain primitives. - return ImportPkcs12(rawData, password); + return ImportPkcs12(rawData, password, ephemeralSpecified); } SafeSecIdentityHandle identityHandle; diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.Pkcs12.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.Pkcs12.cs index 87e45b117e83..8ff5988f557d 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.Pkcs12.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.Pkcs12.cs @@ -22,11 +22,12 @@ internal sealed partial class AppleCertificatePal : ICertificatePal private static AppleCertificatePal ImportPkcs12( ReadOnlySpan rawData, - SafePasswordHandle password) + SafePasswordHandle password, + bool ephemeralSpecified) { using (ApplePkcs12Reader reader = new ApplePkcs12Reader(rawData)) { - reader.Decrypt(password); + reader.Decrypt(password, ephemeralSpecified); return ImportPkcs12(reader.GetSingleCert()); } } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/StorePal.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/StorePal.cs index efb7d68349a0..47d7485c0f98 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/StorePal.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/StorePal.cs @@ -37,6 +37,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle return new CertCollectionLoader(certificateList); } + bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet); X509ContentType contentType = AppleCertificatePal.GetDerCertContentType(rawData); if (contentType == X509ContentType.Pkcs7) @@ -52,7 +53,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle try { - reader.Decrypt(password); + reader.Decrypt(password, ephemeralSpecified); return new ApplePkcs12CertLoader(reader, password); } catch diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxFormatTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxFormatTests.cs index d9a40d068f03..240c0209d21e 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxFormatTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxFormatTests.cs @@ -29,7 +29,11 @@ public abstract partial class PfxFormatTests protected static readonly X509KeyStorageFlags s_exportableImportFlags = s_importFlags | X509KeyStorageFlags.Exportable; + protected static readonly X509KeyStorageFlags s_perphemeralImportFlags = + X509KeyStorageFlags.UserKeySet; + private static readonly Pkcs9LocalKeyId s_keyIdOne = new Pkcs9LocalKeyId(new byte[] { 1 }); + private static readonly Pkcs9LocalKeyId s_keyIdTwo = new Pkcs9LocalKeyId(new byte[] { 2 }); // Windows 7 and 8.1 both fail the PFX load if any key is unloadable (even if not referenced). // Windows 10 1903, 1809, and 1607 all only fail when an unloadable key was actually needed. @@ -41,10 +45,36 @@ public abstract partial class PfxFormatTests OperatingSystem.IsWindows() && !PlatformDetection.IsWindows10Version1607OrGreater; + private void ReadPfx( + byte[] pfxBytes, + string correctPassword, + X509Certificate2 expectedCert, + Action otherWork = null) + { + ReadPfx(pfxBytes, correctPassword, expectedCert, s_importFlags, otherWork); + } + + private void ReadMultiPfx( + byte[] pfxBytes, + string correctPassword, + X509Certificate2 expectedSingleCert, + X509Certificate2[] expectedOrder, + Action perCertOtherWork = null) + { + ReadMultiPfx( + pfxBytes, + correctPassword, + expectedSingleCert, + expectedOrder, + s_importFlags, + perCertOtherWork); + } + protected abstract void ReadPfx( byte[] pfxBytes, string correctPassword, X509Certificate2 expectedCert, + X509KeyStorageFlags nonExportFlags, Action otherWork = null); protected abstract void ReadMultiPfx( @@ -52,14 +82,26 @@ protected abstract void ReadMultiPfx( string correctPassword, X509Certificate2 expectedSingleCert, X509Certificate2[] expectedOrder, + X509KeyStorageFlags nonExportFlags, Action perCertOtherWork = null); + private void ReadUnreadablePfx( + byte[] pfxBytes, + string bestPassword, + // NTE_FAIL + int win32Error = -2146893792, + int altWin32Error = 0) + { + ReadUnreadablePfx(pfxBytes, bestPassword, s_importFlags, win32Error, altWin32Error); + } + protected abstract void ReadEmptyPfx(byte[] pfxBytes, string correctPassword); protected abstract void ReadWrongPassword(byte[] pfxBytes, string wrongPassword); protected abstract void ReadUnreadablePfx( byte[] pfxBytes, string bestPassword, + X509KeyStorageFlags importFlags, // NTE_FAIL int win32Error = -2146893792, int altWin32Error = 0); @@ -657,7 +699,7 @@ public void SameCertTwice_NoKeys(bool addLocalKeyId) [Fact] public void TwoCerts_CrossedKeys() { - string pw = nameof(SameCertTwice_NoKeys); + string pw = nameof(TwoCerts_CrossedKeys); using (var cert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, s_exportableImportFlags)) using (var cert2 = new X509Certificate2(TestData.MsCertificate)) @@ -680,12 +722,48 @@ public void TwoCerts_CrossedKeys() byte[] pfxBytes = builder.Encode(); // Windows seems to be applying both the implicit match and the LocalKeyId match, - // so it detects two hits against the same key and fails. - ReadUnreadablePfx( - pfxBytes, - pw, - // NTE_BAD_DATA - -2146893819); + // so it detects two hits against the same key and fails for ephemeral import. + + if (s_importFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet)) + { + ReadUnreadablePfx( + pfxBytes, + pw, + // NTE_BAD_DATA + -2146893819); + } + else + { + // This is somewhat circular logic, but it's hard to bind cert2 with the wrong + // private key any other way. + + using (ImportedCollection coll = Cert.Import(pfxBytes, pw, s_perphemeralImportFlags)) + { + X509Certificate2 cert2WithKey = coll.Collection[0]; + + ReadMultiPfx( + pfxBytes, + pw, + cert2WithKey, + new[] { cert2WithKey, cert }, + x => + { + if (x.Equals(cert)) + { + CheckMultiBoundKeyConsistency(x); + } + else if (x.HasPrivateKey) + { + // On macOS cert2WithKey.HasPrivateKey is + // false because the SecIdentityRef won't + // bind the mismatched private key. + CheckMultiBoundKeyConsistencyFails(x); + } + }); + + AssertExtensions.SequenceEqual(cert2.RawData, cert2WithKey.RawData); + } + } } } @@ -724,14 +802,17 @@ public void CertAndKeyTwice(bool addLocalKeyId, bool crossIdentifiers) builder.SealWithMac(pw, s_digestAlgorithm, MacCount); byte[] pfxBytes = builder.Encode(); - if (addLocalKeyId) + // If we add the localKeyId values then everything works out, otherwise + // we end up doing SubjectPublicKeyInfo matching logic which says two certs + // mapped to one key. This fails for ephemeral import, and succeeds otherwise. + if (addLocalKeyId || !s_importFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet)) { ReadMultiPfx( pfxBytes, pw, cert, new[] { cert, cert }, - CheckKeyConsistency); + addLocalKeyId ? CheckKeyConsistency : CheckMultiBoundKeyConsistency); } else { @@ -774,11 +855,23 @@ public void CertAndKeyTwice_KeysUntagged() builder.SealWithMac(pw, s_digestAlgorithm, MacCount); byte[] pfxBytes = builder.Encode(); - ReadUnreadablePfx( - pfxBytes, - pw, - // NTE_BAD_DATA - -2146893819); + if (s_importFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet)) + { + ReadUnreadablePfx( + pfxBytes, + pw, + // NTE_BAD_DATA + -2146893819); + } + else + { + ReadMultiPfx( + pfxBytes, + pw, + cert, + new[] { cert, cert }, + CheckMultiBoundKeyConsistency); + } } } @@ -812,11 +905,88 @@ public void CertTwice_KeyOnce(bool addLocalKeyId) builder.SealWithMac(pw, s_digestAlgorithm, MacCount); byte[] pfxBytes = builder.Encode(); - ReadUnreadablePfx( + if (s_importFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet)) + { + ReadUnreadablePfx( + pfxBytes, + pw, + // NTE_BAD_DATA + -2146893819); + } + + // On Windows with perphemeral import the private key gets written then + // erased during import (cleaning resources associated with non-returned certs) + // so on Windows with a single-object load using the key will fail. + // + // The collection import is on a razors edge with the perphemeral import: + // once one of the certs gets disposed the other(s) can no longer open + // their private key. + + ReadMultiPfx( + pfxBytes, + pw, + cert, + new[] { cert, cert }, + s_perphemeralImportFlags, + CheckMultiBoundKeyConsistency); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CertTwice_KeyOnce_OtherCertBetter(bool addLocalKeyId) + { + string pw = nameof(CertTwice_KeyOnce); + + using (var rsaCert = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, s_exportableImportFlags)) + using (var ecCert = new X509Certificate2(TestData.ECDsaP256_DigitalSignature_Pfx_Windows, "Test", s_exportableImportFlags)) + using (RSA rsa = rsaCert.GetRSAPrivateKey()) + using (ECDsa ecdsa = ecCert.GetECDsaPrivateKey()) + { + Pkcs12Builder builder = new Pkcs12Builder(); + Pkcs12SafeContents keyContents = new Pkcs12SafeContents(); + Pkcs12SafeContents certContents = new Pkcs12SafeContents(); + + Pkcs12SafeBag rsaKeyBag = keyContents.AddShroudedKey(rsa, pw, s_windowsPbe); + Pkcs12SafeBag ecKeyBag = keyContents.AddShroudedKey(ecdsa, pw, s_windowsPbe); + Pkcs12SafeBag certBag = certContents.AddCertificate(ecCert); + Pkcs12SafeBag certBag2 = certContents.AddCertificate(ecCert); + Pkcs12SafeBag rsaCertBag = certContents.AddCertificate(rsaCert); + + if (addLocalKeyId) + { + certBag.Attributes.Add(s_keyIdOne); + certBag2.Attributes.Add(s_keyIdOne); + ecKeyBag.Attributes.Add(s_keyIdOne); + rsaCertBag.Attributes.Add(s_keyIdTwo); + rsaKeyBag.Attributes.Add(s_keyIdTwo); + } + + AddContents(keyContents, builder, pw, encrypt: false); + AddContents(certContents, builder, pw, encrypt: true); + builder.SealWithMac(pw, s_digestAlgorithm, MacCount); + byte[] pfxBytes = builder.Encode(); + + if (s_importFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet)) + { + ReadUnreadablePfx( + pfxBytes, + pw, + // NTE_BAD_DATA + -2146893819); + } + + // Because cert2 is what gets returned from the single cert ctor, the + // multiply referenced key doesn't cause a runtime problem on Windows. + + ReadMultiPfx( pfxBytes, pw, - // NTE_BAD_DATA - -2146893819); + rsaCert, + new[] { rsaCert, ecCert, ecCert }, + s_perphemeralImportFlags, + CheckKeyConsistency); } } @@ -928,18 +1098,46 @@ public void TwoCerts_TwoKeys_ManySafeContentsValues(bool invertCertOrder, bool i private static void CheckKeyConsistency(X509Certificate2 cert) { - using (RSA priv = cert.GetRSAPrivateKey()) - using (RSA pub = cert.GetRSAPublicKey()) + byte[] data = { 2, 7, 4 }; + + switch (cert.GetKeyAlgorithm()) { - byte[] data = { 2, 7, 4 }; - byte[] signature = priv.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + case "1.2.840.113549.1.1.1": + { + using (RSA priv = cert.GetRSAPrivateKey()) + using (RSA pub = cert.GetRSAPublicKey()) + { + byte[] signature = priv.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - Assert.True( - pub.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1), - "Cert public key verifies signature from cert private key"); + Assert.True( + pub.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1), + "Cert public key verifies signature from cert private key"); + } + + break; + } + default: + { + using (ECDsa priv = cert.GetECDsaPrivateKey()) + using (ECDsa pub = cert.GetECDsaPublicKey()) + { + byte[] signature = priv.SignData(data, HashAlgorithmName.SHA256); + + Assert.True( + pub.VerifyData(data, signature, HashAlgorithmName.SHA256), + "Cert public key verifies signature from cert private key"); + } + + break; + } } } + protected virtual void CheckMultiBoundKeyConsistency(X509Certificate2 cert) + { + CheckKeyConsistency(cert); + } + private static void CheckKeyConsistencyFails(X509Certificate2 cert) { using (RSA priv = cert.GetRSAPrivateKey()) @@ -954,6 +1152,11 @@ private static void CheckKeyConsistencyFails(X509Certificate2 cert) } } + protected virtual void CheckMultiBoundKeyConsistencyFails(X509Certificate2 cert) + { + CheckKeyConsistencyFails(cert); + } + private static void AddContents( Pkcs12SafeContents contents, Pkcs12Builder builder, diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxFormatTests_Collection.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxFormatTests_Collection.cs index 37ca1cc30232..c25d3391ddb9 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxFormatTests_Collection.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxFormatTests_Collection.cs @@ -12,10 +12,13 @@ protected override void ReadPfx( byte[] pfxBytes, string correctPassword, X509Certificate2 expectedCert, + X509KeyStorageFlags nonExportFlags, Action otherWork) { - ReadPfx(pfxBytes, correctPassword, expectedCert, null, otherWork, s_importFlags); - ReadPfx(pfxBytes, correctPassword, expectedCert, null, otherWork, s_exportableImportFlags); + X509KeyStorageFlags exportFlags = nonExportFlags | X509KeyStorageFlags.Exportable; + + ReadPfx(pfxBytes, correctPassword, expectedCert, null, otherWork, nonExportFlags); + ReadPfx(pfxBytes, correctPassword, expectedCert, null, otherWork, exportFlags); } protected override void ReadMultiPfx( @@ -23,6 +26,7 @@ protected override void ReadMultiPfx( string correctPassword, X509Certificate2 expectedSingleCert, X509Certificate2[] expectedOrder, + X509KeyStorageFlags nonExportFlags, Action perCertOtherWork) { ReadPfx( @@ -31,7 +35,7 @@ protected override void ReadMultiPfx( expectedSingleCert, expectedOrder, perCertOtherWork, - s_importFlags); + nonExportFlags); ReadPfx( pfxBytes, @@ -39,7 +43,7 @@ protected override void ReadMultiPfx( expectedSingleCert, expectedOrder, perCertOtherWork, - s_exportableImportFlags); + nonExportFlags | X509KeyStorageFlags.Exportable); } private void ReadPfx( @@ -89,13 +93,14 @@ protected override void ReadWrongPassword(byte[] pfxBytes, string wrongPassword) protected override void ReadUnreadablePfx( byte[] pfxBytes, string bestPassword, + X509KeyStorageFlags importFlags, int win32Error, int altWin32Error) { X509Certificate2Collection coll = new X509Certificate2Collection(); CryptographicException ex = Assert.ThrowsAny( - () => coll.Import(pfxBytes, bestPassword, s_importFlags)); + () => coll.Import(pfxBytes, bestPassword, importFlags)); if (OperatingSystem.IsWindows()) { diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxFormatTests_SingleCert.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxFormatTests_SingleCert.cs index 39f736d259df..4e219e346a9b 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxFormatTests_SingleCert.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxFormatTests_SingleCert.cs @@ -11,10 +11,13 @@ protected override void ReadPfx( byte[] pfxBytes, string correctPassword, X509Certificate2 expectedCert, + X509KeyStorageFlags nonExportFlags, Action otherWork) { - ReadPfx(pfxBytes, correctPassword, expectedCert, otherWork, s_importFlags); - ReadPfx(pfxBytes, correctPassword, expectedCert, otherWork, s_exportableImportFlags); + X509KeyStorageFlags exportFlags = nonExportFlags | X509KeyStorageFlags.Exportable; + + ReadPfx(pfxBytes, correctPassword, expectedCert, otherWork, nonExportFlags); + ReadPfx(pfxBytes, correctPassword, expectedCert, otherWork, exportFlags); } protected override void ReadMultiPfx( @@ -22,10 +25,13 @@ protected override void ReadMultiPfx( string correctPassword, X509Certificate2 expectedSingleCert, X509Certificate2[] expectedOrder, + X509KeyStorageFlags nonExportFlags, Action perCertOtherWork) { - ReadPfx(pfxBytes, correctPassword, expectedSingleCert, perCertOtherWork, s_importFlags); - ReadPfx(pfxBytes, correctPassword, expectedSingleCert, perCertOtherWork, s_exportableImportFlags); + X509KeyStorageFlags exportFlags = nonExportFlags | X509KeyStorageFlags.Exportable; + + ReadPfx(pfxBytes, correctPassword, expectedSingleCert, perCertOtherWork, nonExportFlags); + ReadPfx(pfxBytes, correctPassword, expectedSingleCert, perCertOtherWork, exportFlags); } private void ReadPfx( @@ -62,11 +68,12 @@ protected override void ReadWrongPassword(byte[] pfxBytes, string wrongPassword) protected override void ReadUnreadablePfx( byte[] pfxBytes, string bestPassword, + X509KeyStorageFlags importFlags, int win32Error, int altWin32Error) { CryptographicException ex = Assert.ThrowsAny( - () => new X509Certificate2(pfxBytes, bestPassword, s_importFlags)); + () => new X509Certificate2(pfxBytes, bestPassword, importFlags)); if (OperatingSystem.IsWindows()) { @@ -80,5 +87,38 @@ protected override void ReadUnreadablePfx( Assert.NotNull(ex.InnerException); } } + + private static void CheckBadKeyset(X509Certificate2 cert) + { + CryptographicException ex = Assert.ThrowsAny( + () => cert.GetRSAPrivateKey()); + + // NTE_BAD_KEYSET + Assert.Equal(-2146893802, ex.HResult); + } + + protected override void CheckMultiBoundKeyConsistency(X509Certificate2 cert) + { + if (PlatformDetection.IsWindows) + { + CheckBadKeyset(cert); + } + else + { + base.CheckMultiBoundKeyConsistency(cert); + } + } + + protected override void CheckMultiBoundKeyConsistencyFails(X509Certificate2 cert) + { + if (PlatformDetection.IsWindows) + { + CheckBadKeyset(cert); + } + else + { + base.CheckMultiBoundKeyConsistencyFails(cert); + } + } } } From b72548e53115d1913f218a1275944fc2d01eaf18 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Tue, 13 Jul 2021 01:38:33 -0700 Subject: [PATCH 052/133] Adjust crypto shim for functions renamed for OSSL3 beta1 --- .../opensslshim.h | 15 +++++++++------ .../osslcompat_30.h | 3 +++ .../System.Security.Cryptography.Native/pal_evp.c | 2 +- .../pal_evp_pkey.c | 2 +- .../pal_evp_pkey_rsa.c | 2 +- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h index 09d8351288c6..050cdc251407 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h @@ -318,7 +318,7 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); REQUIRED_FUNCTION(EVP_MD_CTX_copy_ex) \ RENAMED_FUNCTION(EVP_MD_CTX_free, EVP_MD_CTX_destroy) \ RENAMED_FUNCTION(EVP_MD_CTX_new, EVP_MD_CTX_create) \ - REQUIRED_FUNCTION(EVP_MD_size) \ + RENAMED_FUNCTION(EVP_MD_get_size, EVP_MD_size) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_ctrl) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_free) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_get0_pkey) \ @@ -329,7 +329,6 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); FALLBACK_FUNCTION(EVP_PKEY_CTX_set_rsa_padding) \ FALLBACK_FUNCTION(EVP_PKEY_CTX_set_rsa_pss_saltlen) \ FALLBACK_FUNCTION(EVP_PKEY_CTX_set_signature_md) \ - REQUIRED_FUNCTION(EVP_PKEY_base_id) \ REQUIRED_FUNCTION(EVP_PKEY_decrypt) \ REQUIRED_FUNCTION(EVP_PKEY_decrypt_init) \ REQUIRED_FUNCTION(EVP_PKEY_derive_set_peer) \ @@ -338,6 +337,8 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); REQUIRED_FUNCTION(EVP_PKEY_encrypt) \ REQUIRED_FUNCTION(EVP_PKEY_encrypt_init) \ REQUIRED_FUNCTION(EVP_PKEY_free) \ + RENAMED_FUNCTION(EVP_PKEY_get_base_id, EVP_PKEY_base_id) \ + RENAMED_FUNCTION(EVP_PKEY_get_size, EVP_PKEY_size) \ FALLBACK_FUNCTION(EVP_PKEY_get0_RSA) \ REQUIRED_FUNCTION(EVP_PKEY_get1_DSA) \ REQUIRED_FUNCTION(EVP_PKEY_get1_EC_KEY) \ @@ -350,7 +351,6 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); REQUIRED_FUNCTION(EVP_PKEY_set1_RSA) \ REQUIRED_FUNCTION(EVP_PKEY_sign) \ REQUIRED_FUNCTION(EVP_PKEY_sign_init) \ - REQUIRED_FUNCTION(EVP_PKEY_size) \ FALLBACK_FUNCTION(EVP_PKEY_up_ref) \ REQUIRED_FUNCTION(EVP_PKEY_verify) \ REQUIRED_FUNCTION(EVP_PKEY_verify_init) \ @@ -754,7 +754,7 @@ FOR_ALL_OPENSSL_FUNCTIONS #define EVP_MD_CTX_copy_ex EVP_MD_CTX_copy_ex_ptr #define EVP_MD_CTX_free EVP_MD_CTX_free_ptr #define EVP_MD_CTX_new EVP_MD_CTX_new_ptr -#define EVP_MD_size EVP_MD_size_ptr +#define EVP_MD_get_size EVP_MD_get_size_ptr #define EVP_PKEY_CTX_ctrl EVP_PKEY_CTX_ctrl_ptr #define EVP_PKEY_CTX_free EVP_PKEY_CTX_free_ptr #define EVP_PKEY_CTX_get0_pkey EVP_PKEY_CTX_get0_pkey_ptr @@ -765,7 +765,6 @@ FOR_ALL_OPENSSL_FUNCTIONS #define EVP_PKEY_CTX_set_rsa_padding EVP_PKEY_CTX_set_rsa_padding_ptr #define EVP_PKEY_CTX_set_rsa_pss_saltlen EVP_PKEY_CTX_set_rsa_pss_saltlen_ptr #define EVP_PKEY_CTX_set_signature_md EVP_PKEY_CTX_set_signature_md_ptr -#define EVP_PKEY_base_id EVP_PKEY_base_id_ptr #define EVP_PKEY_decrypt_init EVP_PKEY_decrypt_init_ptr #define EVP_PKEY_decrypt EVP_PKEY_decrypt_ptr #define EVP_PKEY_derive_set_peer EVP_PKEY_derive_set_peer_ptr @@ -774,6 +773,8 @@ FOR_ALL_OPENSSL_FUNCTIONS #define EVP_PKEY_encrypt_init EVP_PKEY_encrypt_init_ptr #define EVP_PKEY_encrypt EVP_PKEY_encrypt_ptr #define EVP_PKEY_free EVP_PKEY_free_ptr +#define EVP_PKEY_get_base_id EVP_PKEY_get_base_id_ptr +#define EVP_PKEY_get_size EVP_PKEY_get_size_ptr #define EVP_PKEY_get0_RSA EVP_PKEY_get0_RSA_ptr #define EVP_PKEY_get1_DSA EVP_PKEY_get1_DSA_ptr #define EVP_PKEY_get1_EC_KEY EVP_PKEY_get1_EC_KEY_ptr @@ -786,7 +787,6 @@ FOR_ALL_OPENSSL_FUNCTIONS #define EVP_PKEY_set1_RSA EVP_PKEY_set1_RSA_ptr #define EVP_PKEY_sign_init EVP_PKEY_sign_init_ptr #define EVP_PKEY_sign EVP_PKEY_sign_ptr -#define EVP_PKEY_size EVP_PKEY_size_ptr #define EVP_PKEY_up_ref EVP_PKEY_up_ref_ptr #define EVP_PKEY_verify_init EVP_PKEY_verify_init_ptr #define EVP_PKEY_verify EVP_PKEY_verify_ptr @@ -1078,6 +1078,9 @@ FOR_ALL_OPENSSL_FUNCTIONS #if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_3_0_RTM // Undo renames for renamed-in-3.0 +#define EVP_MD_get_size EVP_MD_size +#define EVP_PKEY_get_base_id EVP_PKEY_base_id +#define EVP_PKEY_get_size EVP_PKEY_size #define SSL_get1_peer_certificate SSL_get_peer_certificate #endif diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/osslcompat_30.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/osslcompat_30.h index bb529df51ee0..dba69f1382d2 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/osslcompat_30.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/osslcompat_30.h @@ -19,10 +19,13 @@ void ERR_new(void); void ERR_set_debug(const char *file, int line, const char *func); void ERR_set_error(int lib, int reason, const char *fmt, ...); int EVP_CIPHER_CTX_get_original_iv(EVP_CIPHER_CTX *ctx, void *buf, size_t len); +int EVP_MD_get_size(const EVP_MD* md); int EVP_PKEY_CTX_set_rsa_keygen_bits(EVP_PKEY_CTX* ctx, int bits); int EVP_PKEY_CTX_set_rsa_oaep_md(EVP_PKEY_CTX* ctx, const EVP_MD* md); int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX* ctx, int pad_mode); int EVP_PKEY_CTX_set_rsa_pss_saltlen(EVP_PKEY_CTX* ctx, int saltlen); int EVP_PKEY_CTX_set_signature_md(EVP_PKEY_CTX* ctx, const EVP_MD* md); +int EVP_PKEY_get_base_id(const EVP_PKEY* pkey); +int EVP_PKEY_get_size(const EVP_PKEY* pkey); OSSL_PROVIDER* OSSL_PROVIDER_try_load(OSSL_LIB_CTX* , const char* name, int retain_fallbacks); X509* SSL_get1_peer_certificate(const SSL* ssl); diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp.c index a119f8178f00..9cf042a29e9f 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp.c @@ -119,7 +119,7 @@ int32_t CryptoNative_EvpDigestOneShot(const EVP_MD* type, const void* source, in int32_t CryptoNative_EvpMdSize(const EVP_MD* md) { - return EVP_MD_size(md); + return EVP_MD_get_size(md); } const EVP_MD* CryptoNative_EvpMd5() diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.c index 83b18f7621f1..a9fd4b2acd5a 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.c @@ -20,7 +20,7 @@ void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey) int32_t CryptoNative_EvpPKeySize(EVP_PKEY* pkey) { assert(pkey != NULL); - return EVP_PKEY_size(pkey); + return EVP_PKEY_get_size(pkey); } int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey) diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c index fa88420cf967..0efe651cfe80 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c @@ -277,7 +277,7 @@ int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, // EVP_PKEY_verify is not consistent on whether a mis-sized hash is an error or just a mismatch. // Normalize to mismatch. - if (hashLen != EVP_MD_size(digest)) + if (hashLen != EVP_MD_get_size(digest)) { ret = 0; goto done; From 455c0d846e56a40d0806a5551e0d88a01b5e4407 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 9 Jul 2021 15:25:19 -0400 Subject: [PATCH 053/133] Update pinned C# compiler version --- eng/Versions.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 24788db6f924..4f3f2a4c21aa 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -17,8 +17,8 @@ release true - - 4.0.0-2.21323.11 + + 4.0.0-3.21362.7 true false false From 85ff03d4a86d58059ab9ee1bb998aafac91ab067 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sat, 10 Jul 2021 09:56:43 -0400 Subject: [PATCH 054/133] Fix async method builder override tests for compiler constraint --- .../tests/PoolingAsyncValueTaskMethodBuilderTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Threading.Tasks.Extensions/tests/PoolingAsyncValueTaskMethodBuilderTests.cs b/src/libraries/System.Threading.Tasks.Extensions/tests/PoolingAsyncValueTaskMethodBuilderTests.cs index c2e83881ce5b..62a6406c309b 100644 --- a/src/libraries/System.Threading.Tasks.Extensions/tests/PoolingAsyncValueTaskMethodBuilderTests.cs +++ b/src/libraries/System.Threading.Tasks.Extensions/tests/PoolingAsyncValueTaskMethodBuilderTests.cs @@ -390,7 +390,7 @@ public static async Task Generic_UsedWithAsyncMethod_CompletesSuccessfully(int y Assert.Equal(42 + yields, await ValueTaskReturningAsyncMethod(42)); Assert.Equal(84 + yields, await ValueTaskReturningAsyncMethod(84)); - [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))] + [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] async ValueTask ValueTaskReturningAsyncMethod(int result) { for (int i = 0; i < yields; i++) @@ -440,7 +440,7 @@ async ValueTask ValueTaskReturningMethod() } } - [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))] + [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] async ValueTask ValueTaskInt32ReturningMethod() { for (int i = 0; i < 3; i++) @@ -499,7 +499,7 @@ await Task.WhenAll(Enumerable.Range(0, Environment.ProcessorCount).Select(async { Assert.Equal(42 + i, await ValueTaskAsync(i)); - [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))] + [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] static async ValueTask ValueTaskAsync(int i) { await Task.Delay(1); @@ -549,7 +549,7 @@ public void PoolingAsyncValueTasksBuilder_ObjectsPooled(string limitEnvVar) Assert.InRange(boxes.Distinct().Count(), 1, boxes.Count - 1); }, new RemoteInvokeOptions() { StartInfo = psi }).Dispose(); - [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))] + [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] static async ValueTask ComputeAsync(int input, ConcurrentQueue boxes) { await RecursiveValueTaskAsync(3, boxes); From 6e6c9c3aa12721108163e01cf86bce7c7ae1823b Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 12 Jul 2021 12:54:46 -0400 Subject: [PATCH 055/133] Temporarily remove string interpolation from NetCoreServer test helper --- .../NetCoreServer/Handlers/EchoWebSocketHandler.cs | 2 +- .../NetCoreServer/Handlers/RedirectHandler.cs | 8 ++++---- .../NetCoreServer/Handlers/StatusCodeHandler.cs | 2 +- .../NetCoreServer/Handlers/VersionHandler.cs | 14 +++++++------- .../NetCoreServer/Helpers/AuthenticationHelper.cs | 9 ++++----- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs index 21ee6346eff8..8e8951179e86 100644 --- a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs @@ -80,7 +80,7 @@ private static async Task ProcessWebSocketRequest( await socket.CloseAsync( closeStatus, replyWithEnhancedCloseMessage ? - $"Server received: {(int)closeStatus} {receiveResult.CloseStatusDescription}" : + ("Server received: " + (int)closeStatus + " " + receiveResult.CloseStatusDescription) : receiveResult.CloseStatusDescription, CancellationToken.None); } diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/RedirectHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/RedirectHandler.cs index cb7ce1629077..596b8be559e4 100644 --- a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/RedirectHandler.cs +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/RedirectHandler.cs @@ -20,14 +20,14 @@ public static void Invoke(HttpContext context) if (statusCode < 300 || statusCode > 308) { context.Response.StatusCode = 400; - context.Response.SetStatusDescription($"Invalid redirect statuscode: {statusCodeString}"); + context.Response.SetStatusDescription("Invalid redirect statuscode: " + statusCodeString); return; } } catch (Exception) { context.Response.StatusCode = 400; - context.Response.SetStatusDescription($"Error parsing statuscode: {statusCodeString}"); + context.Response.SetStatusDescription("Error parsing statuscode: " + statusCodeString); return; } } @@ -36,7 +36,7 @@ public static void Invoke(HttpContext context) if (string.IsNullOrEmpty(redirectUri)) { context.Response.StatusCode = 400; - context.Response.SetStatusDescription($"Missing redirection uri"); + context.Response.SetStatusDescription("Missing redirection uri"); return; } @@ -51,7 +51,7 @@ public static void Invoke(HttpContext context) catch (Exception) { context.Response.StatusCode = 400; - context.Response.SetStatusDescription($"Error parsing hops: {hopsString}"); + context.Response.SetStatusDescription("Error parsing hops: " + hopsString); return; } } diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/StatusCodeHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/StatusCodeHandler.cs index ae039a3c38f7..73cb4bba880f 100644 --- a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/StatusCodeHandler.cs +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/StatusCodeHandler.cs @@ -22,7 +22,7 @@ public static void Invoke(HttpContext context) catch (Exception) { context.Response.StatusCode = 400; - context.Response.SetStatusDescription($"Error parsing statuscode: {statusCodeString}"); + context.Response.SetStatusDescription("Error parsing statuscode: " + statusCodeString); } } } diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/VersionHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/VersionHandler.cs index 2cffa413a759..cba65e49d520 100644 --- a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/VersionHandler.cs +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/VersionHandler.cs @@ -30,13 +30,13 @@ private static string GetVersionInfo() FileVersionInfo fi = FileVersionInfo.GetVersionInfo(path); var buffer = new StringBuilder(); - buffer.AppendLine($"Information for: {Path.GetFileName(path)}"); - buffer.AppendLine($"Location: {Path.GetDirectoryName(path)}"); - buffer.AppendLine($"Framework: {RuntimeInformation.FrameworkDescription}"); - buffer.AppendLine($"File Version: {fi.FileVersion}"); - buffer.AppendLine($"Product Version: {fi.ProductVersion}"); - buffer.AppendLine($"Creation Date: {File.GetCreationTime(path)}"); - buffer.AppendLine($"Last Modified: {File.GetLastWriteTime(path)}"); + buffer.AppendLine("Information for: " + Path.GetFileName(path)); + buffer.AppendLine("Location: " + Path.GetDirectoryName(path)); + buffer.AppendLine("Framework: " + RuntimeInformation.FrameworkDescription); + buffer.AppendLine("File Version: " + fi.FileVersion); + buffer.AppendLine("Product Version: " + fi.ProductVersion); + buffer.AppendLine("Creation Date: " + File.GetCreationTime(path)); + buffer.AppendLine("Last Modified: " + File.GetLastWriteTime(path)); return buffer.ToString(); } diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/AuthenticationHelper.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/AuthenticationHelper.cs index c0d0d2e1dee6..e7a89febbc25 100644 --- a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/AuthenticationHelper.cs +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/AuthenticationHelper.cs @@ -34,7 +34,7 @@ public static bool HandleAuthentication(HttpContext context) else if (authType != null) { context.Response.StatusCode = 501; - context.Response.SetStatusDescription($"Unsupported auth type: {authType}"); + context.Response.SetStatusDescription("Unsupported auth type: " + authType); return false; } @@ -57,14 +57,14 @@ private static bool HandleBasicAuthentication(HttpContext context, string user, if (split.Length < 2) { context.Response.StatusCode = 500; - context.Response.SetStatusDescription($"Invalid Authorization header: {authHeader}"); + context.Response.SetStatusDescription("Invalid Authorization header: " + authHeader); ; return false; } if (!string.Equals("basic", split[0], StringComparison.OrdinalIgnoreCase)) { context.Response.StatusCode = 500; - context.Response.SetStatusDescription($"Unsupported auth type: {split[0]}"); + context.Response.SetStatusDescription("Unsupported auth type: " + split[0]); return false; } @@ -106,8 +106,7 @@ private static bool HandleChallengeResponseAuthentication( // We don't fully support this authentication method. context.Response.StatusCode = 501; - context.Response.SetStatusDescription( - $"Attempt to use unsupported challenge/response auth type. {authType}: {authHeader}"); + context.Response.SetStatusDescription("Attempt to use unsupported challenge/response auth type. " + authType + ": " + authHeader); return false; } From 7eba2f3989eaafe9fb7676dd63a9f5a285f65fa5 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 12 Jul 2021 14:32:36 -0400 Subject: [PATCH 056/133] Workaround eventpipe deadlock due to ArrayPool usage --- src/tests/profiler/eventpipe/eventpipe.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tests/profiler/eventpipe/eventpipe.cs b/src/tests/profiler/eventpipe/eventpipe.cs index 17f6d3f6ed99..5b58b6d62fe6 100644 --- a/src/tests/profiler/eventpipe/eventpipe.cs +++ b/src/tests/profiler/eventpipe/eventpipe.cs @@ -3,6 +3,7 @@ using Profiler.Tests; using System; +using System.Buffers; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.Tracing; @@ -35,6 +36,8 @@ public static int Main(string[] args) public static int RunTest() { + ArrayPool.Shared.Rent(1); // workaround for https://github.com/dotnet/runtime/issues/1892 + bool success = true; int allTypesEventCount = 0; int arrayTypeEventCount = 0; From d7d32f835bacdebfae6e9b819761c9b227d025dd Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 12 Jul 2021 15:18:29 -0400 Subject: [PATCH 057/133] Avoid obscuring failure by ODE thrown from Dispose --- src/libraries/System.Console/tests/Helpers.cs | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/src/libraries/System.Console/tests/Helpers.cs b/src/libraries/System.Console/tests/Helpers.cs index f1d6205dc9b1..8c057a1c32df 100644 --- a/src/libraries/System.Console/tests/Helpers.cs +++ b/src/libraries/System.Console/tests/Helpers.cs @@ -17,29 +17,17 @@ public static void SetAndReadHelper(Action setHelper, Func Date: Mon, 12 Jul 2021 21:37:58 -0400 Subject: [PATCH 058/133] Fix nullable warning in AssemblyLoadContext30Extensions.cs --- .../AssemblyLoadContext30Extensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tests/Loader/AssemblyLoadContext30Extensions/AssemblyLoadContext30Extensions.cs b/src/tests/Loader/AssemblyLoadContext30Extensions/AssemblyLoadContext30Extensions.cs index bb05cb2ec724..cd730ea27bad 100644 --- a/src/tests/Loader/AssemblyLoadContext30Extensions/AssemblyLoadContext30Extensions.cs +++ b/src/tests/Loader/AssemblyLoadContext30Extensions/AssemblyLoadContext30Extensions.cs @@ -1,5 +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.Reflection; using System.Reflection.Emit; @@ -218,7 +219,7 @@ public static void GetLoadContextForDynamicAssembly(bool isCollectible) assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect); } - AssemblyLoadContext? context = AssemblyLoadContext.GetLoadContext(assemblyBuilder); + AssemblyLoadContext context = AssemblyLoadContext.GetLoadContext(assemblyBuilder); Assert(context != null); Assert(alc == context); From 50f86dde07a4fb9ece65133ba53b923b5d8725e7 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Tue, 13 Jul 2021 15:04:15 +0200 Subject: [PATCH 059/133] Fix UMThunkMarshInfo delete (#55559) There was a forgotten call to c++ delete that should have been converted to LoaderHeap::BackoutMem in a code path invoked only if two threads raced for allocating the UMThunkMarshInfo and the one that lost the race needed to delete it. --- src/coreclr/vm/comdelegate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index 1b61e16dec5d..03cecd838023 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -1263,7 +1263,7 @@ LPVOID COMDelegate::ConvertToCallback(OBJECTREF pDelegateObj) pUMThunkMarshInfo, NULL ) != NULL) { - delete pUMThunkMarshInfo; + pMT->GetLoaderAllocator()->GetStubHeap()->BackoutMem(pUMThunkMarshInfo, sizeof(UMThunkMarshInfo)); pUMThunkMarshInfo = pClass->m_pUMThunkMarshInfo; } } From 38c187aa5de1719cdf7167fb5880a3a7dd01ec15 Mon Sep 17 00:00:00 2001 From: Maxim Lipnin Date: Tue, 13 Jul 2021 16:16:17 +0300 Subject: [PATCH 060/133] Use ReflectionOnly as data contract serializer option for iOS/tvOS/MacCatalyst (#55503) An attempt to address #47114 by making the System.Runtime.Serialization.DataContractSerializer.Option property return ReflectionOnly value when RuntimeFeature.IsDynamicCodeSupported is true. --- .../Serialization/DataContractSerializer.cs | 2 +- .../XmlFormatWriterGeneratorAOT/Program.cs | 30 +++++++++++++++++++ ...or.XmlFormatWriterGeneratorAot.Test.csproj | 19 ++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/tests/FunctionalTests/iOS/Simulator/XmlFormatWriterGeneratorAOT/Program.cs create mode 100644 src/tests/FunctionalTests/iOS/Simulator/XmlFormatWriterGeneratorAOT/iOS.Simulator.XmlFormatWriterGeneratorAot.Test.csproj diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContractSerializer.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContractSerializer.cs index 71f4530f4f3d..72229faaf8d3 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContractSerializer.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContractSerializer.cs @@ -38,7 +38,7 @@ public sealed class DataContractSerializer : XmlObjectSerializer private static bool _optionAlreadySet; internal static SerializationOption Option { - get { return _option; } + get { return RuntimeFeature.IsDynamicCodeSupported ? _option : SerializationOption.ReflectionOnly; } set { if (_optionAlreadySet) diff --git a/src/tests/FunctionalTests/iOS/Simulator/XmlFormatWriterGeneratorAOT/Program.cs b/src/tests/FunctionalTests/iOS/Simulator/XmlFormatWriterGeneratorAOT/Program.cs new file mode 100644 index 000000000000..884dfa7ae893 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/XmlFormatWriterGeneratorAOT/Program.cs @@ -0,0 +1,30 @@ +// 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.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; + +public static class Program +{ + [DllImport("__Internal")] + public static extern void mono_ios_set_summary (string value); + + public static async Task Main(string[] args) + { + mono_ios_set_summary($"Starting functional test"); + + var ds = new DataContractSerializer (typeof (IEnumerable)); + using (var xw = XmlWriter.Create (System.IO.Stream.Null)) + ds.WriteObject (xw, new int [] { 1, 2, 3 }); + + Console.WriteLine("Done!"); + await Task.Delay(5000); + + return 42; + } +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/XmlFormatWriterGeneratorAOT/iOS.Simulator.XmlFormatWriterGeneratorAot.Test.csproj b/src/tests/FunctionalTests/iOS/Simulator/XmlFormatWriterGeneratorAOT/iOS.Simulator.XmlFormatWriterGeneratorAot.Test.csproj new file mode 100644 index 000000000000..f0a56aa7fdf0 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/XmlFormatWriterGeneratorAOT/iOS.Simulator.XmlFormatWriterGeneratorAot.Test.csproj @@ -0,0 +1,19 @@ + + + + Exe + false + true + true + $(NetCoreAppCurrent) + iOSSimulator + iOS.Simulator.XmlFormatWriterGeneratorAot.Test.dll + false + 42 + true + + + + + + From 0aa781c265e941efd73b1e6f35d6e182255bb94d Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 13 Jul 2021 15:27:54 +0200 Subject: [PATCH 061/133] Fix exception (#55324) --- src/libraries/System.Text.Json/src/Resources/Strings.resx | 2 +- .../src/System/Text/Json/ThrowHelper.Serialization.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index e289dfbb1ea6..e654d368051d 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -352,7 +352,7 @@ The data extension property '{0}.{1}' does not match the required signature of 'IDictionary<string, JsonElement>', 'IDictionary<string, object>' or 'JsonObject'. - The type '{0}' cannot have more than one property that has the attribute '{1}'. + The type '{0}' cannot have more than one member that has the attribute '{1}'. The type '{0}' is not supported. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index 42af83d147a5..61d953ac94ce 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -421,7 +421,7 @@ public static void ThrowInvalidOperationException_SerializationDuplicateTypeAttr [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type classType) { - throw new InvalidOperationException(SR.Format(SR.SerializationDuplicateTypeAttribute, classType, typeof(Attribute))); + throw new InvalidOperationException(SR.Format(SR.SerializationDuplicateTypeAttribute, classType, typeof(TAttribute))); } [DoesNotReturn] From f91513241907441106934c9398799f63d3f8bc85 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 13 Jul 2021 15:43:49 +0200 Subject: [PATCH 062/133] Process.Unix: consider executable permission while searching PATH. (#55504) --- .../Unix/System.Native/Interop.GetGroups.cs | 50 +++++++++++++++++ .../Unix/System.Native/Interop.Permissions.cs | 2 + .../Native/Unix/System.Native/entrypoints.c | 1 + .../Native/Unix/System.Native/pal_uid.c | 8 +++ .../Native/Unix/System.Native/pal_uid.h | 9 ++++ .../src/System.Diagnostics.Process.csproj | 12 ++++- .../src/System/Diagnostics/Process.Unix.cs | 53 ++++++++++++++++++- .../tests/ProcessTests.Unix.cs | 34 ++++++++++++ .../System.Private.CoreLib.Shared.projitems | 2 +- 9 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetGroups.cs diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetGroups.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetGroups.cs new file mode 100644 index 000000000000..2fd31563f174 --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetGroups.cs @@ -0,0 +1,50 @@ +// 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.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + internal static unsafe uint[]? GetGroups() + { + const int InitialGroupsLength = +#if DEBUG + 1; +#else + 64; +#endif + Span groups = stackalloc uint[InitialGroupsLength]; + do + { + int rv; + fixed (uint* pGroups = groups) + { + rv = Interop.Sys.GetGroups(groups.Length, pGroups); + } + + if (rv >= 0) + { + // success + return groups.Slice(0, rv).ToArray(); + } + else if (rv == -1 && Interop.Sys.GetLastError() == Interop.Error.EINVAL) + { + // increase buffer size + groups = new uint[groups.Length * 2]; + } + else + { + // failure + return null; + } + } + while (true); + } + + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetGroups", SetLastError = true)] + private static extern unsafe int GetGroups(int ngroups, uint* groups); + } +} diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Permissions.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Permissions.cs index cbc54d4072c4..8248077deaf5 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Permissions.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Permissions.cs @@ -26,6 +26,8 @@ internal enum Permissions S_IROTH = 0x4, S_IWOTH = 0x2, S_IXOTH = 0x1, + + S_IXUGO = S_IXUSR | S_IXGRP | S_IXOTH, } } } diff --git a/src/libraries/Native/Unix/System.Native/entrypoints.c b/src/libraries/Native/Unix/System.Native/entrypoints.c index bf6f98953f9a..c8b678ec0834 100644 --- a/src/libraries/Native/Unix/System.Native/entrypoints.c +++ b/src/libraries/Native/Unix/System.Native/entrypoints.c @@ -256,6 +256,7 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_HandleNonCanceledPosixSignal) DllImportEntry(SystemNative_SetPosixSignalHandler) DllImportEntry(SystemNative_GetPlatformSignalNumber) + DllImportEntry(SystemNative_GetGroups) }; EXTERN_C const void* SystemResolveDllImport(const char* name); diff --git a/src/libraries/Native/Unix/System.Native/pal_uid.c b/src/libraries/Native/Unix/System.Native/pal_uid.c index 2807d06b77d6..6571fb1e600c 100644 --- a/src/libraries/Native/Unix/System.Native/pal_uid.c +++ b/src/libraries/Native/Unix/System.Native/pal_uid.c @@ -234,3 +234,11 @@ int32_t SystemNative_GetGroupList(const char* name, uint32_t group, uint32_t* gr return rv; } + +int32_t SystemNative_GetGroups(int32_t ngroups, uint32_t* groups) +{ + assert(ngroups >= 0); + assert(groups != NULL); + + return getgroups(ngroups, groups); +} diff --git a/src/libraries/Native/Unix/System.Native/pal_uid.h b/src/libraries/Native/Unix/System.Native/pal_uid.h index 03d347a61e32..d0b16eef97a0 100644 --- a/src/libraries/Native/Unix/System.Native/pal_uid.h +++ b/src/libraries/Native/Unix/System.Native/pal_uid.h @@ -81,3 +81,12 @@ PALEXPORT int32_t SystemNative_GetGroupList(const char* name, uint32_t group, ui * Always succeeds. */ PALEXPORT uint32_t SystemNative_GetUid(void); + +/** +* Gets groups associated with current process. +* +* Returns number of groups for success. +* On error, -1 is returned and errno is set. +* If the buffer is too small, errno is EINVAL. +*/ +PALEXPORT int32_t SystemNative_GetGroups(int32_t ngroups, uint32_t* groups); diff --git a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index 882b1beb85ff..e4f10fa90630 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -273,6 +273,16 @@ Link="Common\Interop\Unix\Interop.WaitPid.cs" /> + + + + + - diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs index cce32e376010..6c1268ebf506 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs @@ -17,6 +17,9 @@ namespace System.Diagnostics public partial class Process : IDisposable { private static volatile bool s_initialized; + private static uint s_euid; + private static uint s_egid; + private static uint[]? s_groups; private static readonly object s_initializedGate = new object(); private static readonly ReaderWriterLockSlim s_processStartLock = new ReaderWriterLockSlim(); @@ -743,7 +746,7 @@ private static string[] CreateEnvp(ProcessStartInfo psi) { string subPath = pathParser.ExtractCurrent(); path = Path.Combine(subPath, program); - if (File.Exists(path)) + if (IsExecutable(path)) { return path; } @@ -752,6 +755,46 @@ private static string[] CreateEnvp(ProcessStartInfo psi) return null; } + private static bool IsExecutable(string fullPath) + { + Interop.Sys.FileStatus fileinfo; + + if (Interop.Sys.Stat(fullPath, out fileinfo) < 0) + { + return false; + } + + // Check if the path is a directory. + if ((fileinfo.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR) + { + return false; + } + + Interop.Sys.Permissions permissions = (Interop.Sys.Permissions)fileinfo.Mode; + + if (s_euid == 0) + { + // We're root. + return (permissions & Interop.Sys.Permissions.S_IXUGO) != 0; + } + + if (s_euid == fileinfo.Uid) + { + // We own the file. + return (permissions & Interop.Sys.Permissions.S_IXUSR) != 0; + } + + if (s_egid == fileinfo.Gid || + (s_groups != null && Array.BinarySearch(s_groups, fileinfo.Gid) >= 0)) + { + // A group we're a member of owns the file. + return (permissions & Interop.Sys.Permissions.S_IXGRP) != 0; + } + + // Other. + return (permissions & Interop.Sys.Permissions.S_IXOTH) != 0; + } + private static long s_ticksPerSecond; /// Convert a number of "jiffies", or ticks, to a TimeSpan. @@ -1021,6 +1064,14 @@ private static unsafe void EnsureInitialized() throw new Win32Exception(); } + s_euid = Interop.Sys.GetEUid(); + s_egid = Interop.Sys.GetEGid(); + s_groups = Interop.Sys.GetGroups(); + if (s_groups != null) + { + Array.Sort(s_groups); + } + // Register our callback. Interop.Sys.RegisterForSigChld(&OnSigChild); SetDelayedSigChildConsoleConfigurationHandler(); diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs index a4ff703332af..63d7f3d6d02b 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs @@ -185,6 +185,40 @@ public void ProcessNameMatchesScriptName() } } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void ProcessStart_SkipsNonExecutableFilesOnPATH() + { + const string ScriptName = "script"; + + // Create a directory named ScriptName. + string path1 = Path.Combine(TestDirectory, "Path1"); + Directory.CreateDirectory(Path.Combine(path1, ScriptName)); + + // Create a non-executable file named ScriptName + string path2 = Path.Combine(TestDirectory, "Path2"); + Directory.CreateDirectory(path2); + File.WriteAllText(Path.Combine(path2, ScriptName), "Not executable"); + + // Create an executable script named ScriptName + string path3 = Path.Combine(TestDirectory, "Path3"); + Directory.CreateDirectory(path3); + string filename = WriteScriptFile(path3, ScriptName, returnValue: 42); + + // Process.Start ScriptName with the above on PATH. + RemoteInvokeOptions options = new RemoteInvokeOptions(); + options.StartInfo.EnvironmentVariables["PATH"] = $"{path1}:{path2}:{path3}"; + RemoteExecutor.Invoke(() => + { + using (var px = Process.Start(new ProcessStartInfo { FileName = ScriptName })) + { + Assert.NotNull(px); + px.WaitForExit(); + Assert.True(px.HasExited); + Assert.Equal(42, px.ExitCode); + } + }, options).Dispose(); + } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] [PlatformSpecific(TestPlatforms.Linux)] // s_allowedProgramsToRun is Linux specific public void ProcessStart_UseShellExecute_OnUnix_FallsBackWhenNotRealExecutable() diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 3dcfc2c7313a..23ffc05ed32a 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1954,7 +1954,7 @@ Common\Interop\Unix\System.Native\Interop.GetCwd.cs - Common\Interop\Unix\System.Native\Interop.GetHostName.cs + Common\Interop\Unix\System.Native\Interop.GetEGid.cs Common\Interop\Unix\System.Native\Interop.GetHostName.cs From aaade776c48b6faa212a30a64586eb907824235f Mon Sep 17 00:00:00 2001 From: Hong Li Date: Tue, 13 Jul 2021 06:44:22 -0700 Subject: [PATCH 063/133] Fix for a couple of Microsoft.XmlSerializer.Generator issues (#55427) * Fix for a couple of Microsoft.XmlSerializer.Generator issues * fix the name of the AssemblyInfo.cs file --- .../Microsoft.XmlSerializer.Generator.targets | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/libraries/Microsoft.XmlSerializer.Generator/pkg/build/Microsoft.XmlSerializer.Generator.targets b/src/libraries/Microsoft.XmlSerializer.Generator/pkg/build/Microsoft.XmlSerializer.Generator.targets index a66dd7eb7a24..6d9059207409 100644 --- a/src/libraries/Microsoft.XmlSerializer.Generator/pkg/build/Microsoft.XmlSerializer.Generator.targets +++ b/src/libraries/Microsoft.XmlSerializer.Generator/pkg/build/Microsoft.XmlSerializer.Generator.targets @@ -4,6 +4,7 @@ <_SerializerDllIntermediateFolder>$(IntermediateOutputPath)$(_SerializationAssemblyName).dll <_SerializerPdbIntermediateFolder>$(IntermediateOutputPath)$(_SerializationAssemblyName).pdb <_SerializerCsIntermediateFolder>$(IntermediateOutputPath)$(_SerializationAssemblyName).cs + <_SerializerCsAssemblyInfoIntermediateFolder>$(IntermediateOutputPath)SgenAssemblyInfo.cs <_SGenWarningText>SGEN: Failed to generate the serializer for $(AssemblyName)$(TargetExt). Please follow the instructions at https://go.microsoft.com/fwlink/?linkid=858594 and try again. <_SGenRspWarningText>Failed to generate response file for Microsoft.XmlSerializer.Generator, serializer is generating with default settings. <_SerializationAssemblyDisabledWarnings>$(NoWarn);219;162;$(SerializationAssemblyDisabledWarnings) @@ -29,6 +30,19 @@ + + + + + + + @@ -36,8 +50,8 @@ - - + + @@ -70,7 +84,7 @@ - + From adad29284f8db4ca1dc852e11cddf797eb309919 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 25 May 2021 20:41:54 -0400 Subject: [PATCH 064/133] Add Span.TryWrite, StringBuilder.Append, and String.Create interpolated strings support --- .../System.Memory/ref/System.Memory.cs | 21 + .../System.Memory/tests/Span/TryWrite.cs | 728 ++++++++++++++++++ .../tests/System.Memory.Tests.csproj | 1 + .../src/System/MemoryExtensions.cs | 436 +++++++++++ .../src/System/String.cs | 15 + .../src/System/Text/StringBuilder.cs | 343 +++++++++ .../System.Runtime/ref/System.Runtime.cs | 25 + .../tests/System.Runtime.Tests.csproj | 1 + .../tests/System/StringTests.cs | 16 + .../Text/StringBuilderInterpolationTests.cs | 653 ++++++++++++++++ 10 files changed, 2239 insertions(+) create mode 100644 src/libraries/System.Memory/tests/Span/TryWrite.cs create mode 100644 src/libraries/System.Runtime/tests/System/Text/StringBuilderInterpolationTests.cs diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index 7318b4a0d3c5..1466b1fa1b7b 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -143,6 +143,27 @@ public static void Sort(this System.Span keys, Sy public static System.ReadOnlySpan Trim(this System.ReadOnlySpan span, T trimElement) where T : System.IEquatable { throw null; } public static System.Span Trim(this System.Span span, System.ReadOnlySpan trimElements) where T : System.IEquatable { throw null; } public static System.Span Trim(this System.Span span, T trimElement) where T : System.IEquatable { throw null; } + public static bool TryWrite(this System.Span destination, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("destination")] ref System.MemoryExtensions.TryWriteInterpolatedStringHandler handler, out int charsWritten) { throw null; } + public static bool TryWrite(this System.Span destination, IFormatProvider? provider, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("destination", "provider")] ref System.MemoryExtensions.TryWriteInterpolatedStringHandler handler, out int charsWritten) { throw null; } + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute] + public ref struct TryWriteInterpolatedStringHandler + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, System.Span destination, out bool success) { throw null; } + public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, System.Span destination, IFormatProvider? provider, out bool success) { throw null; } + public bool AppendLiteral(string value) { throw null; } + public bool AppendFormatted(System.ReadOnlySpan value) { throw null; } + public bool AppendFormatted(System.ReadOnlySpan value, int alignment = 0, string? format = null) { throw null; } + public bool AppendFormatted(T value) { throw null; } + public bool AppendFormatted(T value, string? format) { throw null; } + public bool AppendFormatted(T value, int alignment) { throw null; } + public bool AppendFormatted(T value, int alignment, string? format) { throw null; } + public bool AppendFormatted(object? value, int alignment = 0, string? format = null) { throw null; } + public bool AppendFormatted(string? value) { throw null; } + public bool AppendFormatted(string? value, int alignment = 0, string? format = null) { throw null; } + } } public readonly partial struct SequencePosition : System.IEquatable { diff --git a/src/libraries/System.Memory/tests/Span/TryWrite.cs b/src/libraries/System.Memory/tests/Span/TryWrite.cs new file mode 100644 index 000000000000..993f68fc36d7 --- /dev/null +++ b/src/libraries/System.Memory/tests/Span/TryWrite.cs @@ -0,0 +1,728 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Globalization; +using System.Tests; +using System.Text; +using Xunit; + +// TODO: Once compiler support is available, augment tests to exercise interpolated strings. + +namespace System.SpanTests +{ + public class TryWriteTests + { + private char[] _largeBuffer = new char[4096]; + + [Theory] + [InlineData(0, 0)] + [InlineData(1, 1)] + [InlineData(42, 84)] + [InlineData(-1, 0)] + [InlineData(-1, -1)] + [InlineData(-16, 1)] + public void LengthAndHoleArguments_Valid(int literalLength, int formattedCount) + { + bool success; + + new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[Math.Max(0, literalLength)], out success); + Assert.True(success); + + new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[1 + Math.Max(0, literalLength)], out success); + Assert.True(success); + + if (literalLength > 0) + { + new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[literalLength - 1], out success); + Assert.False(success); + } + + foreach (IFormatProvider provider in new IFormatProvider[] { null, new ConcatFormatter(), CultureInfo.InvariantCulture, CultureInfo.CurrentCulture, new CultureInfo("en-US"), new CultureInfo("fr-FR") }) + { + new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[Math.Max(0, literalLength)], out success); + Assert.True(success); + + new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[1 + Math.Max(0, literalLength)], out success); + Assert.True(success); + + if (literalLength > 0) + { + new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[literalLength - 1], out success); + Assert.False(success); + } + } + } + + [Fact] + public void AppendLiteral() + { + var expected = new StringBuilder(); + MemoryExtensions.TryWriteInterpolatedStringHandler actual = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, _largeBuffer, out _); + + foreach (string s in new[] { "", "a", "bc", "def", "this is a long string", "!" }) + { + expected.Append(s); + actual.AppendLiteral(s); + } + + Assert.True(MemoryExtensions.TryWrite(_largeBuffer, ref actual, out int charsWritten)); + Assert.Equal(expected.ToString(), _largeBuffer.AsSpan(0, charsWritten).ToString()); + } + + [Fact] + public void AppendFormatted_ReadOnlySpanChar() + { + var expected = new StringBuilder(); + MemoryExtensions.TryWriteInterpolatedStringHandler actual = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, _largeBuffer, out _); + + foreach (string s in new[] { "", "a", "bc", "def", "this is a longer string", "!" }) + { + // span + expected.Append(s); + actual.AppendFormatted((ReadOnlySpan)s); + + // span, format + expected.AppendFormat("{0:X2}", s); + actual.AppendFormatted((ReadOnlySpan)s, format: "X2"); + + foreach (int alignment in new[] { 0, 3, -3 }) + { + // span, alignment + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + "}", s); + actual.AppendFormatted((ReadOnlySpan)s, alignment); + + // span, alignment, format + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + ":X2}", s); + actual.AppendFormatted((ReadOnlySpan)s, alignment, "X2"); + } + } + + Assert.True(MemoryExtensions.TryWrite(_largeBuffer, ref actual, out int charsWritten)); + Assert.Equal(expected.ToString(), _largeBuffer.AsSpan(0, charsWritten).ToString()); + } + + [Fact] + public void AppendFormatted_String() + { + var expected = new StringBuilder(); + MemoryExtensions.TryWriteInterpolatedStringHandler actual = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, _largeBuffer, out _); + + foreach (string s in new[] { null, "", "a", "bc", "def", "this is a longer string", "!" }) + { + // string + expected.AppendFormat("{0}", s); + actual.AppendFormatted(s); + + // string, format + expected.AppendFormat("{0:X2}", s); + actual.AppendFormatted(s, "X2"); + + foreach (int alignment in new[] { 0, 3, -3 }) + { + // string, alignment + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + "}", s); + actual.AppendFormatted(s, alignment); + + // string, alignment, format + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + ":X2}", s); + actual.AppendFormatted(s, alignment, "X2"); + } + } + + Assert.True(MemoryExtensions.TryWrite(_largeBuffer, ref actual, out int charsWritten)); + Assert.Equal(expected.ToString(), _largeBuffer.AsSpan(0, charsWritten).ToString()); + } + + [Fact] + public void AppendFormatted_String_ICustomFormatter() + { + var provider = new ConcatFormatter(); + + var expected = new StringBuilder(); + MemoryExtensions.TryWriteInterpolatedStringHandler actual = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, _largeBuffer, provider, out _); + + foreach (string s in new[] { null, "", "a" }) + { + // string + expected.AppendFormat(provider, "{0}", s); + actual.AppendFormatted(s); + + // string, format + expected.AppendFormat(provider, "{0:X2}", s); + actual.AppendFormatted(s, "X2"); + + // string, alignment + expected.AppendFormat(provider, "{0,3}", s); + actual.AppendFormatted(s, 3); + + // string, alignment, format + expected.AppendFormat(provider, "{0,-3:X2}", s); + actual.AppendFormatted(s, -3, "X2"); + } + + Assert.True(MemoryExtensions.TryWrite(_largeBuffer, ref actual, out int charsWritten)); + Assert.Equal(expected.ToString(), _largeBuffer.AsSpan(0, charsWritten).ToString()); + } + + [Fact] + public void AppendFormatted_ReferenceTypes() + { + var expected = new StringBuilder(); + MemoryExtensions.TryWriteInterpolatedStringHandler actual = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, _largeBuffer, out _); + + foreach (string rawInput in new[] { null, "", "a", "bc", "def", "this is a longer string", "!" }) + { + foreach (object o in new object[] + { + rawInput, // raw string directly; ToString will return itself + new StringWrapper(rawInput), // wrapper object that returns string from ToString + new FormattableStringWrapper(rawInput), // IFormattable wrapper around string + new SpanFormattableStringWrapper(rawInput) // ISpanFormattable wrapper around string + }) + { + // object + expected.AppendFormat("{0}", o); + actual.AppendFormatted(o); + if (o is IHasToStringState tss1) + { + Assert.True(string.IsNullOrEmpty(tss1.ToStringState.LastFormat)); + AssertModeMatchesType(tss1); + } + + // object, format + expected.AppendFormat("{0:X2}", o); + actual.AppendFormatted(o, "X2"); + if (o is IHasToStringState tss2) + { + Assert.Equal("X2", tss2.ToStringState.LastFormat); + AssertModeMatchesType(tss2); + } + + foreach (int alignment in new[] { 0, 3, -3 }) + { + // object, alignment + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + "}", o); + actual.AppendFormatted(o, alignment); + if (o is IHasToStringState tss3) + { + Assert.True(string.IsNullOrEmpty(tss3.ToStringState.LastFormat)); + AssertModeMatchesType(tss3); + } + + // object, alignment, format + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + ":X2}", o); + actual.AppendFormatted(o, alignment, "X2"); + if (o is IHasToStringState tss4) + { + Assert.Equal("X2", tss4.ToStringState.LastFormat); + AssertModeMatchesType(tss4); + } + } + } + } + + Assert.True(MemoryExtensions.TryWrite(_largeBuffer, ref actual, out int charsWritten)); + Assert.Equal(expected.ToString(), _largeBuffer.AsSpan(0, charsWritten).ToString()); + } + + [Fact] + public void AppendFormatted_ReferenceTypes_CreateProviderFlowed() + { + var provider = new CultureInfo("en-US"); + MemoryExtensions.TryWriteInterpolatedStringHandler handler = new MemoryExtensions.TryWriteInterpolatedStringHandler(1, 2, _largeBuffer, provider, out _); + + foreach (IHasToStringState tss in new IHasToStringState[] { new FormattableStringWrapper("hello"), new SpanFormattableStringWrapper("hello") }) + { + handler.AppendFormatted(tss); + Assert.Same(provider, tss.ToStringState.LastProvider); + + handler.AppendFormatted(tss, 1); + Assert.Same(provider, tss.ToStringState.LastProvider); + + handler.AppendFormatted(tss, "X2"); + Assert.Same(provider, tss.ToStringState.LastProvider); + + handler.AppendFormatted(tss, 1, "X2"); + Assert.Same(provider, tss.ToStringState.LastProvider); + } + } + + [Fact] + public void AppendFormatted_ReferenceTypes_ICustomFormatter() + { + var provider = new ConcatFormatter(); + + var expected = new StringBuilder(); + MemoryExtensions.TryWriteInterpolatedStringHandler actual = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, _largeBuffer, provider, out _); + + foreach (string s in new[] { null, "", "a" }) + { + foreach (IHasToStringState tss in new IHasToStringState[] { new FormattableStringWrapper(s), new SpanFormattableStringWrapper(s) }) + { + void AssertTss(IHasToStringState tss, string format) + { + Assert.Equal(format, tss.ToStringState.LastFormat); + Assert.Same(provider, tss.ToStringState.LastProvider); + Assert.Equal(ToStringMode.ICustomFormatterFormat, tss.ToStringState.ToStringMode); + } + + // object + expected.AppendFormat(provider, "{0}", tss); + actual.AppendFormatted(tss); + AssertTss(tss, null); + + // object, format + expected.AppendFormat(provider, "{0:X2}", tss); + actual.AppendFormatted(tss, "X2"); + AssertTss(tss, "X2"); + + // object, alignment + expected.AppendFormat(provider, "{0,3}", tss); + actual.AppendFormatted(tss, 3); + AssertTss(tss, null); + + // object, alignment, format + expected.AppendFormat(provider, "{0,-3:X2}", tss); + actual.AppendFormatted(tss, -3, "X2"); + AssertTss(tss, "X2"); + } + } + + Assert.True(MemoryExtensions.TryWrite(_largeBuffer, ref actual, out int charsWritten)); + Assert.Equal(expected.ToString(), _largeBuffer.AsSpan(0, charsWritten).ToString()); + } + + [Fact] + public void AppendFormatted_ValueTypes() + { + void Test(T t) + { + var expected = new StringBuilder(); + MemoryExtensions.TryWriteInterpolatedStringHandler actual = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, _largeBuffer, out _); + + // struct + expected.AppendFormat("{0}", t); + actual.AppendFormatted(t); + Assert.True(string.IsNullOrEmpty(((IHasToStringState)t).ToStringState.LastFormat)); + AssertModeMatchesType(((IHasToStringState)t)); + + // struct, format + expected.AppendFormat("{0:X2}", t); + actual.AppendFormatted(t, "X2"); + Assert.Equal("X2", ((IHasToStringState)t).ToStringState.LastFormat); + AssertModeMatchesType(((IHasToStringState)t)); + + foreach (int alignment in new[] { 0, 3, -3 }) + { + // struct, alignment + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + "}", t); + actual.AppendFormatted(t, alignment); + Assert.True(string.IsNullOrEmpty(((IHasToStringState)t).ToStringState.LastFormat)); + AssertModeMatchesType(((IHasToStringState)t)); + + // struct, alignment, format + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + ":X2}", t); + actual.AppendFormatted(t, alignment, "X2"); + Assert.Equal("X2", ((IHasToStringState)t).ToStringState.LastFormat); + AssertModeMatchesType(((IHasToStringState)t)); + } + + Assert.True(MemoryExtensions.TryWrite(_largeBuffer, ref actual, out int charsWritten)); + Assert.Equal(expected.ToString(), _largeBuffer.AsSpan(0, charsWritten).ToString()); + } + + Test(new FormattableInt32Wrapper(42)); + Test(new SpanFormattableInt32Wrapper(84)); + Test((FormattableInt32Wrapper?)new FormattableInt32Wrapper(42)); + Test((SpanFormattableInt32Wrapper?)new SpanFormattableInt32Wrapper(84)); + } + + [Fact] + public void AppendFormatted_ValueTypes_CreateProviderFlowed() + { + void Test(T t) + { + var provider = new CultureInfo("en-US"); + MemoryExtensions.TryWriteInterpolatedStringHandler handler = new MemoryExtensions.TryWriteInterpolatedStringHandler(1, 2, _largeBuffer, provider, out _); + + handler.AppendFormatted(t); + Assert.Same(provider, ((IHasToStringState)t).ToStringState.LastProvider); + + handler.AppendFormatted(t, 1); + Assert.Same(provider, ((IHasToStringState)t).ToStringState.LastProvider); + + handler.AppendFormatted(t, "X2"); + Assert.Same(provider, ((IHasToStringState)t).ToStringState.LastProvider); + + handler.AppendFormatted(t, 1, "X2"); + Assert.Same(provider, ((IHasToStringState)t).ToStringState.LastProvider); + } + + Test(new FormattableInt32Wrapper(42)); + Test(new SpanFormattableInt32Wrapper(84)); + Test((FormattableInt32Wrapper?)new FormattableInt32Wrapper(42)); + Test((SpanFormattableInt32Wrapper?)new SpanFormattableInt32Wrapper(84)); + } + + [Fact] + public void AppendFormatted_ValueTypes_ICustomFormatter() + { + var provider = new ConcatFormatter(); + + void Test(T t) + { + void AssertTss(T tss, string format) + { + Assert.Equal(format, ((IHasToStringState)tss).ToStringState.LastFormat); + Assert.Same(provider, ((IHasToStringState)tss).ToStringState.LastProvider); + Assert.Equal(ToStringMode.ICustomFormatterFormat, ((IHasToStringState)tss).ToStringState.ToStringMode); + } + + var expected = new StringBuilder(); + MemoryExtensions.TryWriteInterpolatedStringHandler actual = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, _largeBuffer, provider, out _); + + // struct + expected.AppendFormat(provider, "{0}", t); + actual.AppendFormatted(t); + AssertTss(t, null); + + // struct, format + expected.AppendFormat(provider, "{0:X2}", t); + actual.AppendFormatted(t, "X2"); + AssertTss(t, "X2"); + + // struct, alignment + expected.AppendFormat(provider, "{0,3}", t); + actual.AppendFormatted(t, 3); + AssertTss(t, null); + + // struct, alignment, format + expected.AppendFormat(provider, "{0,-3:X2}", t); + actual.AppendFormatted(t, -3, "X2"); + AssertTss(t, "X2"); + + Assert.True(MemoryExtensions.TryWrite(_largeBuffer, ref actual, out int charsWritten)); + Assert.Equal(expected.ToString(), _largeBuffer.AsSpan(0, charsWritten).ToString()); + } + + Test(new FormattableInt32Wrapper(42)); + Test(new SpanFormattableInt32Wrapper(84)); + Test((FormattableInt32Wrapper?)new FormattableInt32Wrapper(42)); + Test((SpanFormattableInt32Wrapper?)new SpanFormattableInt32Wrapper(84)); + } + + [Fact] + public void AppendFormatted_EmptyBuffer_ZeroLengthWritesSuccessful() + { + var buffer = new char[100]; + + MemoryExtensions.TryWriteInterpolatedStringHandler b = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, buffer.AsSpan(0, 0), out bool success); + Assert.True(success); + + Assert.True(b.AppendLiteral("")); + Assert.True(b.AppendFormatted((object)"", alignment: 0, format: "X2")); + Assert.True(b.AppendFormatted(null)); + Assert.True(b.AppendFormatted("")); + Assert.True(b.AppendFormatted("", alignment: 0, format: "X2")); + Assert.True(b.AppendFormatted("")); + Assert.True(b.AppendFormatted("", alignment: 0)); + Assert.True(b.AppendFormatted("", format: "X2")); + Assert.True(b.AppendFormatted("", alignment: 0, format: "X2")); + Assert.True(b.AppendFormatted("".AsSpan())); + Assert.True(b.AppendFormatted("".AsSpan(), alignment: 0, format: "X2")); + + Assert.True(MemoryExtensions.TryWrite(buffer.AsSpan(0, 0), ref b, out int charsWritten)); + Assert.Equal(0, charsWritten); + } + + [Theory] + [InlineData(0)] + [InlineData(100)] + public void AppendFormatted_BufferTooSmall(int bufferLength) + { + var buffer = new char[bufferLength]; + + for (int i = 0; i <= 29; i++) + { + MemoryExtensions.TryWriteInterpolatedStringHandler b = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, buffer, out bool success); + Assert.True(success); + + Assert.True(b.AppendLiteral(new string('s', bufferLength))); + + bool result = i switch + { + 0 => b.AppendLiteral(" "), + 1 => b.AppendFormatted((object)" ", alignment: 0, format: "X2"), + 2 => b.AppendFormatted(" "), + 3 => b.AppendFormatted(" ", alignment: 0, format: "X2"), + 4 => b.AppendFormatted(" "), + 5 => b.AppendFormatted(" ", alignment: 0), + 6 => b.AppendFormatted(" ", format: "X2"), + 7 => b.AppendFormatted(" ", alignment: 0, format: "X2"), + 8 => b.AppendFormatted(" ".AsSpan()), + 9 => b.AppendFormatted(" ".AsSpan(), alignment: 0, format: "X2"), + 10 => b.AppendFormatted(new FormattableStringWrapper(" ")), + 11 => b.AppendFormatted(new FormattableStringWrapper(" "), alignment: 0), + 12 => b.AppendFormatted(new FormattableStringWrapper(" "), format: "X2"), + 13 => b.AppendFormatted(new FormattableStringWrapper(" "), alignment: 0, format: "X2"), + 14 => b.AppendFormatted(new SpanFormattableStringWrapper(" ")), + 15 => b.AppendFormatted(new SpanFormattableStringWrapper(" "), alignment: 0), + 16 => b.AppendFormatted(new SpanFormattableStringWrapper(" "), format: "X2"), + 17 => b.AppendFormatted(new SpanFormattableStringWrapper(" "), alignment: 0, format: "X2"), + 18 => b.AppendFormatted(new FormattableInt32Wrapper(1)), + 19 => b.AppendFormatted(new FormattableInt32Wrapper(1), alignment: 0), + 20 => b.AppendFormatted(new FormattableInt32Wrapper(1), format: "X2"), + 21 => b.AppendFormatted(new FormattableInt32Wrapper(1), alignment: 0, format: "X2"), + 22 => b.AppendFormatted(new SpanFormattableInt32Wrapper(1)), + 23 => b.AppendFormatted(new SpanFormattableInt32Wrapper(1), alignment: 0), + 24 => b.AppendFormatted(new SpanFormattableInt32Wrapper(1), format: "X2"), + 25 => b.AppendFormatted(new SpanFormattableInt32Wrapper(1), alignment: 0, format: "X2"), + 26 => b.AppendFormatted("", alignment: 1), + 27 => b.AppendFormatted("", alignment: -1), + 28 => b.AppendFormatted(" ", alignment: 1, format: "X2"), + 29 => b.AppendFormatted(" ", alignment: -1, format: "X2"), + _ => throw new Exception(), + }; + Assert.False(result); + + Assert.False(MemoryExtensions.TryWrite(buffer.AsSpan(0, 0), ref b, out int charsWritten)); + Assert.Equal(0, charsWritten); + } + } + [Fact] + public void AppendFormatted_BufferTooSmall_CustomFormatter() + { + var buffer = new char[100]; + var provider = new ConstFormatter(" "); + + { + MemoryExtensions.TryWriteInterpolatedStringHandler b = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, buffer.AsSpan(0, 0), provider, out bool success); + Assert.True(success); + + // don't use custom formatter + Assert.True(b.AppendLiteral("")); + Assert.True(b.AppendFormatted("".AsSpan())); + Assert.True(b.AppendFormatted("".AsSpan(), alignment: 0, format: "X2")); + + // do use custom formatter + Assert.False(b.AppendFormatted((object)"", alignment: 0, format: "X2")); + Assert.False(b.AppendFormatted(null)); + Assert.False(b.AppendFormatted("")); + Assert.False(b.AppendFormatted("", alignment: 0, format: "X2")); + Assert.False(b.AppendFormatted("")); + Assert.False(b.AppendFormatted("", alignment: 0)); + Assert.False(b.AppendFormatted("", format: "X2")); + Assert.False(b.AppendFormatted("", alignment: 0, format: "X2")); + + Assert.False(MemoryExtensions.TryWrite(buffer.AsSpan(0, 0), ref b, out int charsWritten)); + Assert.Equal(0, charsWritten); + } + } + + private static void AssertModeMatchesType(T tss) where T : IHasToStringState + { + ToStringMode expected = + tss is ISpanFormattable ? ToStringMode.ISpanFormattableTryFormat : + tss is IFormattable ? ToStringMode.IFormattableToString : + ToStringMode.ObjectToString; + Assert.Equal(expected, tss.ToStringState.ToStringMode); + } + + private sealed class SpanFormattableStringWrapper : IFormattable, ISpanFormattable, IHasToStringState + { + private readonly string _value; + public ToStringState ToStringState { get; } = new ToStringState(); + + public SpanFormattableStringWrapper(string value) => _value = value; + + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider provider) + { + ToStringState.LastFormat = format.ToString(); + ToStringState.LastProvider = provider; + ToStringState.ToStringMode = ToStringMode.ISpanFormattableTryFormat; + + if (_value is null) + { + charsWritten = 0; + return true; + } + + if (_value.Length > destination.Length) + { + charsWritten = 0; + return false; + } + + charsWritten = _value.Length; + _value.AsSpan().CopyTo(destination); + return true; + } + + public string ToString(string format, IFormatProvider formatProvider) + { + ToStringState.LastFormat = format; + ToStringState.LastProvider = formatProvider; + ToStringState.ToStringMode = ToStringMode.IFormattableToString; + return _value; + } + + public override string ToString() + { + ToStringState.LastFormat = null; + ToStringState.LastProvider = null; + ToStringState.ToStringMode = ToStringMode.ObjectToString; + return _value; + } + } + + private struct SpanFormattableInt32Wrapper : IFormattable, ISpanFormattable, IHasToStringState + { + private readonly int _value; + public ToStringState ToStringState { get; } + + public SpanFormattableInt32Wrapper(int value) + { + ToStringState = new ToStringState(); + _value = value; + } + + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider provider) + { + ToStringState.LastFormat = format.ToString(); + ToStringState.LastProvider = provider; + ToStringState.ToStringMode = ToStringMode.ISpanFormattableTryFormat; + + return _value.TryFormat(destination, out charsWritten, format, provider); + } + + public string ToString(string format, IFormatProvider formatProvider) + { + ToStringState.LastFormat = format; + ToStringState.LastProvider = formatProvider; + ToStringState.ToStringMode = ToStringMode.IFormattableToString; + return _value.ToString(format, formatProvider); + } + + public override string ToString() + { + ToStringState.LastFormat = null; + ToStringState.LastProvider = null; + ToStringState.ToStringMode = ToStringMode.ObjectToString; + return _value.ToString(); + } + } + + private sealed class FormattableStringWrapper : IFormattable, IHasToStringState + { + private readonly string _value; + public ToStringState ToStringState { get; } = new ToStringState(); + + public FormattableStringWrapper(string s) => _value = s; + + public string ToString(string format, IFormatProvider formatProvider) + { + ToStringState.LastFormat = format; + ToStringState.LastProvider = formatProvider; + ToStringState.ToStringMode = ToStringMode.IFormattableToString; + return _value; + } + + public override string ToString() + { + ToStringState.LastFormat = null; + ToStringState.LastProvider = null; + ToStringState.ToStringMode = ToStringMode.ObjectToString; + return _value; + } + } + + private struct FormattableInt32Wrapper : IFormattable, IHasToStringState + { + private readonly int _value; + public ToStringState ToStringState { get; } + + public FormattableInt32Wrapper(int i) + { + ToStringState = new ToStringState(); + _value = i; + } + + public string ToString(string format, IFormatProvider formatProvider) + { + ToStringState.LastFormat = format; + ToStringState.LastProvider = formatProvider; + ToStringState.ToStringMode = ToStringMode.IFormattableToString; + return _value.ToString(format, formatProvider); + } + + public override string ToString() + { + ToStringState.LastFormat = null; + ToStringState.LastProvider = null; + ToStringState.ToStringMode = ToStringMode.ObjectToString; + return _value.ToString(); + } + } + + private sealed class ToStringState + { + public string LastFormat { get; set; } + public IFormatProvider LastProvider { get; set; } + public ToStringMode ToStringMode { get; set; } + } + + private interface IHasToStringState + { + ToStringState ToStringState { get; } + } + + private enum ToStringMode + { + ObjectToString, + IFormattableToString, + ISpanFormattableTryFormat, + ICustomFormatterFormat, + } + + private sealed class StringWrapper + { + private readonly string _value; + + public StringWrapper(string s) => _value = s; + + public override string ToString() => _value; + } + + private sealed class ConcatFormatter : IFormatProvider, ICustomFormatter + { + public object GetFormat(Type formatType) => formatType == typeof(ICustomFormatter) ? this : null; + + public string Format(string format, object arg, IFormatProvider formatProvider) + { + string s = format + " " + arg + formatProvider; + + if (arg is IHasToStringState tss) + { + // Set after using arg.ToString() in concat above + tss.ToStringState.LastFormat = format; + tss.ToStringState.LastProvider = formatProvider; + tss.ToStringState.ToStringMode = ToStringMode.ICustomFormatterFormat; + } + + return s; + } + } + + private sealed class ConstFormatter : IFormatProvider, ICustomFormatter + { + private readonly string _value; + + public ConstFormatter(string value) => _value = value; + + public object GetFormat(Type formatType) => formatType == typeof(ICustomFormatter) ? this : null; + + public string Format(string format, object arg, IFormatProvider formatProvider) => _value; + } + } +} diff --git a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj index 5ab1670c7df7..e6638ed601f9 100644 --- a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj @@ -112,6 +112,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 31608d643a29..bcf142a1f8a7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -1868,5 +1870,439 @@ public static void Sort(this Span keys, Span items, ArraySortHelper.Default.Sort(keys, items, new ComparisonComparer(comparison)); } } + + /// Writes the specified interpolated string to the character span. + /// The span to which the interpolated string should be formatted. + /// The interpolated string. + /// The number of characters written to the span. + /// true if the entire interpolated string could be formatted successfully; otherwise, false. + public static bool TryWrite(this Span destination, [InterpolatedStringHandlerArgument("destination")] ref TryWriteInterpolatedStringHandler handler, out int charsWritten) + { + // The span argument isn't used directly in the method; rather, it'll be used by the compiler to create the handler. + // We could validate here that span == handler._destination, but that doesn't seem necessary. + if (handler._success) + { + charsWritten = handler._pos; + return true; + } + + charsWritten = 0; + return false; + } + + /// Writes the specified interpolated string to the character span. + /// The span to which the interpolated string should be formatted. + /// An object that supplies culture-specific formatting information. + /// The interpolated string. + /// The number of characters written to the span. + /// true if the entire interpolated string could be formatted successfully; otherwise, false. + public static bool TryWrite(this Span destination, IFormatProvider? provider, [InterpolatedStringHandlerArgument("destination", "provider")] ref TryWriteInterpolatedStringHandler handler, out int charsWritten) => + // The provider is passed to the handler by the compiler, so the actual implementation of the method + // is the same as the non-provider overload. + TryWrite(destination, ref handler, out charsWritten); + + /// Provides a handler used by the language compiler to format interpolated strings into character spans. + [EditorBrowsable(EditorBrowsableState.Never)] + [InterpolatedStringHandler] + public ref struct TryWriteInterpolatedStringHandler + { + // Implementation note: + // As this type is only intended to be targeted by the compiler, public APIs eschew argument validation logic + // in a variety of places, e.g. allowing a null input when one isn't expected to produce a NullReferenceException rather + // than an ArgumentNullException. + + /// The destination buffer. + private readonly Span _destination; + /// Optional provider to pass to IFormattable.ToString or ISpanFormattable.TryFormat calls. + private readonly IFormatProvider? _provider; + /// The number of characters written to . + internal int _pos; + /// true if all formatting operations have succeeded; otherwise, false. + internal bool _success; + /// Whether provides an ICustomFormatter. + /// + /// Custom formatters are very rare. We want to support them, but it's ok if we make them more expensive + /// in order to make them as pay-for-play as possible. So, we avoid adding another reference type field + /// to reduce the size of the handler and to reduce required zero'ing, by only storing whether the provider + /// provides a formatter, rather than actually storing the formatter. This in turn means, if there is a + /// formatter, we pay for the extra interface call on each AppendFormatted that needs it. + /// + private readonly bool _hasCustomFormatter; + + /// Creates a handler used to write an interpolated string into a . + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// The destination buffer. + /// Upon return, true if the destination may be long enough to support the formatting, or false if it won't be. + /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. + public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, Span destination, out bool success) + { + _destination = destination; + _provider = null; + _pos = 0; + _success = success = destination.Length >= literalLength; + _hasCustomFormatter = false; + } + + /// Creates a handler used to write an interpolated string into a . + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// The destination buffer. + /// An object that supplies culture-specific formatting information. + /// Upon return, true if the destination may be long enough to support the formatting, or false if it won't be. + /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. + public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, Span destination, IFormatProvider? provider, out bool success) + { + _destination = destination; + _provider = provider; + _pos = 0; + _success = success = destination.Length >= literalLength; + _hasCustomFormatter = provider is not null && DefaultInterpolatedStringHandler.HasCustomFormatter(provider); + } + + /// Writes the specified string to the handler. + /// The string to write. + /// true if the value could be formatted to the span; otherwise, false. + public bool AppendLiteral(string value) + { + if (value.TryCopyTo(_destination.Slice(_pos))) + { + _pos += value.Length; + return true; + } + + return Fail(); + } + + #region AppendFormatted + // Design note: + // This provides the same set of overloads and semantics as DefaultInterpolatedStringHandler. + + #region AppendFormatted T + /// Writes the specified value to the handler. + /// The value to write. + public bool AppendFormatted(T value) + { + // This method could delegate to AppendFormatted with a null format, but explicitly passing + // default as the format to TryFormat helps to improve code quality in some cases when TryFormat is inlined, + // e.g. for Int32 it enables the JIT to eliminate code in the inlined method based on a length check on the format. + + // If there's a custom formatter, always use it. + if (_hasCustomFormatter) + { + return AppendCustomFormatter(value, format: null); + } + + // Check first for IFormattable, even though we'll prefer to use ISpanFormattable, as the latter + // derives from the former. For value types, it won't matter as the type checks devolve into + // JIT-time constants. For reference types, they're more likely to implement IFormattable + // than they are to implement ISpanFormattable: if they don't implement either, we save an + // interface check over first checking for ISpanFormattable and then for IFormattable, and + // if it only implements IFormattable, we come out even: only if it implements both do we + // end up paying for an extra interface check. + string? s; + if (value is IFormattable) + { + // If the value can format itself directly into our buffer, do so. + if (value is ISpanFormattable) + { + int charsWritten; + if (((ISpanFormattable)value).TryFormat(_destination.Slice(_pos), out charsWritten, default, _provider)) // constrained call avoiding boxing for value types + { + _pos += charsWritten; + return true; + } + + return Fail(); + } + + s = ((IFormattable)value).ToString(format: null, _provider); // constrained call avoiding boxing for value types + } + else + { + s = value?.ToString(); + } + + return s is null || AppendLiteral(s); // use AppendLiteral to avoid going back through this method recursively + } + + /// Writes the specified value to the handler. + /// The value to write. + /// The format string. + public bool AppendFormatted(T value, string? format) + { + // If there's a custom formatter, always use it. + if (_hasCustomFormatter) + { + return AppendCustomFormatter(value, format); + } + + // Check first for IFormattable, even though we'll prefer to use ISpanFormattable, as the latter + // derives from the former. For value types, it won't matter as the type checks devolve into + // JIT-time constants. For reference types, they're more likely to implement IFormattable + // than they are to implement ISpanFormattable: if they don't implement either, we save an + // interface check over first checking for ISpanFormattable and then for IFormattable, and + // if it only implements IFormattable, we come out even: only if it implements both do we + // end up paying for an extra interface check. + string? s; + if (value is IFormattable) + { + // If the value can format itself directly into our buffer, do so. + if (value is ISpanFormattable) + { + int charsWritten; + if (((ISpanFormattable)value).TryFormat(_destination.Slice(_pos), out charsWritten, format, _provider)) // constrained call avoiding boxing for value types + { + _pos += charsWritten; + return true; + } + + return Fail(); + } + + s = ((IFormattable)value).ToString(format, _provider); // constrained call avoiding boxing for value types + } + else + { + s = value?.ToString(); + } + + return s is null || AppendLiteral(s); // use AppendLiteral to avoid going back through this method recursively + } + + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + public bool AppendFormatted(T value, int alignment) + { + int startingPos = _pos; + if (AppendFormatted(value)) + { + return alignment == 0 || TryAppendOrInsertAlignmentIfNeeded(startingPos, alignment); + } + + return Fail(); + } + + /// Writes the specified value to the handler. + /// The value to write. + /// The format string. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + public bool AppendFormatted(T value, int alignment, string? format) + { + int startingPos = _pos; + if (AppendFormatted(value, format)) + { + return alignment == 0 || TryAppendOrInsertAlignmentIfNeeded(startingPos, alignment); + } + + return Fail(); + } + #endregion + + #region AppendFormatted ReadOnlySpan + /// Writes the specified character span to the handler. + /// The span to write. + public bool AppendFormatted(ReadOnlySpan value) + { + // Fast path for when the value fits in the current buffer + if (value.TryCopyTo(_destination.Slice(_pos))) + { + _pos += value.Length; + return true; + } + + return Fail(); + } + + /// Writes the specified string of chars to the handler. + /// The span to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public bool AppendFormatted(ReadOnlySpan value, int alignment = 0, string? format = null) + { + bool leftAlign = false; + if (alignment < 0) + { + leftAlign = true; + alignment = -alignment; + } + + int paddingRequired = alignment - value.Length; + if (paddingRequired <= 0) + { + // The value is as large or larger than the required amount of padding, + // so just write the value. + return AppendFormatted(value); + } + + // Write the value along with the appropriate padding. + Debug.Assert(alignment > value.Length); + if (alignment <= _destination.Length - _pos) + { + if (leftAlign) + { + value.CopyTo(_destination.Slice(_pos)); + _pos += value.Length; + _destination.Slice(_pos, paddingRequired).Fill(' '); + _pos += paddingRequired; + } + else + { + _destination.Slice(_pos, paddingRequired).Fill(' '); + _pos += paddingRequired; + value.CopyTo(_destination.Slice(_pos)); + _pos += value.Length; + } + + return true; + } + + return Fail(); + } + #endregion + + #region AppendFormatted string + /// Writes the specified value to the handler. + /// The value to write. + public bool AppendFormatted(string? value) + { + // Fast-path for no custom formatter and a non-null string that fits in the current destination buffer. + if (!_hasCustomFormatter && + value is not null && + value.TryCopyTo(_destination.Slice(_pos))) + { + _pos += value.Length; + return true; + } + + return AppendFormattedSlow(value); + } + + /// Writes the specified value to the handler. + /// The value to write. + /// + /// Slow path to handle a custom formatter, potentially null value, + /// or a string that doesn't fit in the current buffer. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private bool AppendFormattedSlow(string? value) + { + if (_hasCustomFormatter) + { + return AppendCustomFormatter(value, format: null); + } + + if (value is null) + { + return true; + } + + if (value.TryCopyTo(_destination.Slice(_pos))) + { + _pos += value.Length; + return true; + } + + return Fail(); + } + + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public bool AppendFormatted(string? value, int alignment = 0, string? format = null) => + // Format is meaningless for strings and doesn't make sense for someone to specify. We have the overload + // simply to disambiguate between ROS and object, just in case someone does specify a format, as + // string is implicitly convertible to both. Just delegate to the T-based implementation. + AppendFormatted(value, alignment, format); + #endregion + + #region AppendFormatted object + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public bool AppendFormatted(object? value, int alignment = 0, string? format = null) => + // This overload is expected to be used rarely, only if either a) something strongly typed as object is + // formatted with both an alignment and a format, or b) the compiler is unable to target type to T. It + // exists purely to help make cases from (b) compile. Just delegate to the T-based implementation. + AppendFormatted(value, alignment, format); + #endregion + #endregion + + /// Formats the value using the custom formatter from the provider. + /// The value to write. + /// The format string. + [MethodImpl(MethodImplOptions.NoInlining)] + private bool AppendCustomFormatter(T value, string? format) + { + // This case is very rare, but we need to handle it prior to the other checks in case + // a provider was used that supplied an ICustomFormatter which wanted to intercept the particular value. + // We do the cast here rather than in the ctor, even though this could be executed multiple times per + // formatting, to make the cast pay for play. + Debug.Assert(_hasCustomFormatter); + Debug.Assert(_provider != null); + + ICustomFormatter? formatter = (ICustomFormatter?)_provider.GetFormat(typeof(ICustomFormatter)); + Debug.Assert(formatter != null, "An incorrectly written provider said it implemented ICustomFormatter, and then didn't"); + + if (formatter is not null && formatter.Format(format, value, _provider) is string customFormatted) + { + return AppendLiteral(customFormatted); + } + + return true; + } + + /// Handles adding any padding required for aligning a formatted value in an interpolation expression. + /// The position at which the written value started. + /// Non-zero minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + private bool TryAppendOrInsertAlignmentIfNeeded(int startingPos, int alignment) + { + Debug.Assert(startingPos >= 0 && startingPos <= _pos); + Debug.Assert(alignment != 0); + + int charsWritten = _pos - startingPos; + + bool leftAlign = false; + if (alignment < 0) + { + leftAlign = true; + alignment = -alignment; + } + + int paddingNeeded = alignment - charsWritten; + if (paddingNeeded <= 0) + { + return true; + } + + if (paddingNeeded <= _destination.Length - _pos) + { + if (leftAlign) + { + _destination.Slice(_pos, paddingNeeded).Fill(' '); + } + else + { + _destination.Slice(startingPos, charsWritten).CopyTo(_destination.Slice(startingPos + paddingNeeded)); + _destination.Slice(startingPos, paddingNeeded).Fill(' '); + } + + _pos += paddingNeeded; + return true; + } + + return Fail(); + } + + /// Marks formatting as having failed and returns false. + private bool Fail() + { + _success = false; + return false; + } + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/String.cs b/src/libraries/System.Private.CoreLib/src/System/String.cs index 25ac8df2c582..7ca88a4b29c7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.cs @@ -391,6 +391,21 @@ public static string Create(int length, TState state, SpanActionCreates a new string by using the specified provider to control the formatting of the specified interpolated string. + /// An object that supplies culture-specific formatting information. + /// The interpolated string. + /// The string that results for formatting the interpolated string using the specified format provider. + public static string Create(IFormatProvider? provider, [InterpolatedStringHandlerArgument("provider")] ref DefaultInterpolatedStringHandler handler) => + handler.ToStringAndClear(); + + /// Creates a new string by using the specified provider to control the formatting of the specified interpolated string. + /// An object that supplies culture-specific formatting information. + /// The initial buffer that may be used as temporary space as part of the formatting operation. The contents of this buffer may be overwritten. + /// The interpolated string. + /// The string that results for formatting the interpolated string using the specified format provider. + public static string Create(IFormatProvider? provider, Span initialBuffer, [InterpolatedStringHandlerArgument("provider", "initialBuffer")] ref DefaultInterpolatedStringHandler handler) => + handler.ToStringAndClear(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator ReadOnlySpan(string? value) => value != null ? new ReadOnlySpan(ref value.GetRawStringData(), value.Length) : default; diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs index 3ff675c1d40a..9bec0d084999 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -1232,6 +1233,28 @@ public StringBuilder Append(ReadOnlySpan value) public StringBuilder Append(ReadOnlyMemory value) => Append(value.Span); + /// Appends the specified interpolated string to this instance. + /// The interpolated string to append. + /// A reference to this instance after the append operation has completed. + public StringBuilder Append([InterpolatedStringHandlerArgument("")] ref AppendInterpolatedStringHandler handler) => this; + + /// Appends the specified interpolated string to this instance. + /// An object that supplies culture-specific formatting information. + /// The interpolated string to append. + /// A reference to this instance after the append operation has completed. + public StringBuilder Append(IFormatProvider? provider, [InterpolatedStringHandlerArgument("", "provider")] ref AppendInterpolatedStringHandler handler) => this; + + /// Appends the specified interpolated string followed by the default line terminator to the end of the current StringBuilder object. + /// The interpolated string to append. + /// A reference to this instance after the append operation has completed. + public StringBuilder AppendLine([InterpolatedStringHandlerArgument("")] ref AppendInterpolatedStringHandler handler) => AppendLine(); + + /// Appends the specified interpolated string followed by the default line terminator to the end of the current StringBuilder object. + /// An object that supplies culture-specific formatting information. + /// The interpolated string to append. + /// A reference to this instance after the append operation has completed. + public StringBuilder AppendLine(IFormatProvider? provider, [InterpolatedStringHandlerArgument("", "provider")] ref AppendInterpolatedStringHandler handler) => AppendLine(); + #region AppendJoin public unsafe StringBuilder AppendJoin(string? separator, params object?[] values) @@ -2600,5 +2623,325 @@ private void Remove(int startIndex, int count, out StringBuilder chunk, out int Debug.Assert(chunk != null, "We fell off the beginning of the string!"); AssertInvariants(); } + + /// Provides a handler used by the language compiler to append interpolated strings into instances. + [EditorBrowsable(EditorBrowsableState.Never)] + [InterpolatedStringHandler] + public struct AppendInterpolatedStringHandler + { + // Implementation note: + // As this type is only intended to be targeted by the compiler, public APIs eschew argument validation logic + // in a variety of places, e.g. allowing a null input when one isn't expected to produce a NullReferenceException rather + // than an ArgumentNullException. + + /// The associated StringBuilder to which to append. + private readonly StringBuilder _stringBuilder; + /// Optional provider to pass to IFormattable.ToString or ISpanFormattable.TryFormat calls. + private readonly IFormatProvider? _provider; + /// Whether provides an ICustomFormatter. + /// + /// Custom formatters are very rare. We want to support them, but it's ok if we make them more expensive + /// in order to make them as pay-for-play as possible. So, we avoid adding another reference type field + /// to reduce the size of the handler and to reduce required zero'ing, by only storing whether the provider + /// provides a formatter, rather than actually storing the formatter. This in turn means, if there is a + /// formatter, we pay for the extra interface call on each AppendFormatted that needs it. + /// + private readonly bool _hasCustomFormatter; + + /// Creates a handler used to append an interpolated string into a . + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// The associated StringBuilder to which to append. + /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. + public AppendInterpolatedStringHandler(int literalLength, int formattedCount, StringBuilder stringBuilder) + { + _stringBuilder = stringBuilder; + _provider = null; + _hasCustomFormatter = false; + } + + /// Creates a handler used to translate an interpolated string into a . + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// The associated StringBuilder to which to append. + /// An object that supplies culture-specific formatting information. + /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. + public AppendInterpolatedStringHandler(int literalLength, int formattedCount, StringBuilder stringBuilder, IFormatProvider? provider) + { + _stringBuilder = stringBuilder; + _provider = provider; + _hasCustomFormatter = provider is not null && DefaultInterpolatedStringHandler.HasCustomFormatter(provider); + } + + /// Writes the specified string to the handler. + /// The string to write. + public void AppendLiteral(string value) => _stringBuilder.Append(value); + + #region AppendFormatted + // Design note: + // This provides the same set of overloads and semantics as DefaultInterpolatedStringHandler. + + #region AppendFormatted T + /// Writes the specified value to the handler. + /// The value to write. + public void AppendFormatted(T value) + { + // This method could delegate to AppendFormatted with a null format, but explicitly passing + // default as the format to TryFormat helps to improve code quality in some cases when TryFormat is inlined, + // e.g. for Int32 it enables the JIT to eliminate code in the inlined method based on a length check on the format. + + if (_hasCustomFormatter) + { + // If there's a custom formatter, always use it. + AppendCustomFormatter(value, format: null); + } + else if (value is IFormattable) + { + // Check first for IFormattable, even though we'll prefer to use ISpanFormattable, as the latter + // requires the former. For value types, it won't matter as the type checks devolve into + // JIT-time constants. For reference types, they're more likely to implement IFormattable + // than they are to implement ISpanFormattable: if they don't implement either, we save an + // interface check over first checking for ISpanFormattable and then for IFormattable, and + // if it only implements IFormattable, we come out even: only if it implements both do we + // end up paying for an extra interface check. + + if (value is ISpanFormattable) + { + Span destination = _stringBuilder.RemainingCurrentChunk; + if (((ISpanFormattable)value).TryFormat(destination, out int charsWritten, default, _provider)) // constrained call avoiding boxing for value types + { + if ((uint)charsWritten > (uint)destination.Length) + { + // Protect against faulty ISpanFormattable implementations returning invalid charsWritten values. + // Other code in _stringBuilder uses unsafe manipulation, and we want to ensure m_ChunkLength remains safe. + FormatError(); + } + + _stringBuilder.m_ChunkLength += charsWritten; + } + else + { + // Not enough room in the current chunk. Take the slow path that formats into temporary space + // and then copies the result into the StringBuilder. + AppendFormattedWithTempSpace(value, 0, format: null); + } + } + else + { + _stringBuilder.Append(((IFormattable)value).ToString(format: null, _provider)); // constrained call avoiding boxing for value types + } + } + else if (value is not null) + { + _stringBuilder.Append(value.ToString()); + } + } + + /// Writes the specified value to the handler. + /// The value to write. + /// The format string. + public void AppendFormatted(T value, string? format) + { + if (_hasCustomFormatter) + { + // If there's a custom formatter, always use it. + AppendCustomFormatter(value, format); + } + else if (value is IFormattable) + { + // Check first for IFormattable, even though we'll prefer to use ISpanFormattable, as the latter + // requires the former. For value types, it won't matter as the type checks devolve into + // JIT-time constants. For reference types, they're more likely to implement IFormattable + // than they are to implement ISpanFormattable: if they don't implement either, we save an + // interface check over first checking for ISpanFormattable and then for IFormattable, and + // if it only implements IFormattable, we come out even: only if it implements both do we + // end up paying for an extra interface check. + + if (value is ISpanFormattable) + { + Span destination = _stringBuilder.RemainingCurrentChunk; + if (((ISpanFormattable)value).TryFormat(destination, out int charsWritten, format, _provider)) // constrained call avoiding boxing for value types + { + if ((uint)charsWritten > (uint)destination.Length) + { + // Protect against faulty ISpanFormattable implementations returning invalid charsWritten values. + // Other code in _stringBuilder uses unsafe manipulation, and we want to ensure m_ChunkLength remains safe. + FormatError(); + } + + _stringBuilder.m_ChunkLength += charsWritten; + } + else + { + // Not enough room in the current chunk. Take the slow path that formats into temporary space + // and then copies the result into the StringBuilder. + AppendFormattedWithTempSpace(value, 0, format); + } + } + else + { + _stringBuilder.Append(((IFormattable)value).ToString(format, _provider)); // constrained call avoiding boxing for value types + } + } + else if (value is not null) + { + _stringBuilder.Append(value.ToString()); + } + } + + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + public void AppendFormatted(T value, int alignment) => + AppendFormatted(value, alignment, format: null); + + /// Writes the specified value to the handler. + /// The value to write. + /// The format string. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + public void AppendFormatted(T value, int alignment, string? format) + { + if (alignment == 0) + { + // This overload is used as a fallback from several disambiguation overloads, so special-case 0. + AppendFormatted(value, format); + } + else if (alignment < 0) + { + // Left aligned: format into the handler, then append any additional padding required. + int start = _stringBuilder.Length; + AppendFormatted(value, format); + int paddingRequired = -alignment - (_stringBuilder.Length - start); + if (paddingRequired > 0) + { + _stringBuilder.Append(' ', paddingRequired); + } + } + else + { + // Right aligned: format into temporary space and then copy that into the handler, appropriately aligned. + AppendFormattedWithTempSpace(value, alignment, format); + } + } + + /// Formats into temporary space and then appends the result into the StringBuilder. + private void AppendFormattedWithTempSpace(T value, int alignment, string? format) + { + // It's expected that either there's not enough space in the current chunk to store this formatted value, + // or we have a non-0 alignment that could require padding inserted. So format into temporary space and + // then append that written span into the StringBuilder: StringBuilder.Append(span) is able to split the + // span across the current chunk and any additional chunks required. + + var handler = new DefaultInterpolatedStringHandler(0, 0, _provider, stackalloc char[256]); + handler.AppendFormatted(value, format); + AppendFormatted(handler.Text, alignment); + handler.Clear(); + } + #endregion + + #region AppendFormatted ReadOnlySpan + /// Writes the specified character span to the handler. + /// The span to write. + public void AppendFormatted(ReadOnlySpan value) => _stringBuilder.Append(value); + + /// Writes the specified string of chars to the handler. + /// The span to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public void AppendFormatted(ReadOnlySpan value, int alignment = 0, string? format = null) + { + if (alignment == 0) + { + _stringBuilder.Append(value); + } + else + { + bool leftAlign = false; + if (alignment < 0) + { + leftAlign = true; + alignment = -alignment; + } + + int paddingRequired = alignment - value.Length; + if (paddingRequired <= 0) + { + _stringBuilder.Append(value); + } + else if (leftAlign) + { + _stringBuilder.Append(value); + _stringBuilder.Append(' ', paddingRequired); + } + else + { + _stringBuilder.Append(' ', paddingRequired); + _stringBuilder.Append(value); + } + } + } + #endregion + + #region AppendFormatted string + /// Writes the specified value to the handler. + /// The value to write. + public void AppendFormatted(string? value) + { + if (!_hasCustomFormatter) + { + _stringBuilder.Append(value); + } + else + { + AppendFormatted(value); + } + } + + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public void AppendFormatted(string? value, int alignment = 0, string? format = null) => + // Format is meaningless for strings and doesn't make sense for someone to specify. We have the overload + // simply to disambiguate between ROS and object, just in case someone does specify a format, as + // string is implicitly convertible to both. Just delegate to the T-based implementation. + AppendFormatted(value, alignment, format); + #endregion + + #region AppendFormatted object + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public void AppendFormatted(object? value, int alignment = 0, string? format = null) => + // This overload is expected to be used rarely, only if either a) something strongly typed as object is + // formatted with both an alignment and a format, or b) the compiler is unable to target type to T. It + // exists purely to help make cases from (b) compile. Just delegate to the T-based implementation. + AppendFormatted(value, alignment, format); + #endregion + #endregion + + /// Formats the value using the custom formatter from the provider. + /// The value to write. + /// The format string. + [MethodImpl(MethodImplOptions.NoInlining)] + private void AppendCustomFormatter(T value, string? format) + { + // This case is very rare, but we need to handle it prior to the other checks in case + // a provider was used that supplied an ICustomFormatter which wanted to intercept the particular value. + // We do the cast here rather than in the ctor, even though this could be executed multiple times per + // formatting, to make the cast pay for play. + Debug.Assert(_hasCustomFormatter); + Debug.Assert(_provider != null); + + ICustomFormatter? formatter = (ICustomFormatter?)_provider.GetFormat(typeof(ICustomFormatter)); + Debug.Assert(formatter != null, "An incorrectly written provider said it implemented ICustomFormatter, and then didn't"); + + if (formatter is not null) + { + _stringBuilder.Append(formatter.Format(format, value, _provider)); + } + } + } } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 4f0c63dd4309..8bd40241869c 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -5763,6 +5763,8 @@ public unsafe String(sbyte* value, int startIndex, int length, System.Text.Encod public void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count) { } public void CopyTo(System.Span destination) { } public static System.String Create(int length, TState state, System.Buffers.SpanAction action) { throw null; } + public static string Create(System.IFormatProvider? provider, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("provider")] ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler handler) { throw null; } + public static string Create(System.IFormatProvider? provider, System.Span initialBuffer, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("provider", "initialBuffer")] ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler handler) { throw null; } public bool EndsWith(char value) { throw null; } public bool EndsWith(System.String value) { throw null; } public bool EndsWith(System.String value, bool ignoreCase, System.Globalization.CultureInfo? culture) { throw null; } @@ -14283,6 +14285,8 @@ public StringBuilder(string? value, int startIndex, int length, int capacity) { public System.Text.StringBuilder Append(uint value) { throw null; } [System.CLSCompliantAttribute(false)] public System.Text.StringBuilder Append(ulong value) { throw null; } + public System.Text.StringBuilder Append([System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("")] ref System.Text.StringBuilder.AppendInterpolatedStringHandler handler) { throw null; } + public System.Text.StringBuilder Append(System.IFormatProvider? provider, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("", "provider")] ref System.Text.StringBuilder.AppendInterpolatedStringHandler handler) { throw null; } public System.Text.StringBuilder AppendFormat(System.IFormatProvider? provider, string format, object? arg0) { throw null; } public System.Text.StringBuilder AppendFormat(System.IFormatProvider? provider, string format, object? arg0, object? arg1) { throw null; } public System.Text.StringBuilder AppendFormat(System.IFormatProvider? provider, string format, object? arg0, object? arg1, object? arg2) { throw null; } @@ -14299,6 +14303,8 @@ public StringBuilder(string? value, int startIndex, int length, int capacity) { public System.Text.StringBuilder AppendJoin(string? separator, System.Collections.Generic.IEnumerable values) { throw null; } public System.Text.StringBuilder AppendLine() { throw null; } public System.Text.StringBuilder AppendLine(string? value) { throw null; } + public System.Text.StringBuilder AppendLine([System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("")] ref System.Text.StringBuilder.AppendInterpolatedStringHandler handler) { throw null; } + public System.Text.StringBuilder AppendLine(System.IFormatProvider? provider, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("", "provider")] ref System.Text.StringBuilder.AppendInterpolatedStringHandler handler) { throw null; } public System.Text.StringBuilder Clear() { throw null; } public void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count) { } public void CopyTo(int sourceIndex, System.Span destination, int count) { } @@ -14346,6 +14352,25 @@ public partial struct ChunkEnumerator public System.Text.StringBuilder.ChunkEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } } + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute] + public struct AppendInterpolatedStringHandler + { + private object _dummy; + private int _dummyPrimitive; + public AppendInterpolatedStringHandler(int literalLength, int formattedCount, System.Text.StringBuilder stringBuilder) { throw null; } + public AppendInterpolatedStringHandler(int literalLength, int formattedCount, System.Text.StringBuilder stringBuilder, System.IFormatProvider? provider) { throw null; } + public void AppendLiteral(string value) { } + public void AppendFormatted(T value) { } + public void AppendFormatted(T value, string? format) { } + public void AppendFormatted(T value, int alignment) { } + public void AppendFormatted(T value, int alignment, string? format) { } + public void AppendFormatted(System.ReadOnlySpan value) { } + public void AppendFormatted(System.ReadOnlySpan value, int alignment = 0, string? format = null) { } + public void AppendFormatted(string? value) { } + public void AppendFormatted(string? value, int alignment = 0, string? format = null) { } + public void AppendFormatted(object? value, int alignment = 0, string? format = null) { } + } } public partial struct StringRuneEnumerator : System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerator, System.Collections.IEnumerable, System.Collections.IEnumerator, System.IDisposable { diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index 0f4092248ae8..0ddefe7dcf06 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -238,6 +238,7 @@ + diff --git a/src/libraries/System.Runtime/tests/System/StringTests.cs b/src/libraries/System.Runtime/tests/System/StringTests.cs index aa311ff6d7d0..b0786bb2370f 100644 --- a/src/libraries/System.Runtime/tests/System/StringTests.cs +++ b/src/libraries/System.Runtime/tests/System/StringTests.cs @@ -148,6 +148,22 @@ public static void Create_ReturnsExpectedString(string expected) Assert.Equal(expected, result); } + [Fact] + public static void Create_InterpolatedString_ConstructsStringAndClearsBuilder() + { + Span initialBuffer = stackalloc char[16]; + + DefaultInterpolatedStringHandler handler = new DefaultInterpolatedStringHandler(0, 0, CultureInfo.InvariantCulture, initialBuffer); + handler.AppendLiteral("hello"); + Assert.Equal("hello", string.Create(CultureInfo.InvariantCulture, initialBuffer, ref handler)); + Assert.Equal("", string.Create(CultureInfo.InvariantCulture, initialBuffer, ref handler)); + + handler = new DefaultInterpolatedStringHandler(0, 0, CultureInfo.InvariantCulture); + handler.AppendLiteral("hello"); + Assert.Equal("hello", string.Create(CultureInfo.InvariantCulture, ref handler)); + Assert.Equal("", string.Create(CultureInfo.InvariantCulture, ref handler)); + } + [Theory] [InlineData("Hello", 'H', true)] [InlineData("Hello", 'Z', false)] diff --git a/src/libraries/System.Runtime/tests/System/Text/StringBuilderInterpolationTests.cs b/src/libraries/System.Runtime/tests/System/Text/StringBuilderInterpolationTests.cs new file mode 100644 index 000000000000..c8de8c8f9a4d --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Text/StringBuilderInterpolationTests.cs @@ -0,0 +1,653 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using Xunit; + +namespace System.Text.Tests +{ + public class StringBuilderInterpolationTests + { + [Fact] + public void Append_Nop() + { + var sb = new StringBuilder(); + StringBuilder.AppendInterpolatedStringHandler iab = new StringBuilder.AppendInterpolatedStringHandler(1, 2, sb); + + Assert.Same(sb, sb.Append(ref iab)); + Assert.Same(sb, sb.Append(CultureInfo.InvariantCulture, ref iab)); + + Assert.Equal(0, sb.Length); + } + + [Fact] + public void AppendLine_AppendsNewLine() + { + var sb = new StringBuilder(); + StringBuilder.AppendInterpolatedStringHandler iab = new StringBuilder.AppendInterpolatedStringHandler(1, 2, sb); + + Assert.Same(sb, sb.AppendLine(ref iab)); + Assert.Same(sb, sb.AppendLine(CultureInfo.InvariantCulture, ref iab)); + + Assert.Equal(Environment.NewLine + Environment.NewLine, sb.ToString()); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(1, 1)] + [InlineData(42, 84)] + [InlineData(-1, 0)] + [InlineData(-1, -1)] + [InlineData(-16, 1)] + public void LengthAndHoleArguments_Valid(int baseLength, int holeCount) + { + var sb = new StringBuilder(); + + new StringBuilder.AppendInterpolatedStringHandler(baseLength, holeCount, sb); + + foreach (IFormatProvider provider in new IFormatProvider[] { null, new ConcatFormatter(), CultureInfo.InvariantCulture, CultureInfo.CurrentCulture, new CultureInfo("en-US"), new CultureInfo("fr-FR") }) + { + new StringBuilder.AppendInterpolatedStringHandler(baseLength, holeCount, sb, provider); + } + } + + [Fact] + public void AppendLiteral() + { + var expected = new StringBuilder(); + var actual = new StringBuilder(); + StringBuilder.AppendInterpolatedStringHandler iab = new StringBuilder.AppendInterpolatedStringHandler(0, 0, actual); + + foreach (string s in new[] { "", "a", "bc", "def", "this is a long string", "!" }) + { + expected.Append(s); + iab.AppendLiteral(s); + } + + actual.Append(ref iab); + + Assert.Equal(expected.ToString(), actual.ToString()); + } + + [Fact] + public void AppendFormatted_ReadOnlySpanChar() + { + var expected = new StringBuilder(); + var actual = new StringBuilder(); + StringBuilder.AppendInterpolatedStringHandler iab = new StringBuilder.AppendInterpolatedStringHandler(0, 0, actual); + + foreach (string s in new[] { "", "a", "bc", "def", "this is a longer string", "!" }) + { + // span + expected.Append(s); + iab.AppendFormatted((ReadOnlySpan)s); + + // span, format + expected.AppendFormat("{0:X2}", s); + iab.AppendFormatted((ReadOnlySpan)s, format: "X2"); + + foreach (int alignment in new[] { 0, 3, -3 }) + { + // span, alignment + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + "}", s); + iab.AppendFormatted((ReadOnlySpan)s, alignment); + + // span, alignment, format + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + ":X2}", s); + iab.AppendFormatted((ReadOnlySpan)s, alignment, "X2"); + } + } + + actual.Append(ref iab); + + Assert.Equal(expected.ToString(), actual.ToString()); + } + + [Fact] + public void AppendFormatted_String() + { + var expected = new StringBuilder(); + var actual = new StringBuilder(); + StringBuilder.AppendInterpolatedStringHandler iab = new StringBuilder.AppendInterpolatedStringHandler(0, 0, actual); + + foreach (string s in new[] { null, "", "a", "bc", "def", "this is a longer string", "!" }) + { + // string + expected.AppendFormat("{0}", s); + iab.AppendFormatted(s); + + // string, format + expected.AppendFormat("{0:X2}", s); + iab.AppendFormatted(s, "X2"); + + foreach (int alignment in new[] { 0, 3, -3 }) + { + // string, alignment + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + "}", s); + iab.AppendFormatted(s, alignment); + + // string, alignment, format + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + ":X2}", s); + iab.AppendFormatted(s, alignment, "X2"); + } + } + + actual.Append(ref iab); + + Assert.Equal(expected.ToString(), actual.ToString()); + } + + [Fact] + public void AppendFormatted_String_ICustomFormatter() + { + var provider = new ConcatFormatter(); + + var expected = new StringBuilder(); + var actual = new StringBuilder(); + StringBuilder.AppendInterpolatedStringHandler iab = new StringBuilder.AppendInterpolatedStringHandler(0, 0, actual, provider); + + foreach (string s in new[] { null, "", "a" }) + { + // string + expected.AppendFormat(provider, "{0}", s); + iab.AppendFormatted(s); + + // string, format + expected.AppendFormat(provider, "{0:X2}", s); + iab.AppendFormatted(s, "X2"); + + // string, alignment + expected.AppendFormat(provider, "{0,3}", s); + iab.AppendFormatted(s, 3); + + // string, alignment, format + expected.AppendFormat(provider, "{0,-3:X2}", s); + iab.AppendFormatted(s, -3, "X2"); + } + + actual.Append(provider, ref iab); + + Assert.Equal(expected.ToString(), actual.ToString()); + } + + [Fact] + public void AppendFormatted_ReferenceTypes() + { + var expected = new StringBuilder(); + var actual = new StringBuilder(); + StringBuilder.AppendInterpolatedStringHandler iab = new StringBuilder.AppendInterpolatedStringHandler(0, 0, actual); + + foreach (string rawInput in new[] { null, "", "a", "bc", "def", "this is a longer string", "!" }) + { + foreach (object o in new object[] + { + rawInput, // raw string directly; ToString will return itself + new StringWrapper(rawInput), // wrapper object that returns string from ToString + new FormattableStringWrapper(rawInput), // IFormattable wrapper around string + new SpanFormattableStringWrapper(rawInput) // ISpanFormattable wrapper around string + }) + { + // object + expected.AppendFormat("{0}", o); + iab.AppendFormatted(o); + if (o is IHasToStringState tss1) + { + Assert.True(string.IsNullOrEmpty(tss1.ToStringState.LastFormat)); + AssertModeMatchesType(tss1); + } + + // object, format + expected.AppendFormat("{0:X2}", o); + iab.AppendFormatted(o, "X2"); + if (o is IHasToStringState tss2) + { + Assert.Equal("X2", tss2.ToStringState.LastFormat); + AssertModeMatchesType(tss2); + } + + foreach (int alignment in new[] { 0, 3, -3 }) + { + // object, alignment + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + "}", o); + iab.AppendFormatted(o, alignment); + if (o is IHasToStringState tss3) + { + Assert.True(string.IsNullOrEmpty(tss3.ToStringState.LastFormat)); + AssertModeMatchesType(tss3); + } + + // object, alignment, format + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + ":X2}", o); + iab.AppendFormatted(o, alignment, "X2"); + if (o is IHasToStringState tss4) + { + Assert.Equal("X2", tss4.ToStringState.LastFormat); + AssertModeMatchesType(tss4); + } + } + } + } + + actual.Append(ref iab); + + Assert.Equal(expected.ToString(), actual.ToString()); + } + + [Fact] + public void AppendFormatted_ReferenceTypes_CreateProviderFlowed() + { + var provider = new CultureInfo("en-US"); + var sb = new StringBuilder(); + StringBuilder.AppendInterpolatedStringHandler iab = new StringBuilder.AppendInterpolatedStringHandler(1, 2, sb, provider); + + foreach (IHasToStringState tss in new IHasToStringState[] { new FormattableStringWrapper("hello"), new SpanFormattableStringWrapper("hello") }) + { + iab.AppendFormatted(tss); + Assert.Same(provider, tss.ToStringState.LastProvider); + + iab.AppendFormatted(tss, 1); + Assert.Same(provider, tss.ToStringState.LastProvider); + + iab.AppendFormatted(tss, "X2"); + Assert.Same(provider, tss.ToStringState.LastProvider); + + iab.AppendFormatted(tss, 1, "X2"); + Assert.Same(provider, tss.ToStringState.LastProvider); + } + + sb.Append(ref iab); + } + + [Fact] + public void AppendFormatted_ReferenceTypes_ICustomFormatter() + { + var provider = new ConcatFormatter(); + + var expected = new StringBuilder(); + var actual = new StringBuilder(); + StringBuilder.AppendInterpolatedStringHandler iab = new StringBuilder.AppendInterpolatedStringHandler(0, 0, actual, provider); + + foreach (string s in new[] { null, "", "a" }) + { + foreach (IHasToStringState tss in new IHasToStringState[] { new FormattableStringWrapper(s), new SpanFormattableStringWrapper(s) }) + { + void AssertTss(IHasToStringState tss, string format) + { + Assert.Equal(format, tss.ToStringState.LastFormat); + Assert.Same(provider, tss.ToStringState.LastProvider); + Assert.Equal(ToStringMode.ICustomFormatterFormat, tss.ToStringState.ToStringMode); + } + + // object + expected.AppendFormat(provider, "{0}", tss); + iab.AppendFormatted(tss); + AssertTss(tss, null); + + // object, format + expected.AppendFormat(provider, "{0:X2}", tss); + iab.AppendFormatted(tss, "X2"); + AssertTss(tss, "X2"); + + // object, alignment + expected.AppendFormat(provider, "{0,3}", tss); + iab.AppendFormatted(tss, 3); + AssertTss(tss, null); + + // object, alignment, format + expected.AppendFormat(provider, "{0,-3:X2}", tss); + iab.AppendFormatted(tss, -3, "X2"); + AssertTss(tss, "X2"); + } + } + + actual.Append(provider, ref iab); + + Assert.Equal(expected.ToString(), actual.ToString()); + } + + [Fact] + public void AppendFormatted_ValueTypes() + { + void Test(T t) + { + var expected = new StringBuilder(); + var actual = new StringBuilder(); + StringBuilder.AppendInterpolatedStringHandler iab = new StringBuilder.AppendInterpolatedStringHandler(0, 0, actual); + + // struct + expected.AppendFormat("{0}", t); + iab.AppendFormatted(t); + Assert.True(string.IsNullOrEmpty(((IHasToStringState)t).ToStringState.LastFormat)); + AssertModeMatchesType(((IHasToStringState)t)); + + // struct, format + expected.AppendFormat("{0:X2}", t); + iab.AppendFormatted(t, "X2"); + Assert.Equal("X2", ((IHasToStringState)t).ToStringState.LastFormat); + AssertModeMatchesType(((IHasToStringState)t)); + + foreach (int alignment in new[] { 0, 3, -3 }) + { + // struct, alignment + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + "}", t); + iab.AppendFormatted(t, alignment); + Assert.True(string.IsNullOrEmpty(((IHasToStringState)t).ToStringState.LastFormat)); + AssertModeMatchesType(((IHasToStringState)t)); + + // struct, alignment, format + expected.AppendFormat("{0," + alignment.ToString(CultureInfo.InvariantCulture) + ":X2}", t); + iab.AppendFormatted(t, alignment, "X2"); + Assert.Equal("X2", ((IHasToStringState)t).ToStringState.LastFormat); + AssertModeMatchesType(((IHasToStringState)t)); + } + + actual.Append(ref iab); + + Assert.Equal(expected.ToString(), actual.ToString()); + } + + Test(new FormattableInt32Wrapper(42)); + Test(new SpanFormattableInt32Wrapper(84)); + Test((FormattableInt32Wrapper?)new FormattableInt32Wrapper(42)); + Test((SpanFormattableInt32Wrapper?)new SpanFormattableInt32Wrapper(84)); + } + + [Fact] + public void AppendFormatted_ValueTypes_CreateProviderFlowed() + { + void Test(T t) + { + var provider = new CultureInfo("en-US"); + var sb = new StringBuilder(); + StringBuilder.AppendInterpolatedStringHandler iab = new StringBuilder.AppendInterpolatedStringHandler(1, 2, sb, provider); + + iab.AppendFormatted(t); + Assert.Same(provider, ((IHasToStringState)t).ToStringState.LastProvider); + + iab.AppendFormatted(t, 1); + Assert.Same(provider, ((IHasToStringState)t).ToStringState.LastProvider); + + iab.AppendFormatted(t, "X2"); + Assert.Same(provider, ((IHasToStringState)t).ToStringState.LastProvider); + + iab.AppendFormatted(t, 1, "X2"); + Assert.Same(provider, ((IHasToStringState)t).ToStringState.LastProvider); + + sb.Append(ref iab); + } + + Test(new FormattableInt32Wrapper(42)); + Test(new SpanFormattableInt32Wrapper(84)); + Test((FormattableInt32Wrapper?)new FormattableInt32Wrapper(42)); + Test((SpanFormattableInt32Wrapper?)new SpanFormattableInt32Wrapper(84)); + } + + [Fact] + public void AppendFormatted_ValueTypes_ICustomFormatter() + { + var provider = new ConcatFormatter(); + + void Test(T t) + { + void AssertTss(T tss, string format) + { + Assert.Equal(format, ((IHasToStringState)tss).ToStringState.LastFormat); + Assert.Same(provider, ((IHasToStringState)tss).ToStringState.LastProvider); + Assert.Equal(ToStringMode.ICustomFormatterFormat, ((IHasToStringState)tss).ToStringState.ToStringMode); + } + + var expected = new StringBuilder(); + var actual = new StringBuilder(); + StringBuilder.AppendInterpolatedStringHandler iab = new StringBuilder.AppendInterpolatedStringHandler(0, 0, actual, provider); + + // struct + expected.AppendFormat(provider, "{0}", t); + iab.AppendFormatted(t); + AssertTss(t, null); + + // struct, format + expected.AppendFormat(provider, "{0:X2}", t); + iab.AppendFormatted(t, "X2"); + AssertTss(t, "X2"); + + // struct, alignment + expected.AppendFormat(provider, "{0,3}", t); + iab.AppendFormatted(t, 3); + AssertTss(t, null); + + // struct, alignment, format + expected.AppendFormat(provider, "{0,-3:X2}", t); + iab.AppendFormatted(t, -3, "X2"); + AssertTss(t, "X2"); + + Assert.Equal(expected.ToString(), actual.ToString()); + + actual.Append(ref iab); + } + + Test(new FormattableInt32Wrapper(42)); + Test(new SpanFormattableInt32Wrapper(84)); + Test((FormattableInt32Wrapper?)new FormattableInt32Wrapper(42)); + Test((SpanFormattableInt32Wrapper?)new SpanFormattableInt32Wrapper(84)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void AppendFormatted_InvalidTryFormatCharsWritten_Throws(bool tooBig) // vs tooSmall + { + StringBuilder.AppendInterpolatedStringHandler iab = new StringBuilder.AppendInterpolatedStringHandler(0, 0, new StringBuilder()); + Assert.Throws(() => iab.AppendFormatted(new InvalidCharsWritten(tooBig))); + } + + private static void AssertModeMatchesType(T tss) where T : IHasToStringState + { + ToStringMode expected = + tss is ISpanFormattable ? ToStringMode.ISpanFormattableTryFormat : + tss is IFormattable ? ToStringMode.IFormattableToString : + ToStringMode.ObjectToString; + Assert.Equal(expected, tss.ToStringState.ToStringMode); + } + + private sealed class SpanFormattableStringWrapper : IFormattable, ISpanFormattable, IHasToStringState + { + private readonly string _value; + public ToStringState ToStringState { get; } = new ToStringState(); + + public SpanFormattableStringWrapper(string value) => _value = value; + + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider provider) + { + ToStringState.LastFormat = format.ToString(); + ToStringState.LastProvider = provider; + ToStringState.ToStringMode = ToStringMode.ISpanFormattableTryFormat; + + if (_value is null) + { + charsWritten = 0; + return true; + } + + if (_value.Length > destination.Length) + { + charsWritten = 0; + return false; + } + + charsWritten = _value.Length; + _value.AsSpan().CopyTo(destination); + return true; + } + + public string ToString(string format, IFormatProvider formatProvider) + { + ToStringState.LastFormat = format; + ToStringState.LastProvider = formatProvider; + ToStringState.ToStringMode = ToStringMode.IFormattableToString; + return _value; + } + + public override string ToString() + { + ToStringState.LastFormat = null; + ToStringState.LastProvider = null; + ToStringState.ToStringMode = ToStringMode.ObjectToString; + return _value; + } + } + + private struct SpanFormattableInt32Wrapper : IFormattable, ISpanFormattable, IHasToStringState + { + private readonly int _value; + public ToStringState ToStringState { get; } + + public SpanFormattableInt32Wrapper(int value) + { + ToStringState = new ToStringState(); + _value = value; + } + + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider provider) + { + ToStringState.LastFormat = format.ToString(); + ToStringState.LastProvider = provider; + ToStringState.ToStringMode = ToStringMode.ISpanFormattableTryFormat; + + return _value.TryFormat(destination, out charsWritten, format, provider); + } + + public string ToString(string format, IFormatProvider formatProvider) + { + ToStringState.LastFormat = format; + ToStringState.LastProvider = formatProvider; + ToStringState.ToStringMode = ToStringMode.IFormattableToString; + return _value.ToString(format, formatProvider); + } + + public override string ToString() + { + ToStringState.LastFormat = null; + ToStringState.LastProvider = null; + ToStringState.ToStringMode = ToStringMode.ObjectToString; + return _value.ToString(); + } + } + + private sealed class FormattableStringWrapper : IFormattable, IHasToStringState + { + private readonly string _value; + public ToStringState ToStringState { get; } = new ToStringState(); + + public FormattableStringWrapper(string s) => _value = s; + + public string ToString(string format, IFormatProvider formatProvider) + { + ToStringState.LastFormat = format; + ToStringState.LastProvider = formatProvider; + ToStringState.ToStringMode = ToStringMode.IFormattableToString; + return _value; + } + + public override string ToString() + { + ToStringState.LastFormat = null; + ToStringState.LastProvider = null; + ToStringState.ToStringMode = ToStringMode.ObjectToString; + return _value; + } + } + + private struct FormattableInt32Wrapper : IFormattable, IHasToStringState + { + private readonly int _value; + public ToStringState ToStringState { get; } + + public FormattableInt32Wrapper(int i) + { + ToStringState = new ToStringState(); + _value = i; + } + + public string ToString(string format, IFormatProvider formatProvider) + { + ToStringState.LastFormat = format; + ToStringState.LastProvider = formatProvider; + ToStringState.ToStringMode = ToStringMode.IFormattableToString; + return _value.ToString(format, formatProvider); + } + + public override string ToString() + { + ToStringState.LastFormat = null; + ToStringState.LastProvider = null; + ToStringState.ToStringMode = ToStringMode.ObjectToString; + return _value.ToString(); + } + } + + private sealed class ToStringState + { + public string LastFormat { get; set; } + public IFormatProvider LastProvider { get; set; } + public ToStringMode ToStringMode { get; set; } + } + + private interface IHasToStringState + { + ToStringState ToStringState { get; } + } + + private enum ToStringMode + { + ObjectToString, + IFormattableToString, + ISpanFormattableTryFormat, + ICustomFormatterFormat, + } + + private sealed class StringWrapper + { + private readonly string _value; + + public StringWrapper(string s) => _value = s; + + public override string ToString() => _value; + } + + private sealed class ConcatFormatter : IFormatProvider, ICustomFormatter + { + public object GetFormat(Type formatType) => formatType == typeof(ICustomFormatter) ? this : null; + + public string Format(string format, object arg, IFormatProvider formatProvider) + { + string s = format + " " + arg + formatProvider; + + if (arg is IHasToStringState tss) + { + // Set after using arg.ToString() in concat above + tss.ToStringState.LastFormat = format; + tss.ToStringState.LastProvider = formatProvider; + tss.ToStringState.ToStringMode = ToStringMode.ICustomFormatterFormat; + } + + return s; + } + } + + private sealed class InvalidCharsWritten : ISpanFormattable + { + private bool _tooBig; + + public InvalidCharsWritten(bool tooBig) => _tooBig = tooBig; + + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider provider) + { + charsWritten = _tooBig ? destination.Length + 1 : -1; + return true; + } + + public string ToString(string format, IFormatProvider formatProvider) => + throw new NotImplementedException(); + } + } +} From f3fedf9f4843b70744b40c5e4268edf511583598 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 18 Jun 2021 17:57:11 -0400 Subject: [PATCH 065/133] Add Debug.Assert/Write{Line}If interpolated overloads --- .../DebugTestsNoListeners.Interpolation.cs | 180 ++++++++++++ .../tests/DebugTestsNoListeners.cs | 2 +- .../System.Diagnostics.Debug.Tests.csproj | 6 +- .../System.Memory/ref/System.Memory.cs | 4 +- .../System.Memory/tests/Span/TryWrite.cs | 38 +-- .../src/System/Diagnostics/Debug.cs | 262 +++++++++++++++--- .../src/System/MemoryExtensions.cs | 21 -- .../src/System/Random.cs | 16 +- .../DefaultInterpolatedStringHandler.cs | 2 +- .../src/System/Text/StringBuilder.cs | 2 +- .../System.Runtime/ref/System.Runtime.cs | 48 ++++ 11 files changed, 487 insertions(+), 94 deletions(-) create mode 100644 src/libraries/System.Diagnostics.Debug/tests/DebugTestsNoListeners.Interpolation.cs diff --git a/src/libraries/System.Diagnostics.Debug/tests/DebugTestsNoListeners.Interpolation.cs b/src/libraries/System.Diagnostics.Debug/tests/DebugTestsNoListeners.Interpolation.cs new file mode 100644 index 000000000000..cd05cdfdc8bd --- /dev/null +++ b/src/libraries/System.Diagnostics.Debug/tests/DebugTestsNoListeners.Interpolation.cs @@ -0,0 +1,180 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#define DEBUG +using System.Text; +using Xunit; + +namespace System.Diagnostics.Tests +{ + public partial class DebugTestsNoListeners : DebugTests + { + [Fact] + public void Asserts_Interpolation() + { + Debug.AssertInterpolatedStringHandler message; + Debug.AssertInterpolatedStringHandler detailedMessage; + bool shouldAppend; + + message = new Debug.AssertInterpolatedStringHandler(0, 0, true, out shouldAppend); + VerifyLogged(() => Debug.Assert(true, message), ""); + + message = new Debug.AssertInterpolatedStringHandler(0, 0, true, out shouldAppend); + detailedMessage = new Debug.AssertInterpolatedStringHandler(0, 0, true, out shouldAppend); + VerifyLogged(() => Debug.Assert(true, message, detailedMessage), ""); + + message = new Debug.AssertInterpolatedStringHandler(0, 0, false, out shouldAppend); + message.AppendLiteral("assert passed"); + VerifyAssert(() => Debug.Assert(false, message), "assert passed"); + + message = new Debug.AssertInterpolatedStringHandler(0, 0, false, out shouldAppend); + message.AppendLiteral("assert passed"); + detailedMessage = new Debug.AssertInterpolatedStringHandler(0, 0, false, out shouldAppend); + detailedMessage.AppendLiteral("nothing is wrong"); + VerifyAssert(() => Debug.Assert(false, message, detailedMessage), "assert passed", "nothing is wrong"); + } + + [Fact] + public void WriteIf_Interpolation() + { + Debug.WriteIfInterpolatedStringHandler handler; + bool shouldAppend; + + handler = new Debug.WriteIfInterpolatedStringHandler(0, 0, true, out shouldAppend); + handler.AppendLiteral("logged"); + VerifyLogged(() => Debug.WriteIf(true, handler), "logged"); + + handler = new Debug.WriteIfInterpolatedStringHandler(0, 0, false, out shouldAppend); + VerifyLogged(() => Debug.WriteIf(false, handler), ""); + + handler = new Debug.WriteIfInterpolatedStringHandler(0, 0, true, out shouldAppend); + handler.AppendLiteral("logged"); + VerifyLogged(() => Debug.WriteIf(true, handler, "category"), "category: logged"); + + handler = new Debug.WriteIfInterpolatedStringHandler(0, 0, false, out shouldAppend); + VerifyLogged(() => Debug.WriteIf(false, handler, "category"), ""); + + GoToNextLine(); + } + + [Fact] + public void WriteLineIf_Interpolation() + { + Debug.WriteIfInterpolatedStringHandler handler; + bool shouldAppend; + + handler = new Debug.WriteIfInterpolatedStringHandler(0, 0, true, out shouldAppend); + handler.AppendLiteral("logged"); + VerifyLogged(() => Debug.WriteLineIf(true, handler), "logged" + Environment.NewLine); + + handler = new Debug.WriteIfInterpolatedStringHandler(0, 0, false, out shouldAppend); + VerifyLogged(() => Debug.WriteLineIf(false, handler), ""); + + handler = new Debug.WriteIfInterpolatedStringHandler(0, 0, true, out shouldAppend); + handler.AppendLiteral("logged"); + VerifyLogged(() => Debug.WriteLineIf(true, handler, "category"), "category: logged" + Environment.NewLine); + + handler = new Debug.WriteIfInterpolatedStringHandler(0, 0, false, out shouldAppend); + VerifyLogged(() => Debug.WriteLineIf(false, handler, "category"), ""); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Condition_ShouldAppend_Matches(bool condition) + { + bool shouldAppend; + + new Debug.AssertInterpolatedStringHandler(1, 2, condition, out shouldAppend); + Assert.Equal(!condition, shouldAppend); + + new Debug.WriteIfInterpolatedStringHandler(1, 2, condition, out shouldAppend); + Assert.Equal(condition, shouldAppend); + } + + [Fact] + public void DebugHandler_AppendOverloads_MatchStringBuilderHandler() + { + var actual = new Debug.AssertInterpolatedStringHandler(0, 0, condition: false, out bool shouldAppend); + Assert.True(shouldAppend); + + var sb = new StringBuilder(); + var expected = new StringBuilder.AppendInterpolatedStringHandler(0, 0, sb); + + actual.AppendLiteral("abcd"); + expected.AppendLiteral("abcd"); + + actual.AppendFormatted(123); + expected.AppendFormatted(123); + + actual.AppendFormatted(45.6, 10); + expected.AppendFormatted(45.6, 10); + + actual.AppendFormatted(default(Guid), "X"); + expected.AppendFormatted(default(Guid), "X"); + + DateTime dt = DateTime.UtcNow; + actual.AppendFormatted(dt, -100, "r"); + expected.AppendFormatted(dt, -100, "r"); + + actual.AppendFormatted("hello"); + expected.AppendFormatted("hello"); + + actual.AppendFormatted("world", -10, null); + expected.AppendFormatted("world", -10, null); + + actual.AppendFormatted((ReadOnlySpan)"nice to"); + expected.AppendFormatted((ReadOnlySpan)"nice to"); + + actual.AppendFormatted((ReadOnlySpan)"nice to", 0, "anything"); + expected.AppendFormatted((ReadOnlySpan)"nice to", 0, "anything"); + + actual.AppendFormatted((object)DayOfWeek.Monday, 42, null); + expected.AppendFormatted((object)DayOfWeek.Monday, 42, null); + + VerifyAssert(() => Debug.Assert(false, actual), sb.ToString()); + } + + [Fact] + public void WriteIfHandler_AppendOverloads_MatchStringBuilderHandler() + { + var actual = new Debug.WriteIfInterpolatedStringHandler(0, 0, condition: true, out bool shouldAppend); + Assert.True(shouldAppend); + + var sb = new StringBuilder(); + var expected = new StringBuilder.AppendInterpolatedStringHandler(0, 0, sb); + + actual.AppendLiteral("abcd"); + expected.AppendLiteral("abcd"); + + actual.AppendFormatted(123); + expected.AppendFormatted(123); + + actual.AppendFormatted(45.6, 10); + expected.AppendFormatted(45.6, 10); + + actual.AppendFormatted(default(Guid), "X"); + expected.AppendFormatted(default(Guid), "X"); + + DateTime dt = DateTime.UtcNow; + actual.AppendFormatted(dt, -100, "r"); + expected.AppendFormatted(dt, -100, "r"); + + actual.AppendFormatted("hello"); + expected.AppendFormatted("hello"); + + actual.AppendFormatted("world", -10, null); + expected.AppendFormatted("world", -10, null); + + actual.AppendFormatted((ReadOnlySpan)"nice to"); + expected.AppendFormatted((ReadOnlySpan)"nice to"); + + actual.AppendFormatted((ReadOnlySpan)"nice to", 0, "anything"); + expected.AppendFormatted((ReadOnlySpan)"nice to", 0, "anything"); + + actual.AppendFormatted((object)DayOfWeek.Monday, 42, null); + expected.AppendFormatted((object)DayOfWeek.Monday, 42, null); + + VerifyLogged(() => Debug.WriteIf(true, actual), sb.ToString()); + } + } +} diff --git a/src/libraries/System.Diagnostics.Debug/tests/DebugTestsNoListeners.cs b/src/libraries/System.Diagnostics.Debug/tests/DebugTestsNoListeners.cs index cabe5a7f4ec9..4277946a7a29 100644 --- a/src/libraries/System.Diagnostics.Debug/tests/DebugTestsNoListeners.cs +++ b/src/libraries/System.Diagnostics.Debug/tests/DebugTestsNoListeners.cs @@ -9,7 +9,7 @@ namespace System.Diagnostics.Tests // These tests test the static Debug class. They cannot be run in parallel // DebugTestsNoListeners: tests Debug behavior before Debug is set with Trace Listeners. [Collection("System.Diagnostics.Debug")] - public class DebugTestsNoListeners : DebugTests + public partial class DebugTestsNoListeners : DebugTests { protected override bool DebugUsesTraceListeners { get { return false; } } diff --git a/src/libraries/System.Diagnostics.Debug/tests/System.Diagnostics.Debug.Tests.csproj b/src/libraries/System.Diagnostics.Debug/tests/System.Diagnostics.Debug.Tests.csproj index 6e8c0ad320d5..e842a465e60c 100644 --- a/src/libraries/System.Diagnostics.Debug/tests/System.Diagnostics.Debug.Tests.csproj +++ b/src/libraries/System.Diagnostics.Debug/tests/System.Diagnostics.Debug.Tests.csproj @@ -10,12 +10,12 @@ - + + + diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index 1466b1fa1b7b..1ddd8df16a6b 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -151,8 +151,8 @@ public ref struct TryWriteInterpolatedStringHandler { private readonly object _dummy; private readonly int _dummyPrimitive; - public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, System.Span destination, out bool success) { throw null; } - public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, System.Span destination, IFormatProvider? provider, out bool success) { throw null; } + public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, System.Span destination, out bool shouldAppend) { throw null; } + public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, System.Span destination, IFormatProvider? provider, out bool shouldAppend) { throw null; } public bool AppendLiteral(string value) { throw null; } public bool AppendFormatted(System.ReadOnlySpan value) { throw null; } public bool AppendFormatted(System.ReadOnlySpan value, int alignment = 0, string? format = null) { throw null; } diff --git a/src/libraries/System.Memory/tests/Span/TryWrite.cs b/src/libraries/System.Memory/tests/Span/TryWrite.cs index 993f68fc36d7..b3d7987bfb65 100644 --- a/src/libraries/System.Memory/tests/Span/TryWrite.cs +++ b/src/libraries/System.Memory/tests/Span/TryWrite.cs @@ -24,32 +24,32 @@ public class TryWriteTests [InlineData(-16, 1)] public void LengthAndHoleArguments_Valid(int literalLength, int formattedCount) { - bool success; + bool shouldAppend; - new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[Math.Max(0, literalLength)], out success); - Assert.True(success); + new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[Math.Max(0, literalLength)], out shouldAppend); + Assert.True(shouldAppend); - new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[1 + Math.Max(0, literalLength)], out success); - Assert.True(success); + new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[1 + Math.Max(0, literalLength)], out shouldAppend); + Assert.True(shouldAppend); if (literalLength > 0) { - new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[literalLength - 1], out success); - Assert.False(success); + new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[literalLength - 1], out shouldAppend); + Assert.False(shouldAppend); } foreach (IFormatProvider provider in new IFormatProvider[] { null, new ConcatFormatter(), CultureInfo.InvariantCulture, CultureInfo.CurrentCulture, new CultureInfo("en-US"), new CultureInfo("fr-FR") }) { - new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[Math.Max(0, literalLength)], out success); - Assert.True(success); + new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[Math.Max(0, literalLength)], out shouldAppend); + Assert.True(shouldAppend); - new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[1 + Math.Max(0, literalLength)], out success); - Assert.True(success); + new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[1 + Math.Max(0, literalLength)], out shouldAppend); + Assert.True(shouldAppend); if (literalLength > 0) { - new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[literalLength - 1], out success); - Assert.False(success); + new MemoryExtensions.TryWriteInterpolatedStringHandler(literalLength, formattedCount, new char[literalLength - 1], out shouldAppend); + Assert.False(shouldAppend); } } } @@ -417,8 +417,8 @@ public void AppendFormatted_EmptyBuffer_ZeroLengthWritesSuccessful() { var buffer = new char[100]; - MemoryExtensions.TryWriteInterpolatedStringHandler b = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, buffer.AsSpan(0, 0), out bool success); - Assert.True(success); + MemoryExtensions.TryWriteInterpolatedStringHandler b = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, buffer.AsSpan(0, 0), out bool shouldAppend); + Assert.True(shouldAppend); Assert.True(b.AppendLiteral("")); Assert.True(b.AppendFormatted((object)"", alignment: 0, format: "X2")); @@ -445,8 +445,8 @@ public void AppendFormatted_BufferTooSmall(int bufferLength) for (int i = 0; i <= 29; i++) { - MemoryExtensions.TryWriteInterpolatedStringHandler b = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, buffer, out bool success); - Assert.True(success); + MemoryExtensions.TryWriteInterpolatedStringHandler b = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, buffer, out bool shouldAppend); + Assert.True(shouldAppend); Assert.True(b.AppendLiteral(new string('s', bufferLength))); @@ -497,8 +497,8 @@ public void AppendFormatted_BufferTooSmall_CustomFormatter() var provider = new ConstFormatter(" "); { - MemoryExtensions.TryWriteInterpolatedStringHandler b = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, buffer.AsSpan(0, 0), provider, out bool success); - Assert.True(success); + MemoryExtensions.TryWriteInterpolatedStringHandler b = new MemoryExtensions.TryWriteInterpolatedStringHandler(0, 0, buffer.AsSpan(0, 0), provider, out bool shouldAppend); + Assert.True(shouldAppend); // don't use custom formatter Assert.True(b.AppendLiteral("")); diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debug.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debug.cs index da744f6d7bee..317e319e3f90 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debug.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debug.cs @@ -4,8 +4,10 @@ // Do not remove this, it is needed to retain calls to these conditional methods in release builds #define DEBUG +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System.Text; using System.Threading; namespace System.Diagnostics @@ -54,37 +56,41 @@ public static int IndentSize } } - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void Close() { } - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void Flush() { } - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void Indent() => IndentLevel++; - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void Unindent() => IndentLevel--; - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void Print(string? message) => WriteLine(message); - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void Print(string format, params object?[] args) => WriteLine(string.Format(null, format, args)); - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void Assert([DoesNotReturnIf(false)] bool condition) => Assert(condition, string.Empty, string.Empty); - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void Assert([DoesNotReturnIf(false)] bool condition, string? message) => Assert(condition, message, string.Empty); - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] + public static void Assert([DoesNotReturnIf(false)] bool condition, [InterpolatedStringHandlerArgument("condition")] AssertInterpolatedStringHandler message) => + Assert(condition, message.ToString()); + + [Conditional("DEBUG")] public static void Assert([DoesNotReturnIf(false)] bool condition, string? message, string? detailMessage) { if (!condition) @@ -93,12 +99,20 @@ public static void Assert([DoesNotReturnIf(false)] bool condition, string? messa } } + [Conditional("DEBUG")] + public static void Assert([DoesNotReturnIf(false)] bool condition, [InterpolatedStringHandlerArgument("condition")] AssertInterpolatedStringHandler message, [InterpolatedStringHandlerArgument("condition")] AssertInterpolatedStringHandler detailMessage) => + Assert(condition, message.ToString(), detailMessage.ToString()); + + [Conditional("DEBUG")] + public static void Assert([DoesNotReturnIf(false)] bool condition, string? message, string detailMessageFormat, params object?[] args) => + Assert(condition, message, string.Format(detailMessageFormat, args)); + internal static void ContractFailure(string message, string detailMessage, string failureKindMessage) { string stackTrace; try { - stackTrace = new StackTrace(2, true).ToString(System.Diagnostics.StackTrace.TraceFormat.Normal); + stackTrace = new StackTrace(2, true).ToString(StackTrace.TraceFormat.Normal); } catch { @@ -108,42 +122,38 @@ internal static void ContractFailure(string message, string detailMessage, strin DebugProvider.FailCore(stackTrace, message, detailMessage, failureKindMessage); } - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] [DoesNotReturn] public static void Fail(string? message) => Fail(message, string.Empty); - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] // Preserve the frame for debugger public static void Fail(string? message, string? detailMessage) => s_provider.Fail(message, detailMessage); - [System.Diagnostics.Conditional("DEBUG")] - public static void Assert([DoesNotReturnIf(false)] bool condition, string? message, string detailMessageFormat, params object?[] args) => - Assert(condition, message, string.Format(detailMessageFormat, args)); - - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void WriteLine(string? message) => s_provider.WriteLine(message); - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void Write(string? message) => s_provider.Write(message); - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void WriteLine(object? value) => WriteLine(value?.ToString()); - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void WriteLine(object? value, string? category) => WriteLine(value?.ToString(), category); - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void WriteLine(string format, params object?[] args) => WriteLine(string.Format(null, format, args)); - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void WriteLine(string? message, string? category) { if (category == null) @@ -156,11 +166,11 @@ public static void WriteLine(string? message, string? category) } } - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void Write(object? value) => Write(value?.ToString()); - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void Write(string? message, string? category) { if (category == null) @@ -173,11 +183,11 @@ public static void Write(string? message, string? category) } } - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void Write(object? value, string? category) => Write(value?.ToString(), category); - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void WriteIf(bool condition, string? message) { if (condition) @@ -186,7 +196,11 @@ public static void WriteIf(bool condition, string? message) } } - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] + public static void WriteIf(bool condition, [InterpolatedStringHandlerArgument("condition")] WriteIfInterpolatedStringHandler message) => + WriteIf(condition, message.ToString()); + + [Conditional("DEBUG")] public static void WriteIf(bool condition, object? value) { if (condition) @@ -195,7 +209,7 @@ public static void WriteIf(bool condition, object? value) } } - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void WriteIf(bool condition, string? message, string? category) { if (condition) @@ -204,7 +218,11 @@ public static void WriteIf(bool condition, string? message, string? category) } } - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] + public static void WriteIf(bool condition, [InterpolatedStringHandlerArgument("condition")] WriteIfInterpolatedStringHandler message, string? category) => + WriteIf(condition, message.ToString(), category); + + [Conditional("DEBUG")] public static void WriteIf(bool condition, object? value, string? category) { if (condition) @@ -213,7 +231,7 @@ public static void WriteIf(bool condition, object? value, string? category) } } - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void WriteLineIf(bool condition, object? value) { if (condition) @@ -222,7 +240,7 @@ public static void WriteLineIf(bool condition, object? value) } } - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void WriteLineIf(bool condition, object? value, string? category) { if (condition) @@ -231,7 +249,7 @@ public static void WriteLineIf(bool condition, object? value, string? category) } } - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] public static void WriteLineIf(bool condition, string? message) { if (condition) @@ -240,7 +258,11 @@ public static void WriteLineIf(bool condition, string? message) } } - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] + public static void WriteLineIf(bool condition, [InterpolatedStringHandlerArgument("condition")] WriteIfInterpolatedStringHandler message) => + WriteLineIf(condition, message.ToString()); + + [Conditional("DEBUG")] public static void WriteLineIf(bool condition, string? message, string? category) { if (condition) @@ -248,5 +270,179 @@ public static void WriteLineIf(bool condition, string? message, string? category WriteLine(message, category); } } + + [Conditional("DEBUG")] + public static void WriteLineIf(bool condition, [InterpolatedStringHandlerArgument("condition")] WriteIfInterpolatedStringHandler message, string? category) => + WriteLineIf(condition, message.ToString(), category); + + /// Provides an interpolated string handler for that only performs formatting if the assert fails. + [EditorBrowsable(EditorBrowsableState.Never)] + [InterpolatedStringHandler] + public struct AssertInterpolatedStringHandler + { + /// The handler we use to perform the formatting. + private StringBuilder.AppendInterpolatedStringHandler _stringBuilderHandler; + + /// Creates an instance of the handler.. + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// The condition Boolean passed to the method. + /// A value indicating whether formatting should proceed. + /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. + public AssertInterpolatedStringHandler(int literalLength, int formattedCount, bool condition, out bool shouldAppend) + { + if (condition) + { + _stringBuilderHandler = default; + shouldAppend = false; + } + else + { + _stringBuilderHandler = new StringBuilder.AppendInterpolatedStringHandler(literalLength, formattedCount, new StringBuilder(DefaultInterpolatedStringHandler.GetDefaultLength(literalLength, formattedCount))); + shouldAppend = true; + } + } + + /// Extracts the built string from the handler. + internal new string ToString() => + _stringBuilderHandler._stringBuilder is StringBuilder sb ? + sb.ToString() : + string.Empty; + + /// Writes the specified string to the handler. + /// The string to write. + public void AppendLiteral(string value) => _stringBuilderHandler.AppendLiteral(value); + + /// Writes the specified value to the handler. + /// The value to write. + public void AppendFormatted(T value) => _stringBuilderHandler.AppendFormatted(value); + + /// Writes the specified value to the handler. + /// The value to write. + /// The format string. + public void AppendFormatted(T value, string? format) => _stringBuilderHandler.AppendFormatted(value, format); + + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + public void AppendFormatted(T value, int alignment) => _stringBuilderHandler.AppendFormatted(value, alignment); + + /// Writes the specified value to the handler. + /// The value to write. + /// The format string. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + public void AppendFormatted(T value, int alignment, string? format) => _stringBuilderHandler.AppendFormatted(value, alignment, format); + + /// Writes the specified character span to the handler. + /// The span to write. + public void AppendFormatted(ReadOnlySpan value) => _stringBuilderHandler.AppendFormatted(value); + + /// Writes the specified string of chars to the handler. + /// The span to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public void AppendFormatted(ReadOnlySpan value, int alignment = 0, string? format = null) => _stringBuilderHandler.AppendFormatted(value, alignment, format); + + /// Writes the specified value to the handler. + /// The value to write. + public void AppendFormatted(string? value) => _stringBuilderHandler.AppendFormatted(value); + + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public void AppendFormatted(string? value, int alignment = 0, string? format = null) => _stringBuilderHandler.AppendFormatted(value, alignment, format); + + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _stringBuilderHandler.AppendFormatted(value, alignment, format); + } + + /// Provides an interpolated string handler for and that only performs formatting if the condition applies. + [EditorBrowsable(EditorBrowsableState.Never)] + [InterpolatedStringHandler] + public struct WriteIfInterpolatedStringHandler + { + /// The handler we use to perform the formatting. + private StringBuilder.AppendInterpolatedStringHandler _stringBuilderHandler; + + /// Creates an instance of the handler.. + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// The condition Boolean passed to the method. + /// A value indicating whether formatting should proceed. + /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. + public WriteIfInterpolatedStringHandler(int literalLength, int formattedCount, bool condition, out bool shouldAppend) + { + if (condition) + { + _stringBuilderHandler = new StringBuilder.AppendInterpolatedStringHandler(literalLength, formattedCount, new StringBuilder(DefaultInterpolatedStringHandler.GetDefaultLength(literalLength, formattedCount))); + shouldAppend = true; + } + else + { + _stringBuilderHandler = default; + shouldAppend = false; + } + } + + /// Extracts the built string from the handler. + internal new string ToString() => + _stringBuilderHandler._stringBuilder is StringBuilder sb ? + sb.ToString() : + string.Empty; + + /// Writes the specified string to the handler. + /// The string to write. + public void AppendLiteral(string value) => _stringBuilderHandler.AppendLiteral(value); + + /// Writes the specified value to the handler. + /// The value to write. + public void AppendFormatted(T value) => _stringBuilderHandler.AppendFormatted(value); + + /// Writes the specified value to the handler. + /// The value to write. + /// The format string. + public void AppendFormatted(T value, string? format) => _stringBuilderHandler.AppendFormatted(value, format); + + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + public void AppendFormatted(T value, int alignment) => _stringBuilderHandler.AppendFormatted(value, alignment); + + /// Writes the specified value to the handler. + /// The value to write. + /// The format string. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + public void AppendFormatted(T value, int alignment, string? format) => _stringBuilderHandler.AppendFormatted(value, alignment, format); + + /// Writes the specified character span to the handler. + /// The span to write. + public void AppendFormatted(ReadOnlySpan value) => _stringBuilderHandler.AppendFormatted(value); + + /// Writes the specified string of chars to the handler. + /// The span to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public void AppendFormatted(ReadOnlySpan value, int alignment = 0, string? format = null) => _stringBuilderHandler.AppendFormatted(value, alignment, format); + + /// Writes the specified value to the handler. + /// The value to write. + public void AppendFormatted(string? value) => _stringBuilderHandler.AppendFormatted(value); + + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public void AppendFormatted(string? value, int alignment = 0, string? format = null) => _stringBuilderHandler.AppendFormatted(value, alignment, format); + + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _stringBuilderHandler.AppendFormatted(value, alignment, format); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index bcf142a1f8a7..9a6eb161d77f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -2166,27 +2166,6 @@ public bool AppendFormatted(ReadOnlySpan value, int alignment = 0, string? /// Writes the specified value to the handler. /// The value to write. public bool AppendFormatted(string? value) - { - // Fast-path for no custom formatter and a non-null string that fits in the current destination buffer. - if (!_hasCustomFormatter && - value is not null && - value.TryCopyTo(_destination.Slice(_pos))) - { - _pos += value.Length; - return true; - } - - return AppendFormattedSlow(value); - } - - /// Writes the specified value to the handler. - /// The value to write. - /// - /// Slow path to handle a custom formatter, potentially null value, - /// or a string that doesn't fit in the current buffer. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private bool AppendFormattedSlow(string? value) { if (_hasCustomFormatter) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Random.cs b/src/libraries/System.Private.CoreLib/src/System/Random.cs index b09da359a628..396af2e5d6ca 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Random.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Random.cs @@ -201,28 +201,18 @@ private static void AssertInRange(long result, long minInclusive, long maxExclus { if (maxExclusive > minInclusive) { - if (result < minInclusive || result >= maxExclusive) - { - Debug.Fail($"Expected {minInclusive} <= {result} < {maxExclusive}"); - } + Debug.Assert(result >= minInclusive && result < maxExclusive, $"Expected {minInclusive} <= {result} < {maxExclusive}"); } else { - if (result != minInclusive) - { - Debug.Fail($"Expected {minInclusive} == {result}"); - } + Debug.Assert(result == minInclusive, $"Expected {minInclusive} == {result}"); } } [Conditional("DEBUG")] private static void AssertInRange(double result) { - if (result < 0.0 || result >= 1.0) - { - // Avoid calling result.ToString() when the Assert condition is not met - Debug.Fail($"Expected 0.0 <= {result} < 1.0"); - } + Debug.Assert(result >= 0.0 && result < 1.0, $"Expected 0.0 <= {result} < 1.0"); } /// Random implementation that delegates all calls to a ThreadStatic Impl instance. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/DefaultInterpolatedStringHandler.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/DefaultInterpolatedStringHandler.cs index efad54ee6041..ae6fb22f32c8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/DefaultInterpolatedStringHandler.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/DefaultInterpolatedStringHandler.cs @@ -91,7 +91,7 @@ public DefaultInterpolatedStringHandler(int literalLength, int formattedCount, I /// The number of constant characters outside of interpolation expressions in the interpolated string. /// The number of interpolation expressions in the interpolated string. [MethodImpl(MethodImplOptions.AggressiveInlining)] // becomes a constant when inputs are constant - private static int GetDefaultLength(int literalLength, int formattedCount) => + internal static int GetDefaultLength(int literalLength, int formattedCount) => Math.Max(MinimumArrayPoolLength, literalLength + (formattedCount * GuessedLengthPerHole)); /// Gets the built . diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs index 9bec0d084999..8835e0ccae7c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs @@ -2635,7 +2635,7 @@ public struct AppendInterpolatedStringHandler // than an ArgumentNullException. /// The associated StringBuilder to which to append. - private readonly StringBuilder _stringBuilder; + internal readonly StringBuilder _stringBuilder; /// Optional provider to pass to IFormattable.ToString or ISpanFormattable.TryFormat calls. private readonly IFormatProvider? _provider; /// Whether provides an ICustomFormatter. diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 8bd40241869c..43ca97b21d3d 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -8669,9 +8669,13 @@ public static partial class Debug public static void Assert([System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute(false)] bool condition) { } [System.Diagnostics.ConditionalAttribute("DEBUG")] public static void Assert([System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute(false)] bool condition, string? message) { } + [System.Diagnostics.Conditional("DEBUG")] + public static void Assert([System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute(false)] bool condition, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("condition")] System.Diagnostics.Debug.AssertInterpolatedStringHandler message) { } [System.Diagnostics.ConditionalAttribute("DEBUG")] public static void Assert([System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute(false)] bool condition, string? message, string? detailMessage) { } [System.Diagnostics.ConditionalAttribute("DEBUG")] + public static void Assert([System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute(false)] bool condition, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("condition")] System.Diagnostics.Debug.AssertInterpolatedStringHandler message, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("condition")] System.Diagnostics.Debug.AssertInterpolatedStringHandler detailMessage) { } + [System.Diagnostics.ConditionalAttribute("DEBUG")] public static void Assert([System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute(false)] bool condition, string? message, string detailMessageFormat, params object?[] args) { } [System.Diagnostics.ConditionalAttribute("DEBUG")] public static void Close() { } @@ -8705,8 +8709,12 @@ public static void WriteIf(bool condition, object? value) { } public static void WriteIf(bool condition, object? value, string? category) { } [System.Diagnostics.ConditionalAttribute("DEBUG")] public static void WriteIf(bool condition, string? message) { } + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteIf(bool condition, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("condition")] System.Diagnostics.Debug.WriteIfInterpolatedStringHandler message) { } [System.Diagnostics.ConditionalAttribute("DEBUG")] public static void WriteIf(bool condition, string? message, string? category) { } + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteIf(bool condition, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("condition")] System.Diagnostics.Debug.WriteIfInterpolatedStringHandler message, string? category) { } [System.Diagnostics.ConditionalAttribute("DEBUG")] public static void WriteLine(object? value) { } [System.Diagnostics.ConditionalAttribute("DEBUG")] @@ -8724,7 +8732,47 @@ public static void WriteLineIf(bool condition, object? value, string? category) [System.Diagnostics.ConditionalAttribute("DEBUG")] public static void WriteLineIf(bool condition, string? message) { } [System.Diagnostics.ConditionalAttribute("DEBUG")] + public static void WriteLineIf(bool condition, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("condition")] System.Diagnostics.Debug.WriteIfInterpolatedStringHandler message) { } + [System.Diagnostics.ConditionalAttribute("DEBUG")] public static void WriteLineIf(bool condition, string? message, string? category) { } + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public static void WriteLineIf(bool condition, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("condition")] System.Diagnostics.Debug.WriteIfInterpolatedStringHandler message, string? category) { } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + [System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute] + public struct AssertInterpolatedStringHandler + { + private object _dummy; + private int _dummyPrimitive; + public AssertInterpolatedStringHandler(int literalLength, int formattedCount, bool condition, out bool shouldAppend) { throw null; } + public void AppendLiteral(string value) { } + public void AppendFormatted(T value) { } + public void AppendFormatted(T value, string? format) { } + public void AppendFormatted(T value, int alignment) { } + public void AppendFormatted(T value, int alignment, string? format) { } + public void AppendFormatted(ReadOnlySpan value) { } + public void AppendFormatted(ReadOnlySpan value, int alignment = 0, string? format = null) { } + public void AppendFormatted(string? value) { } + public void AppendFormatted(string? value, int alignment = 0, string? format = null) { } + public void AppendFormatted(object? value, int alignment = 0, string? format = null) { } + } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + [System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute] + public struct WriteIfInterpolatedStringHandler + { + private object _dummy; + private int _dummyPrimitive; + public WriteIfInterpolatedStringHandler(int literalLength, int formattedCount, bool condition, out bool shouldAppend) { throw null; } + public void AppendLiteral(string value) { } + public void AppendFormatted(T value) { } + public void AppendFormatted(T value, string? format) { } + public void AppendFormatted(T value, int alignment) { } + public void AppendFormatted(T value, int alignment, string? format) { } + public void AppendFormatted(ReadOnlySpan value) { } + public void AppendFormatted(ReadOnlySpan value, int alignment = 0, string? format = null) { } + public void AppendFormatted(string? value) { } + public void AppendFormatted(string? value, int alignment = 0, string? format = null) { } + public void AppendFormatted(object? value, int alignment = 0, string? format = null) { } + } } [System.AttributeUsageAttribute(System.AttributeTargets.Assembly | System.AttributeTargets.Module, AllowMultiple=false)] public sealed partial class DebuggableAttribute : System.Attribute From 1446bdb7762920357732a5c47dd7d546a696a031 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sat, 10 Jul 2021 10:09:23 -0400 Subject: [PATCH 066/133] Fix LangVersion on several test projects --- .../tests/StaticTestGenerator/StaticTestGenerator.csproj | 4 ++-- .../System.Net.Http.WinHttpHandler.Functional.Tests.csproj | 4 ++-- .../HPackHuffmanBenchmark/HPackHuffmanBenchmark.csproj | 6 +++--- .../generators/System.Private.CoreLib.Generators.csproj | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libraries/Common/tests/StaticTestGenerator/StaticTestGenerator.csproj b/src/libraries/Common/tests/StaticTestGenerator/StaticTestGenerator.csproj index b4107546efdd..035387e854df 100644 --- a/src/libraries/Common/tests/StaticTestGenerator/StaticTestGenerator.csproj +++ b/src/libraries/Common/tests/StaticTestGenerator/StaticTestGenerator.csproj @@ -1,7 +1,7 @@ Exe - net5.0 + net6.0 false preview true @@ -21,4 +21,4 @@ - \ No newline at end of file + diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj index 4b6596d14322..47dbdd201719 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj @@ -1,9 +1,9 @@ - + $(NetCoreAppCurrent)-windows;net48-windows true $(DefineConstants);WINHTTPHANDLER_TEST - 8.0 + 10.0 + Exe - net5.0 + net6.0 ../../../src/Resources/Strings.resx enable - 9.0 + preview diff --git a/src/libraries/System.Private.CoreLib/generators/System.Private.CoreLib.Generators.csproj b/src/libraries/System.Private.CoreLib/generators/System.Private.CoreLib.Generators.csproj index d6a0cb1c6930..bf6c663b5643 100644 --- a/src/libraries/System.Private.CoreLib/generators/System.Private.CoreLib.Generators.csproj +++ b/src/libraries/System.Private.CoreLib/generators/System.Private.CoreLib.Generators.csproj @@ -1,7 +1,7 @@ netstandard2.0 - latest + 10.0 enable false $(NoWarn);CS3001 From dfd618dc648ba9b11dd0f8034f78113d69f223cd Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 12 Jul 2021 06:30:34 -0400 Subject: [PATCH 067/133] Use interpolated strings in more places --- .../Interop.ProcFsStat.ParseMapModules.cs | 3 +- .../Interop.ProcFsStat.TryReadStatusFile.cs | 5 +- .../Linux/procfs/Interop.ProcFsStat.cs | 36 ++--------- .../Interop.GssBuffer.cs | 2 +- .../Interop.OpenSsl.cs | 4 +- .../SspiCli/SecurityPackageInfoClass.cs | 11 +--- .../src/System/CodeDom/CodeTypeReference.cs | 2 +- .../Diagnostics/DebuggerTraceWriter.cs | 3 +- .../Data/Common/DbConnectionOptions.Common.cs | 4 +- .../src/System/Drawing/ColorTranslator.cs | 4 +- .../Common/src/System/IO/RowConfigReader.cs | 2 +- .../Security/NegotiateStreamPal.Windows.cs | 4 +- .../tests/StaticTestGenerator/Program.cs | 24 ++++---- .../System/Net/Http/HttpProtocolTests.cs | 2 +- .../System/Security/Cryptography/ByteUtils.cs | 2 +- .../Xml/XmlCoreTest/ManagedNodeWriter.cs | 12 ++-- .../System/Xml/XmlDiff/XmlDiffDocument.cs | 2 +- .../System/Text/Unicode/ParsedUnicodeData.cs | 2 +- .../src/Microsoft.CSharp.csproj | 1 + .../RuntimeBinder/ComInterop/VariantArray.cs | 2 +- .../System/ComponentModel/Win32Exception.cs | 4 +- .../Microsoft/Win32/RegistryKey.Windows.cs | 2 +- .../Microsoft/CSharp/CSharpCodeGenerator.cs | 6 +- .../Microsoft/VisualBasic/VBCodeGenerator.cs | 2 +- .../Concurrent/PartitionerStatic.cs | 6 +- .../ReflectionModel/GenericServices.cs | 2 +- .../System/ComponentModel/Design/CommandID.cs | 2 +- .../ReflectTypeDescriptionProvider.cs | 2 +- .../src/System/Configuration/XmlUtilWriter.cs | 2 +- .../System.Console/src/System/TermInfo.cs | 5 +- .../Data/Common/DbConnectionStringBuilder.cs | 2 +- .../src/System/Data/DataException.cs | 2 +- .../src/System/Data/Filter/AggregateNode.cs | 2 +- .../src/System/Data/Filter/FunctionNode.cs | 36 +++++------ .../src/System/Data/Filter/NameNode.cs | 2 +- .../src/System/Data/ForeignKeyConstraint.cs | 4 +- .../src/System/Data/Merger.cs | 2 +- .../src/System/Data/SQLTypes/SQLBinary.cs | 3 +- .../src/System/Data/SQLTypes/SQLDecimal.cs | 2 +- .../src/System/Data/XDRSchema.cs | 6 +- .../src/System/Data/XMLSchema.cs | 10 ++-- .../src/System/Data/xmlsaver.cs | 2 +- .../tests/System/Data/DataSetTest2.cs | 4 +- .../src/System/Data/Odbc/OdbcParameter.cs | 2 +- .../tests/TestCommon/DataTestUtility.cs | 7 +-- .../System.Data.OleDb/src/ColumnBinding.cs | 2 +- .../src/DbConnectionOptions.cs | 8 +-- .../System.Data.OleDb/src/DbPropSet.cs | 2 +- .../System.Data.OleDb/src/OleDbConnection.cs | 2 +- .../System.Data.OleDb/src/OleDbDataReader.cs | 18 +++--- .../System.Data.OleDb/src/OleDbStruct.cs | 52 ++++++++-------- .../System.Data.OleDb/src/OleDb_Util.cs | 8 +-- .../src/System/Data/Common/AdapterUtil.cs | 10 ++-- .../Diagnostics/FileVersionInfo.Windows.cs | 28 +++++---- .../Diagnostics/TextWriterTraceListener.cs | 2 +- .../src/System/Diagnostics/TraceListener.cs | 14 +++-- .../src/System/Drawing/Brush.cs | 2 +- .../Drawing/Drawing2D/GraphicsPath.Windows.cs | 2 +- .../Drawing/Drawing2D/GraphicsPathIterator.cs | 2 +- .../Drawing2D/SafeCustomLineCapHandle.cs | 2 +- .../src/System/Drawing/Font.Unix.cs | 2 +- .../src/System/Drawing/Font.cs | 2 +- .../src/System/Drawing/FontConverter.cs | 6 +- .../src/System/Drawing/FontFamily.cs | 2 +- .../src/System/Drawing/Graphics.Windows.cs | 2 +- .../src/System/Drawing/Image.Windows.cs | 2 +- .../System/Drawing/Imaging/FrameDimension.cs | 2 +- .../System/Drawing/Imaging/ImageAttributes.cs | 2 +- .../src/System/Drawing/Imaging/ImageFormat.cs | 2 +- .../Drawing/Internal/SystemColorTracker.cs | 2 +- .../src/System/Drawing/Pen.cs | 2 +- .../src/System/Drawing/Printing/Margins.cs | 10 +--- .../Drawing/Printing/PageSettings.Unix.cs | 6 +- .../Drawing/Printing/PageSettings.Windows.cs | 13 +--- .../src/System/Drawing/Printing/PaperSize.cs | 9 +-- .../System/Drawing/Printing/PaperSource.cs | 7 +-- .../Drawing/Printing/PrintDocument.Unix.cs | 5 +- .../Drawing/Printing/PrintDocument.Windows.cs | 5 +- .../Drawing/Printing/PrinterSettings.Unix.cs | 8 +-- .../Printing/PrinterSettings.Windows.cs | 20 +++---- .../src/System/Drawing/Region.cs | 2 +- .../src/System/Drawing/StringFormat.cs | 7 +-- .../Drawing/Text/PrivateFontCollection.cs | 2 +- .../src/System/Drawing/Color.cs | 19 ++---- .../src/System/Drawing/Point.cs | 2 +- .../src/System/Drawing/PointF.cs | 2 +- .../src/System/Drawing/Rectangle.cs | 4 +- .../src/System/Drawing/RectangleF.cs | 4 +- .../src/System/Drawing/Size.cs | 2 +- .../src/System/Drawing/SizeF.cs | 2 +- .../tests/Writer/WriteInteger.cs | 4 +- .../tests/Normalization/NormalizationAll.cs | 3 +- .../Globalization/CharUnicodeInfoTests.cs | 2 +- .../tests/PortedCommon/CommonUtilities.cs | 2 +- .../tests/NonCryptoHashTestDriver.cs | 2 +- .../IO/IsolatedStorage/IsolatedStorage.cs | 2 +- .../MemoryMappedFile.Unix.cs | 2 +- .../tests/Infrastructure/TestMemoryPool.cs | 2 +- .../System/IO/Pipes/PipeCompletionSource.cs | 2 +- .../System/IO/Ports/SerialStream.Windows.cs | 6 +- .../tests/SerialPort/GetPortNames.cs | 4 +- .../Expressions/Interpreter/BranchLabel.cs | 6 +- .../Interpreter/InstructionList.cs | 4 +- .../Expressions/Interpreter/LightCompiler.cs | 6 +- .../Expressions/Interpreter/LightLambda.cs | 2 +- .../Expressions/Interpreter/LocalVariables.cs | 6 +- .../Interpreter/StackOperations.cs | 6 +- .../System/Net/Http/WinHttpRequestState.cs | 2 +- .../src/System/Net/Http/WinHttpTraceHelper.cs | 2 +- .../Net/Http/Headers/AltSvcHeaderValue.cs | 5 +- .../Http/Headers/CacheControlHeaderValue.cs | 8 +-- .../System/Net/Http/Headers/HttpHeaders.cs | 2 +- .../Net/Http/Headers/RangeItemHeaderValue.cs | 13 ++-- .../Headers/StringWithQualityHeaderValue.cs | 2 +- .../Net/Http/Headers/WarningHeaderValue.cs | 2 +- .../src/System/Net/Http/HttpContent.cs | 24 ++++---- .../AuthenticationHelper.NtAuth.cs | 2 +- .../SocketsHttpHandler/Http3Connection.cs | 3 +- .../SocketsHttpHandler/HttpConnectionPool.cs | 9 +-- .../src/System/Net/HttpListenerException.cs | 8 +-- .../Net/HttpListenerRequestUriBuilder.cs | 8 +-- .../Net/Windows/HttpListener.Windows.cs | 4 +- .../Net/Windows/HttpRequestStream.Windows.cs | 4 +- .../Windows/HttpResponseStreamAsyncResult.cs | 2 +- .../Windows/ListenerAsyncResult.Windows.cs | 2 +- .../WebSockets/HttpWebSocket.Windows.cs | 9 --- .../Net/Windows/WebSockets/WebSocketBase.cs | 60 +------------------ .../Net/Windows/WebSockets/WebSocketBuffer.cs | 36 ++++------- .../System/Net/Mail/DomainLiteralReader.cs | 2 +- .../src/System/Net/Mail/DotAtomReader.cs | 2 +- .../src/System/Net/Mail/MailAddressParser.cs | 8 +-- .../src/System/Net/Mail/QuotedPairReader.cs | 6 +- .../Net/Mail/QuotedStringFormatReader.cs | 6 +- .../src/System/Net/Mail/SmtpClient.cs | 2 +- .../src/System/Net/Mime/MimeMultiPart.cs | 7 +-- .../src/System/Net/NameResolutionPal.Unix.cs | 2 +- .../StringParsingHelpers.Addresses.cs | 13 ++-- .../StringParsingHelpers.Connections.cs | 22 ++++--- .../src/System/Net/Cookie.cs | 2 +- .../src/System/Net/CredentialCache.cs | 4 +- .../src/System/Net/Security/SecureChannel.cs | 2 +- .../ExtendedProtectionPolicy.cs | 4 +- .../src/System/Net/WebClient.cs | 2 +- .../src/System/Net/WebProxy.cs | 7 ++- .../Net/WebSockets/WebSocketHandle.Managed.cs | 7 +-- .../src/Internal/Win32/RegistryKey.cs | 2 +- .../src/System/ApplicationId.cs | 12 +--- .../Collections/Generic/KeyValuePair.cs | 34 ++--------- .../Diagnostics/Tracing/ActivityTracker.cs | 2 +- .../Diagnostics/Tracing/EventCounter.cs | 2 +- .../System/Diagnostics/Tracing/EventSource.cs | 12 ++-- .../Diagnostics/Tracing/PollingCounter.cs | 2 +- .../src/System/Environment.Win32.cs | 2 +- .../System/Globalization/DateTimeFormat.cs | 2 +- .../src/System/Globalization/DateTimeParse.cs | 2 +- .../System/Globalization/HebrewCalendar.cs | 2 +- .../src/System/IO/BufferedStream.cs | 4 +- .../Strategies/BufferedFileStreamStrategy.cs | 4 +- .../src/System/MemoryExtensions.cs | 12 ++-- .../src/System/Numerics/Plane.cs | 3 +- .../src/System/OperatingSystem.cs | 5 +- .../src/System/Resources/ResourceReader.cs | 4 +- .../Runtime/InteropServices/COMException.cs | 3 +- .../InteropServices/ExternalException.cs | 3 +- .../StandardOleMarshalObject.Windows.cs | 2 +- .../System.Private.CoreLib/src/System/SR.cs | 2 +- .../System/Text/DecoderExceptionFallback.cs | 4 +- .../Text/EncoderLatin1BestFitFallback.cs | 4 +- .../src/System/Text/Rune.cs | 2 +- .../src/System/Text/UnicodeDebug.cs | 38 ++++-------- .../src/System/Text/UnicodeEncoding.cs | 4 +- ...TimeZoneInfo.FullGlobalizationData.Unix.cs | 2 +- .../src/System/TimeZoneInfo.Unix.cs | 2 +- .../src/System/Version.cs | 60 ++++++------------- .../Serialization/Json/JsonWriterDelegator.cs | 2 +- .../Serialization/Json/XmlJsonWriter.cs | 2 +- .../Serialization/XmlObjectSerializer.cs | 2 +- .../XmlObjectSerializerWriteContext.cs | 4 +- .../Serialization/XmlWriterDelegator.cs | 2 +- .../src/System/Xml/XmlBaseReader.cs | 8 +-- .../src/System/Xml/XmlDictionaryReader.cs | 8 +-- .../src/System/IPv4AddressHelper.cs | 5 +- .../System.Private.Uri/src/System/Uri.cs | 17 ++---- .../IriRelativeFileResolutionTest.cs | 12 ++-- .../UriRelativeResolutionTest.cs | 14 ++--- .../src/System/Xml/Linq/XNodeReader.cs | 3 +- .../XDocument.Common/ManagedNodeWriter.cs | 6 +- .../src/System/Xml/Cache/XPathNode.cs | 6 +- .../Xml/Core/CharEntityEncoderFallback.cs | 4 +- .../src/System/Xml/Core/XmlTextWriter.cs | 2 +- .../src/System/Xml/Schema/ContentValidator.cs | 6 +- .../src/System/Xml/Schema/Inference/Infer.cs | 2 +- .../Xml/Schema/SchemaCollectionCompiler.cs | 2 +- .../src/System/Xml/Schema/XsdDateTime.cs | 2 +- .../Serialization/XmlSerializationReader.cs | 38 ++++++------ .../XmlSerializationReaderILGen.cs | 24 ++++---- .../Serialization/XmlSerializationWriter.cs | 22 +++---- .../XmlSerializationWriterILGen.cs | 12 ++-- .../src/System/Xml/XmlConvert.cs | 8 +-- .../src/System/Xml/XmlException.cs | 7 +-- .../Xml/Xsl/IlGen/IteratorDescriptor.cs | 2 +- .../System/Xml/Xsl/IlGen/OptimizerPatterns.cs | 2 +- .../Xml/Xsl/IlGen/XmlILOptimizerVisitor.cs | 2 +- .../src/System/Xml/Xsl/IlGen/XmlIlVisitor.cs | 2 +- .../System/Xml/Xsl/Runtime/EarlyBoundInfo.cs | 2 +- .../System/Xml/Xsl/Runtime/TreeIterators.cs | 2 +- .../System/Xml/Xsl/Runtime/XmlQueryOutput.cs | 34 +++++------ .../System/Xml/Xsl/Runtime/XmlQueryRuntime.cs | 6 +- .../src/System/Xml/Xsl/Runtime/XslNumber.cs | 2 +- .../src/System/Xml/Xsl/Runtime/XsltConvert.cs | 4 +- .../System/Xml/Xsl/Runtime/XsltFunctions.cs | 2 +- .../src/System/Xml/Xsl/Runtime/XsltLibrary.cs | 2 +- .../System/Xml/Xsl/XPath/XPathQilFactory.cs | 2 +- .../src/System/Xml/Xsl/Xslt/MatcherBuilder.cs | 2 +- .../src/System/Xml/Xsl/Xslt/XsltQilFactory.cs | 2 +- .../XslCompiledTransform.cs | 4 +- .../Metadata/Internal/StringHeap.cs | 2 +- .../tests/src/TestUtils/TestUtils.cs | 2 +- .../src/System/Runtime/Caching/CacheUsage.cs | 2 +- .../Runtime/Caching/HostFileChangeMonitor.cs | 2 +- .../Caching/MemoryCacheEntryChangeMonitor.cs | 4 +- .../Runtime/Caching/PhysicalMemoryMonitor.cs | 9 +-- .../tests/System/Convert.FromHexString.cs | 2 +- .../tests/System/Convert.ToHexString.cs | 2 +- .../tests/System/Math.cs | 4 +- ....InteropServices.RuntimeInformation.csproj | 1 + .../RuntimeInformation.Windows.cs | 5 +- .../RuntimeInformation/RuntimeInformation.cs | 13 ++-- .../Marshal/GetObjectForNativeVariantTests.cs | 2 +- .../BigInteger/BigIntegerToStringTests.cs | 4 +- .../tests/SerializationTypes.RuntimeOnly.cs | 4 +- .../Internal/Cryptography/OidLookup.Unix.cs | 2 +- .../Cryptography/Xml/CanonicalXmlAttribute.cs | 2 +- .../Cryptography/Xml/CanonicalXmlElement.cs | 2 +- .../Xml/CanonicalXmlProcessingInstruction.cs | 2 +- .../Cryptography/Xml/SignedXmlDebugLog.cs | 2 +- .../tests/XmlDsigXsltTransformTest.cs | 2 +- .../ServiceModel/Syndication/FeedUtils.cs | 2 +- .../tests/Utils/XmlDiffDocument.cs | 2 +- .../src/Result/RecognizedPhrase.cs | 2 +- .../src/System/Text/BaseCodePageEncoding.cs | 2 +- .../src/System/Text/DBCSCodePageEncoding.cs | 2 +- .../src/System/Text/EncoderBestFitFallback.cs | 4 +- .../src/System/Text/GB18030Encoding.cs | 8 +-- .../src/System/Text/ISCIIEncoding.cs | 8 +-- .../Text/RegularExpressions/RegexCode.cs | 6 +- .../RegularExpressions/RegexLWCGCompiler.cs | 2 +- .../tests/RegexCharacterSetTests.cs | 2 +- .../Transactions/TransactionsEtwProvider.cs | 6 +- .../tests/ExtensionsTests.cs | 40 ++++++------- .../src/System/Web/HttpUtility.cs | 4 +- .../src/System/Web/Util/HttpEncoder.cs | 3 +- src/tasks/AppleAppBuilder/Xcode.cs | 28 ++++----- .../WasmAppBuilder/PInvokeTableGenerator.cs | 8 +-- 254 files changed, 668 insertions(+), 976 deletions(-) diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs index eeb472a19e5d..8be9f32cbede 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs @@ -15,8 +15,7 @@ internal static partial class procfs { private const string MapsFileName = "/maps"; - private static string GetMapsFilePathForProcess(int pid) => - RootPath + pid.ToString(CultureInfo.InvariantCulture) + MapsFileName; + private static string GetMapsFilePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{MapsFileName}"); internal static ProcessModuleCollection? ParseMapsModules(int pid) { diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.TryReadStatusFile.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.TryReadStatusFile.cs index 5dc24477beef..22b06128926f 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.TryReadStatusFile.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.TryReadStatusFile.cs @@ -30,10 +30,7 @@ internal struct ParsedStatus internal ulong VmPeak; } - internal static string GetStatusFilePathForProcess(int pid) - { - return RootPath + pid.ToString(CultureInfo.InvariantCulture) + StatusFileName; - } + internal static string GetStatusFilePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{StatusFileName}"); internal static bool TryReadStatusFile(int pid, out ParsedStatus result) { diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs index 0ca179b736ec..a85ec0a5f4ff 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs @@ -76,41 +76,17 @@ internal struct ParsedStat //internal long cguest_time; } - internal static string GetExeFilePathForProcess(int pid) - { - return RootPath + pid.ToString(CultureInfo.InvariantCulture) + ExeFileName; - } + internal static string GetExeFilePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{ExeFileName}"); - internal static string GetCmdLinePathForProcess(int pid) - { - return RootPath + pid.ToString(CultureInfo.InvariantCulture) + CmdLineFileName; - } + internal static string GetCmdLinePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{CmdLineFileName}"); - internal static string GetStatFilePathForProcess(int pid) - { - return RootPath + pid.ToString(CultureInfo.InvariantCulture) + StatFileName; - } + internal static string GetStatFilePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{StatFileName}"); - internal static string GetTaskDirectoryPathForProcess(int pid) - { - return RootPath + pid.ToString(CultureInfo.InvariantCulture) + TaskDirectoryName; - } + internal static string GetTaskDirectoryPathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{TaskDirectoryName}"); - internal static string GetFileDescriptorDirectoryPathForProcess(int pid) - { - return RootPath + pid.ToString(CultureInfo.InvariantCulture) + FileDescriptorDirectoryName; - } + internal static string GetFileDescriptorDirectoryPathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{FileDescriptorDirectoryName}"); - private static string GetStatFilePathForThread(int pid, int tid) - { - // Perf note: Calling GetTaskDirectoryPathForProcess will allocate a string, - // which we then use in another Concat call to produce another string. The straightforward alternative, - // though, since we have five input strings, is to use the string.Concat overload that takes a params array. - // This results in allocating not only the params array but also a defensive copy inside of Concat, - // which means allocating two five-element arrays. This two-string approach will result not only in fewer - // allocations, but also typically in less memory allocated, and it's a bit more maintainable. - return GetTaskDirectoryPathForProcess(pid) + tid.ToString(CultureInfo.InvariantCulture) + StatFileName; - } + private static string GetStatFilePathForThread(int pid, int tid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{TaskDirectoryName}{(uint)tid}{StatFileName}"); internal static bool TryReadStatFile(int pid, out ParsedStat result) { diff --git a/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssBuffer.cs b/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssBuffer.cs index a2c6255541e2..f33447929001 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssBuffer.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssBuffer.cs @@ -19,7 +19,7 @@ internal struct GssBuffer : IDisposable internal int Copy(byte[] destination, int offset) { Debug.Assert(destination != null, "target destination cannot be null"); - Debug.Assert((offset >= 0 && offset < destination.Length) || destination.Length == 0, "invalid offset " + offset); + Debug.Assert((offset >= 0 && offset < destination.Length) || destination.Length == 0, $"invalid offset {offset}"); if (_data == IntPtr.Zero || _length == 0) { diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs index 604ac8b4c798..47a9312266e4 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs @@ -295,7 +295,7 @@ internal static int Encrypt(SafeSslHandle context, ReadOnlySpan input, ref { #if DEBUG ulong assertNoError = Crypto.ErrPeekError(); - Debug.Assert(assertNoError == 0, "OpenSsl error queue is not empty, run: 'openssl errstr " + assertNoError.ToString("X") + "' for original error."); + Debug.Assert(assertNoError == 0, $"OpenSsl error queue is not empty, run: 'openssl errstr {assertNoError:X}' for original error."); #endif errorCode = Ssl.SslErrorCode.SSL_ERROR_NONE; @@ -349,7 +349,7 @@ internal static int Decrypt(SafeSslHandle context, Span buffer, out Ssl.Ss { #if DEBUG ulong assertNoError = Crypto.ErrPeekError(); - Debug.Assert(assertNoError == 0, "OpenSsl error queue is not empty, run: 'openssl errstr " + assertNoError.ToString("X") + "' for original error."); + Debug.Assert(assertNoError == 0, $"OpenSsl error queue is not empty, run: 'openssl errstr {assertNoError:X}' for original error."); #endif errorCode = Ssl.SslErrorCode.SSL_ERROR_NONE; diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SecurityPackageInfoClass.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SecurityPackageInfoClass.cs index ea65be9600eb..ada41ba189fc 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SecurityPackageInfoClass.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SecurityPackageInfoClass.cs @@ -59,14 +59,7 @@ internal unsafe SecurityPackageInfoClass(SafeHandle safeHandle, int index) if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, this.ToString()); } - public override string ToString() - { - return "Capabilities:" + string.Format(CultureInfo.InvariantCulture, "0x{0:x}", Capabilities) - + " Version:" + Version.ToString(NumberFormatInfo.InvariantInfo) - + " RPCID:" + RPCID.ToString(NumberFormatInfo.InvariantInfo) - + " MaxToken:" + MaxToken.ToString(NumberFormatInfo.InvariantInfo) - + " Name:" + ((Name == null) ? "(null)" : Name) - + " Comment:" + ((Comment == null) ? "(null)" : Comment); - } + public override string ToString() => + $"Capabilities:0x{Capabilities:x} Version:{Version} RPCID:{RPCID} MaxToken:{MaxToken} Name:{Name ?? "(null)"} Comment: {Comment ?? "(null)"}"; } } diff --git a/src/libraries/Common/src/System/CodeDom/CodeTypeReference.cs b/src/libraries/Common/src/System/CodeDom/CodeTypeReference.cs index 281691f7907f..4ddf9950f5cc 100644 --- a/src/libraries/Common/src/System/CodeDom/CodeTypeReference.cs +++ b/src/libraries/Common/src/System/CodeDom/CodeTypeReference.cs @@ -327,7 +327,7 @@ public string BaseType string returnType = _baseType; return _needsFixup && TypeArguments.Count > 0 ? - returnType + '`' + TypeArguments.Count.ToString(CultureInfo.InvariantCulture) : + $"{returnType}`{(uint)TypeArguments.Count}" : returnType; } set diff --git a/src/libraries/Common/src/System/Composition/Diagnostics/DebuggerTraceWriter.cs b/src/libraries/Common/src/System/Composition/Diagnostics/DebuggerTraceWriter.cs index 8630d366a866..f716210055d6 100644 --- a/src/libraries/Common/src/System/Composition/Diagnostics/DebuggerTraceWriter.cs +++ b/src/libraries/Common/src/System/Composition/Diagnostics/DebuggerTraceWriter.cs @@ -57,8 +57,7 @@ private static string CreateLogMessage(TraceEventType eventType, CompositionTrac StringBuilder messageBuilder = new StringBuilder(); // Format taken from TraceListener.TraceEvent in .NET Framework - messageBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0} {1}: {2} : ", - s_sourceName, eventType.ToString(), (int)traceId); + messageBuilder.Append($"{s_sourceName} {eventType}: {(int)traceId} : "); if (arguments == null) { diff --git a/src/libraries/Common/src/System/Data/Common/DbConnectionOptions.Common.cs b/src/libraries/Common/src/System/Data/Common/DbConnectionOptions.Common.cs index 80a092104ae5..54df9fa77982 100644 --- a/src/libraries/Common/src/System/Data/Common/DbConnectionOptions.Common.cs +++ b/src/libraries/Common/src/System/Data/Common/DbConnectionOptions.Common.cs @@ -525,11 +525,11 @@ private static void ParseComparison(Dictionary parsetable, stri } } } - Debug.Assert(isEquivalent, "ParseInternal code vs regex message mismatch: <" + msg1 + "> <" + msg2 + ">"); + Debug.Assert(isEquivalent, $"ParseInternal code vs regex message mismatch: <{msg1}> <{msg2}>"); } else { - Debug.Fail("ParseInternal code vs regex throw mismatch " + f.Message); + Debug.Fail($"ParseInternal code vs regex throw mismatch {f.Message}"); } e = null; } diff --git a/src/libraries/Common/src/System/Drawing/ColorTranslator.cs b/src/libraries/Common/src/System/Drawing/ColorTranslator.cs index d1caed09be28..b8fc7866abe2 100644 --- a/src/libraries/Common/src/System/Drawing/ColorTranslator.cs +++ b/src/libraries/Common/src/System/Drawing/ColorTranslator.cs @@ -386,9 +386,7 @@ public static string ToHtml(Color c) } else { - colorString = "#" + c.R.ToString("X2", null) + - c.G.ToString("X2", null) + - c.B.ToString("X2", null); + colorString = $"#{c.R:X2}{c.G:X2}{c.B:X2}"; } return colorString; diff --git a/src/libraries/Common/src/System/IO/RowConfigReader.cs b/src/libraries/Common/src/System/IO/RowConfigReader.cs index a6bfd96daf41..571aa9afd9e8 100644 --- a/src/libraries/Common/src/System/IO/RowConfigReader.cs +++ b/src/libraries/Common/src/System/IO/RowConfigReader.cs @@ -132,7 +132,7 @@ private bool TryFindNextKeyOccurrence(string key, int startIndex, out int keyInd } // Check If the match is at the beginning of the string, or is preceded by a newline. else if (keyIndex == 0 - || (keyIndex >= Environment.NewLine.Length && _buffer.Substring(keyIndex - Environment.NewLine.Length, Environment.NewLine.Length) == Environment.NewLine)) + || (keyIndex >= Environment.NewLine.Length && _buffer.AsSpan(keyIndex - Environment.NewLine.Length, Environment.NewLine.Length).SequenceEqual(Environment.NewLine))) { // Check if the match is followed by whitespace, meaning it is not part of a larger word. if (HasFollowingWhitespace(keyIndex, key.Length)) diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs index 4e4e0c672b49..a145951254a7 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs @@ -212,7 +212,7 @@ internal static int VerifySignature(SafeDeleteContext securityContext, byte[] bu // throw if error if (errorCode != 0) { - NetEventSource.Info($"VerifySignature threw error: {errorCode.ToString("x", NumberFormatInfo.InvariantInfo)}"); + NetEventSource.Info($"VerifySignature threw error: {errorCode:x}"); throw new Win32Exception(errorCode); } @@ -256,7 +256,7 @@ internal static int MakeSignature(SafeDeleteContext securityContext, byte[] buff // throw if error if (errorCode != 0) { - NetEventSource.Info($"MakeSignature threw error: {errorCode.ToString("x", NumberFormatInfo.InvariantInfo)}"); + NetEventSource.Info($"MakeSignature threw error: {errorCode:x}"); throw new Win32Exception(errorCode); } diff --git a/src/libraries/Common/tests/StaticTestGenerator/Program.cs b/src/libraries/Common/tests/StaticTestGenerator/Program.cs index 1c25d5ccd4cd..2274463eba96 100644 --- a/src/libraries/Common/tests/StaticTestGenerator/Program.cs +++ b/src/libraries/Common/tests/StaticTestGenerator/Program.cs @@ -710,12 +710,12 @@ private static string EncodeLiteral(object? literal, Type? expectedType) if (literal is IntPtr ptr) { - return $"new IntPtr(0x{((long)ptr).ToString("X")})"; + return $"new IntPtr(0x{(long)ptr:X})"; } if (literal is UIntPtr uptr) { - return $"new UIntPtr(0x{((ulong)uptr).ToString("X")})"; + return $"new UIntPtr(0x{(ulong)uptr:X})"; } string? result = null; @@ -732,34 +732,34 @@ private static string EncodeLiteral(object? literal, Type? expectedType) result = ((bool)literal).ToString().ToLowerInvariant(); break; case TypeCode.Char: - result = $"'\\u{((int)(char)literal).ToString("X4")}'"; + result = $"'\\u{(int)(char)literal:X4}'"; break; case TypeCode.SByte: - result = $"(sbyte)({literal.ToString()})"; + result = $"(sbyte)({literal})"; break; case TypeCode.Byte: - result = $"(byte){literal.ToString()}"; + result = $"(byte){literal}"; break; case TypeCode.Int16: - result = $"(short)({literal.ToString()})"; + result = $"(short)({literal})"; break; case TypeCode.UInt16: - result = $"(ushort){literal.ToString()}"; + result = $"(ushort){literal}"; break; case TypeCode.Int32: - result = $"({literal.ToString()})"; + result = $"({literal})"; break; case TypeCode.UInt32: - result = $"{literal.ToString()}U"; + result = $"{literal}U"; break; case TypeCode.Int64: - result = $"({literal.ToString()}L)"; + result = $"({literal}L)"; break; case TypeCode.UInt64: - result = $"{literal.ToString()}UL"; + result = $"{literal}UL"; break; case TypeCode.Decimal: - result = $"({literal.ToString()}M)"; + result = $"({literal}M)"; break; case TypeCode.Single: result = diff --git a/src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs b/src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs index ec7bb197e81a..b90cffafeab5 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs @@ -447,7 +447,7 @@ await server.AcceptConnectionAsync(async connection => { int bytesRemaining = expectedData.Length - bytesSent; int bytesToSend = rand.Next(1, Math.Min(bytesRemaining, maxChunkSize + 1)); - await connection.WriteStringAsync(bytesToSend.ToString("X") + lineEnding); + await connection.WriteStringAsync($"{bytesToSend:X}{lineEnding}"); await connection.Stream.WriteAsync(new Memory(expectedData, bytesSent, bytesToSend)); await connection.WriteStringAsync(lineEnding); bytesSent += bytesToSend; diff --git a/src/libraries/Common/tests/System/Security/Cryptography/ByteUtils.cs b/src/libraries/Common/tests/System/Security/Cryptography/ByteUtils.cs index 997b09af0cbf..8a1a95bb0984 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/ByteUtils.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/ByteUtils.cs @@ -55,7 +55,7 @@ internal static string ByteArrayToHex(this ReadOnlySpan bytes) for (int i = 0; i < bytes.Length; i++) { - builder.Append(bytes[i].ToString("X2")); + builder.Append($"{bytes[i]:X2}"); } return builder.ToString(); diff --git a/src/libraries/Common/tests/System/Xml/XmlCoreTest/ManagedNodeWriter.cs b/src/libraries/Common/tests/System/Xml/XmlCoreTest/ManagedNodeWriter.cs index a96b25090b70..d51c5e3cf871 100644 --- a/src/libraries/Common/tests/System/Xml/XmlCoreTest/ManagedNodeWriter.cs +++ b/src/libraries/Common/tests/System/Xml/XmlCoreTest/ManagedNodeWriter.cs @@ -109,14 +109,14 @@ public void WriteDocType(string name, string sysid, string pubid, string subset) if (pubid == null) { if (sysid != null) - dt.Append(" SYSTEM " + sysid); + dt.Append($" SYSTEM {sysid}"); } else { - dt.Append(" PUBLIC " + pubid); + dt.Append($" PUBLIC {pubid}"); if (sysid != null) { - dt.Append(" " + sysid); + dt.Append($" {sysid}"); } } @@ -298,7 +298,7 @@ public void PutByte() /// public void PutCData() { - _q.Append(""); + _q.Append($""); } /// @@ -306,7 +306,7 @@ public void PutCData() /// public void PutPI() { - _q.Append(""); + _q.Append($""); } /// @@ -314,7 +314,7 @@ public void PutPI() /// public void PutComment() { - _q.Append(""); + _q.Append($""); } /// diff --git a/src/libraries/Common/tests/System/Xml/XmlDiff/XmlDiffDocument.cs b/src/libraries/Common/tests/System/Xml/XmlDiff/XmlDiffDocument.cs index c765bf4a589a..23cfda1c710f 100644 --- a/src/libraries/Common/tests/System/Xml/XmlDiff/XmlDiffDocument.cs +++ b/src/libraries/Common/tests/System/Xml/XmlDiff/XmlDiffDocument.cs @@ -1594,7 +1594,7 @@ public override void WriteTo(XmlWriter w) w.WriteString(Value); break; default: - Debug.Assert(false, "Wrong type for text-like node : " + this._nodetype.ToString()); + Debug.Assert(false, $"Wrong type for text-like node : {this._nodetype}"); break; } } diff --git a/src/libraries/Common/tests/TestUtilities.Unicode/System/Text/Unicode/ParsedUnicodeData.cs b/src/libraries/Common/tests/TestUtilities.Unicode/System/Text/Unicode/ParsedUnicodeData.cs index 6496568c75bc..c7155243400b 100644 --- a/src/libraries/Common/tests/TestUtilities.Unicode/System/Text/Unicode/ParsedUnicodeData.cs +++ b/src/libraries/Common/tests/TestUtilities.Unicode/System/Text/Unicode/ParsedUnicodeData.cs @@ -151,7 +151,7 @@ private static Dictionary ProcessDerivedNameFile() string baseName = value.PropName[..^1]; for (int i = value.FirstCodePoint; i <= value.LastCodePoint /* inclusive */; i++) { - dict.Add(i, baseName + i.ToString("X4", CultureInfo.InvariantCulture)); + dict.Add(i, $"{baseName}{i:X4}"); } } } diff --git a/src/libraries/Microsoft.CSharp/src/Microsoft.CSharp.csproj b/src/libraries/Microsoft.CSharp/src/Microsoft.CSharp.csproj index 322c4c10680f..8f9de151dfda 100644 --- a/src/libraries/Microsoft.CSharp/src/Microsoft.CSharp.csproj +++ b/src/libraries/Microsoft.CSharp/src/Microsoft.CSharp.csproj @@ -247,6 +247,7 @@ + diff --git a/src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/ComInterop/VariantArray.cs b/src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/ComInterop/VariantArray.cs index 289a05314584..9d2373f0b69f 100644 --- a/src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/ComInterop/VariantArray.cs +++ b/src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/ComInterop/VariantArray.cs @@ -82,7 +82,7 @@ internal static Type GetStructType(int args) // See if we can find an existing type foreach (Type t in s_generatedTypes) { - int arity = int.Parse(t.Name.Substring("VariantArray".Length), CultureInfo.InvariantCulture); + int arity = int.Parse(t.Name.AsSpan("VariantArray".Length), provider: CultureInfo.InvariantCulture); if (size == arity) { return t; diff --git a/src/libraries/Microsoft.Win32.Primitives/src/System/ComponentModel/Win32Exception.cs b/src/libraries/Microsoft.Win32.Primitives/src/System/ComponentModel/Win32Exception.cs index bf78cbc03c92..e3159637dc7e 100644 --- a/src/libraries/Microsoft.Win32.Primitives/src/System/ComponentModel/Win32Exception.cs +++ b/src/libraries/Microsoft.Win32.Primitives/src/System/ComponentModel/Win32Exception.cs @@ -91,11 +91,11 @@ public override string ToString() : NativeErrorCode.ToString(CultureInfo.InvariantCulture); if (HResult == E_FAIL) { - s.AppendFormat(CultureInfo.InvariantCulture, " ({0})", nativeErrorString); + s.AppendFormat($" ({nativeErrorString})"); } else { - s.AppendFormat(CultureInfo.InvariantCulture, " ({0:X8}, {1})", HResult, nativeErrorString); + s.AppendFormat($" ({HResult:X8}, {nativeErrorString})"); } if (!(string.IsNullOrEmpty(message))) diff --git a/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.Windows.cs b/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.Windows.cs index 16b3d1aecfbf..7e9998e9516c 100644 --- a/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.Windows.cs +++ b/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.Windows.cs @@ -173,7 +173,7 @@ private void DeleteValueCore(string name, bool throwOnMissingValue) } // We really should throw an exception here if errorCode was bad, // but we can't for compatibility reasons. - Debug.Assert(errorCode == 0, "RegDeleteValue failed. Here's your error code: " + errorCode); + Debug.Assert(errorCode == 0, $"RegDeleteValue failed. Here's your error code: {errorCode}"); } /// diff --git a/src/libraries/System.CodeDom/src/Microsoft/CSharp/CSharpCodeGenerator.cs b/src/libraries/System.CodeDom/src/Microsoft/CSharp/CSharpCodeGenerator.cs index 0d7bc48f361b..356529d80d2b 100644 --- a/src/libraries/System.CodeDom/src/Microsoft/CSharp/CSharpCodeGenerator.cs +++ b/src/libraries/System.CodeDom/src/Microsoft/CSharp/CSharpCodeGenerator.cs @@ -793,12 +793,12 @@ private void AppendEscapedChar(StringBuilder b, char value) if (b == null) { Output.Write("\\u"); - Output.Write(((int)value).ToString("X4", CultureInfo.InvariantCulture)); + Output.Write(((int)value).ToString("X4")); } else { b.Append("\\u"); - b.Append(((int)value).ToString("X4", CultureInfo.InvariantCulture)); + b.Append(((int)value).ToString("X4")); } } @@ -2501,7 +2501,7 @@ private void GenerateChecksumPragma(CodeChecksumPragma checksumPragma) { foreach (byte b in checksumPragma.ChecksumData) { - Output.Write(b.ToString("X2", CultureInfo.InvariantCulture)); + Output.Write(b.ToString("X2")); } } Output.WriteLine("\""); diff --git a/src/libraries/System.CodeDom/src/Microsoft/VisualBasic/VBCodeGenerator.cs b/src/libraries/System.CodeDom/src/Microsoft/VisualBasic/VBCodeGenerator.cs index a25eb3ed7f4f..5c55dfc06506 100644 --- a/src/libraries/System.CodeDom/src/Microsoft/VisualBasic/VBCodeGenerator.cs +++ b/src/libraries/System.CodeDom/src/Microsoft/VisualBasic/VBCodeGenerator.cs @@ -2273,7 +2273,7 @@ private void GenerateChecksumPragma(CodeChecksumPragma checksumPragma) { foreach (byte b in checksumPragma.ChecksumData) { - Output.Write(b.ToString("X2", CultureInfo.InvariantCulture)); + Output.Write(b.ToString("X2")); } } Output.WriteLine("\")"); diff --git a/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/PartitionerStatic.cs b/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/PartitionerStatic.cs index 1433e38cf956..3d494454c0f2 100644 --- a/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/PartitionerStatic.cs +++ b/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/PartitionerStatic.cs @@ -707,9 +707,9 @@ internal bool GrabChunk(KeyValuePair[] destArray, int requestedCh internal bool GrabChunk_Single(KeyValuePair[] destArray, int requestedChunkSize, ref int actualNumElementsGrabbed) { Debug.Assert(_useSingleChunking, "Expected _useSingleChecking to be true"); - Debug.Assert(requestedChunkSize == 1, "Got requested chunk size of " + requestedChunkSize + " when single-chunking was on"); - Debug.Assert(actualNumElementsGrabbed == 0, "Expected actualNumElementsGrabbed == 0, instead it is " + actualNumElementsGrabbed); - Debug.Assert(destArray.Length == 1, "Expected destArray to be of length 1, instead its length is " + destArray.Length); + Debug.Assert(requestedChunkSize == 1, $"Got requested chunk size of {requestedChunkSize} when single-chunking was on"); + Debug.Assert(actualNumElementsGrabbed == 0, $"Expected actualNumElementsGrabbed == 0, instead it is {actualNumElementsGrabbed}"); + Debug.Assert(destArray.Length == 1, $"Expected destArray to be of length 1, instead its length is {destArray.Length}"); lock (_sharedLock) { diff --git a/src/libraries/System.ComponentModel.Composition/src/System/ComponentModel/Composition/ReflectionModel/GenericServices.cs b/src/libraries/System.ComponentModel.Composition/src/System/ComponentModel/Composition/ReflectionModel/GenericServices.cs index 907b838a8f70..999a67dc4397 100644 --- a/src/libraries/System.ComponentModel.Composition/src/System/ComponentModel/Composition/ReflectionModel/GenericServices.cs +++ b/src/libraries/System.ComponentModel.Composition/src/System/ComponentModel/Composition/ReflectionModel/GenericServices.cs @@ -79,7 +79,7 @@ public static string GetGenericName(string originalGenericName, int[] genericPar string[] genericFormatArgs = new string[genericArity]; for (int i = 0; i < genericParametersOrder.Length; i++) { - genericFormatArgs[genericParametersOrder[i]] = string.Format(CultureInfo.InvariantCulture, "{{{0}}}", i); + genericFormatArgs[genericParametersOrder[i]] = $"{{{i}}}"; } return string.Format(CultureInfo.InvariantCulture, originalGenericName, genericFormatArgs); } diff --git a/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/Design/CommandID.cs b/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/Design/CommandID.cs index 5640c431c5eb..827acc9c4e83 100644 --- a/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/Design/CommandID.cs +++ b/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/Design/CommandID.cs @@ -46,6 +46,6 @@ public override bool Equals([NotNullWhen(true)] object? obj) /// /// Overrides Object's ToString method. /// - public override string ToString() => Guid.ToString() + " : " + ID.ToString(CultureInfo.CurrentCulture); + public override string ToString() => $"{Guid} : {ID}"; } } diff --git a/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/ReflectTypeDescriptionProvider.cs b/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/ReflectTypeDescriptionProvider.cs index f41b9fca2a13..ef3cc90c16db 100644 --- a/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/ReflectTypeDescriptionProvider.cs +++ b/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/ReflectTypeDescriptionProvider.cs @@ -1114,7 +1114,7 @@ private static EventDescriptor[] ReflectGetEvents( #if DEBUG foreach (EventDescriptor dbgEvent in events) { - Debug.Assert(dbgEvent != null, "Holes in event array for type " + type); + Debug.Assert(dbgEvent != null, $"Holes in event array for type {type}"); } #endif eventCache[type] = events; diff --git a/src/libraries/System.Configuration.ConfigurationManager/src/System/Configuration/XmlUtilWriter.cs b/src/libraries/System.Configuration.ConfigurationManager/src/System/Configuration/XmlUtilWriter.cs index 9094bc6c4490..69017ea3367e 100644 --- a/src/libraries/System.Configuration.ConfigurationManager/src/System/Configuration/XmlUtilWriter.cs +++ b/src/libraries/System.Configuration.ConfigurationManager/src/System/Configuration/XmlUtilWriter.cs @@ -176,7 +176,7 @@ internal int AppendEntityRef(string entityRef) internal int AppendCharEntity(char ch) { - string numberToWrite = ((int)ch).ToString("X", CultureInfo.InvariantCulture); + string numberToWrite = ((int)ch).ToString("X"); Write('&'); Write('#'); Write('x'); diff --git a/src/libraries/System.Console/src/System/TermInfo.cs b/src/libraries/System.Console/src/System/TermInfo.cs index eb545649d366..19e94551a6f5 100644 --- a/src/libraries/System.Console/src/System/TermInfo.cs +++ b/src/libraries/System.Console/src/System/TermInfo.cs @@ -245,9 +245,10 @@ private static bool TryOpen(string filePath, [NotNullWhen(true)] out SafeFileHan return null; } + Span stackBuffer = stackalloc char[256]; SafeFileHandle? fd; - if (!TryOpen(directoryPath + "/" + term[0].ToString() + "/" + term, out fd) && // /directory/termFirstLetter/term (Linux) - !TryOpen(directoryPath + "/" + ((int)term[0]).ToString("X") + "/" + term, out fd)) // /directory/termFirstLetterAsHex/term (Mac) + if (!TryOpen(string.Create(null, stackBuffer, $"{directoryPath}/{term[0]}/{term}"), out fd) && // /directory/termFirstLetter/term (Linux) + !TryOpen(string.Create(null, stackBuffer, $"{directoryPath}/{(int)term[0]:X}/{term}"), out fd)) // /directory/termFirstLetterAsHex/term (Mac) { return null; } diff --git a/src/libraries/System.Data.Common/src/System/Data/Common/DbConnectionStringBuilder.cs b/src/libraries/System.Data.Common/src/System/Data/Common/DbConnectionStringBuilder.cs index 27ffe33faa3b..1b6c5fe1b7eb 100644 --- a/src/libraries/System.Data.Common/src/System/Data/Common/DbConnectionStringBuilder.cs +++ b/src/libraries/System.Data.Common/src/System/Data/Common/DbConnectionStringBuilder.cs @@ -242,7 +242,7 @@ public virtual ICollection Values { keylist.MoveNext(); values[i] = this[keylist.Current]; - Debug.Assert(null != values[i], "null value " + keylist.Current); + Debug.Assert(null != values[i], $"null value {keylist.Current}"); } return new ReadOnlyCollection(values); } diff --git a/src/libraries/System.Data.Common/src/System/Data/DataException.cs b/src/libraries/System.Data.Common/src/System/Data/DataException.cs index 301075bf5430..91409b4aa8c2 100644 --- a/src/libraries/System.Data.Common/src/System/Data/DataException.cs +++ b/src/libraries/System.Data.Common/src/System/Data/DataException.cs @@ -649,7 +649,7 @@ public static Exception RemovePrimaryKey(DataTable table) => table.TableName.Len public static Exception RangeArgument(int min, int max) => _Argument(SR.Format(SR.Range_Argument, (min).ToString(CultureInfo.InvariantCulture), (max).ToString(CultureInfo.InvariantCulture))); public static Exception NullRange() => _Data(SR.Range_NullRange); public static Exception NegativeMinimumCapacity() => _Argument(SR.RecordManager_MinimumCapacity); - public static Exception ProblematicChars(char charValue) => _Argument(SR.Format(SR.DataStorage_ProblematicChars, "0x" + ((ushort)charValue).ToString("X", CultureInfo.InvariantCulture))); + public static Exception ProblematicChars(char charValue) => _Argument(SR.Format(SR.DataStorage_ProblematicChars, $"0x{(ushort)charValue:X}")); public static Exception StorageSetFailed() => _Argument(SR.DataStorage_SetInvalidDataType); diff --git a/src/libraries/System.Data.Common/src/System/Data/Filter/AggregateNode.cs b/src/libraries/System.Data.Common/src/System/Data/Filter/AggregateNode.cs index 0dca43b20ca6..edefa695a40c 100644 --- a/src/libraries/System.Data.Common/src/System/Data/Filter/AggregateNode.cs +++ b/src/libraries/System.Data.Common/src/System/Data/Filter/AggregateNode.cs @@ -114,7 +114,7 @@ internal override void Bind(DataTable table, List list) // add column to the dependency list, do not add duplicate columns - Debug.Assert(_column != null, "Failed to bind column " + _columnName); + Debug.Assert(_column != null, $"Failed to bind column {_columnName}"); int i; for (i = 0; i < list.Count; i++) diff --git a/src/libraries/System.Data.Common/src/System/Data/Filter/FunctionNode.cs b/src/libraries/System.Data.Common/src/System/Data/Filter/FunctionNode.cs index 79577f945b94..2202a8c9f811 100644 --- a/src/libraries/System.Data.Common/src/System/Data/Filter/FunctionNode.cs +++ b/src/libraries/System.Data.Common/src/System/Data/Filter/FunctionNode.cs @@ -318,7 +318,7 @@ private object EvalFunction(FunctionId id, object[] argumentValues, DataRow? row switch (id) { case FunctionId.Abs: - Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider)); + Debug.Assert(_argumentCount == 1, $"Invalid argument argumentCount for {s_funcs[_info]._name} : {_argumentCount.ToString(FormatProvider)}"); storageType = DataStorage.GetStorageType(argumentValues[0].GetType()); if (ExpressionNode.IsInteger(storageType)) @@ -329,7 +329,7 @@ private object EvalFunction(FunctionId id, object[] argumentValues, DataRow? row throw ExprException.ArgumentTypeInteger(s_funcs[_info]._name, 1); case FunctionId.cBool: - Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider)); + Debug.Assert(_argumentCount == 1, $"Invalid argument argumentCount for {s_funcs[_info]._name} : {_argumentCount.ToString(FormatProvider)}"); storageType = DataStorage.GetStorageType(argumentValues[0].GetType()); return storageType switch @@ -341,26 +341,26 @@ private object EvalFunction(FunctionId id, object[] argumentValues, DataRow? row _ => throw ExprException.DatatypeConvertion(argumentValues[0].GetType(), typeof(bool)), }; case FunctionId.cInt: - Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider)); + Debug.Assert(_argumentCount == 1, $"Invalid argument argumentCount for {s_funcs[_info]._name} : {_argumentCount.ToString(FormatProvider)}"); return Convert.ToInt32(argumentValues[0], FormatProvider); case FunctionId.cDate: - Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider)); + Debug.Assert(_argumentCount == 1, $"Invalid argument argumentCount for {s_funcs[_info]._name} : {_argumentCount.ToString(FormatProvider)}"); return Convert.ToDateTime(argumentValues[0], FormatProvider); case FunctionId.cDbl: - Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider)); + Debug.Assert(_argumentCount == 1, $"Invalid argument argumentCount for {s_funcs[_info]._name} : {_argumentCount.ToString(FormatProvider)}"); return Convert.ToDouble(argumentValues[0], FormatProvider); case FunctionId.cStr: - Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider)); + Debug.Assert(_argumentCount == 1, $"Invalid argument argumentCount for {s_funcs[_info]._name} : {_argumentCount.ToString(FormatProvider)}"); return Convert.ToString(argumentValues[0], FormatProvider)!; case FunctionId.Charindex: - Debug.Assert(_argumentCount == 2, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider)); + Debug.Assert(_argumentCount == 2, $"Invalid argument argumentCount for {s_funcs[_info]._name} : {_argumentCount.ToString(FormatProvider)}"); - Debug.Assert(argumentValues[0] is string, "Invalid argument type for " + s_funcs[_info]._name); - Debug.Assert(argumentValues[1] is string, "Invalid argument type for " + s_funcs[_info]._name); + Debug.Assert(argumentValues[0] is string, $"Invalid argument type for {s_funcs[_info]._name}"); + Debug.Assert(argumentValues[1] is string, $"Invalid argument type for {s_funcs[_info]._name}"); if (DataStorage.IsObjectNull(argumentValues[0]) || DataStorage.IsObjectNull(argumentValues[1])) return DBNull.Value; @@ -374,7 +374,7 @@ private object EvalFunction(FunctionId id, object[] argumentValues, DataRow? row return ((string)argumentValues[1]).IndexOf((string)argumentValues[0], StringComparison.Ordinal); case FunctionId.Iif: - Debug.Assert(_argumentCount == 3, "Invalid argument argumentCount: " + _argumentCount.ToString(FormatProvider)); + Debug.Assert(_argumentCount == 3, $"Invalid argument argumentCount: {_argumentCount.ToString(FormatProvider)}"); object first = _arguments![0].Eval(row, version); @@ -401,8 +401,8 @@ private object EvalFunction(FunctionId id, object[] argumentValues, DataRow? row return argumentValues[0]; case FunctionId.Len: - Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider)); - Debug.Assert((argumentValues[0] is string) || (argumentValues[0] is SqlString), "Invalid argument type for " + s_funcs[_info]._name); + Debug.Assert(_argumentCount == 1, $"Invalid argument argumentCount for {s_funcs[_info]._name} : {_argumentCount.ToString(FormatProvider)}"); + Debug.Assert((argumentValues[0] is string) || (argumentValues[0] is SqlString), $"Invalid argument type for {s_funcs[_info]._name}"); if (argumentValues[0] is SqlString) { @@ -420,10 +420,10 @@ private object EvalFunction(FunctionId id, object[] argumentValues, DataRow? row case FunctionId.Substring: - Debug.Assert(_argumentCount == 3, "Invalid argument argumentCount: " + _argumentCount.ToString(FormatProvider)); - Debug.Assert((argumentValues[0] is string) || (argumentValues[0] is SqlString), "Invalid first argument " + argumentValues[0].GetType().FullName + " in " + s_funcs[_info]._name); - Debug.Assert(argumentValues[1] is int, "Invalid second argument " + argumentValues[1].GetType().FullName + " in " + s_funcs[_info]._name); - Debug.Assert(argumentValues[2] is int, "Invalid third argument " + argumentValues[2].GetType().FullName + " in " + s_funcs[_info]._name); + Debug.Assert(_argumentCount == 3, $"Invalid argument argumentCount: {_argumentCount.ToString(FormatProvider)}"); + Debug.Assert((argumentValues[0] is string) || (argumentValues[0] is SqlString), $"Invalid first argument {argumentValues[0].GetType().FullName} in {s_funcs[_info]._name}"); + Debug.Assert(argumentValues[1] is int, $"Invalid second argument {argumentValues[1].GetType().FullName} in {s_funcs[_info]._name}"); + Debug.Assert(argumentValues[2] is int, $"Invalid third argument {argumentValues[2].GetType().FullName} in {s_funcs[_info]._name}"); // work around the differences in .NET and VBA implementation of the Substring function // 1. The Argument is 0-based in .NET, and 1-based in VBA @@ -462,8 +462,8 @@ private object EvalFunction(FunctionId id, object[] argumentValues, DataRow? row case FunctionId.Trim: { - Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider)); - Debug.Assert((argumentValues[0] is string) || (argumentValues[0] is SqlString), "Invalid argument type for " + s_funcs[_info]._name); + Debug.Assert(_argumentCount == 1, $"Invalid argument argumentCount for {s_funcs[_info]._name} : {_argumentCount.ToString(FormatProvider)}"); + Debug.Assert((argumentValues[0] is string) || (argumentValues[0] is SqlString), $"Invalid argument type for {s_funcs[_info]._name}"); if (DataStorage.IsObjectNull(argumentValues[0])) return DBNull.Value; diff --git a/src/libraries/System.Data.Common/src/System/Data/Filter/NameNode.cs b/src/libraries/System.Data.Common/src/System/Data/Filter/NameNode.cs index ded0f3982a6c..ea2cc9de54a8 100644 --- a/src/libraries/System.Data.Common/src/System/Data/Filter/NameNode.cs +++ b/src/libraries/System.Data.Common/src/System/Data/Filter/NameNode.cs @@ -58,7 +58,7 @@ internal override void Bind(DataTable table, List list) _found = true; // add column to the dependency list, do not add duplicate columns - Debug.Assert(_column != null, "Failed to bind column " + _name); + Debug.Assert(_column != null, $"Failed to bind column {_name}"); int i; for (i = 0; i < list.Count; i++) diff --git a/src/libraries/System.Data.Common/src/System/Data/ForeignKeyConstraint.cs b/src/libraries/System.Data.Common/src/System/Data/ForeignKeyConstraint.cs index 1d7ee94a478c..39d130e4ff0f 100644 --- a/src/libraries/System.Data.Common/src/System/Data/ForeignKeyConstraint.cs +++ b/src/libraries/System.Data.Common/src/System/Data/ForeignKeyConstraint.cs @@ -504,7 +504,7 @@ internal void CheckCanClearParentTable(DataTable table) internal void CheckCanRemoveParentRow(DataRow row) { - Debug.Assert(Table?.DataSet != null, "Relation " + ConstraintName + " isn't part of a DataSet, so this check shouldn't be happening."); + Debug.Assert(Table?.DataSet != null, $"Relation {ConstraintName} isn't part of a DataSet, so this check shouldn't be happening."); if (!Table.DataSet.EnforceConstraints) { return; @@ -517,7 +517,7 @@ internal void CheckCanRemoveParentRow(DataRow row) internal void CheckCascade(DataRow row, DataRowAction action) { - Debug.Assert(Table?.DataSet != null, "ForeignKeyConstraint " + ConstraintName + " isn't part of a DataSet, so this check shouldn't be happening."); + Debug.Assert(Table?.DataSet != null, $"ForeignKeyConstraint {ConstraintName} isn't part of a DataSet, so this check shouldn't be happening."); if (row._inCascade) { diff --git a/src/libraries/System.Data.Common/src/System/Data/Merger.cs b/src/libraries/System.Data.Common/src/System/Data/Merger.cs index 5a4c7b1179f0..616acba1372b 100644 --- a/src/libraries/System.Data.Common/src/System/Data/Merger.cs +++ b/src/libraries/System.Data.Common/src/System/Data/Merger.cs @@ -608,7 +608,7 @@ private void MergeRelation(DataRelation relation) } else { - Debug.Assert(MissingSchemaAction.Error == _missingSchemaAction, "Unexpected value of MissingSchemaAction parameter : " + _missingSchemaAction.ToString()); + Debug.Assert(MissingSchemaAction.Error == _missingSchemaAction, $"Unexpected value of MissingSchemaAction parameter : {_missingSchemaAction}"); throw ExceptionBuilder.MergeMissingDefinition(relation.RelationName); } } diff --git a/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLBinary.cs b/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLBinary.cs index 3792151f36d9..a757be446e73 100644 --- a/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLBinary.cs +++ b/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLBinary.cs @@ -119,8 +119,7 @@ public int Length /// /// Returns a string describing a object. /// - public override string ToString() => - _value is null ? SQLResource.NullString : "SqlBinary(" + _value.Length.ToString(CultureInfo.InvariantCulture) + ")"; + public override string ToString() => _value is null ? SQLResource.NullString : $"SqlBinary({_value.Length})"; // Unary operators diff --git a/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLDecimal.cs b/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLDecimal.cs index fd41a8fb1912..64e7815e2945 100644 --- a/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLDecimal.cs +++ b/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLDecimal.cs @@ -1911,7 +1911,7 @@ private bool FGt10_38() private bool FGt10_38(Span rglData) { - Debug.Assert(rglData.Length == 4, "rglData.Length == 4", "Wrong array length: " + rglData.Length.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(rglData.Length == 4, "rglData.Length == 4", $"Wrong array length: {rglData.Length}"); return rglData[3] >= 0x4b3b4ca8L && ((rglData[3] > 0x4b3b4ca8L) || (rglData[2] > 0x5a86c47aL) || diff --git a/src/libraries/System.Data.Common/src/System/Data/XDRSchema.cs b/src/libraries/System.Data.Common/src/System/Data/XDRSchema.cs index df76c1da336d..a025c3f7f2be 100644 --- a/src/libraries/System.Data.Common/src/System/Data/XDRSchema.cs +++ b/src/libraries/System.Data.Common/src/System/Data/XDRSchema.cs @@ -130,7 +130,7 @@ internal void LoadSchema(XmlElement schemaRoot, DataSet ds) internal bool IsTextOnlyContent(XmlElement node) { - Debug.Assert(FEqualIdentity(node, Keywords.XDR_ELEMENTTYPE, Keywords.XDRNS), "Invalid node type " + node.LocalName); + Debug.Assert(FEqualIdentity(node, Keywords.XDR_ELEMENTTYPE, Keywords.XDRNS), $"Invalid node type {node.LocalName}"); string value = node.GetAttribute(Keywords.CONTENT); if (value == null || value.Length == 0) @@ -282,12 +282,12 @@ private static NameType FindNameType(string name) // Let's check that we realy don't have this name: foreach (NameType nt in s_mapNameTypeXdr) { - Debug.Assert(nt.name != name, "FindNameType('" + name + "') -- failed. Existed name not found"); + Debug.Assert(nt.name != name, $"FindNameType('{name}') -- failed. Existed name not found"); } #endif throw ExceptionBuilder.UndefinedDatatype(name); } - Debug.Assert(s_mapNameTypeXdr[index].name == name, "FindNameType('" + name + "') -- failed. Wrong name found"); + Debug.Assert(s_mapNameTypeXdr[index].name == name, $"FindNameType('{name}') -- failed. Wrong name found"); return s_mapNameTypeXdr[index]; } diff --git a/src/libraries/System.Data.Common/src/System/Data/XMLSchema.cs b/src/libraries/System.Data.Common/src/System/Data/XMLSchema.cs index 1094e6ed536e..ab2fef64dbc5 100644 --- a/src/libraries/System.Data.Common/src/System/Data/XMLSchema.cs +++ b/src/libraries/System.Data.Common/src/System/Data/XMLSchema.cs @@ -1790,7 +1790,7 @@ public static Type XsdtoClr(string xsdTypeName) #if DEBUG for (int i = 1; i < s_mapNameTypeXsd.Length; ++i) { - Debug.Assert((s_mapNameTypeXsd[i - 1].CompareTo(s_mapNameTypeXsd[i].name)) < 0, "incorrect sorting " + s_mapNameTypeXsd[i].name); + Debug.Assert((s_mapNameTypeXsd[i - 1].CompareTo(s_mapNameTypeXsd[i].name)) < 0, $"incorrect sorting {s_mapNameTypeXsd[i].name}"); } #endif int index = Array.BinarySearch(s_mapNameTypeXsd, xsdTypeName); @@ -1856,7 +1856,7 @@ private static NameType FindNameType(string name) #if DEBUG for (int i = 1; i < s_mapNameTypeXsd.Length; ++i) { - Debug.Assert((s_mapNameTypeXsd[i - 1].CompareTo(s_mapNameTypeXsd[i].name)) < 0, "incorrect sorting " + s_mapNameTypeXsd[i].name); + Debug.Assert((s_mapNameTypeXsd[i - 1].CompareTo(s_mapNameTypeXsd[i].name)) < 0, $"incorrect sorting {s_mapNameTypeXsd[i].name}"); } #endif int index = Array.BinarySearch(s_mapNameTypeXsd, name); @@ -1904,7 +1904,7 @@ internal static bool IsXsdType(string name) #if DEBUG for (int i = 1; i < s_mapNameTypeXsd.Length; ++i) { - Debug.Assert((s_mapNameTypeXsd[i - 1].CompareTo(s_mapNameTypeXsd[i].name)) < 0, "incorrect sorting " + s_mapNameTypeXsd[i].name); + Debug.Assert((s_mapNameTypeXsd[i - 1].CompareTo(s_mapNameTypeXsd[i].name)) < 0, $"incorrect sorting {s_mapNameTypeXsd[i].name}"); } #endif int index = Array.BinarySearch(s_mapNameTypeXsd, name); @@ -1914,12 +1914,12 @@ internal static bool IsXsdType(string name) // Let's check that we realy don't have this name: foreach (NameType nt in s_mapNameTypeXsd) { - Debug.Assert(nt.name != name, "FindNameType('" + name + "') -- failed. Existed name not found"); + Debug.Assert(nt.name != name, $"FindNameType('{name}') -- failed. Existed name not found"); } #endif return false; } - Debug.Assert(s_mapNameTypeXsd[index].name == name, "FindNameType('" + name + "') -- failed. Wrong name found"); + Debug.Assert(s_mapNameTypeXsd[index].name == name, $"FindNameType('{name}') -- failed. Wrong name found"); return true; } diff --git a/src/libraries/System.Data.Common/src/System/Data/xmlsaver.cs b/src/libraries/System.Data.Common/src/System/Data/xmlsaver.cs index 46ad2ac3a806..a65a2798e5f6 100644 --- a/src/libraries/System.Data.Common/src/System/Data/xmlsaver.cs +++ b/src/libraries/System.Data.Common/src/System/Data/xmlsaver.cs @@ -1295,7 +1295,7 @@ internal void HandleColumnType(DataColumn col, XmlDocument dc, XmlElement root, { #if DEBUG // enzol: TO DO: replace the constructor with IsEqual(XmlElement) - // Debug.Assert(col.SimpleType.IsEqual(new SimpleType(elmSimpeType)), "simpleTypes with the same name have to be the same: "+name); + // Debug.Assert(col.SimpleType.IsEqual(new SimpleType(elmSimpeType)), $"simpleTypes with the same name have to be the same: {name}"); #endif } } diff --git a/src/libraries/System.Data.Common/tests/System/Data/DataSetTest2.cs b/src/libraries/System.Data.Common/tests/System/Data/DataSetTest2.cs index d5943b3da388..f2d3d31cdfe7 100644 --- a/src/libraries/System.Data.Common/tests/System/Data/DataSetTest2.cs +++ b/src/libraries/System.Data.Common/tests/System/Data/DataSetTest2.cs @@ -435,7 +435,7 @@ public void GetXml() StringBuilder resultXML = new StringBuilder(); - resultXML.Append("<" + ds.DataSetName + "xmlns=\"namespace\">"); + resultXML.Append($"<{ds.DataSetName}xmlns=\"namespace\">"); resultXML.Append(""); resultXML.Append("1"); @@ -455,7 +455,7 @@ public void GetXml() resultXML.Append("Value5"); resultXML.Append(""); - resultXML.Append(""); + resultXML.Append($""); ds.Tables.Add(dt); string strXML = ds.GetXml(); diff --git a/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcParameter.cs b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcParameter.cs index 08c95dc69d08..7bcba69eb243 100644 --- a/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcParameter.cs +++ b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcParameter.cs @@ -527,7 +527,7 @@ private int GetParameterSize(object? value, int offset, int ordinal) } } } - Debug.Assert((0 <= ccb) && (ccb < 0x3fffffff), "GetParameterSize: out of range " + ccb); + Debug.Assert((0 <= ccb) && (ccb < 0x3fffffff), $"GetParameterSize: out of range {ccb}"); return ccb; } diff --git a/src/libraries/System.Data.Odbc/tests/TestCommon/DataTestUtility.cs b/src/libraries/System.Data.Odbc/tests/TestCommon/DataTestUtility.cs index 5055dfb97ce0..b563a7e7b858 100644 --- a/src/libraries/System.Data.Odbc/tests/TestCommon/DataTestUtility.cs +++ b/src/libraries/System.Data.Odbc/tests/TestCommon/DataTestUtility.cs @@ -23,12 +23,7 @@ public static bool AreConnStringsSetup() // some providers does not support names (Oracle supports up to 30) public static string GetUniqueName(string prefix, string escapeLeft, string escapeRight) { - string uniqueName = string.Format("{0}{1}_{2}_{3}{4}", - escapeLeft, - prefix, - DateTime.Now.Ticks.ToString("X", CultureInfo.InvariantCulture), // up to 8 characters - Guid.NewGuid().ToString().Substring(0, 6), // take the first 6 characters only - escapeRight); + string uniqueName = $"{escapeLeft}{prefix}_{DateTime.Now.Ticks:X}_{Guid.NewGuid().ToString().Substring(0, 6)}{escapeRight}"; return uniqueName; } diff --git a/src/libraries/System.Data.OleDb/src/ColumnBinding.cs b/src/libraries/System.Data.OleDb/src/ColumnBinding.cs index 8eca4d628067..344ccae3b8ab 100644 --- a/src/libraries/System.Data.OleDb/src/ColumnBinding.cs +++ b/src/libraries/System.Data.OleDb/src/ColumnBinding.cs @@ -55,7 +55,7 @@ internal ColumnBinding(OleDbDataReader dataReader, int index, int indexForAccess { Debug.Assert(null != rowbinding, "null rowbinding"); Debug.Assert(null != bindings, "null bindings"); - Debug.Assert(ODB.SizeOf_tagDBBINDING <= offset, "invalid offset" + offset); + Debug.Assert(ODB.SizeOf_tagDBBINDING <= offset, $"invalid offset {offset}"); _dataReader = dataReader; _rowbinding = rowbinding; diff --git a/src/libraries/System.Data.OleDb/src/DbConnectionOptions.cs b/src/libraries/System.Data.OleDb/src/DbConnectionOptions.cs index 2d47d32c4d9f..afce7a7dbc3a 100644 --- a/src/libraries/System.Data.OleDb/src/DbConnectionOptions.cs +++ b/src/libraries/System.Data.OleDb/src/DbConnectionOptions.cs @@ -844,8 +844,8 @@ private static void ParseComparison(Hashtable parsetable, string connectionStrin string keyname = (string)entry.Key; string? value1 = (string?)entry.Value; string? value2 = (string?)parsetable[keyname]; - Debug.Assert(parsetable.Contains(keyname), "ParseInternal code vs. regex mismatch keyname <" + keyname + ">"); - Debug.Assert(value1 == value2, "ParseInternal code vs. regex mismatch keyvalue <" + value1 + "> <" + value2 + ">"); + Debug.Assert(parsetable.Contains(keyname), $"ParseInternal code vs. regex mismatch keyname <{keyname}>"); + Debug.Assert(value1 == value2, $"ParseInternal code vs. regex mismatch keyvalue <{value1}> <{value2}>"); } } @@ -871,11 +871,11 @@ private static void ParseComparison(Hashtable parsetable, string connectionStrin } } } - Debug.Assert(isEquivalent, "ParseInternal code vs regex message mismatch: <" + msg1 + "> <" + msg2 + ">"); + Debug.Assert(isEquivalent, $"ParseInternal code vs regex message mismatch: <{msg1}> <{msg2}>"); } else { - Debug.Assert(false, "ParseInternal code vs regex throw mismatch " + f.Message); + Debug.Assert(false, $"ParseInternal code vs regex throw mismatch {f.Message}"); } e = null; } diff --git a/src/libraries/System.Data.OleDb/src/DbPropSet.cs b/src/libraries/System.Data.OleDb/src/DbPropSet.cs index 756c4612c3f1..c0b719c73737 100644 --- a/src/libraries/System.Data.OleDb/src/DbPropSet.cs +++ b/src/libraries/System.Data.OleDb/src/DbPropSet.cs @@ -254,7 +254,7 @@ internal void SetPropertySet(int index, Guid propertySet, ItagDBPROP[] propertie for (int i = 0; i < properties.Length; ++i) { - Debug.Assert(null != properties[i], "null tagDBPROP " + i.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(null != properties[i], $"null tagDBPROP {i.ToString(CultureInfo.InvariantCulture)}"); IntPtr propertyPtr = ADP.IntPtrOffset(propset.rgProperties, i * ODB.SizeOf_tagDBPROP); Marshal.StructureToPtr(properties[i], propertyPtr, false/*deleteold*/); } diff --git a/src/libraries/System.Data.OleDb/src/OleDbConnection.cs b/src/libraries/System.Data.OleDb/src/OleDbConnection.cs index 6417087aa623..8d78f2ac4932 100644 --- a/src/libraries/System.Data.OleDb/src/OleDbConnection.cs +++ b/src/libraries/System.Data.OleDb/src/OleDbConnection.cs @@ -235,7 +235,7 @@ public void ResetState() break; default: // have to assume everything is okay - Debug.Assert(false, "Unknown 'Connection Status' value " + connectionStatus.ToString("G", CultureInfo.InvariantCulture)); + Debug.Assert(false, $"Unknown 'Connection Status' value {connectionStatus.ToString("G", CultureInfo.InvariantCulture)}"); break; } } diff --git a/src/libraries/System.Data.OleDb/src/OleDbDataReader.cs b/src/libraries/System.Data.OleDb/src/OleDbDataReader.cs index e50ba5a23b1d..e305bfd04167 100644 --- a/src/libraries/System.Data.OleDb/src/OleDbDataReader.cs +++ b/src/libraries/System.Data.OleDb/src/OleDbDataReader.cs @@ -584,7 +584,7 @@ private void BuildSchemaTableInfoTable(int columnCount, IntPtr columnInfos, bool #if DEBUG if (AdapterSwitches.DataSchema.TraceVerbose) { - Debug.WriteLine("OleDbDataReader[" + info.ordinal.ToInt64().ToString(CultureInfo.InvariantCulture) + ", " + dbColumnInfo.pwszName + "]=" + dbType.enumOleDbType.ToString() + "," + dbType.dataSourceType + ", " + dbType.wType); + Debug.WriteLine($"OleDbDataReader[{info.ordinal}, {dbColumnInfo.pwszName}]={dbType.enumOleDbType},{dbType.dataSourceType}, {dbType.wType}"); } #endif rowCount++; @@ -2108,7 +2108,7 @@ private int AppendSchemaPrimaryKey(Hashtable baseColumnNames, object?[] restrict #if DEBUG if (AdapterSwitches.DataSchema.TraceVerbose) { - Debug.WriteLine("PartialKeyColumn detected: <" + name + "> metaindex=" + metaindex); + Debug.WriteLine($"PartialKeyColumn detected: <{name}> metaindex={metaindex}"); } #endif partialPrimaryKey = true; @@ -2199,7 +2199,7 @@ private void AppendSchemaUniqueIndexAsKey(Hashtable baseColumnNames, object?[] r #if DEBUG if (AdapterSwitches.DataSchema.TraceVerbose) { - Debug.WriteLine("MultipleUniqueIndexes detected: <" + uniqueIndexName + "> <" + indexname + ">"); + Debug.WriteLine($"MultipleUniqueIndexes detected: <{uniqueIndexName}> <{indexname}>"); } #endif uniq = null; @@ -2211,7 +2211,7 @@ private void AppendSchemaUniqueIndexAsKey(Hashtable baseColumnNames, object?[] r #if DEBUG if (AdapterSwitches.DataSchema.TraceVerbose) { - Debug.WriteLine("PartialKeyColumn detected: " + name); + Debug.WriteLine($"PartialKeyColumn detected: {name}"); } #endif partialPrimaryKey = true; @@ -2226,7 +2226,7 @@ private void AppendSchemaUniqueIndexAsKey(Hashtable baseColumnNames, object?[] r #if DEBUG if (AdapterSwitches.DataSchema.TraceVerbose) { - Debug.WriteLine("PartialUniqueIndexes detected: <" + uniqueIndexName + "> <" + indexname + ">"); + Debug.WriteLine($"PartialUniqueIndexes detected: <{uniqueIndexName}> <{indexname}>"); } #endif uniq = null; @@ -2247,7 +2247,7 @@ private void AppendSchemaUniqueIndexAsKey(Hashtable baseColumnNames, object?[] r #if DEBUG if (AdapterSwitches.DataSchema.TraceVerbose) { - Debug.WriteLine("upgrade single unique index to be a key: <" + uniqueIndexName + ">"); + Debug.WriteLine($"upgrade single unique index to be a key: <{uniqueIndexName}>"); } #endif // upgrade single unique index to be a key @@ -2498,7 +2498,7 @@ internal void DumpToSchemaTable(UnsafeNativeMethods.IRowset? rowset) #if DEBUG if (AdapterSwitches.DataSchema.TraceVerbose) { - Debug.WriteLine("Filtered Column: DBCOLUMN_GUID=DBCOL_SPECIALCOL DBCOLUMN_NAME=" + info.columnName + " DBCOLUMN_KEYCOLUMN=" + info.isKeyColumn); + Debug.WriteLine($"Filtered Column: DBCOLUMN_GUID=DBCOL_SPECIALCOL DBCOLUMN_NAME={info.columnName} DBCOLUMN_KEYCOLUMN={info.isKeyColumn}"); } #endif info.isHidden = true; @@ -2509,7 +2509,7 @@ internal void DumpToSchemaTable(UnsafeNativeMethods.IRowset? rowset) #if DEBUG if (AdapterSwitches.DataSchema.TraceVerbose) { - Debug.WriteLine("Filtered Column: DBCOLUMN_NUMBER=" + info.ordinal.ToInt64().ToString(CultureInfo.InvariantCulture) + " DBCOLUMN_NAME=" + info.columnName); + Debug.WriteLine($"Filtered Column: DBCOLUMN_NUMBER={info.ordinal} DBCOLUMN_NAME={info.columnName}"); } #endif info.isHidden = true; @@ -2520,7 +2520,7 @@ internal void DumpToSchemaTable(UnsafeNativeMethods.IRowset? rowset) #if DEBUG if (AdapterSwitches.DataSchema.TraceVerbose) { - Debug.WriteLine("Filtered Column: DBCOLUMN_FLAGS=" + info.flags.ToString("X8", null) + " DBCOLUMN_NAME=" + info.columnName); + Debug.WriteLine($"Filtered Column: DBCOLUMN_FLAGS={info.flags:X8} DBCOLUMN_NAME={info.columnName}"); } #endif info.isHidden = true; diff --git a/src/libraries/System.Data.OleDb/src/OleDbStruct.cs b/src/libraries/System.Data.OleDb/src/OleDbStruct.cs index 69a0e4807db7..0c7dd5ad35c8 100644 --- a/src/libraries/System.Data.OleDb/src/OleDbStruct.cs +++ b/src/libraries/System.Data.OleDb/src/OleDbStruct.cs @@ -52,10 +52,10 @@ public override string ToString() { builder.Append("pwszDataSourceType =").Append(Marshal.PtrToStringUni(pwszDataSourceType)).Append(Environment.NewLine); } - builder.Append("\tulParamSize =" + ulParamSize.ToInt64().ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\tdwFlags =0x" + dwFlags.ToString("X4", CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\tPrecision =" + bPrecision.ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\tScale =" + bScale.ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); + builder.AppendLine($"\tulParamSize ={ulParamSize}"); + builder.AppendLine($"\tdwFlags =0x{dwFlags:X4}"); + builder.AppendLine($"\tPrecision ={bPrecision}"); + builder.AppendLine($"\tScale ={bScale}"); return builder.ToString(); } #endif @@ -78,12 +78,12 @@ public override string ToString() builder.Append("tagDBPARAMBINDINFO").Append(Environment.NewLine); if (IntPtr.Zero != pwszDataSourceType) { - builder.Append("pwszDataSourceType =").Append(Marshal.PtrToStringUni(pwszDataSourceType)).Append(Environment.NewLine); + builder.AppendLine($"pwszDataSourceType ={Marshal.PtrToStringUni(pwszDataSourceType)}"); } - builder.Append("\tulParamSize =" + ulParamSize.ToInt64().ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\tdwFlags =0x" + dwFlags.ToString("X4", CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\tPrecision =" + bPrecision.ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\tScale =" + bScale.ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); + builder.AppendLine($"\tulParamSize ={ulParamSize}"); + builder.AppendLine($"\tdwFlags =0x{dwFlags:X4}"); + builder.AppendLine($"\tPrecision ={bPrecision}"); + builder.AppendLine($"\tScale ={bScale}"); return builder.ToString(); } #endif @@ -144,15 +144,15 @@ internal tagDBBINDING() public override string ToString() { StringBuilder builder = new StringBuilder(); - builder.Append("tagDBBINDING").Append(Environment.NewLine); - builder.Append("\tOrdinal =" + iOrdinal.ToInt64().ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\tValueOffset =" + obValue.ToInt64().ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\tLengthOffset=" + obLength.ToInt64().ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\tStatusOffset=" + obStatus.ToInt64().ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\tMaxLength =" + cbMaxLen.ToInt64().ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\tDB_Type =" + ODB.WLookup(wType)).Append(Environment.NewLine); - builder.Append("\tPrecision =" + bPrecision.ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\tScale =" + bScale.ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); + builder.AppendLine("tagDBBINDING"); + builder.AppendLine($"\tOrdinal ={iOrdinal}"); + builder.AppendLine($"\tValueOffset ={obValue}"); + builder.AppendLine($"\tLengthOffset={obLength}"); + builder.AppendLine($"\tStatusOffset={obStatus}"); + builder.AppendLine($"\tMaxLength ={cbMaxLen}"); + builder.AppendLine($"\tDB_Type ={ODB.WLookup(wType)}"); + builder.AppendLine($"\tPrecision ={bPrecision}"); + builder.AppendLine($"\tScale ={bScale}"); return builder.ToString(); } #endif @@ -442,14 +442,14 @@ internal tagDBCOLUMNINFO() public override string ToString() { StringBuilder builder = new StringBuilder(); - builder.Append("tagDBCOLUMNINFO: " + Convert.ToString(pwszName, CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\t" + iOrdinal.ToInt64().ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\t" + "0x" + dwFlags.ToString("X8", CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\t" + ulColumnSize.ToInt64().ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\t" + "0x" + wType.ToString("X2", CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\t" + bPrecision.ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\t" + bScale.ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); - builder.Append("\t" + columnid.eKind.ToString(CultureInfo.InvariantCulture)).Append(Environment.NewLine); + builder.AppendLine($"tagDBCOLUMNINFO: {Convert.ToString(pwszName, CultureInfo.InvariantCulture)}"); + builder.AppendLine($"\t{iOrdinal.ToInt64().ToString(CultureInfo.InvariantCulture)}"); + builder.AppendLine($"\t0x{dwFlags:X8}"); + builder.AppendLine($"\t{ulColumnSize}"); + builder.AppendLine($"\t0x{wType:X2}"); + builder.AppendLine($"\t{bPrecision}"); + builder.AppendLine($"\t{bScale}"); + builder.AppendLine($"\t{columnid.eKind}"); return builder.ToString(); } #endif diff --git a/src/libraries/System.Data.OleDb/src/OleDb_Util.cs b/src/libraries/System.Data.OleDb/src/OleDb_Util.cs index b0a39dea96c7..1426320311c4 100644 --- a/src/libraries/System.Data.OleDb/src/OleDb_Util.cs +++ b/src/libraries/System.Data.OleDb/src/OleDb_Util.cs @@ -697,9 +697,7 @@ internal static string ELookup(OleDbHResult hr) { builder.Length = 0; } - builder.Append("(0x"); - builder.Append(((int)hr).ToString("X8", CultureInfo.InvariantCulture)); - builder.Append(')'); + builder.Append($"(0x{(int)hr:X8})"); return builder.ToString(); } @@ -710,9 +708,7 @@ internal static string WLookup(short id) string? value = (string?)g_wlookpup[id]; if (null == value) { - value = "0x" + ((short)id).ToString("X2", CultureInfo.InvariantCulture) + " " + ((short)id); - value += " " + ((DBTypeEnum)id).ToString(); - g_wlookpup[id] = value; + g_wlookpup[id] = value = $"0x{(short)id:X2} {(short)id} {(DBTypeEnum)id}"; } return value; } diff --git a/src/libraries/System.Data.OleDb/src/System/Data/Common/AdapterUtil.cs b/src/libraries/System.Data.OleDb/src/System/Data/Common/AdapterUtil.cs index f5bacfd62391..473789ae89fb 100644 --- a/src/libraries/System.Data.OleDb/src/System/Data/Common/AdapterUtil.cs +++ b/src/libraries/System.Data.OleDb/src/System/Data/Common/AdapterUtil.cs @@ -266,7 +266,7 @@ internal static ArgumentOutOfRangeException InvalidCommandType(CommandType value case CommandType.Text: case CommandType.StoredProcedure: case CommandType.TableDirect: - Debug.Assert(false, "valid CommandType " + value.ToString()); + Debug.Assert(false, $"valid CommandType {value}"); break; } #endif @@ -283,7 +283,7 @@ internal static ArgumentOutOfRangeException InvalidDataRowVersion(DataRowVersion case DataRowVersion.Current: case DataRowVersion.Original: case DataRowVersion.Proposed: - Debug.Assert(false, "valid DataRowVersion " + value.ToString()); + Debug.Assert(false, $"valid DataRowVersion {value}"); break; } #endif @@ -303,7 +303,7 @@ internal static ArgumentOutOfRangeException InvalidIsolationLevel(IsolationLevel case IsolationLevel.RepeatableRead: case IsolationLevel.Serializable: case IsolationLevel.Snapshot: - Debug.Assert(false, "valid IsolationLevel " + value.ToString()); + Debug.Assert(false, $"valid IsolationLevel {value}"); break; } #endif @@ -320,7 +320,7 @@ internal static ArgumentOutOfRangeException InvalidParameterDirection(ParameterD case ParameterDirection.Output: case ParameterDirection.InputOutput: case ParameterDirection.ReturnValue: - Debug.Assert(false, "valid ParameterDirection " + value.ToString()); + Debug.Assert(false, $"valid ParameterDirection {value}"); break; } #endif @@ -337,7 +337,7 @@ internal static ArgumentOutOfRangeException InvalidUpdateRowSource(UpdateRowSour case UpdateRowSource.OutputParameters: case UpdateRowSource.FirstReturnedRecord: case UpdateRowSource.Both: - Debug.Assert(false, "valid UpdateRowSource " + value.ToString()); + Debug.Assert(false, $"valid UpdateRowSource {value}"); break; } #endif diff --git a/src/libraries/System.Diagnostics.FileVersionInfo/src/System/Diagnostics/FileVersionInfo.Windows.cs b/src/libraries/System.Diagnostics.FileVersionInfo/src/System/Diagnostics/FileVersionInfo.Windows.cs index d92bd9471ba6..ce77b52cc678 100644 --- a/src/libraries/System.Diagnostics.FileVersionInfo/src/System/Diagnostics/FileVersionInfo.Windows.cs +++ b/src/libraries/System.Diagnostics.FileVersionInfo/src/System/Diagnostics/FileVersionInfo.Windows.cs @@ -65,7 +65,7 @@ private unsafe FileVersionInfo(string fileName) private static string ConvertTo8DigitHex(uint value) { - return value.ToString("X8", CultureInfo.InvariantCulture); + return value.ToString("X8"); } private static Interop.Version.VS_FIXEDFILEINFO GetFixedFileInfo(IntPtr memPtr) @@ -126,18 +126,20 @@ private static uint GetVarEntry(IntPtr memPtr) // private bool GetVersionInfoForCodePage(IntPtr memIntPtr, string codepage) { - _companyName = GetFileVersionString(memIntPtr, $"\\\\StringFileInfo\\\\{codepage}\\\\CompanyName"); - _fileDescription = GetFileVersionString(memIntPtr, $"\\\\StringFileInfo\\\\{codepage}\\\\FileDescription"); - _fileVersion = GetFileVersionString(memIntPtr, $"\\\\StringFileInfo\\\\{codepage}\\\\FileVersion"); - _internalName = GetFileVersionString(memIntPtr, $"\\\\StringFileInfo\\\\{codepage}\\\\InternalName"); - _legalCopyright = GetFileVersionString(memIntPtr, $"\\\\StringFileInfo\\\\{codepage}\\\\LegalCopyright"); - _originalFilename = GetFileVersionString(memIntPtr, $"\\\\StringFileInfo\\\\{codepage}\\\\OriginalFilename"); - _productName = GetFileVersionString(memIntPtr, $"\\\\StringFileInfo\\\\{codepage}\\\\ProductName"); - _productVersion = GetFileVersionString(memIntPtr, $"\\\\StringFileInfo\\\\{codepage}\\\\ProductVersion"); - _comments = GetFileVersionString(memIntPtr, $"\\\\StringFileInfo\\\\{codepage}\\\\Comments"); - _legalTrademarks = GetFileVersionString(memIntPtr, $"\\\\StringFileInfo\\\\{codepage}\\\\LegalTrademarks"); - _privateBuild = GetFileVersionString(memIntPtr, $"\\\\StringFileInfo\\\\{codepage}\\\\PrivateBuild"); - _specialBuild = GetFileVersionString(memIntPtr, $"\\\\StringFileInfo\\\\{codepage}\\\\SpecialBuild"); + Span stackBuffer = stackalloc char[256]; + + _companyName = GetFileVersionString(memIntPtr, string.Create(null, stackBuffer, $"\\\\StringFileInfo\\\\{codepage}\\\\CompanyName")); + _fileDescription = GetFileVersionString(memIntPtr, string.Create(null, stackBuffer, $"\\\\StringFileInfo\\\\{codepage}\\\\FileDescription")); + _fileVersion = GetFileVersionString(memIntPtr, string.Create(null, stackBuffer, $"\\\\StringFileInfo\\\\{codepage}\\\\FileVersion")); + _internalName = GetFileVersionString(memIntPtr, string.Create(null, stackBuffer, $"\\\\StringFileInfo\\\\{codepage}\\\\InternalName")); + _legalCopyright = GetFileVersionString(memIntPtr, string.Create(null, stackBuffer, $"\\\\StringFileInfo\\\\{codepage}\\\\LegalCopyright")); + _originalFilename = GetFileVersionString(memIntPtr, string.Create(null, stackBuffer, $"\\\\StringFileInfo\\\\{codepage}\\\\OriginalFilename")); + _productName = GetFileVersionString(memIntPtr, string.Create(null, stackBuffer, $"\\\\StringFileInfo\\\\{codepage}\\\\ProductName")); + _productVersion = GetFileVersionString(memIntPtr, string.Create(null, stackBuffer, $"\\\\StringFileInfo\\\\{codepage}\\\\ProductVersion")); + _comments = GetFileVersionString(memIntPtr, string.Create(null, stackBuffer, $"\\\\StringFileInfo\\\\{codepage}\\\\Comments")); + _legalTrademarks = GetFileVersionString(memIntPtr, string.Create(null, stackBuffer, $"\\\\StringFileInfo\\\\{codepage}\\\\LegalTrademarks")); + _privateBuild = GetFileVersionString(memIntPtr, string.Create(null, stackBuffer, $"\\\\StringFileInfo\\\\{codepage}\\\\PrivateBuild")); + _specialBuild = GetFileVersionString(memIntPtr, string.Create(null, stackBuffer, $"\\\\StringFileInfo\\\\{codepage}\\\\SpecialBuild")); _language = GetFileVersionLanguage(memIntPtr); diff --git a/src/libraries/System.Diagnostics.TextWriterTraceListener/src/System/Diagnostics/TextWriterTraceListener.cs b/src/libraries/System.Diagnostics.TextWriterTraceListener/src/System/Diagnostics/TextWriterTraceListener.cs index 9c4015842b11..28325cc356a9 100644 --- a/src/libraries/System.Diagnostics.TextWriterTraceListener/src/System/Diagnostics/TextWriterTraceListener.cs +++ b/src/libraries/System.Diagnostics.TextWriterTraceListener/src/System/Diagnostics/TextWriterTraceListener.cs @@ -240,7 +240,7 @@ internal void EnsureWriter() } catch (IOException) { - fileNameOnly = Guid.NewGuid().ToString() + fileNameOnly; + fileNameOnly = $"{Guid.NewGuid()}{fileNameOnly}"; fullPath = Path.Combine(dirPath, fileNameOnly); continue; } diff --git a/src/libraries/System.Diagnostics.TraceSource/src/System/Diagnostics/TraceListener.cs b/src/libraries/System.Diagnostics.TraceSource/src/System/Diagnostics/TraceListener.cs index 3935b1609fa1..a8441268574b 100644 --- a/src/libraries/System.Diagnostics.TraceSource/src/System/Diagnostics/TraceListener.cs +++ b/src/libraries/System.Diagnostics.TraceSource/src/System/Diagnostics/TraceListener.cs @@ -184,7 +184,7 @@ public virtual void Close() public virtual void TraceTransfer(TraceEventCache? eventCache, string source, int id, string? message, Guid relatedActivityId) { - TraceEvent(eventCache, source, TraceEventType.Transfer, id, message + ", relatedActivityId=" + relatedActivityId.ToString()); + TraceEvent(eventCache, source, TraceEventType.Transfer, id, string.Create(null, stackalloc char[256], $"{message}, relatedActivityId={relatedActivityId}")); } /// @@ -201,8 +201,8 @@ public virtual void Fail(string? message) public virtual void Fail(string? message, string? detailMessage) { WriteLine(detailMessage is null ? - SR.TraceListenerFail + " " + message : - SR.TraceListenerFail + " " + message + " " + detailMessage); + $"{SR.TraceListenerFail} {message}" : + $"{SR.TraceListenerFail} {message} {detailMessage}"); } /// @@ -390,7 +390,7 @@ public virtual void TraceEvent(TraceEventCache? eventCache, string source, Trace private void WriteHeader(string source, TraceEventType eventType, int id) { - Write($"{source} {eventType.ToString()}: {id.ToString(CultureInfo.InvariantCulture)} : "); + Write(string.Create(CultureInfo.InvariantCulture, stackalloc char[256], $"{source} {eventType}: {id} : ")); } private void WriteFooter(TraceEventCache? eventCache) @@ -425,14 +425,16 @@ private void WriteFooter(TraceEventCache? eventCache) WriteLine(string.Empty); } + Span stackBuffer = stackalloc char[128]; + if (IsEnabled(TraceOptions.ThreadId)) WriteLine("ThreadId=" + eventCache.ThreadId); if (IsEnabled(TraceOptions.DateTime)) - WriteLine("DateTime=" + eventCache.DateTime.ToString("o", CultureInfo.InvariantCulture)); + WriteLine(string.Create(null, stackBuffer, $"DateTime={eventCache.DateTime:o}")); if (IsEnabled(TraceOptions.Timestamp)) - WriteLine("Timestamp=" + eventCache.Timestamp); + WriteLine(string.Create(null, stackBuffer, $"Timestamp={eventCache.Timestamp}")); if (IsEnabled(TraceOptions.Callstack)) WriteLine("Callstack=" + eventCache.Callstack); diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Brush.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Brush.cs index af4e81ea8af7..de964a2b13d3 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Brush.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Brush.cs @@ -47,7 +47,7 @@ protected virtual void Dispose(bool disposing) #endif Gdip.GdipDeleteBrush(new HandleRef(this, _nativeBrush)); #if DEBUG - Debug.Assert(status == Gdip.Ok, "GDI+ returned an error status: " + status.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(status == Gdip.Ok, $"GDI+ returned an error status: {status.ToString(CultureInfo.InvariantCulture)}"); #endif } catch (Exception ex) when (!ClientUtils.IsSecurityOrCriticalException(ex)) diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/GraphicsPath.Windows.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/GraphicsPath.Windows.cs index a1c04e7d2f5c..f0a1bd9aec3b 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/GraphicsPath.Windows.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/GraphicsPath.Windows.cs @@ -94,7 +94,7 @@ private void Dispose(bool disposing) #endif Gdip.GdipDeletePath(new HandleRef(this, _nativePath)); #if DEBUG - Debug.Assert(status == Gdip.Ok, "GDI+ returned an error status: " + status.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(status == Gdip.Ok, $"GDI+ returned an error status: {status.ToString(CultureInfo.InvariantCulture)}"); #endif } catch (Exception ex) diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/GraphicsPathIterator.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/GraphicsPathIterator.cs index 4b4135665eed..8e83841c4f6c 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/GraphicsPathIterator.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/GraphicsPathIterator.cs @@ -38,7 +38,7 @@ private void Dispose(bool disposing) #endif Gdip.GdipDeletePathIter(new HandleRef(this, nativeIter)); #if DEBUG - Debug.Assert(status == Gdip.Ok, "GDI+ returned an error status: " + status.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(status == Gdip.Ok, $"GDI+ returned an error status: {status.ToString(CultureInfo.InvariantCulture)}"); #endif } catch (Exception ex) diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/SafeCustomLineCapHandle.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/SafeCustomLineCapHandle.cs index b01b88392b94..410f4478913b 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/SafeCustomLineCapHandle.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/SafeCustomLineCapHandle.cs @@ -48,7 +48,7 @@ protected override bool ReleaseHandle() { handle = IntPtr.Zero; } - Debug.Assert(status == Gdip.Ok, "GDI+ returned an error status: " + status.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(status == Gdip.Ok, $"GDI+ returned an error status: {status.ToString(CultureInfo.InvariantCulture)}"); } return status == Gdip.Ok; } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Font.Unix.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Font.Unix.cs index 91a932b60dc7..8163d0eefb15 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Font.Unix.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Font.Unix.cs @@ -65,7 +65,7 @@ private void CreateFont(string familyName, float emSize, FontStyle style, Graphi int status = Gdip.GdipCreateFont(new HandleRef(this, family.NativeFamily), emSize, style, unit, out _nativeFont); if (status == Gdip.FontStyleNotFound) - throw new ArgumentException($"Style {style.ToString()} isn't supported by font {familyName}."); + throw new ArgumentException($"Style {style} isn't supported by font {familyName}."); Gdip.CheckStatus(status); } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Font.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Font.cs index 6634ee788416..0a738e0e3e86 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Font.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Font.cs @@ -183,7 +183,7 @@ private void Dispose(bool disposing) #endif Gdip.GdipDeleteFont(new HandleRef(this, _nativeFont)); #if DEBUG - Debug.Assert(status == Gdip.Ok, "GDI+ returned an error status: " + status.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(status == Gdip.Ok, $"GDI+ returned an error status: {status.ToString(CultureInfo.InvariantCulture)}"); #endif } catch (Exception ex) when (!ClientUtils.IsCriticalException(ex)) diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/FontConverter.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/FontConverter.cs index 5bb82adbc808..ace1758b4d57 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/FontConverter.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/FontConverter.cs @@ -40,7 +40,8 @@ public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destina ValueStringBuilder sb = default; sb.Append(font.Name); - sb.Append(culture.TextInfo.ListSeparator[0] + " "); + sb.Append(culture.TextInfo.ListSeparator[0]); + sb.Append(" "); sb.Append(font.Size.ToString(culture.NumberFormat)); switch (font.Unit) @@ -79,7 +80,8 @@ public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destina if (font.Style != FontStyle.Regular) { - sb.Append(culture.TextInfo.ListSeparator[0] + " style="); + sb.Append(culture.TextInfo.ListSeparator[0]); + sb.Append(" style="); sb.Append(font.Style.ToString()); } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/FontFamily.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/FontFamily.cs index 6e0438be8858..89b80a06cbcf 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/FontFamily.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/FontFamily.cs @@ -162,7 +162,7 @@ private void Dispose(bool disposing) #endif Gdip.GdipDeleteFontFamily(new HandleRef(this, _nativeFamily)); #if DEBUG - Debug.Assert(status == Gdip.Ok, "GDI+ returned an error status: " + status.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(status == Gdip.Ok, $"GDI+ returned an error status: {status.ToString(CultureInfo.InvariantCulture)}"); #endif } catch (Exception ex) when (!ClientUtils.IsCriticalException(ex)) diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.Windows.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.Windows.cs index 929ea19c4fad..8dd2ad15b22d 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.Windows.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.Windows.cs @@ -178,7 +178,7 @@ private void Dispose(bool disposing) Gdip.GdipDeleteGraphics(new HandleRef(this, NativeGraphics)); #if DEBUG - Debug.Assert(status == Gdip.Ok, "GDI+ returned an error status: " + status.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(status == Gdip.Ok, $"GDI+ returned an error status: {status.ToString(CultureInfo.InvariantCulture)}"); #endif } catch (Exception ex) when (!ClientUtils.IsSecurityOrCriticalException(ex)) diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Image.Windows.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Image.Windows.cs index ccea5ff42d52..c359e7af7fdd 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Image.Windows.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Image.Windows.cs @@ -95,7 +95,7 @@ protected virtual void Dispose(bool disposing) #endif Gdip.GdipDisposeImage(new HandleRef(this, nativeImage)); #if DEBUG - Debug.Assert(status == Gdip.Ok, "GDI+ returned an error status: " + status.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(status == Gdip.Ok, $"GDI+ returned an error status: {status.ToString(CultureInfo.InvariantCulture)}"); #endif } catch (Exception ex) diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/FrameDimension.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/FrameDimension.cs index 5fc556efad7f..44d36d291cb3 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/FrameDimension.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/FrameDimension.cs @@ -78,7 +78,7 @@ public override string ToString() if (this == s_time) return "Time"; if (this == s_resolution) return "Resolution"; if (this == s_page) return "Page"; - return "[FrameDimension: " + _guid + "]"; + return $"[FrameDimension: {_guid}]"; } } } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/ImageAttributes.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/ImageAttributes.cs index 5831014b1e60..dca1674b2d53 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/ImageAttributes.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/ImageAttributes.cs @@ -94,7 +94,7 @@ private void Dispose(bool disposing) #endif Gdip.GdipDisposeImageAttributes(new HandleRef(this, nativeImageAttributes)); #if DEBUG - Debug.Assert(status == Gdip.Ok, "GDI+ returned an error status: " + status.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(status == Gdip.Ok, $"GDI+ returned an error status: {status.ToString(CultureInfo.InvariantCulture)}"); #endif } catch (Exception ex) diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/ImageFormat.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/ImageFormat.cs index 705fad55ee57..ca4e867fcba2 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/ImageFormat.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/ImageFormat.cs @@ -170,7 +170,7 @@ public override string ToString() if (this.Guid == s_tiff.Guid) return "Tiff"; if (this.Guid == s_exif.Guid) return "Exif"; if (this.Guid == s_icon.Guid) return "Icon"; - return "[ImageFormat: " + _guid + "]"; + return $"[ImageFormat: {_guid}]"; } } } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Internal/SystemColorTracker.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Internal/SystemColorTracker.cs index 9d2343d83b59..8645b31c10cf 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Internal/SystemColorTracker.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Internal/SystemColorTracker.cs @@ -55,7 +55,7 @@ internal static void Add(ISystemColorTracker obj) list[index] = new WeakReference(obj); else { - Debug.Assert(list[index].Target == null, "Trying to reuse a weak reference that isn't broken yet: list[" + index + "], length =" + list.Length); + Debug.Assert(list[index].Target == null, $"Trying to reuse a weak reference that isn't broken yet: list[{index}], length = {list.Length}"); list[index].Target = obj; } } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Pen.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Pen.cs index 606f8cc57af3..ca5d045000dd 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Pen.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Pen.cs @@ -161,7 +161,7 @@ private void Dispose(bool disposing) #endif Gdip.GdipDeletePen(new HandleRef(this, NativePen)); #if DEBUG - Debug.Assert(status == Gdip.Ok, "GDI+ returned an error status: " + status.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(status == Gdip.Ok, $"GDI+ returned an error status: {status.ToString(CultureInfo.InvariantCulture)}"); #endif } catch (Exception ex) when (!ClientUtils.IsSecurityOrCriticalException(ex)) diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/Margins.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/Margins.cs index 39691a9ad5e6..05764ce8f21a 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/Margins.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/Margins.cs @@ -235,14 +235,6 @@ public override bool Equals([NotNullWhen(true)] object? obj) /// /// Provides some interesting information for the Margins in String form. /// - public override string ToString() - { - return "[Margins" - + " Left=" + Left.ToString(CultureInfo.InvariantCulture) - + " Right=" + Right.ToString(CultureInfo.InvariantCulture) - + " Top=" + Top.ToString(CultureInfo.InvariantCulture) - + " Bottom=" + Bottom.ToString(CultureInfo.InvariantCulture) - + "]"; - } + public override string ToString() => $"[Margins Left={Left} Right={Right} Top={Top} Bottom={Bottom}]"; } } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PageSettings.Unix.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PageSettings.Unix.cs index a7e8c1dbdbcf..a79fb02f0a28 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PageSettings.Unix.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PageSettings.Unix.cs @@ -255,9 +255,7 @@ public void SetHdevmode(IntPtr hdevmode) throw new NotImplementedException(); } - public override string ToString() - { - return $"[{nameof(PageSettings)}: {nameof(Color)}={color}, {nameof(Landscape)}={landscape}, {nameof(Margins)}={margins}, {nameof(PaperSize)}={paperSize}, {nameof(PaperSource)}={paperSource}, {nameof(PrinterResolution)}={printerResolution}]"; - } + public override string ToString() => + $"[{nameof(PageSettings)}: {nameof(Color)}={color}, {nameof(Landscape)}={landscape}, {nameof(Margins)}={margins}, {nameof(PaperSize)}={paperSize}, {nameof(PaperSource)}={paperSource}, {nameof(PrinterResolution)}={printerResolution}]"; } } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PageSettings.Windows.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PageSettings.Windows.cs index 8ac8e9ce247a..f0e41dbc667d 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PageSettings.Windows.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PageSettings.Windows.cs @@ -536,16 +536,7 @@ public void SetHdevmode(IntPtr hdevmode) /// /// Provides some interesting information about the PageSettings in String form. /// - public override string ToString() - { - return "[PageSettings:" - + " Color=" + Color.ToString() - + ", Landscape=" + Landscape.ToString() - + ", Margins=" + Margins.ToString() - + ", PaperSize=" + PaperSize.ToString() - + ", PaperSource=" + PaperSource.ToString() - + ", PrinterResolution=" + PrinterResolution.ToString() - + "]"; - } + public override string ToString() => + $"[{nameof(PageSettings)}: Color={Color}, Landscape={Landscape}, Margins={Margins}, PaperSize={PaperSize}, PaperSource={PaperSource}, PrinterResolution={PrinterResolution}]"; } } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PaperSize.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PaperSize.cs index 34b12d52c9db..4b9b49ae33d0 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PaperSize.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PaperSize.cs @@ -127,13 +127,6 @@ public int Width /// /// Provides some interesting information about the PaperSize in String form. /// - public override string ToString() - { - return "[PaperSize " + PaperName - + " Kind=" + Kind.ToString() - + " Height=" + Height.ToString(CultureInfo.InvariantCulture) - + " Width=" + Width.ToString(CultureInfo.InvariantCulture) - + "]"; - } + public override string ToString() => $"[PaperSize {PaperName} Kind={Kind.ToString()} Height={Height.ToString(CultureInfo.InvariantCulture)} Width={Width.ToString(CultureInfo.InvariantCulture)}]"; } } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PaperSource.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PaperSource.cs index 9db904210508..5a321cc4d81f 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PaperSource.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PaperSource.cs @@ -63,11 +63,6 @@ public string SourceName /// /// Provides some interesting information about the PaperSource in String form. /// - public override string ToString() - { - return "[PaperSource " + SourceName - + " Kind=" + Kind.ToString() - + "]"; - } + public override string ToString() => $"[PaperSource {SourceName} Kind={Kind}]"; } } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintDocument.Unix.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintDocument.Unix.cs index 25ea2116850d..2d3306ed8a2f 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintDocument.Unix.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintDocument.Unix.cs @@ -187,10 +187,7 @@ public void Print() PrintController.OnEndPrint(this, printArgs); } - public override string ToString() - { - return "[PrintDocument " + this.DocumentName + "]"; - } + public override string ToString() => $"[PrintDocument {this.DocumentName}]"; // events protected virtual void OnBeginPrint(PrintEventArgs e) diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintDocument.Windows.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintDocument.Windows.cs index 3d0b09a7321d..eaa73074f872 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintDocument.Windows.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintDocument.Windows.cs @@ -248,9 +248,6 @@ public void Print() /// /// Provides some interesting information about the PrintDocument in String form. /// - public override string ToString() - { - return "[PrintDocument " + DocumentName + "]"; - } + public override string ToString() => $"[PrintDocument {DocumentName}]"; } } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrinterSettings.Unix.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrinterSettings.Unix.cs index d3e567777cde..4043ec722134 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrinterSettings.Unix.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrinterSettings.Unix.cs @@ -365,13 +365,7 @@ public void SetHdevnames(IntPtr hdevnames) throw new NotImplementedException(); } - public override string ToString() - { - return "Printer [PrinterSettings " + printer_name + " Copies=" + copies + " Collate=" + collate - + " Duplex=" + can_duplex + " FromPage=" + from_page + " LandscapeAngle=" + landscape_angle - + " MaximumCopies=" + maximum_copies + " OutputPort=" + " ToPage=" + to_page + "]"; - - } + public override string ToString() => $"Printer [PrinterSettings {printer_name} Copies={copies} Collate={collate} Duplex={can_duplex} FromPage={from_page} LandscapeAngle={landscape_angle} MaximumCopies={maximum_copies} OutputPort= ToPage={to_page}]"; // Public subclasses #region Public Subclasses diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrinterSettings.Windows.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrinterSettings.Windows.cs index 1889516f87ad..d750390d07d6 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrinterSettings.Windows.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrinterSettings.Windows.cs @@ -1258,16 +1258,16 @@ public override string ToString() { string printerName = PrinterName; return "[PrinterSettings " - + printerName - + " Copies=" + Copies.ToString(CultureInfo.InvariantCulture) - + " Collate=" + Collate.ToString(CultureInfo.InvariantCulture) - + " Duplex=" + Duplex.ToString() - + " FromPage=" + FromPage.ToString(CultureInfo.InvariantCulture) - + " LandscapeAngle=" + LandscapeAngle.ToString(CultureInfo.InvariantCulture) - + " MaximumCopies=" + MaximumCopies.ToString(CultureInfo.InvariantCulture) - + " OutputPort=" + OutputPort.ToString(CultureInfo.InvariantCulture) - + " ToPage=" + ToPage.ToString(CultureInfo.InvariantCulture) - + "]"; + + printerName + + " Copies=" + Copies.ToString(CultureInfo.InvariantCulture) + + " Collate=" + Collate.ToString(CultureInfo.InvariantCulture) + + " Duplex=" + Duplex.ToString() + + " FromPage=" + FromPage.ToString(CultureInfo.InvariantCulture) + + " LandscapeAngle=" + LandscapeAngle.ToString(CultureInfo.InvariantCulture) + + " MaximumCopies=" + MaximumCopies.ToString(CultureInfo.InvariantCulture) + + " OutputPort=" + OutputPort.ToString(CultureInfo.InvariantCulture) + + " ToPage=" + ToPage.ToString(CultureInfo.InvariantCulture) + + "]"; } // Write null terminated string, return length of string in characters (including null) diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Region.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Region.cs index d87c739d8ca4..379fc61e2e15 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Region.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Region.cs @@ -101,7 +101,7 @@ private void Dispose(bool disposing) #endif Gdip.GdipDeleteRegion(new HandleRef(this, NativeRegion)); #if DEBUG - Debug.Assert(status == Gdip.Ok, "GDI+ returned an error status: " + status.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(status == Gdip.Ok, $"GDI+ returned an error status: {status.ToString(CultureInfo.InvariantCulture)}"); #endif } catch (Exception ex) when (!ClientUtils.IsSecurityOrCriticalException(ex)) diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/StringFormat.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/StringFormat.cs index 6a9059182008..cd0a7d074d1c 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/StringFormat.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/StringFormat.cs @@ -86,7 +86,7 @@ private void Dispose(bool disposing) #endif Gdip.GdipDeleteStringFormat(new HandleRef(this, nativeFormat)); #if DEBUG - Debug.Assert(status == Gdip.Ok, "GDI+ returned an error status: " + status.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(status == Gdip.Ok, $"GDI+ returned an error status: {status.ToString(CultureInfo.InvariantCulture)}"); #endif } catch (Exception ex) @@ -442,9 +442,6 @@ internal int GetMeasurableCharacterRangeCount() /// /// Converts this to a human-readable string. /// - public override string ToString() - { - return "[StringFormat, FormatFlags=" + FormatFlags.ToString() + "]"; - } + public override string ToString() => $"[StringFormat, FormatFlags={FormatFlags.ToString()}]"; } } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Text/PrivateFontCollection.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Text/PrivateFontCollection.cs index 8541e3069c2f..609ccae35b8d 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Text/PrivateFontCollection.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Text/PrivateFontCollection.cs @@ -37,7 +37,7 @@ protected override void Dispose(bool disposing) #endif Gdip.GdipDeletePrivateFontCollection(ref _nativeFontCollection); #if DEBUG - Debug.Assert(status == Gdip.Ok, "GDI+ returned an error status: " + status.ToString(CultureInfo.InvariantCulture)); + Debug.Assert(status == Gdip.Ok, $"GDI+ returned an error status: {status.ToString(CultureInfo.InvariantCulture)}"); #endif } catch (Exception ex) when (!ClientUtils.IsSecurityOrCriticalException(ex)) diff --git a/src/libraries/System.Drawing.Primitives/src/System/Drawing/Color.cs b/src/libraries/System.Drawing.Primitives/src/System/Drawing/Color.cs index f711d9a5fe66..a7f116edada7 100644 --- a/src/libraries/System.Drawing.Primitives/src/System/Drawing/Color.cs +++ b/src/libraries/System.Drawing.Primitives/src/System/Drawing/Color.cs @@ -565,21 +565,10 @@ public float GetSaturation() public KnownColor ToKnownColor() => (KnownColor)knownColor; - public override string ToString() - { - if (IsNamedColor) - { - return nameof(Color) + " [" + Name + "]"; - } - else if ((state & StateValueMask) != 0) - { - return nameof(Color) + " [A=" + A.ToString() + ", R=" + R.ToString() + ", G=" + G.ToString() + ", B=" + B.ToString() + "]"; - } - else - { - return nameof(Color) + " [Empty]"; - } - } + public override string ToString() => + IsNamedColor ? $"{nameof(Color)} [{Name}]": + (state & StateValueMask) != 0 ? $"{nameof(Color)} [A={A}, R={R}, G={G}, B={B}]" : + $"{nameof(Color)} [Empty]"; public static bool operator ==(Color left, Color right) => left.value == right.value diff --git a/src/libraries/System.Drawing.Primitives/src/System/Drawing/Point.cs b/src/libraries/System.Drawing.Primitives/src/System/Drawing/Point.cs index 36c56ce1d0c0..678cecd313e2 100644 --- a/src/libraries/System.Drawing.Primitives/src/System/Drawing/Point.cs +++ b/src/libraries/System.Drawing.Primitives/src/System/Drawing/Point.cs @@ -165,7 +165,7 @@ public void Offset(int dx, int dy) /// /// Converts this to a human readable string. /// - public override readonly string ToString() => "{X=" + X.ToString() + ",Y=" + Y.ToString() + "}"; + public override readonly string ToString() => $"{{X={X},Y={Y}}}"; private static short HighInt16(int n) => unchecked((short)((n >> 16) & 0xffff)); diff --git a/src/libraries/System.Drawing.Primitives/src/System/Drawing/PointF.cs b/src/libraries/System.Drawing.Primitives/src/System/Drawing/PointF.cs index 47ae16677be5..1a35a72826c4 100644 --- a/src/libraries/System.Drawing.Primitives/src/System/Drawing/PointF.cs +++ b/src/libraries/System.Drawing.Primitives/src/System/Drawing/PointF.cs @@ -140,6 +140,6 @@ public float Y public override readonly int GetHashCode() => HashCode.Combine(X.GetHashCode(), Y.GetHashCode()); - public override readonly string ToString() => "{X=" + x.ToString() + ", Y=" + y.ToString() + "}"; + public override readonly string ToString() => $"{{X={x}, Y={y}}}"; } } diff --git a/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs b/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs index 7d728a1fea56..e4f7500ced2c 100644 --- a/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs +++ b/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs @@ -340,8 +340,6 @@ public void Offset(int x, int y) /// /// Converts the attributes of this to a human readable string. /// - public override readonly string ToString() => - "{X=" + X.ToString() + ",Y=" + Y.ToString() + - ",Width=" + Width.ToString() + ",Height=" + Height.ToString() + "}"; + public override readonly string ToString() => $"{{X={X},Y={Y},Width={Width},Height={Height}}}"; } } diff --git a/src/libraries/System.Drawing.Primitives/src/System/Drawing/RectangleF.cs b/src/libraries/System.Drawing.Primitives/src/System/Drawing/RectangleF.cs index 508cd1252dbd..96f75808613b 100644 --- a/src/libraries/System.Drawing.Primitives/src/System/Drawing/RectangleF.cs +++ b/src/libraries/System.Drawing.Primitives/src/System/Drawing/RectangleF.cs @@ -326,8 +326,6 @@ public void Offset(float x, float y) /// Converts the and /// of this to a human-readable string. /// - public override readonly string ToString() => - "{X=" + X.ToString() + ",Y=" + Y.ToString() + - ",Width=" + Width.ToString() + ",Height=" + Height.ToString() + "}"; + public override readonly string ToString() => $"{{X={X},Y={Y},Width={Width},Height={Height}}}"; } } diff --git a/src/libraries/System.Drawing.Primitives/src/System/Drawing/Size.cs b/src/libraries/System.Drawing.Primitives/src/System/Drawing/Size.cs index bd140b7e0801..c23a66d91423 100644 --- a/src/libraries/System.Drawing.Primitives/src/System/Drawing/Size.cs +++ b/src/libraries/System.Drawing.Primitives/src/System/Drawing/Size.cs @@ -189,7 +189,7 @@ public static Size Round(SizeF value) => /// /// Creates a human-readable string that represents this . /// - public override readonly string ToString() => "{Width=" + width.ToString() + ", Height=" + height.ToString() + "}"; + public override readonly string ToString() => $"{{Width={width}, Height={height}}}"; /// /// Multiplies by an producing . diff --git a/src/libraries/System.Drawing.Primitives/src/System/Drawing/SizeF.cs b/src/libraries/System.Drawing.Primitives/src/System/Drawing/SizeF.cs index 4d0eb252dd67..d386f4dad859 100644 --- a/src/libraries/System.Drawing.Primitives/src/System/Drawing/SizeF.cs +++ b/src/libraries/System.Drawing.Primitives/src/System/Drawing/SizeF.cs @@ -177,7 +177,7 @@ public float Height /// /// Creates a human-readable string that represents this . /// - public override readonly string ToString() => "{Width=" + width.ToString() + ", Height=" + height.ToString() + "}"; + public override readonly string ToString() => $"{{Width={width}, Height={height}}}"; /// /// Multiplies by a producing . diff --git a/src/libraries/System.Formats.Asn1/tests/Writer/WriteInteger.cs b/src/libraries/System.Formats.Asn1/tests/Writer/WriteInteger.cs index 29e2f2ce89c7..938c758bd570 100644 --- a/src/libraries/System.Formats.Asn1/tests/Writer/WriteInteger.cs +++ b/src/libraries/System.Formats.Asn1/tests/Writer/WriteInteger.cs @@ -278,7 +278,7 @@ public void VerifyWriteInteger_Private16_BigInteger( [InlineData("FEFDFCFBFAF9F8F7F6F5F4F3F2F1F100")] public void VerifyWriteInteger_EncodedBytes(string valueHex) { - string expectedHex = "02" + (valueHex.Length / 2).ToString("X2") + valueHex; + string expectedHex = $"02{valueHex.Length / 2:X2}{valueHex}"; AsnWriter writer = new AsnWriter(AsnEncodingRules.BER); writer.WriteInteger(valueHex.HexToByteArray()); @@ -298,7 +298,7 @@ public void VerifyWriteInteger_EncodedBytes(string valueHex) [InlineData("FEFDFCFBFAF9F8F7F6F5F4F3F2F1F100")] public void VerifyWriteInteger_Context4_EncodedBytes(string valueHex) { - string expectedHex = "84" + (valueHex.Length / 2).ToString("X2") + valueHex; + string expectedHex = $"84{valueHex.Length / 2:X2}{valueHex}"; AsnWriter writer = new AsnWriter(AsnEncodingRules.BER); writer.WriteInteger(valueHex.HexToByteArray(), new Asn1Tag(TagClass.ContextSpecific, 4)); diff --git a/src/libraries/System.Globalization.Extensions/tests/Normalization/NormalizationAll.cs b/src/libraries/System.Globalization.Extensions/tests/Normalization/NormalizationAll.cs index 0f52f67c6b54..48f73a4527a3 100644 --- a/src/libraries/System.Globalization.Extensions/tests/Normalization/NormalizationAll.cs +++ b/src/libraries/System.Globalization.Extensions/tests/Normalization/NormalizationAll.cs @@ -167,8 +167,7 @@ private static string DumpStringAsCodepoints(string s) StringBuilder sb = new StringBuilder(); for (int i=0; i()); _listOfAllDirs.Add(dirName); Directory.CreateDirectory(dirName); diff --git a/src/libraries/System.IO.Hashing/tests/NonCryptoHashTestDriver.cs b/src/libraries/System.IO.Hashing/tests/NonCryptoHashTestDriver.cs index 979e60a28065..3cec2c098362 100644 --- a/src/libraries/System.IO.Hashing/tests/NonCryptoHashTestDriver.cs +++ b/src/libraries/System.IO.Hashing/tests/NonCryptoHashTestDriver.cs @@ -331,7 +331,7 @@ internal static string ToHexString(ReadOnlySpan input) foreach (byte b in input) { - builder.Append(b.ToString("X2")); + builder.Append($"{b:X2}"); } return builder.ToString(); diff --git a/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorage.cs b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorage.cs index 170ce2feb972..1ad1801c2705 100644 --- a/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorage.cs +++ b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorage.cs @@ -153,7 +153,7 @@ protected void InitStore(IsolatedStorageScope scope, Type? domainEvidenceType, T if (Helper.IsDomain(scope)) { _domainIdentity = identity; - hash = $"{hash}{SeparatorExternal}{hash}"; + hash = string.Create(null, stackalloc char[128], $"{hash}{SeparatorExternal}{hash}"); } _assemblyIdentity = identity; diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs b/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs index 174b91bb219a..389ab072d9a6 100644 --- a/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs +++ b/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs @@ -174,7 +174,7 @@ private static FileStream CreateSharedBackingObject(Interop.Sys.MemoryMappedProt Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability) { // The POSIX shared memory object name must begin with '/'. After that we just want something short and unique. - string mapName = "/corefx_map_" + Guid.NewGuid().ToString("N"); + string mapName = string.Create(null, stackalloc char[128], $"/corefx_map_{Guid.NewGuid():N}"); // Determine the flags to use when creating the shared memory object Interop.Sys.OpenFlags flags = (protections & Interop.Sys.MemoryMappedProtections.PROT_WRITE) != 0 ? diff --git a/src/libraries/System.IO.Pipelines/tests/Infrastructure/TestMemoryPool.cs b/src/libraries/System.IO.Pipelines/tests/Infrastructure/TestMemoryPool.cs index 9567b03e903a..f990345a9bcf 100644 --- a/src/libraries/System.IO.Pipelines/tests/Infrastructure/TestMemoryPool.cs +++ b/src/libraries/System.IO.Pipelines/tests/Infrastructure/TestMemoryPool.cs @@ -59,7 +59,7 @@ public PooledMemory(IMemoryOwner owner, TestMemoryPool pool) ~PooledMemory() { - Debug.Assert(_returned, "Block being garbage collected instead of returned to pool" + Environment.NewLine + _leaser); + Debug.Assert(_returned, $"Block being garbage collected instead of returned to pool{Environment.NewLine}{_leaser}"); } protected override void Dispose(bool disposing) diff --git a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeCompletionSource.cs b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeCompletionSource.cs index 39f47249a691..dc2180b39b70 100644 --- a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeCompletionSource.cs +++ b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeCompletionSource.cs @@ -156,7 +156,7 @@ private void Cancel() private void CompleteCallback(int resultState) { - Debug.Assert(resultState == ResultSuccess || resultState == ResultError, "Unexpected result state " + resultState); + Debug.Assert(resultState == ResultSuccess || resultState == ResultError, $"Unexpected result state {resultState}"); CancellationToken cancellationToken = _cancellationRegistration.Token; ReleaseResources(); diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs index 0e46efe6998d..85eb2f4eaf8a 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs @@ -1005,7 +1005,7 @@ internal unsafe int Read(byte[] array, int offset, int count, int timeout) if (count == 0) return 0; // return immediately if no bytes requested; no need for overhead. - Debug.Assert(timeout == SerialPort.InfiniteTimeout || timeout >= 0, "Serial Stream Read - called with timeout " + timeout); + Debug.Assert(timeout == SerialPort.InfiniteTimeout || timeout >= 0, $"Serial Stream Read - called with timeout {timeout}"); int numBytes = 0; if (_isAsync) @@ -1069,7 +1069,7 @@ internal unsafe void Write(byte[] array, int offset, int count, int timeout) if (count == 0) return; // no need to expend overhead in creating asyncResult, etc. - Debug.Assert(timeout == SerialPort.InfiniteTimeout || timeout >= 0, "Serial Stream Write - write timeout is " + timeout); + Debug.Assert(timeout == SerialPort.InfiniteTimeout || timeout >= 0, $"Serial Stream Write - write timeout is {timeout}"); int numBytes; if (_isAsync) @@ -1627,7 +1627,7 @@ internal unsafe void WaitForCommEvent() // if we get IO pending, MSDN says we should wait on the WaitHandle, then call GetOverlappedResult // to get the results of WaitCommEvent. bool success = waitCommEventWaitHandle.WaitOne(); - Debug.Assert(success, "waitCommEventWaitHandle.WaitOne() returned error " + Marshal.GetLastWin32Error()); + Debug.Assert(success, $"waitCommEventWaitHandle.WaitOne() returned error {Marshal.GetLastWin32Error()}"); do { diff --git a/src/libraries/System.IO.Ports/tests/SerialPort/GetPortNames.cs b/src/libraries/System.IO.Ports/tests/SerialPort/GetPortNames.cs index 8376f729aa55..99be124701c0 100644 --- a/src/libraries/System.IO.Ports/tests/SerialPort/GetPortNames.cs +++ b/src/libraries/System.IO.Ports/tests/SerialPort/GetPortNames.cs @@ -80,8 +80,8 @@ static string PortInformationString get { var sb = new StringBuilder(); - sb.AppendLine("PortHelper Ports: " + string.Join(",", PortHelper.GetPorts())); - sb.AppendLine("SerialPort Ports: " + string.Join(",", SerialPort.GetPortNames())); + sb.AppendLine($"PortHelper Ports: {string.Join(",", PortHelper.GetPorts())}"); + sb.AppendLine($"SerialPort Ports: {string.Join(",", SerialPort.GetPortNames())}"); return sb.ToString(); } } diff --git a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/BranchLabel.cs b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/BranchLabel.cs index 0c3ef9769ae4..585d5901506b 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/BranchLabel.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/BranchLabel.cs @@ -20,10 +20,8 @@ public RuntimeLabel(int index, int continuationStackDepth, int stackDepth) StackDepth = stackDepth; } - public override string ToString() - { - return string.Format(CultureInfo.InvariantCulture, "->{0} C({1}) S({2})", Index, ContinuationStackDepth, StackDepth); - } + public override string ToString() => + string.Create(CultureInfo.InvariantCulture, $"->{Index} C({ContinuationStackDepth}) S({StackDepth})"); } internal sealed class BranchLabel diff --git a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/InstructionList.cs b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/InstructionList.cs index 0fbabd6289f3..cf1f973204a3 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/InstructionList.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/InstructionList.cs @@ -195,7 +195,7 @@ private void UpdateStackDepth(Instruction instruction) instruction.ConsumedContinuations >= 0 && instruction.ProducedContinuations >= 0, "bad instruction " + instruction.ToString()); _currentStackDepth -= instruction.ConsumedStack; - Debug.Assert(_currentStackDepth >= 0, "negative stack depth " + instruction.ToString()); + Debug.Assert(_currentStackDepth >= 0, $"negative stack depth {instruction}"); _currentStackDepth += instruction.ProducedStack; if (_currentStackDepth > _maxStackDepth) { @@ -203,7 +203,7 @@ private void UpdateStackDepth(Instruction instruction) } _currentContinuationsDepth -= instruction.ConsumedContinuations; - Debug.Assert(_currentContinuationsDepth >= 0, "negative continuations " + instruction.ToString()); + Debug.Assert(_currentContinuationsDepth >= 0, $"negative continuations {instruction}"); _currentContinuationsDepth += instruction.ProducedContinuations; if (_currentContinuationsDepth > _maxContinuationDepth) { diff --git a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LightCompiler.cs b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LightCompiler.cs index 784baac2de9a..b5753f67732f 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LightCompiler.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LightCompiler.cs @@ -51,7 +51,7 @@ internal ExceptionHandler(int labelIndex, int handlerStartIndex, int handlerEndI public bool Matches(Type exceptionType) => _exceptionType.IsAssignableFrom(exceptionType); public override string ToString() => - string.Format(CultureInfo.InvariantCulture, "catch ({0}) [{1}->{2}]", _exceptionType.Name, HandlerStartIndex, HandlerEndIndex); + string.Create(CultureInfo.InvariantCulture, $"catch ({_exceptionType.Name}) [{HandlerStartIndex}->{HandlerEndIndex}]"); } internal sealed class TryCatchFinallyHandler @@ -251,11 +251,11 @@ public override string ToString() { if (IsClear) { - return string.Format(CultureInfo.InvariantCulture, "{0}: clear", Index); + return string.Create(CultureInfo.InvariantCulture, $"{Index}: clear"); } else { - return string.Format(CultureInfo.InvariantCulture, "{0}: [{1}-{2}] '{3}'", Index, StartLine, EndLine, FileName); + return string.Create(CultureInfo.InvariantCulture, $"{Index}: [{StartLine}-{EndLine}] '{FileName}'"); } } } diff --git a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LightLambda.cs b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LightLambda.cs index 3956e67b29a2..77b399b366d8 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LightLambda.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LightLambda.cs @@ -167,7 +167,7 @@ public override string ToString() InstructionList.DebugView.InstructionView instructionView = instructionViews[i]; - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}IP_{1}: {2}", _indent, i.ToString().PadLeft(4, '0'), instructionView.GetValue()).AppendLine(); + sb.AppendLine(CultureInfo.InvariantCulture, $"{_indent}IP_{i.ToString().PadLeft(4, '0')}: {instructionView.GetValue()}"); } EmitExits(sb, instructions.Length); diff --git a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LocalVariables.cs b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LocalVariables.cs index 83177991b77a..d078d82a597f 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LocalVariables.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LocalVariables.cs @@ -40,10 +40,8 @@ internal LocalVariable(int index, bool closure) _flags = (closure ? InClosureFlag : 0); } - public override string ToString() - { - return string.Format(CultureInfo.InvariantCulture, "{0}: {1} {2}", Index, IsBoxed ? "boxed" : null, InClosure ? "in closure" : null); - } + public override string ToString() => + string.Create(CultureInfo.InvariantCulture, $"{Index}: {(IsBoxed ? "boxed" : null)} {(InClosure ? "in closure" : null)}"); } internal readonly struct LocalDefinition diff --git a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/StackOperations.cs b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/StackOperations.cs index 0a3586ed514d..53e3fa831e17 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/StackOperations.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/StackOperations.cs @@ -45,10 +45,8 @@ public override int Run(InterpretedFrame frame) return 1; } - public override string ToDebugString(int instructionIndex, object? cookie, Func labelIndexer, IReadOnlyList? objects) - { - return string.Format(CultureInfo.InvariantCulture, "LoadCached({0}: {1})", _index, objects![(int)_index]); - } + public override string ToDebugString(int instructionIndex, object? cookie, Func labelIndexer, IReadOnlyList? objects) => + string.Create(CultureInfo.InvariantCulture, $"LoadCached({_index}: {objects![(int)_index]})"); public override string ToString() => "LoadCached(" + _index + ")"; } diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestState.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestState.cs index 93595b1d12af..40275b5c8d71 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestState.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestState.cs @@ -175,7 +175,7 @@ private void Dispose(bool disposing) #if DEBUG Interlocked.Increment(ref s_dbg_callDispose); #endif - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"GCHandle=0x{ToIntPtr().ToString("X")}, disposed={_disposed}, disposing={disposing}"); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"GCHandle=0x{ToIntPtr():X}, disposed={_disposed}, disposing={disposing}"); // Since there is no finalizer and this class is sealed, the disposing parameter should be TRUE. Debug.Assert(disposing, "WinHttpRequestState.Dispose() should have disposing=TRUE"); diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpTraceHelper.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpTraceHelper.cs index 104808b472a9..e039d50dad19 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpTraceHelper.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpTraceHelper.cs @@ -15,7 +15,7 @@ public static void TraceCallbackStatus(object thisOrContextObject, IntPtr handle NetEventSource.Info( thisOrContextObject, - $"handle=0x{handle.ToString("X")}, context=0x{context.ToString("X")}, {GetStringFromInternetStatus(status)}", + $"handle=0x{handle:X}, context=0x{context:X}, {GetStringFromInternetStatus(status)}", memberName); } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderValue.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderValue.cs index ce534b0e1d54..f04f569b7255 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderValue.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderValue.cs @@ -56,13 +56,12 @@ public override string ToString() sb.Append("=\""); if (Host != null) sb.Append(Host); sb.Append(':'); - sb.Append(Port.ToString(CultureInfo.InvariantCulture)); + sb.Append((uint)Port); sb.Append('"'); if (MaxAge != TimeSpan.FromTicks(AltSvcHeaderParser.DefaultMaxAgeTicks)) { - sb.Append("; ma="); - sb.Append((MaxAge.Ticks / TimeSpan.TicksPerSecond).ToString(CultureInfo.InvariantCulture)); + sb.Append(CultureInfo.InvariantCulture, $"; ma={MaxAge.Ticks / TimeSpan.TicksPerSecond}"); } if (Persist) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/CacheControlHeaderValue.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/CacheControlHeaderValue.cs index b38c3396ca9d..e8186404aa4f 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/CacheControlHeaderValue.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/CacheControlHeaderValue.cs @@ -205,7 +205,7 @@ public override string ToString() { // In the corner case where the value is negative, ensure it uses // the invariant's negative sign rather than the current culture's. - sb.Append(maxAge.ToString(NumberFormatInfo.InvariantInfo)); + sb.Append(NumberFormatInfo.InvariantInfo, $"{maxAge}"); } } @@ -222,7 +222,7 @@ public override string ToString() { // In the corner case where the value is negative, ensure it uses // the invariant's negative sign rather than the current culture's. - sb.Append(sharedMaxAge.ToString(NumberFormatInfo.InvariantInfo)); + sb.Append(NumberFormatInfo.InvariantInfo, $"{sharedMaxAge}"); } } @@ -241,7 +241,7 @@ public override string ToString() { // In the corner case where the value is negative, ensure it uses // the invariant's negative sign rather than the current culture's. - sb.Append(maxStaleLimit.ToString(NumberFormatInfo.InvariantInfo)); + sb.Append(NumberFormatInfo.InvariantInfo, $"{maxStaleLimit}"); } } } @@ -259,7 +259,7 @@ public override string ToString() { // In the corner case where the value is negative, ensure it uses // the invariant's negative sign rather than the current culture's. - sb.Append(minFresh.ToString(NumberFormatInfo.InvariantInfo)); + sb.Append(NumberFormatInfo.InvariantInfo, $"{minFresh}"); } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs index fd1074f16f93..d52a92875a6a 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs @@ -449,7 +449,7 @@ internal bool RemoveParsedValue(HeaderDescriptor descriptor, object value) if (info.IsEmpty) { bool headerRemoved = Remove(descriptor); - Debug.Assert(headerRemoved, "Existing header '" + descriptor.Name + "' couldn't be removed."); + Debug.Assert(headerRemoved, $"Existing header '{descriptor.Name}' couldn't be removed."); } return result; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/RangeItemHeaderValue.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/RangeItemHeaderValue.cs index cba711f89a2d..91262983d8cd 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/RangeItemHeaderValue.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/RangeItemHeaderValue.cs @@ -56,17 +56,20 @@ internal RangeItemHeaderValue(RangeItemHeaderValue source) public override string ToString() { + Span stackBuffer = stackalloc char[128]; + if (!_from.HasValue) { Debug.Assert(_to != null); - return "-" + _to.Value.ToString(NumberFormatInfo.InvariantInfo); + return string.Create(CultureInfo.InvariantCulture, stackBuffer, $"-{_to.Value}"); } - else if (!_to.HasValue) + + if (!_to.HasValue) { - return _from.Value.ToString(NumberFormatInfo.InvariantInfo) + "-"; + return string.Create(CultureInfo.InvariantCulture, stackBuffer, $"{_from.Value}-"); ; } - return _from.Value.ToString(NumberFormatInfo.InvariantInfo) + "-" + - _to.Value.ToString(NumberFormatInfo.InvariantInfo); + + return string.Create(CultureInfo.InvariantCulture, stackBuffer, $"{_from.Value}-{_to.Value}"); } public override bool Equals([NotNullWhen(true)] object? obj) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/StringWithQualityHeaderValue.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/StringWithQualityHeaderValue.cs index 814a053f5ad6..377a2113dfa8 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/StringWithQualityHeaderValue.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/StringWithQualityHeaderValue.cs @@ -54,7 +54,7 @@ public override string ToString() { if (_quality.HasValue) { - return _value + "; q=" + _quality.Value.ToString("0.0##", NumberFormatInfo.InvariantInfo); + return string.Create(CultureInfo.InvariantCulture, stackalloc char[128], $"{_value}; q={_quality.Value:0.0##}"); } return _value; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/WarningHeaderValue.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/WarningHeaderValue.cs index c232ba8371df..caa70684ec7d 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/WarningHeaderValue.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/WarningHeaderValue.cs @@ -74,7 +74,7 @@ public override string ToString() StringBuilder sb = StringBuilderCache.Acquire(); // Warning codes are always 3 digits according to RFC2616 - sb.Append(_code.ToString("000", NumberFormatInfo.InvariantInfo)); + sb.Append(NumberFormatInfo.InvariantInfo, $"{_code:000}"); sb.Append(' '); sb.Append(_agent); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpContent.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpContent.cs index afb7082a57ab..eabc0efe68e0 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpContent.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpContent.cs @@ -80,35 +80,31 @@ private static void AssertEncodingConstants(Encoding encoding, int codePage, int Debug.Assert(preamble != null); Debug.Assert(codePage == encoding.CodePage, - "Encoding code page mismatch for encoding: " + encoding.EncodingName, - "Expected (constant): {0}, Actual (Encoding.CodePage): {1}", codePage, encoding.CodePage); + $"Encoding code page mismatch for encoding: {encoding.EncodingName}", + $"Expected (constant): {codePage}, Actual (Encoding.CodePage): {encoding.CodePage}"); byte[] actualPreamble = encoding.GetPreamble(); Debug.Assert(preambleLength == actualPreamble.Length, - "Encoding preamble length mismatch for encoding: " + encoding.EncodingName, - "Expected (constant): {0}, Actual (Encoding.GetPreamble().Length): {1}", preambleLength, actualPreamble.Length); + $"Encoding preamble length mismatch for encoding: {encoding.EncodingName}", + $"Expected (constant): {preambleLength}, Actual (Encoding.GetPreamble().Length): {actualPreamble.Length}"); Debug.Assert(actualPreamble.Length >= 2); int actualFirst2Bytes = actualPreamble[0] << 8 | actualPreamble[1]; Debug.Assert(first2Bytes == actualFirst2Bytes, - "Encoding preamble first 2 bytes mismatch for encoding: " + encoding.EncodingName, - "Expected (constant): {0}, Actual: {1}", first2Bytes, actualFirst2Bytes); + $"Encoding preamble first 2 bytes mismatch for encoding: {encoding.EncodingName}", + $"Expected (constant): {first2Bytes}, Actual: {actualFirst2Bytes}"); Debug.Assert(preamble.Length == actualPreamble.Length, - "Encoding preamble mismatch for encoding: " + encoding.EncodingName, - "Expected (constant): {0}, Actual (Encoding.GetPreamble()): {1}", - BitConverter.ToString(preamble), - BitConverter.ToString(actualPreamble)); + $"Encoding preamble mismatch for encoding: {encoding.EncodingName}", + $"Expected (constant): {BitConverter.ToString(preamble)}, Actual (Encoding.GetPreamble()): {BitConverter.ToString(actualPreamble)}"); for (int i = 0; i < preamble.Length; i++) { Debug.Assert(preamble[i] == actualPreamble[i], - "Encoding preamble mismatch for encoding: " + encoding.EncodingName, - "Expected (constant): {0}, Actual (Encoding.GetPreamble()): {1}", - BitConverter.ToString(preamble), - BitConverter.ToString(actualPreamble)); + $"Encoding preamble mismatch for encoding: {encoding.EncodingName}", + $"Expected (constant): {BitConverter.ToString(preamble)}, Actual (Encoding.GetPreamble()): {BitConverter.ToString(actualPreamble)}"); } } #endif diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs index 74abe97e80de..e9468042f64e 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs @@ -108,7 +108,7 @@ private static async Task SendWithNtAuthAsync(HttpRequestMe if (!isProxyAuth && !authUri.IsDefaultPort) { - hostName = $"{hostName}:{authUri.Port}"; + hostName = string.Create(null, stackalloc char[128], $"{hostName}:{authUri.Port}"); } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs index ce4f3e74169b..4667b7bec32f 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs @@ -9,6 +9,7 @@ using System.IO; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Net.Http.Headers; using System.Net.Security; @@ -78,7 +79,7 @@ public Http3Connection(HttpConnectionPool pool, HttpAuthority? origin, HttpAutho _connection = connection; bool altUsedDefaultPort = pool.Kind == HttpConnectionKind.Http && authority.Port == HttpConnectionPool.DefaultHttpPort || pool.Kind == HttpConnectionKind.Https && authority.Port == HttpConnectionPool.DefaultHttpsPort; - string altUsedValue = altUsedDefaultPort ? authority.IdnHost : authority.IdnHost + ":" + authority.Port.ToString(Globalization.CultureInfo.InvariantCulture); + string altUsedValue = altUsedDefaultPort ? authority.IdnHost : string.Create(CultureInfo.InvariantCulture, $"{authority.IdnHost}:{authority.Port}"); _altUsedEncodedHeader = QPack.QPackEncoder.EncodeLiteralHeaderFieldWithoutNameReferenceToArray(KnownHeaders.AltUsed.Name, altUsedValue); // Errors are observed via Abort(). diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index 00a1a376799e..e84f21dce3b4 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -349,15 +349,12 @@ public byte[] Http2AltSvcOriginUri var sb = new StringBuilder(); Debug.Assert(_originAuthority != null); - sb - .Append(IsSecure ? "https://" : "http://") - .Append(_originAuthority.IdnHost); + sb.Append(IsSecure ? "https://" : "http://") + .Append(_originAuthority.IdnHost); if (_originAuthority.Port != (IsSecure ? DefaultHttpsPort : DefaultHttpPort)) { - sb - .Append(':') - .Append(_originAuthority.Port.ToString(CultureInfo.InvariantCulture)); + sb.Append(CultureInfo.InvariantCulture, $":{_originAuthority.Port}"); } _http2AltSvcOriginUri = Encoding.ASCII.GetBytes(sb.ToString()); diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerException.cs b/src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerException.cs index 968adb53345d..249e579d47d1 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerException.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerException.cs @@ -13,23 +13,23 @@ public class HttpListenerException : Win32Exception { public HttpListenerException() : base(Marshal.GetLastPInvokeError()) { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, NativeErrorCode.ToString() + ":" + Message); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"{NativeErrorCode}:{Message}"); } public HttpListenerException(int errorCode) : base(errorCode) { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, NativeErrorCode.ToString() + ":" + Message); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"{NativeErrorCode}:{Message}"); } public HttpListenerException(int errorCode, string message) : base(errorCode, message) { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, NativeErrorCode.ToString() + ":" + Message); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"{NativeErrorCode}:{Message}"); } protected HttpListenerException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, NativeErrorCode.ToString() + ":" + Message); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"{NativeErrorCode}:{Message}"); } // the base class returns the HResult with this property diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerRequestUriBuilder.cs b/src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerRequestUriBuilder.cs index 61f05ac248e9..f078b5bdbee6 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerRequestUriBuilder.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerRequestUriBuilder.cs @@ -119,8 +119,7 @@ private void BuildRequestUriUsingRawPath() private static Encoding GetEncoding(EncodingType type) { - Debug.Assert((type == EncodingType.Primary) || (type == EncodingType.Secondary), - "Unknown 'EncodingType' value: " + type.ToString()); + Debug.Assert((type == EncodingType.Primary) || (type == EncodingType.Secondary), $"Unknown 'EncodingType' value: {type}"); if (type == EncodingType.Secondary) { @@ -330,8 +329,7 @@ private static void AppendOctetsPercentEncoded(StringBuilder target, IEnumerable { foreach (byte octet in octets) { - target.Append('%'); - target.Append(octet.ToString("X2", CultureInfo.InvariantCulture)); + target.Append($"%{octet:X2}"); } } @@ -350,7 +348,7 @@ private static string GetOctetsAsString(IEnumerable octets) { octetString.Append(' '); } - octetString.Append(octet.ToString("X2", CultureInfo.InvariantCulture)); + octetString.Append($"{octet:X2}"); } return octetString.ToString(); diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpListener.Windows.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpListener.Windows.cs index 13562495cb46..63c96497909f 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpListener.Windows.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpListener.Windows.cs @@ -962,7 +962,7 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, - $"HandleAuthentication creating new WindowsIdentity from user context: {userContext.DangerousGetHandle().ToString("x8")}"); + $"HandleAuthentication creating new WindowsIdentity from user context: {userContext.DangerousGetHandle():x8}"); } WindowsPrincipal windowsPrincipal = new WindowsPrincipal( @@ -1881,7 +1881,7 @@ private static unsafe void IOCompleted(DisconnectAsyncResult asyncResult, uint e private static unsafe void WaitCallback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped) { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"errorCode: {errorCode}, numBytes: {numBytes}, nativeOverlapped: {((IntPtr)nativeOverlapped).ToString("x")}"); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"errorCode: {errorCode}, numBytes: {numBytes}, nativeOverlapped: {(IntPtr)nativeOverlapped:x}"); // take the DisconnectAsyncResult object from the state DisconnectAsyncResult asyncResult = (DisconnectAsyncResult)ThreadPoolBoundHandle.GetNativeOverlappedState(nativeOverlapped)!; IOCompleted(asyncResult, errorCode, numBytes, nativeOverlapped); diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpRequestStream.Windows.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpRequestStream.Windows.cs index 3eadb3cf7e5e..ecbadd5943c9 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpRequestStream.Windows.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpRequestStream.Windows.cs @@ -306,7 +306,7 @@ internal void IOCompleted(uint errorCode, uint numBytes) private static void IOCompleted(HttpRequestStreamAsyncResult asyncResult, uint errorCode, uint numBytes) { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"asyncResult: {asyncResult} errorCode:0x {errorCode.ToString("x8")} numBytes: {numBytes}"); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"asyncResult: {asyncResult} errorCode:0x {errorCode:x8} numBytes: {numBytes}"); object? result = null; try { @@ -333,7 +333,7 @@ private static unsafe void Callback(uint errorCode, uint numBytes, NativeOverlap { HttpRequestStreamAsyncResult asyncResult = (HttpRequestStreamAsyncResult)ThreadPoolBoundHandle.GetNativeOverlappedState(nativeOverlapped)!; - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"asyncResult: {asyncResult} errorCode:0x {errorCode.ToString("x8")} numBytes: {numBytes} nativeOverlapped:0x {((IntPtr)nativeOverlapped).ToString("x8")}"); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"asyncResult: {asyncResult} errorCode:0x {errorCode:x8} numBytes: {numBytes} nativeOverlapped:0x{(IntPtr)nativeOverlapped:x8}"); IOCompleted(asyncResult, errorCode, numBytes); } diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpResponseStreamAsyncResult.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpResponseStreamAsyncResult.cs index 5a41b0ddeed6..d41ff445938c 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpResponseStreamAsyncResult.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpResponseStreamAsyncResult.cs @@ -185,7 +185,7 @@ internal void IOCompleted(uint errorCode, uint numBytes) private static void IOCompleted(HttpResponseStreamAsyncResult asyncResult, uint errorCode, uint numBytes) { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"errorCode:0x {errorCode.ToString("x8")} numBytes: {numBytes}"); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"errorCode:0x{errorCode:x8} numBytes: {numBytes}"); object? result = null; try { diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/ListenerAsyncResult.Windows.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/ListenerAsyncResult.Windows.cs index b36252e9de82..0b57602b1b74 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/ListenerAsyncResult.Windows.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/ListenerAsyncResult.Windows.cs @@ -106,7 +106,7 @@ internal uint QueueBeginGetContext() while (true) { Debug.Assert(_requestContext != null); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Calling Interop.HttpApi.HttpReceiveHttpRequest RequestId: {_requestContext.RequestBlob->RequestId}Buffer:0x {((IntPtr)_requestContext.RequestBlob).ToString("x")} Size: {_requestContext.Size}"); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Calling Interop.HttpApi.HttpReceiveHttpRequest RequestId: {_requestContext.RequestBlob->RequestId} Buffer: 0x{(IntPtr)_requestContext.RequestBlob:x} Size: {_requestContext.Size}"); uint bytesTransferred = 0; Debug.Assert(AsyncObject != null); HttpListenerSession listenerSession = (HttpListenerSession)AsyncObject!; diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/HttpWebSocket.Windows.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/HttpWebSocket.Windows.cs index ee844910e4de..9b9c501746de 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/HttpWebSocket.Windows.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/HttpWebSocket.Windows.cs @@ -137,15 +137,6 @@ private static async Task AcceptWebSocketAsyncCore return webSocketContext; } - internal static string GetTraceMsgForParameters(int offset, int count, CancellationToken cancellationToken) - { - return string.Format(CultureInfo.InvariantCulture, - "offset: {0}, count: {1}, cancellationToken.CanBeCanceled: {2}", - offset, - count, - cancellationToken.CanBeCanceled); - } - internal static ConfiguredTaskAwaitable SuppressContextFlow(this Task task) { // We don't flow the synchronization context within WebSocket.xxxAsync - but the calling application diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/WebSocketBase.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/WebSocketBase.cs index 84d4222f8cd1..9355cd36ccb2 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/WebSocketBase.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/WebSocketBase.cs @@ -63,20 +63,6 @@ protected WebSocketBase(Stream innerStream, HttpWebSocket.ValidateOptions(subProtocol, internalBuffer.ReceiveBufferSize, internalBuffer.SendBufferSize, keepAliveInterval); - string parameters = string.Empty; - - if (NetEventSource.Log.IsEnabled()) - { - parameters = string.Format(CultureInfo.InvariantCulture, - "ReceiveBufferSize: {0}, SendBufferSize: {1}, Protocols: {2}, KeepAliveInterval: {3}, innerStream: {4}, internalBuffer: {5}", - internalBuffer.ReceiveBufferSize, - internalBuffer.SendBufferSize, - subProtocol, - keepAliveInterval, - NetEventSource.GetHashCode(innerStream), - NetEventSource.GetHashCode(internalBuffer)); - } - _thisLock = new object(); _innerStream = innerStream; @@ -251,15 +237,6 @@ private async Task SendAsyncCore(ArraySegment buffer, "'messageType' MUST be either 'WebSocketMessageType.Binary' or 'WebSocketMessageType.Text'."); Debug.Assert(buffer.Array != null); - string inputParameter = string.Empty; - if (NetEventSource.Log.IsEnabled()) - { - inputParameter = string.Format(CultureInfo.InvariantCulture, - "messageType: {0}, endOfMessage: {1}", - messageType, - endOfMessage); - } - ThrowIfPendingException(); ThrowIfDisposed(); ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived); @@ -421,15 +398,6 @@ private async Task CloseOutputAsyncCore(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) { - string inputParameter = string.Empty; - if (NetEventSource.Log.IsEnabled()) - { - inputParameter = string.Format(CultureInfo.InvariantCulture, - "closeStatus: {0}, statusDescription: {1}", - closeStatus, - statusDescription); - } - ThrowIfPendingException(); if (IsStateTerminal(State)) { @@ -648,15 +616,6 @@ private async Task CloseAsyncCore(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken cancellationToken) { - string inputParameter = string.Empty; - if (NetEventSource.Log.IsEnabled()) - { - inputParameter = string.Format(CultureInfo.InvariantCulture, - "closeStatus: {0}, statusDescription: {1}", - closeStatus, - statusDescription); - } - ThrowIfPendingException(); if (IsStateTerminal(State)) { @@ -1067,15 +1026,7 @@ private static WebSocketMessageType GetMessageType(WebSocketProtocolComponent.Bu // This indicates a contract violation of the websocket protocol component, // because we currently don't support any WebSocket extensions and would // not accept a Websocket handshake requesting extensions - Debug.Fail(string.Format(CultureInfo.InvariantCulture, - "The value of 'bufferType' ({0}) is invalid. Valid buffer types: {1}, {2}, {3}, {4}, {5}.", - bufferType, - WebSocketProtocolComponent.BufferType.Close, - WebSocketProtocolComponent.BufferType.BinaryFragment, - WebSocketProtocolComponent.BufferType.BinaryMessage, - WebSocketProtocolComponent.BufferType.UTF8Fragment, - WebSocketProtocolComponent.BufferType.UTF8Message)); - + Debug.Fail($"The value of 'bufferType' ({bufferType}) is invalid."); throw new WebSocketException(WebSocketError.NativeError, SR.Format(SR.net_WebSockets_InvalidBufferType, bufferType, @@ -1337,14 +1288,7 @@ private void FinishOnCloseReceived(WebSocketCloseStatus closeStatus, _closeStatus = closeStatus; _closeStatusDescription = closeStatusDescription; - if (NetEventSource.Log.IsEnabled()) - { - string parameters = string.Format(CultureInfo.InvariantCulture, - "closeStatus: {0}, closeStatusDescription: {1}, _State: {2}", - closeStatus, closeStatusDescription, _state); - - NetEventSource.Info(this, parameters); - } + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"closeStatus: {closeStatus}, closeStatusDescription: {closeStatusDescription}, _State: {_state}"); } private static async void OnKeepAlive(object? sender) diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/WebSocketBuffer.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/WebSocketBuffer.cs index 838963d98d8d..712b0d7f39d6 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/WebSocketBuffer.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/WebSocketBuffer.cs @@ -49,14 +49,10 @@ internal sealed class WebSocketBuffer : IDisposable private WebSocketBuffer(ArraySegment internalBuffer, int receiveBufferSize, int sendBufferSize) { Debug.Assert(internalBuffer.Array != null, "'internalBuffer.Array' MUST NOT be NULL."); - Debug.Assert(receiveBufferSize >= HttpWebSocket.MinReceiveBufferSize, - "'receiveBufferSize' MUST be at least " + HttpWebSocket.MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - Debug.Assert(sendBufferSize >= HttpWebSocket.MinSendBufferSize, - "'sendBufferSize' MUST be at least " + HttpWebSocket.MinSendBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - Debug.Assert(receiveBufferSize <= HttpWebSocket.MaxBufferSize, - "'receiveBufferSize' MUST NOT exceed " + HttpWebSocket.MaxBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - Debug.Assert(sendBufferSize <= HttpWebSocket.MaxBufferSize, - "'sendBufferSize' MUST NOT exceed " + HttpWebSocket.MaxBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); + Debug.Assert(receiveBufferSize >= HttpWebSocket.MinReceiveBufferSize, $"'receiveBufferSize' MUST be at least {HttpWebSocket.MinReceiveBufferSize}."); + Debug.Assert(sendBufferSize >= HttpWebSocket.MinSendBufferSize, $"'sendBufferSize' MUST be at least {HttpWebSocket.MinSendBufferSize}."); + Debug.Assert(receiveBufferSize <= HttpWebSocket.MaxBufferSize, $"'receiveBufferSize' MUST NOT exceed {HttpWebSocket.MaxBufferSize}."); + Debug.Assert(sendBufferSize <= HttpWebSocket.MaxBufferSize, $"'sendBufferSize' MUST NOT exceed {HttpWebSocket.MaxBufferSize}."); _receiveBufferSize = receiveBufferSize; _sendBufferSize = sendBufferSize; @@ -634,10 +630,8 @@ private void CleanUp() internal static ArraySegment CreateInternalBufferArraySegment(int receiveBufferSize, int sendBufferSize, bool isServerBuffer) { - Debug.Assert(receiveBufferSize >= HttpWebSocket.MinReceiveBufferSize, - "'receiveBufferSize' MUST be at least " + HttpWebSocket.MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - Debug.Assert(sendBufferSize >= HttpWebSocket.MinSendBufferSize, - "'sendBufferSize' MUST be at least " + HttpWebSocket.MinSendBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); + Debug.Assert(receiveBufferSize >= HttpWebSocket.MinReceiveBufferSize, $"'receiveBufferSize' MUST be at least {HttpWebSocket.MinReceiveBufferSize}."); + Debug.Assert(sendBufferSize >= HttpWebSocket.MinSendBufferSize, $"'sendBufferSize' MUST be at least {HttpWebSocket.MinSendBufferSize}."); int internalBufferSize = GetInternalBufferSize(receiveBufferSize, sendBufferSize, isServerBuffer); return new ArraySegment(new byte[internalBufferSize]); @@ -645,10 +639,8 @@ internal static ArraySegment CreateInternalBufferArraySegment(int receiveB internal static void Validate(int count, int receiveBufferSize, int sendBufferSize, bool isServerBuffer) { - Debug.Assert(receiveBufferSize >= HttpWebSocket.MinReceiveBufferSize, - "'receiveBufferSize' MUST be at least " + HttpWebSocket.MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - Debug.Assert(sendBufferSize >= HttpWebSocket.MinSendBufferSize, - "'sendBufferSize' MUST be at least " + HttpWebSocket.MinSendBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); + Debug.Assert(receiveBufferSize >= HttpWebSocket.MinReceiveBufferSize, $"'receiveBufferSize' MUST be at least {HttpWebSocket.MinReceiveBufferSize}."); + Debug.Assert(sendBufferSize >= HttpWebSocket.MinSendBufferSize, $"'sendBufferSize' MUST be at least {HttpWebSocket.MinSendBufferSize}."); int minBufferSize = GetInternalBufferSize(receiveBufferSize, sendBufferSize, isServerBuffer); if (count < minBufferSize) @@ -660,15 +652,11 @@ internal static void Validate(int count, int receiveBufferSize, int sendBufferSi private static int GetInternalBufferSize(int receiveBufferSize, int sendBufferSize, bool isServerBuffer) { - Debug.Assert(receiveBufferSize >= HttpWebSocket.MinReceiveBufferSize, - "'receiveBufferSize' MUST be at least " + HttpWebSocket.MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - Debug.Assert(sendBufferSize >= HttpWebSocket.MinSendBufferSize, - "'sendBufferSize' MUST be at least " + HttpWebSocket.MinSendBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); + Debug.Assert(receiveBufferSize >= HttpWebSocket.MinReceiveBufferSize, $"'receiveBufferSize' MUST be at least {HttpWebSocket.MinReceiveBufferSize}."); + Debug.Assert(sendBufferSize >= HttpWebSocket.MinSendBufferSize, $"'sendBufferSize' MUST be at least {HttpWebSocket.MinSendBufferSize}."); - Debug.Assert(receiveBufferSize <= HttpWebSocket.MaxBufferSize, - "'receiveBufferSize' MUST be less than or equal to " + HttpWebSocket.MaxBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - Debug.Assert(sendBufferSize <= HttpWebSocket.MaxBufferSize, - "'sendBufferSize' MUST be at less than or equal to " + HttpWebSocket.MaxBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); + Debug.Assert(receiveBufferSize <= HttpWebSocket.MaxBufferSize, $"'receiveBufferSize' MUST be less than or equal to {HttpWebSocket.MaxBufferSize}."); + Debug.Assert(sendBufferSize <= HttpWebSocket.MaxBufferSize, $"'sendBufferSize' MUST be at less than or equal to {HttpWebSocket.MaxBufferSize}."); int nativeSendBufferSize = GetNativeSendBufferSize(sendBufferSize, isServerBuffer); return 2 * receiveBufferSize + nativeSendBufferSize + NativeOverheadBufferSize + s_PropertyBufferSize; diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/DomainLiteralReader.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/DomainLiteralReader.cs index 29a31a1fc4cb..2f03cf45b458 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/DomainLiteralReader.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/DomainLiteralReader.cs @@ -30,7 +30,7 @@ internal static class DomainLiteralReader // internal static bool TryReadReverse(string data, int index, out int outIndex, bool throwExceptionIfFail) { - Debug.Assert(0 <= index && index < data.Length, "index was outside the bounds of the string: " + index); + Debug.Assert(0 <= index && index < data.Length, $"index was outside the bounds of the string: {index}"); Debug.Assert(data[index] == MailBnfHelper.EndSquareBracket, "data did not end with a square bracket"); // Skip the end bracket diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/DotAtomReader.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/DotAtomReader.cs index f3a1da7361bc..f50b9946f4fe 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/DotAtomReader.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/DotAtomReader.cs @@ -36,7 +36,7 @@ internal static class DotAtomReader // internal static bool TryReadReverse(string data, int index, out int outIndex, bool throwExceptionIfFail) { - Debug.Assert(0 <= index && index < data.Length, "index was outside the bounds of the string: " + index); + Debug.Assert(0 <= index && index < data.Length, $"index was outside the bounds of the string: {index}"); int startIndex = index; diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddressParser.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddressParser.cs index 38867b921b9c..fe57dd93c145 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddressParser.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddressParser.cs @@ -27,7 +27,7 @@ internal static bool TryParseAddress(string data, out ParseAddressInfo parsedAdd { int index = data.Length - 1; bool parseSuccess = TryParseAddress(data, false, ref index, out parsedAddress, throwExceptionIfFail); - Debug.Assert(!parseSuccess || index == -1, "The index indicates that part of the address was not parsed: " + index); + Debug.Assert(!parseSuccess || index == -1, $"The index indicates that part of the address was not parsed: {index}"); return parseSuccess; } @@ -66,7 +66,7 @@ internal static List ParseMultipleAddresses(string data) private static bool TryParseAddress(string data, bool expectMultipleAddresses, ref int index, out ParseAddressInfo parseAddressInfo, bool throwExceptionIfFail) { Debug.Assert(!string.IsNullOrEmpty(data)); - Debug.Assert(index >= 0 && index < data.Length, "Index out of range: " + index + ", " + data.Length); + Debug.Assert(index >= 0 && index < data.Length, $"Index out of range: {index}, {data.Length}"); // Parsed components to be assembled as a MailAddress later string? displayName; @@ -378,7 +378,7 @@ private static bool TryParseDisplayName(string data, ref int index, bool expectM return false; } - Debug.Assert(data[index + 1] == MailBnfHelper.Quote, "Mis-aligned index: " + index); + Debug.Assert(data[index + 1] == MailBnfHelper.Quote, $"Mis-aligned index: {index}"); // Do not include the bounding quotes on the display name int leftIndex = index + 2; @@ -417,7 +417,7 @@ private static bool TryParseDisplayName(string data, ref int index, bool expectM return false; } - Debug.Assert(index < 0 || data[index] == MailBnfHelper.Comma, "Mis-aligned index: " + index); + Debug.Assert(index < 0 || data[index] == MailBnfHelper.Comma, $"Mis-aligned index: {index}"); // Do not include the Comma (if any), and because there were no bounding quotes, // trim extra whitespace. diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/QuotedPairReader.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/QuotedPairReader.cs index e98a031e77ca..34079edf51c8 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/QuotedPairReader.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/QuotedPairReader.cs @@ -33,7 +33,7 @@ internal static class QuotedPairReader // Throws a FormatException or false is returned if the an escaped Unicode character is found but was not permitted. internal static bool TryCountQuotedChars(string data, int index, bool permitUnicodeEscaping, out int outIndex, bool throwExceptionIfFail) { - Debug.Assert(0 <= index && index < data.Length, "Index out of range: " + index + ", " + data.Length); + Debug.Assert(0 <= index && index < data.Length, $"Index out of range: {index}, {data.Length}"); if (index <= 0 || data[index - 1] != MailBnfHelper.Backslash) { @@ -81,7 +81,7 @@ internal static bool TryCountQuotedChars(string data, int index, bool permitUnic // Return value: The number of consecutive backslashes, including the initial one at data[index]. private static int CountBackslashes(string data, int index) { - Debug.Assert(index >= 0 && data[index] == MailBnfHelper.Backslash, "index was not a backslash: " + index); + Debug.Assert(index >= 0 && data[index] == MailBnfHelper.Backslash, $"index was not a backslash: {index}"); // Find all the backslashes. It's possible that there are multiple escaped/quoted backslashes. int backslashCount = 0; @@ -92,7 +92,7 @@ private static int CountBackslashes(string data, int index) } while (index >= 0 && data[index] == MailBnfHelper.Backslash); // At this point data[index] should not be a backslash - Debug.Assert(index < 0 || data[index] != MailBnfHelper.Backslash, "index was a backslash: " + index); + Debug.Assert(index < 0 || data[index] != MailBnfHelper.Backslash, $"index was a backslash: {index}"); return backslashCount; } diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/QuotedStringFormatReader.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/QuotedStringFormatReader.cs index c62b7f83500f..e12d73164007 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/QuotedStringFormatReader.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/QuotedStringFormatReader.cs @@ -34,9 +34,9 @@ internal static class QuotedStringFormatReader // internal static bool TryReadReverseQuoted(string data, int index, bool permitUnicode, out int outIndex, bool throwExceptionIfFail) { - Debug.Assert(0 <= index && index < data.Length, "Index out of range: " + index + ", " + data.Length); + Debug.Assert(0 <= index && index < data.Length, $"Index out of range: {index}, {data.Length}"); // Check for the first bounding quote - Debug.Assert(data[index] == MailBnfHelper.Quote, "Initial char at index " + index + " was not a quote."); + Debug.Assert(data[index] == MailBnfHelper.Quote, $"Initial char at index {index} was not a quote."); // Skip the bounding quote index--; @@ -125,7 +125,7 @@ internal static bool TryReadReverseQuoted(string data, int index, bool permitUni // internal static bool TryReadReverseUnQuoted(string data, int index, bool permitUnicode, bool expectCommaDelimiter, out int outIndex, bool throwExceptionIfFail) { - Debug.Assert(0 <= index && index < data.Length, "Index out of range: " + index + ", " + data.Length); + Debug.Assert(0 <= index && index < data.Length, $"Index out of range: {index}, {data.Length}"); do { diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs index cceff5d15fa1..415c1e6e972d 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs @@ -393,7 +393,7 @@ internal MailWriter GetFileMailWriter(string? pickupDirectory) string pathAndFilename; while (true) { - filename = Guid.NewGuid().ToString() + ".eml"; + filename = $"{Guid.NewGuid()}.eml"; pathAndFilename = Path.Combine(pickupDirectory, filename); if (!File.Exists(pathAndFilename)) break; diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeMultiPart.cs b/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeMultiPart.cs index bb300289dfa8..4365cee08cb9 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeMultiPart.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeMultiPart.cs @@ -36,7 +36,7 @@ internal MimeMultiPartType MimeMultiPartType private void SetType(MimeMultiPartType type) { - ContentType.MediaType = "multipart" + "/" + type.ToString().ToLowerInvariant(); + ContentType.MediaType = "multipart/" + type.ToString().ToLowerInvariant(); ContentType.Boundary = GetNextBoundary(); } @@ -249,10 +249,7 @@ internal override void Send(BaseWriter writer, bool allowUnicode) internal string GetNextBoundary() { int b = Interlocked.Increment(ref s_boundary) - 1; - string boundaryString = "--boundary_" + b.ToString(CultureInfo.InvariantCulture) + "_" + Guid.NewGuid().ToString(null, CultureInfo.InvariantCulture); - - - return boundaryString; + return $"--boundary_{(uint)b}_{Guid.NewGuid()}"; } } } diff --git a/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs b/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs index 74c62684a82c..f18ee68a35a9 100644 --- a/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs +++ b/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs @@ -39,7 +39,7 @@ private static SocketError GetSocketErrorForNativeError(int error) case (int)Interop.Sys.GetAddrInfoErrorFlags.EAI_MEMORY: throw new OutOfMemoryException(); default: - Debug.Fail("Unexpected error: " + error.ToString()); + Debug.Fail($"Unexpected error: {error}"); return SocketError.SocketError; } } diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Addresses.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Addresses.cs index 59463667713d..c898a8539e4f 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Addresses.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Addresses.cs @@ -99,8 +99,8 @@ internal static void ParseDhcpServerAddressesFromLeasesFile(List coll int interfaceIndex = fileContents.IndexOf("interface", firstBrace, blockLength, StringComparison.Ordinal); int afterName = fileContents.IndexOf(';', interfaceIndex); int beforeName = fileContents.LastIndexOf(' ', afterName); - string interfaceName = fileContents.Substring(beforeName + 2, afterName - beforeName - 3); - if (interfaceName != name) + ReadOnlySpan interfaceName = fileContents.AsSpan(beforeName + 2, afterName - beforeName - 3); + if (!interfaceName.SequenceEqual(name)) { continue; } @@ -108,9 +108,8 @@ internal static void ParseDhcpServerAddressesFromLeasesFile(List coll int indexOfDhcp = fileContents.IndexOf("dhcp-server-identifier", firstBrace, blockLength, StringComparison.Ordinal); int afterAddress = fileContents.IndexOf(';', indexOfDhcp); int beforeAddress = fileContents.LastIndexOf(' ', afterAddress); - string dhcpAddressString = fileContents.Substring(beforeAddress + 1, afterAddress - beforeAddress - 1); - IPAddress? dhcpAddress; - if (IPAddress.TryParse(dhcpAddressString, out dhcpAddress)) + ReadOnlySpan dhcpAddressSpan = fileContents.AsSpan(beforeAddress + 1, afterAddress - beforeAddress - 1); + if (IPAddress.TryParse(dhcpAddressSpan, out IPAddress? dhcpAddress)) { collection.Add(dhcpAddress); } @@ -143,8 +142,8 @@ internal static List ParseWinsServerAddressesFromSmbConfFile(string s } } int endOfLine = fileContents.IndexOf(Environment.NewLine, labelIndex, StringComparison.Ordinal); - string addressString = fileContents.Substring(labelIndex + label.Length, endOfLine - (labelIndex + label.Length)); - IPAddress address = IPAddress.Parse(addressString); + ReadOnlySpan addressSpan = fileContents.AsSpan(labelIndex + label.Length, endOfLine - (labelIndex + label.Length)); + IPAddress address = IPAddress.Parse(addressSpan); collection.Add(address); } } diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Connections.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Connections.cs index d53d00eb5887..ca042d47b749 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Connections.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Connections.cs @@ -249,12 +249,11 @@ private static IPEndPoint ParseLocalConnectionInformation(string line) throw ExceptionHelper.CreateForParseFailure(); } - string remoteAddressString = localAddressAndPort.Substring(0, indexOfColon); - IPAddress localIPAddress = ParseHexIPAddress(remoteAddressString); + IPAddress localIPAddress = ParseHexIPAddress(localAddressAndPort.AsSpan(0, indexOfColon)); - string portString = localAddressAndPort.Substring(indexOfColon + 1, localAddressAndPort.Length - (indexOfColon + 1)); + ReadOnlySpan portSpan = localAddressAndPort.AsSpan(indexOfColon + 1, localAddressAndPort.Length - (indexOfColon + 1)); int localPort; - if (!int.TryParse(portString, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out localPort)) + if (!int.TryParse(portSpan, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out localPort)) { throw ExceptionHelper.CreateForParseFailure(); } @@ -270,12 +269,11 @@ private static IPEndPoint ParseAddressAndPort(string colonSeparatedAddress) throw ExceptionHelper.CreateForParseFailure(); } - string remoteAddressString = colonSeparatedAddress.Substring(0, indexOfColon); - IPAddress ipAddress = ParseHexIPAddress(remoteAddressString); + IPAddress ipAddress = ParseHexIPAddress(colonSeparatedAddress.AsSpan(0, indexOfColon)); - string portString = colonSeparatedAddress.Substring(indexOfColon + 1, colonSeparatedAddress.Length - (indexOfColon + 1)); + ReadOnlySpan portSpan = colonSeparatedAddress.AsSpan(indexOfColon + 1, colonSeparatedAddress.Length - (indexOfColon + 1)); int port; - if (!int.TryParse(portString, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out port)) + if (!int.TryParse(portSpan, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out port)) { throw ExceptionHelper.CreateForParseFailure(); } @@ -289,7 +287,7 @@ private static TcpState MapTcpState(int state) return Interop.Sys.MapTcpState((int)state); } - internal static IPAddress ParseHexIPAddress(string remoteAddressString) + internal static IPAddress ParseHexIPAddress(ReadOnlySpan remoteAddressString) { if (remoteAddressString.Length <= 8) // IPv4 Address { @@ -307,7 +305,7 @@ internal static IPAddress ParseHexIPAddress(string remoteAddressString) // Simply converts the hex string into a long and uses the IPAddress(long) constructor. // Strings passed to this method must be 8 or less characters in length (32-bit address). - private static IPAddress ParseIPv4HexString(string hexAddress) + private static IPAddress ParseIPv4HexString(ReadOnlySpan hexAddress) { IPAddress ipAddress; long addressValue; @@ -328,10 +326,10 @@ private static IPAddress ParseIPv4HexString(string hexAddress) // It's represenation in /proc/net/tcp6: 00-00-80-FE 00-00-00-00 FF-5D-15-02 02-04-00-FE // (dashes and spaces added above for readability) // Strings passed to this must be 32 characters in length. - private static IPAddress ParseIPv6HexString(string hexAddress, bool isNetworkOrder = false) + private static IPAddress ParseIPv6HexString(ReadOnlySpan hexAddress, bool isNetworkOrder = false) { Debug.Assert(hexAddress.Length == 32); - byte[] addressBytes = new byte[16]; + Span addressBytes = stackalloc byte[16]; if (isNetworkOrder || !BitConverter.IsLittleEndian) { for (int i = 0; i < 16; i++) diff --git a/src/libraries/System.Net.Primitives/src/System/Net/Cookie.cs b/src/libraries/System.Net.Primitives/src/System/Net/Cookie.cs index 3893ae10651a..8ed63ef9f97b 100644 --- a/src/libraries/System.Net.Primitives/src/System/Net/Cookie.cs +++ b/src/libraries/System.Net.Primitives/src/System/Net/Cookie.cs @@ -742,7 +742,7 @@ internal void ToString(StringBuilder sb) { sb.Append(SpecialAttributeLiteral + CookieFields.VersionAttributeName + EqualsLiteral); // const strings if (IsQuotedVersion) sb.Append('"'); - sb.Append(m_version.ToString(NumberFormatInfo.InvariantInfo)); + sb.Append(NumberFormatInfo.InvariantInfo, $"{m_version}"); if (IsQuotedVersion) sb.Append('"'); sb.Append(SeparatorLiteral); } diff --git a/src/libraries/System.Net.Primitives/src/System/Net/CredentialCache.cs b/src/libraries/System.Net.Primitives/src/System/Net/CredentialCache.cs index 212e3b6e0941..bb7801cbebb8 100644 --- a/src/libraries/System.Net.Primitives/src/System/Net/CredentialCache.cs +++ b/src/libraries/System.Net.Primitives/src/System/Net/CredentialCache.cs @@ -452,7 +452,7 @@ public override bool Equals([NotNullWhen(true)] object? obj) => obj is CredentialHostKey && Equals((CredentialHostKey)obj); public override string ToString() => - Host + ":" + Port.ToString(NumberFormatInfo.InvariantInfo) + ":" + AuthenticationType; + string.Create(CultureInfo.InvariantCulture, $"{Host}:{Port}:{AuthenticationType}"); } internal sealed class CredentialKey : IEquatable @@ -543,6 +543,6 @@ public bool Equals([NotNullWhen(true)] CredentialKey? other) public override bool Equals([NotNullWhen(true)] object? obj) => Equals(obj as CredentialKey); public override string ToString() => - "[" + UriPrefixLength.ToString(NumberFormatInfo.InvariantInfo) + "]:" + UriPrefix + ":" + AuthenticationType; + string.Create(CultureInfo.InvariantCulture, $"[{UriPrefixLength}]:{UriPrefix}:{AuthenticationType}"); } } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs index 2ebe218c4070..10dd32c03165 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs @@ -737,7 +737,7 @@ internal ProtocolToken NextMessage(ReadOnlySpan incomingBuffer) { if (token.Failed) { - NetEventSource.Error(this, $"Authentication failed. Status: {status.ToString()}, Exception message: {token.GetException()!.Message}"); + NetEventSource.Error(this, $"Authentication failed. Status: {status}, Exception message: {token.GetException()!.Message}"); } } return token; diff --git a/src/libraries/System.Net.Security/src/System/Security/Authentication/ExtendedProtection/ExtendedProtectionPolicy.cs b/src/libraries/System.Net.Security/src/System/Security/Authentication/ExtendedProtection/ExtendedProtectionPolicy.cs index fe69152dc10a..6a59d81957a4 100644 --- a/src/libraries/System.Net.Security/src/System/Security/Authentication/ExtendedProtection/ExtendedProtectionPolicy.cs +++ b/src/libraries/System.Net.Security/src/System/Security/Authentication/ExtendedProtection/ExtendedProtectionPolicy.cs @@ -105,9 +105,9 @@ public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append("ProtectionScenario="); - sb.Append(_protectionScenario.ToString()); + sb.Append($"{_protectionScenario}"); sb.Append("; PolicyEnforcement="); - sb.Append(_policyEnforcement.ToString()); + sb.Append($"{_policyEnforcement}"); sb.Append("; CustomChannelBinding="); if (_customChannelBinding == null) diff --git a/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs b/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs index 0c32b6d906d2..27289121acd0 100644 --- a/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs +++ b/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs @@ -501,7 +501,7 @@ private void OpenFileInternal( { if (needsHeaderAndBoundary) { - string boundary = "---------------------" + DateTime.Now.Ticks.ToString("x", NumberFormatInfo.InvariantInfo); + string boundary = $"---------------------{DateTime.Now.Ticks:x}"; headers[HttpKnownHeaderNames.ContentType] = UploadFileContentType + "; boundary=" + boundary; diff --git a/src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs b/src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs index 368cf805542b..9959c66918e3 100644 --- a/src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs +++ b/src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs @@ -35,7 +35,7 @@ public WebProxy(Uri? Address, bool BypassOnLocal, string[]? BypassList, ICredent } public WebProxy(string Host, int Port) - : this(new Uri("http://" + Host + ":" + Port.ToString(CultureInfo.InvariantCulture)), false, null, null) + : this(new Uri(string.Create(CultureInfo.InvariantCulture, $"http://{Host}:{Port}")), false, null, null) { } @@ -142,9 +142,10 @@ private bool IsMatchInBypassList(Uri input) if (_regexBypassList != null) { + Span stackBuffer = stackalloc char[128]; string matchUriString = input.IsDefaultPort ? - $"{input.Scheme}://{input.Host}" : - $"{input.Scheme}://{input.Host}:{(uint)input.Port}"; + string.Create(null, stackBuffer, $"{input.Scheme}://{input.Host}") : + string.Create(null, stackBuffer, $"{input.Scheme}://{input.Host}:{(uint)input.Port}"); foreach (Regex r in _regexBypassList) { diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs index 0af0cd5fb145..f19f9a572772 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs @@ -338,8 +338,7 @@ static string GetDeflateOptions(WebSocketDeflateOptions options) if (options.ClientMaxWindowBits != WebSocketValidate.MaxDeflateWindowBits) { - builder.Append(ClientWebSocketDeflateConstants.ClientMaxWindowBits).Append('=') - .Append(options.ClientMaxWindowBits.ToString(CultureInfo.InvariantCulture)); + builder.Append(CultureInfo.InvariantCulture, $"{ClientWebSocketDeflateConstants.ClientMaxWindowBits}={options.ClientMaxWindowBits}"); } else { @@ -354,9 +353,7 @@ static string GetDeflateOptions(WebSocketDeflateOptions options) if (options.ServerMaxWindowBits != WebSocketValidate.MaxDeflateWindowBits) { - builder.Append("; ") - .Append(ClientWebSocketDeflateConstants.ServerMaxWindowBits).Append('=') - .Append(options.ServerMaxWindowBits.ToString(CultureInfo.InvariantCulture)); + builder.Append(CultureInfo.InvariantCulture, $"; {ClientWebSocketDeflateConstants.ServerMaxWindowBits}={options.ServerMaxWindowBits}"); } if (!options.ServerContextTakeover) diff --git a/src/libraries/System.Private.CoreLib/src/Internal/Win32/RegistryKey.cs b/src/libraries/System.Private.CoreLib/src/Internal/Win32/RegistryKey.cs index f73025d545b7..68eab6c5bbac 100644 --- a/src/libraries/System.Private.CoreLib/src/Internal/Win32/RegistryKey.cs +++ b/src/libraries/System.Private.CoreLib/src/Internal/Win32/RegistryKey.cs @@ -67,7 +67,7 @@ public void DeleteValue(string name, bool throwOnMissingValue) } // We really should throw an exception here if errorCode was bad, // but we can't for compatibility reasons. - Debug.Assert(errorCode == 0, "RegDeleteValue failed. Here's your error code: " + errorCode); + Debug.Assert(errorCode == 0, $"RegDeleteValue failed. Here's your error code: {errorCode}"); } internal static RegistryKey OpenBaseKey(IntPtr hKey) diff --git a/src/libraries/System.Private.CoreLib/src/System/ApplicationId.cs b/src/libraries/System.Private.CoreLib/src/System/ApplicationId.cs index 30542d17ece7..11f807986d31 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ApplicationId.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ApplicationId.cs @@ -42,13 +42,9 @@ public override string ToString() sb.Append(Name); if (Culture != null) { - sb.Append(", culture=\""); - sb.Append(Culture); - sb.Append('"'); + sb.Append($", culture=\"{Culture}\""); } - sb.Append(", version=\""); - sb.Append(Version.ToString()); - sb.Append('"'); + sb.Append($", version=\"{Version}\""); if (_publicKeyToken != null) { sb.Append(", publicKeyToken=\""); @@ -57,9 +53,7 @@ public override string ToString() } if (ProcessorArchitecture != null) { - sb.Append(", processorArchitecture =\""); - sb.Append(ProcessorArchitecture); - sb.Append('"'); + sb.Append($", processorArchitecture =\"{ProcessorArchitecture}\""); } return sb.ToString(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/KeyValuePair.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/KeyValuePair.cs index 83f4fda598eb..61325d2058f2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/KeyValuePair.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/KeyValuePair.cs @@ -11,36 +11,12 @@ namespace System.Collections.Generic public static class KeyValuePair { // Creates a new KeyValuePair from the given values. - public static KeyValuePair Create(TKey key, TValue value) - { - return new KeyValuePair(key, value); - } - - /// - /// Used by KeyValuePair.ToString to reduce generic code - /// - internal static string PairToString(object? key, object? value) - { - var s = new ValueStringBuilder(stackalloc char[64]); - - s.Append('['); + public static KeyValuePair Create(TKey key, TValue value) => + new KeyValuePair(key, value); - if (key != null) - { - s.Append(key.ToString()); - } - - s.Append(", "); - - if (value != null) - { - s.Append(value.ToString()); - } - - s.Append(']'); - - return s.ToString(); - } + /// Used by KeyValuePair.ToString to reduce generic code + internal static string PairToString(object? key, object? value) => + string.Create(null, stackalloc char[256], $"[{key}, {value}]"); } // A KeyValuePair holds a key and a value from a dictionary. diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/ActivityTracker.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/ActivityTracker.cs index a1f724612d46..8aebbc4f7aae 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/ActivityTracker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/ActivityTracker.cs @@ -321,7 +321,7 @@ public static string Path(ActivityInfo? activityInfo) { if (activityInfo == null) return ""; - return Path(activityInfo.m_creator) + "/" + activityInfo.m_uniqueId.ToString(); + return $"{Path(activityInfo.m_creator)}/{activityInfo.m_uniqueId}"; } public override string ToString() diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventCounter.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventCounter.cs index 8a53d5690821..0c4460facc30 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventCounter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventCounter.cs @@ -71,7 +71,7 @@ public override string ToString() int count = Volatile.Read(ref _count); return count == 0 ? $"EventCounter '{Name}' Count 0" : - $"EventCounter '{Name}' Count {count} Mean {(_sum / count).ToString("n3")}"; + $"EventCounter '{Name}' Count {count} Mean {_sum / count:n3}"; } #region Statistics Calculation diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs index d6093b4fb66b..4800a2cde473 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs @@ -3790,7 +3790,9 @@ internal void ReportOutOfBandMessage(string msg) try { if (m_outOfBandMessageCount < 16 - 1) // Note this is only if size byte + { m_outOfBandMessageCount++; + } else { if (m_outOfBandMessageCount == 16) @@ -3800,7 +3802,7 @@ internal void ReportOutOfBandMessage(string msg) } // send message to debugger - System.Diagnostics.Debugger.Log(0, null, string.Format("EventSource Error: {0}{1}", msg, System.Environment.NewLine)); + Debugger.Log(0, null, $"EventSource Error: {msg}{System.Environment.NewLine}"); // Send it to all listeners. WriteEventString(msg); @@ -5220,14 +5222,12 @@ public ManifestBuilder(string providerName, Guid providerGuid, string? dllName, sb.AppendLine(""); sb.AppendLine(" "); sb.AppendLine(" "); - sb.Append(""); + sb.AppendLine($" symbol=\"{symbolsName}\">"); } public void AddOpcode(string name, int value) diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/PollingCounter.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/PollingCounter.cs index fee9867d2c9f..ed9697d24502 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/PollingCounter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/PollingCounter.cs @@ -42,7 +42,7 @@ public PollingCounter(string name, EventSource eventSource, Func metricP Publish(); } - public override string ToString() => $"PollingCounter '{Name}' Count 1 Mean {_lastVal.ToString("n3")}"; + public override string ToString() => $"PollingCounter '{Name}' Count 1 Mean {_lastVal:n3}"; private readonly Func _metricProvider; private double _lastVal; diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Win32.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Win32.cs index a499d8984371..8e8e8c5e6898 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Win32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Win32.cs @@ -56,7 +56,7 @@ private static void SetEnvironmentVariableFromRegistry(string variable, string? fixed (char* lParam = "Environment") { IntPtr r = Interop.User32.SendMessageTimeout(new IntPtr(Interop.User32.HWND_BROADCAST), Interop.User32.WM_SETTINGCHANGE, IntPtr.Zero, (IntPtr)lParam, 0, 1000, out IntPtr _); - Debug.Assert(r != IntPtr.Zero, "SetEnvironmentVariable failed: " + Marshal.GetLastPInvokeError()); + Debug.Assert(r != IntPtr.Zero, $"SetEnvironmentVariable failed: {Marshal.GetLastPInvokeError()}"); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs index e9e3618bfc60..593a9ab1e4fb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs @@ -680,7 +680,7 @@ private static StringBuilder FormatCustomized( } else { - result.Append(year.ToString("D" + tokenLen.ToString(), CultureInfo.InvariantCulture)); + result.Append(year.ToString("D" + tokenLen.ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture)); } } bTimeOnly = false; diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs index ce6e23b8c856..b4796fd92e63 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs @@ -5213,7 +5213,7 @@ private static string Hex(ReadOnlySpan str) if (str[i] <= '\x007f') buffer.Append(str[i]); else - buffer.Append("\\u").Append(((int)str[i]).ToString("x4", CultureInfo.InvariantCulture)); + buffer.Append($"\\u{(int)str[i]:x4}"); } buffer.Append('"'); return buffer.ToString(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/HebrewCalendar.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/HebrewCalendar.cs index 4a6aef540a5a..2f9d5f729e45 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/HebrewCalendar.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/HebrewCalendar.cs @@ -660,7 +660,7 @@ public override int GetDaysInMonth(int year, int month, int era) CheckHebrewMonthValue(year, month, era); Debug.Assert(hebrewYearType >= 1 && hebrewYearType <= 6, - "hebrewYearType should be from 1 to 6, but now hebrewYearType = " + hebrewYearType + " for hebrew year " + year); + $"hebrewYearType should be from 1 to 6, but now hebrewYearType = {hebrewYearType} for hebrew year {year}"); int monthDays = LunarMonthLen[hebrewYearType * MaxMonthPlusOne + month]; if (monthDays == 0) { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/BufferedStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/BufferedStream.cs index 3834bbeeffe7..14da1aa038b7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/BufferedStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/BufferedStream.cs @@ -389,7 +389,7 @@ private void FlushRead() private void ClearReadBufferBeforeWrite() { Debug.Assert(_stream != null); - Debug.Assert(_readPos <= _readLen, "_readPos <= _readLen [" + _readPos + " <= " + _readLen + "]"); + Debug.Assert(_readPos <= _readLen, $"_readPos <= _readLen [{_readPos} <= {_readLen}]"); // No read data in the buffer: if (_readPos == _readLen) @@ -1241,7 +1241,7 @@ public override long Seek(long offset, SeekOrigin origin) _readPos = _readLen = 0; } - Debug.Assert(newPos == Position, "newPos (=" + newPos + ") == Position (=" + Position + ")"); + Debug.Assert(newPos == Position, $"newPos (={newPos}) == Position (={Position})"); return newPos; } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs index 11d21121beea..9aaa16ff5194 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs @@ -985,7 +985,7 @@ public override long Seek(long offset, SeekOrigin origin) _readPos = _readLen = 0; } - Debug.Assert(newPos == Position, "newPos (=" + newPos + ") == Position (=" + Position + ")"); + Debug.Assert(newPos == Position, $"newPos (={newPos}) == Position (={Position})"); return newPos; } @@ -1023,7 +1023,7 @@ private void FlushWrite() /// private void ClearReadBufferBeforeWrite() { - Debug.Assert(_readPos <= _readLen, "_readPos <= _readLen [" + _readPos + " <= " + _readLen + "]"); + Debug.Assert(_readPos <= _readLen, $"_readPos <= _readLen [{_readPos} <= {_readLen}]"); // No read data in the buffer: if (_readPos == _readLen) diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 9a6eb161d77f..f7acdcd21d07 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -1933,14 +1933,14 @@ public ref struct TryWriteInterpolatedStringHandler /// The number of constant characters outside of interpolation expressions in the interpolated string. /// The number of interpolation expressions in the interpolated string. /// The destination buffer. - /// Upon return, true if the destination may be long enough to support the formatting, or false if it won't be. + /// Upon return, true if the destination may be long enough to support the formatting, or false if it won't be. /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. - public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, Span destination, out bool success) + public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, Span destination, out bool shouldAppend) { _destination = destination; _provider = null; _pos = 0; - _success = success = destination.Length >= literalLength; + _success = shouldAppend = destination.Length >= literalLength; _hasCustomFormatter = false; } @@ -1949,14 +1949,14 @@ public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, /// The number of interpolation expressions in the interpolated string. /// The destination buffer. /// An object that supplies culture-specific formatting information. - /// Upon return, true if the destination may be long enough to support the formatting, or false if it won't be. + /// Upon return, true if the destination may be long enough to support the formatting, or false if it won't be. /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. - public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, Span destination, IFormatProvider? provider, out bool success) + public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, Span destination, IFormatProvider? provider, out bool shouldAppend) { _destination = destination; _provider = provider; _pos = 0; - _success = success = destination.Length >= literalLength; + _success = shouldAppend = destination.Length >= literalLength; _hasCustomFormatter = provider is not null && DefaultInterpolatedStringHandler.HasCustomFormatter(provider); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Plane.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Plane.cs index aaab1903dc47..74f9b93fa672 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Plane.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Plane.cs @@ -321,7 +321,6 @@ public override readonly int GetHashCode() /// Returns the string representation of this plane object. /// A string that represents this object. /// The string representation of a object use the formatting conventions of the current culture to format the numeric values in the returned string. For example, a object whose string representation is formatted by using the conventions of the en-US culture might appear as {Normal:<1.1, 2.2, 3.3> D:4.4}. - public override readonly string ToString() => - $"{{Normal:{Normal.ToString()} D:{D.ToString()}}}"; + public override readonly string ToString() => $"{{Normal:{Normal} D:{D}}}"; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs b/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs index 0b483517b97b..a0e17bfb3496 100644 --- a/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs +++ b/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs @@ -75,9 +75,10 @@ public string VersionString os = " "; break; } + Span stackBuffer = stackalloc char[128]; _versionString = string.IsNullOrEmpty(_servicePack) ? - os + _version.ToString() : - os + _version.ToString(3) + " " + _servicePack; + string.Create(null, stackBuffer, $"{os}{_version}") : + string.Create(null, stackBuffer, $"{os}{_version.ToString(3)} {_servicePack}"); } return _versionString; diff --git a/src/libraries/System.Private.CoreLib/src/System/Resources/ResourceReader.cs b/src/libraries/System.Private.CoreLib/src/System/Resources/ResourceReader.cs index 78ead82418ca..16e2b161fc0d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Resources/ResourceReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Resources/ResourceReader.cs @@ -180,7 +180,7 @@ private void SkipString() private unsafe int GetNameHash(int index) { - Debug.Assert(index >= 0 && index < _numResources, "Bad index into hash array. index: " + index); + Debug.Assert(index >= 0 && index < _numResources, $"Bad index into hash array. index: {index}"); if (_ums == null) { @@ -196,7 +196,7 @@ private unsafe int GetNameHash(int index) private unsafe int GetNamePosition(int index) { - Debug.Assert(index >= 0 && index < _numResources, "Bad index into name position array. index: " + index); + Debug.Assert(index >= 0 && index < _numResources, $"Bad index into name position array. index: {index}"); int r; if (_ums == null) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/COMException.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/COMException.cs index 5713153e7b00..5fddcf973729 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/COMException.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/COMException.cs @@ -48,8 +48,7 @@ public override string ToString() { StringBuilder s = new StringBuilder(); - string className = GetType().ToString(); - s.Append(className).Append(" (0x").Append(HResult.ToString("X8", CultureInfo.InvariantCulture)).Append(')'); + s.Append($"{GetType()} (0x{HResult:X8})"); string message = Message; if (!string.IsNullOrEmpty(message)) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ExternalException.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ExternalException.cs index 33c8855e751c..13b85e991393 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ExternalException.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ExternalException.cs @@ -56,9 +56,8 @@ protected ExternalException(SerializationInfo info, StreamingContext context) public override string ToString() { string message = Message; - string className = GetType().ToString(); - string s = className + " (0x" + HResult.ToString("X8", CultureInfo.InvariantCulture) + ")"; + string s = $"{GetType()} (0x{HResult:X8})"; if (!string.IsNullOrEmpty(message)) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/StandardOleMarshalObject.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/StandardOleMarshalObject.Windows.cs index 175c1fc58d0a..f5e59ee8c329 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/StandardOleMarshalObject.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/StandardOleMarshalObject.Windows.cs @@ -31,7 +31,7 @@ private IntPtr GetStdMarshaler(ref Guid riid, int dwDestContext, int mshlflags) int hr = Interop.Ole32.CoGetStandardMarshal(ref riid, pUnknown, dwDestContext, IntPtr.Zero, mshlflags, out pStandardMarshal); if (hr == HResults.S_OK) { - Debug.Assert(pStandardMarshal != IntPtr.Zero, "Failed to get marshaler for interface '" + riid.ToString() + "', CoGetStandardMarshal returned S_OK"); + Debug.Assert(pStandardMarshal != IntPtr.Zero, $"Failed to get marshaler for interface '{riid}', CoGetStandardMarshal returned S_OK"); return pStandardMarshal; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/SR.cs b/src/libraries/System.Private.CoreLib/src/System/SR.cs index 097aeffb0d87..6f54dc2000e7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SR.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SR.cs @@ -88,7 +88,7 @@ private static string InternalGetResourceString(string key) string? s = ResourceManager.GetString(key, null); _currentlyLoading.RemoveAt(_currentlyLoading.Count - 1); // Pop - Debug.Assert(s != null, "Managed resource string lookup failed. Was your resource name misspelled? Did you rebuild mscorlib after adding a resource to resources.txt? Debug this w/ cordbg and bug whoever owns the code that called SR.GetResourceString. Resource name was: \"" + key + "\""); + Debug.Assert(s != null, $"Managed resource string lookup failed. Was your resource name misspelled? Did you rebuild mscorlib after adding a resource to resources.txt? Debug this w/ cordbg and bug whoever owns the code that called SR.GetResourceString. Resource name was: \"{key}\""); return s ?? key; } catch diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/DecoderExceptionFallback.cs b/src/libraries/System.Private.CoreLib/src/System/Text/DecoderExceptionFallback.cs index 94b909011469..e3eb90b5393c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/DecoderExceptionFallback.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/DecoderExceptionFallback.cs @@ -51,9 +51,7 @@ private static void Throw(byte[] bytesUnknown, int index) const int MaxLength = 20; for (int i = 0; i < bytesUnknown.Length && i < MaxLength; i++) { - strBytes.Append('['); - strBytes.Append(bytesUnknown[i].ToString("X2", CultureInfo.InvariantCulture)); - strBytes.Append(']'); + strBytes.Append($"[{bytesUnknown[i]:X2}]"); } // In case the string's really long diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/EncoderLatin1BestFitFallback.cs b/src/libraries/System.Private.CoreLib/src/System/Text/EncoderLatin1BestFitFallback.cs index 3a83763ea6a2..8fb951500184 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/EncoderLatin1BestFitFallback.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/EncoderLatin1BestFitFallback.cs @@ -39,7 +39,7 @@ public override bool Fallback(char charUnknown, int index) // If we had a buffer already we're being recursive, throw, it's probably at the suspect // character in our array. // Shouldn't be able to get here for all of our code pages, table would have to be messed up. - Debug.Assert(_iCount < 1, "[EncoderLatin1BestFitFallbackBuffer.Fallback(non surrogate)] Fallback char " + ((int)_cBestFit).ToString("X4", CultureInfo.InvariantCulture) + " caused recursive fallback"); + Debug.Assert(_iCount < 1, $"[EncoderLatin1BestFitFallbackBuffer.Fallback(non surrogate)] Fallback char {(int)_cBestFit:X4} caused recursive fallback"); _iCount = _iSize = 1; _cBestFit = TryBestFit(charUnknown); @@ -65,7 +65,7 @@ public override bool Fallback(char charUnknownHigh, char charUnknownLow, int ind // If we had a buffer already we're being recursive, throw, it's probably at the suspect // character in our array. 0 is processing last character, < 0 is not falling back // Shouldn't be able to get here, table would have to be messed up. - Debug.Assert(_iCount < 1, "[EncoderLatin1BestFitFallbackBuffer.Fallback(surrogate)] Fallback char " + ((int)_cBestFit).ToString("X4", CultureInfo.InvariantCulture) + " caused recursive fallback"); + Debug.Assert(_iCount < 1, $"[EncoderLatin1BestFitFallbackBuffer.Fallback(surrogate)] Fallback char {(int)_cBestFit:X4} caused recursive fallback"); // Go ahead and get our fallback, surrogates don't have best fit _cBestFit = '?'; diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/Rune.cs b/src/libraries/System.Private.CoreLib/src/System/Text/Rune.cs index 7678b5e48026..3ef7028bacbe 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/Rune.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/Rune.cs @@ -140,7 +140,7 @@ private Rune(uint scalarValue, bool unused) public static explicit operator Rune(int value) => new Rune(value); // Displayed as "'' (U+XXXX)"; e.g., "'e' (U+0065)" - private string DebuggerDisplay => FormattableString.Invariant($"U+{_value:X4} '{(IsValid(_value) ? ToString() : "\uFFFD")}'"); + private string DebuggerDisplay => string.Create(CultureInfo.InvariantCulture, $"U+{_value:X4} '{(IsValid(_value) ? ToString() : "\uFFFD")}'"); /// /// Returns true if and only if this scalar value is ASCII ([ U+0000..U+007F ]) diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/UnicodeDebug.cs b/src/libraries/System.Private.CoreLib/src/System/Text/UnicodeDebug.cs index 4caacbf856cc..ecd50f3f7c55 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/UnicodeDebug.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/UnicodeDebug.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Globalization; namespace System.Text { @@ -10,55 +11,39 @@ internal static class UnicodeDebug [Conditional("DEBUG")] internal static void AssertIsBmpCodePoint(uint codePoint) { - if (!UnicodeUtility.IsBmpCodePoint(codePoint)) - { - Debug.Fail($"The value {ToHexString(codePoint)} is not a valid BMP code point."); - } + Debug.Assert(UnicodeUtility.IsBmpCodePoint(codePoint), $"The value {ToHexString(codePoint)} is not a valid BMP code point."); } [Conditional("DEBUG")] internal static void AssertIsHighSurrogateCodePoint(uint codePoint) { - if (!UnicodeUtility.IsHighSurrogateCodePoint(codePoint)) - { - Debug.Fail($"The value {ToHexString(codePoint)} is not a valid UTF-16 high surrogate code point."); - } + Debug.Assert(UnicodeUtility.IsHighSurrogateCodePoint(codePoint), $"The value {ToHexString(codePoint)} is not a valid UTF-16 high surrogate code point."); } [Conditional("DEBUG")] internal static void AssertIsLowSurrogateCodePoint(uint codePoint) { - if (!UnicodeUtility.IsLowSurrogateCodePoint(codePoint)) - { - Debug.Fail($"The value {ToHexString(codePoint)} is not a valid UTF-16 low surrogate code point."); - } + Debug.Assert(UnicodeUtility.IsLowSurrogateCodePoint(codePoint), $"The value {ToHexString(codePoint)} is not a valid UTF-16 low surrogate code point."); } [Conditional("DEBUG")] internal static void AssertIsValidCodePoint(uint codePoint) { - if (!UnicodeUtility.IsValidCodePoint(codePoint)) - { - Debug.Fail($"The value {ToHexString(codePoint)} is not a valid Unicode code point."); - } + Debug.Assert(UnicodeUtility.IsValidCodePoint(codePoint), $"The value {ToHexString(codePoint)} is not a valid Unicode code point."); } [Conditional("DEBUG")] internal static void AssertIsValidScalar(uint scalarValue) { - if (!UnicodeUtility.IsValidUnicodeScalar(scalarValue)) - { - Debug.Fail($"The value {ToHexString(scalarValue)} is not a valid Unicode scalar value."); - } + Debug.Assert(UnicodeUtility.IsValidUnicodeScalar(scalarValue), $"The value {ToHexString(scalarValue)} is not a valid Unicode scalar value."); } [Conditional("DEBUG")] internal static void AssertIsValidSupplementaryPlaneScalar(uint scalarValue) { - if (!UnicodeUtility.IsValidUnicodeScalar(scalarValue) || UnicodeUtility.IsBmpCodePoint(scalarValue)) - { - Debug.Fail($"The value {ToHexString(scalarValue)} is not a valid supplementary plane Unicode scalar value."); - } + Debug.Assert( + UnicodeUtility.IsValidUnicodeScalar(scalarValue) && !UnicodeUtility.IsBmpCodePoint(scalarValue), + $"The value {ToHexString(scalarValue)} is not a valid supplementary plane Unicode scalar value."); } /// @@ -67,9 +52,6 @@ internal static void AssertIsValidSupplementaryPlaneScalar(uint scalarValue) /// /// The input value doesn't have to be a real code point in the Unicode codespace. It can be any integer. /// - private static string ToHexString(uint codePoint) - { - return FormattableString.Invariant($"U+{codePoint:X4}"); - } + private static string ToHexString(uint codePoint) => $"U+{codePoint:X4}"; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/UnicodeEncoding.cs b/src/libraries/System.Private.CoreLib/src/System/Text/UnicodeEncoding.cs index 7e859d19e7af..50faeb94ea26 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/UnicodeEncoding.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/UnicodeEncoding.cs @@ -1740,9 +1740,7 @@ internal sealed override unsafe int GetChars( if (decoder != null) { Debug.Assert(!decoder.MustFlush || ((lastChar == (char)0) && (lastByte == -1)), - "[UnicodeEncoding.GetChars] Expected no left over chars or bytes if flushing" - // + " " + ((int)lastChar).ToString("X4") + " " + lastByte.ToString("X2") - ); + "[UnicodeEncoding.GetChars] Expected no left over chars or bytes if flushing"); decoder._bytesUsed = (int)(bytes - byteStart); decoder.lastChar = lastChar; diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.FullGlobalizationData.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.FullGlobalizationData.Unix.cs index d1f306abb0f6..5a80d7b44864 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.FullGlobalizationData.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.FullGlobalizationData.Unix.cs @@ -125,7 +125,7 @@ private static void GetFullValueForDisplayNameField(string timeZoneId, TimeSpan // Get the base offset to prefix in front of the time zone. // Only UTC and its aliases have "(UTC)", handled earlier. All other zones include an offset, even if it's zero. - string baseOffsetText = $"(UTC{(baseUtcOffset >= TimeSpan.Zero ? '+' : '-')}{baseUtcOffset:hh\\:mm})"; + string baseOffsetText = string.Create(null, stackalloc char[128], $"(UTC{(baseUtcOffset >= TimeSpan.Zero ? '+' : '-')}{baseUtcOffset:hh\\:mm})"); // Get the generic location name. string? genericLocationName = null; diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs index 1356e65758c5..4756a9327159 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs @@ -109,7 +109,7 @@ private TimeZoneInfo(byte[] data, string id, bool dstDisabled) // These are expected in environments without time zone globalization data _standardDisplayName = standardAbbrevName; _daylightDisplayName = daylightAbbrevName ?? standardAbbrevName; - _displayName = $"(UTC{(_baseUtcOffset >= TimeSpan.Zero ? '+' : '-')}{_baseUtcOffset:hh\\:mm}) {_id}"; + _displayName = string.Create(null, stackalloc char[256], $"(UTC{(_baseUtcOffset >= TimeSpan.Zero ? '+' : '-')}{_baseUtcOffset:hh\\:mm}) {_id}"); // Try to populate the display names from the globalization data TryPopulateTimeZoneDisplayNamesFromGlobalizationData(_id, _baseUtcOffset, ref _standardDisplayName, ref _daylightDisplayName, ref _displayName); diff --git a/src/libraries/System.Private.CoreLib/src/System/Version.cs b/src/libraries/System.Private.CoreLib/src/System/Version.cs index 455978045f3c..bf0ce8e2b4c5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Version.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Version.cs @@ -197,55 +197,33 @@ public bool TryFormat(Span destination, out int charsWritten) => public bool TryFormat(Span destination, int fieldCount, out int charsWritten) { - string? failureUpperBound = (uint)fieldCount switch + switch (fieldCount) { - > 4 => "4", - >= 3 when _Build == -1 => "2", - 4 when _Revision == -1 => "3", - _ => null - }; - if (failureUpperBound is not null) - { - throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", failureUpperBound), nameof(fieldCount)); - } + case 0: + charsWritten = 0; + return true; - int totalCharsWritten = 0; + case 1: + return ((uint)_Major).TryFormat(destination, out charsWritten); - for (int i = 0; i < fieldCount; i++) - { - if (i != 0) - { - if (destination.IsEmpty) - { - charsWritten = 0; - return false; - } + case 2: + return destination.TryWrite($"{(uint)_Major}.{(uint)_Minor}", out charsWritten); - destination[0] = '.'; - destination = destination.Slice(1); - totalCharsWritten++; - } - - int value = i switch - { - 0 => _Major, - 1 => _Minor, - 2 => _Build, - _ => _Revision - }; + case 3: + if (_Build == -1) throw CreateBoundException("3"); + return destination.TryWrite($"{(uint)_Major}.{(uint)_Minor}.{(uint)_Build}", out charsWritten); - if (!value.TryFormat(destination, out int valueCharsWritten)) - { - charsWritten = 0; - return false; - } + case 4: + if (_Build == -1) throw CreateBoundException("2"); + if (_Revision == -1) throw CreateBoundException("3"); + return destination.TryWrite($"{(uint)_Major}.{(uint)_Minor}.{(uint)_Build}.{(uint)_Revision}", out charsWritten); - totalCharsWritten += valueCharsWritten; - destination = destination.Slice(valueCharsWritten); + default: + throw CreateBoundException("4"); } - charsWritten = totalCharsWritten; - return true; + static Exception CreateBoundException(string failureUpperBound) => + new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", failureUpperBound), nameof(fieldCount)); } bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/JsonWriterDelegator.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/JsonWriterDelegator.cs index 9c164eed1130..eab7d2e6c28e 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/JsonWriterDelegator.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/JsonWriterDelegator.cs @@ -209,7 +209,7 @@ private void WriteDateTimeInDefaultFormat(DateTime value) // +"zzzz"; //TimeSpan ts = TimeZone.CurrentTimeZone.GetUtcOffset(value.ToLocalTime()); TimeSpan ts = TimeZoneInfo.Local.GetUtcOffset(value.ToLocalTime()); - writer.WriteString(string.Format(CultureInfo.InvariantCulture, "{0:+00;-00}{1:00;00}", ts.Hours, ts.Minutes)); + writer.WriteString(string.Create(CultureInfo.InvariantCulture, $"{ts.Hours:+00;-00}{ts.Minutes:00;00}")); break; case DateTimeKind.Utc: break; diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/XmlJsonWriter.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/XmlJsonWriter.cs index 103013f09598..d52d1fabaf4a 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/XmlJsonWriter.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/XmlJsonWriter.cs @@ -1420,7 +1420,7 @@ private unsafe void WriteEscapedJsonString(string str) _nodeWriter.WriteChars(chars + i, j - i); _nodeWriter.WriteText(BACK_SLASH); _nodeWriter.WriteText('u'); - _nodeWriter.WriteText(string.Format(CultureInfo.InvariantCulture, "{0:x4}", (int)ch)); + _nodeWriter.WriteText($"{(int)ch:x4}"); i = j + 1; } } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlObjectSerializer.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlObjectSerializer.cs index 97e134fe4e36..40584d6d2d54 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlObjectSerializer.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlObjectSerializer.cs @@ -393,7 +393,7 @@ internal static void CheckNull(object obj, string name) internal static string TryAddLineInfo(XmlReaderDelegator reader, string errorMessage) { if (reader.HasLineInfo()) - return string.Format(CultureInfo.InvariantCulture, "{0} {1}", SR.Format(SR.ErrorInLine, reader.LineNumber, reader.LinePosition), errorMessage); + return string.Create(CultureInfo.InvariantCulture, $"{SR.Format(SR.ErrorInLine, reader.LineNumber, reader.LinePosition)} {errorMessage}"); return errorMessage; } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlObjectSerializerWriteContext.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlObjectSerializerWriteContext.cs index 48273f979220..967e30458d3f 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlObjectSerializerWriteContext.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlObjectSerializerWriteContext.cs @@ -221,12 +221,12 @@ internal bool OnHandleIsReference(XmlWriterDelegator xmlWriter, DataContract con if (isNew) { xmlWriter.WriteAttributeString(Globals.SerPrefix, DictionaryGlobals.IdLocalName, - DictionaryGlobals.SerializationNamespace, string.Format(CultureInfo.InvariantCulture, "i{0}", objectId)); + DictionaryGlobals.SerializationNamespace, string.Create(CultureInfo.InvariantCulture, $"i{objectId}")); return false; } else { - xmlWriter.WriteAttributeString(Globals.SerPrefix, DictionaryGlobals.RefLocalName, DictionaryGlobals.SerializationNamespace, string.Format(CultureInfo.InvariantCulture, "i{0}", objectId)); + xmlWriter.WriteAttributeString(Globals.SerPrefix, DictionaryGlobals.RefLocalName, DictionaryGlobals.SerializationNamespace, string.Create(CultureInfo.InvariantCulture, $"i{objectId}")); return true; } } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlWriterDelegator.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlWriterDelegator.cs index 50f78a1dd4a4..f0be0f4e66f5 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlWriterDelegator.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlWriterDelegator.cs @@ -84,7 +84,7 @@ internal void WriteXmlnsAttribute(string? ns) string? prefix = writer.LookupPrefix(ns); if (prefix == null) { - prefix = string.Format(CultureInfo.InvariantCulture, "d{0}p{1}", depth, _prefixes); + prefix = string.Create(CultureInfo.InvariantCulture, $"d{depth}p{_prefixes}"); _prefixes++; writer.WriteAttributeString("xmlns", prefix, null, ns); } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBaseReader.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBaseReader.cs index 0fe175fc2277..50d93601ae47 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBaseReader.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBaseReader.cs @@ -1085,7 +1085,7 @@ public override int IndexOfLocalName(string[] localNames, string namespaceUri) { string value = localNames[i]; if (value == null) - throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(string.Format(CultureInfo.InvariantCulture, "localNames[{0}]", i)); + throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull($"localNames[{i}]"); if (localName == value) { return i; @@ -1100,7 +1100,7 @@ public override int IndexOfLocalName(string[] localNames, string namespaceUri) { string value = localNames[i]; if (value == null) - throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(string.Format(CultureInfo.InvariantCulture, "localNames[{0}]", i)); + throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull($"localNames[{i}]"); if (prefix == value) { return i; @@ -1127,7 +1127,7 @@ public override int IndexOfLocalName(XmlDictionaryString[] localNames, XmlDictio { XmlDictionaryString value = localNames[i]; if (value == null) - throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(string.Format(CultureInfo.InvariantCulture, "localNames[{0}]", i)); + throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull($"localNames[{i}]"); if (localName == value) { return i; @@ -1142,7 +1142,7 @@ public override int IndexOfLocalName(XmlDictionaryString[] localNames, XmlDictio { XmlDictionaryString value = localNames[i]; if (value == null) - throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(string.Format(CultureInfo.InvariantCulture, "localNames[{0}]", i)); + throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull($"localNames[{i}]"); if (prefix == value) { return i; diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlDictionaryReader.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlDictionaryReader.cs index 13098a6aa295..6692a66ac2b1 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlDictionaryReader.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlDictionaryReader.cs @@ -312,7 +312,7 @@ public virtual int IndexOfLocalName(string[] localNames, string namespaceUri) { string value = localNames[i]; if (value == null) - throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(string.Format(CultureInfo.InvariantCulture, "localNames[{0}]", i)); + throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull($"localNames[{i}]"); if (localName == value) { return i; @@ -338,7 +338,7 @@ public virtual int IndexOfLocalName(XmlDictionaryString[] localNames, XmlDiction { XmlDictionaryString value = localNames[i]; if (value == null) - throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(string.Format(CultureInfo.InvariantCulture, "localNames[{0}]", i)); + throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull($"localNames[{i}]"); if (localName == value.Value) { return i; @@ -630,7 +630,7 @@ public virtual string ReadContentAsString(string[] strings, out int index) { string value = strings[i]; if (value == null) - throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(string.Format(CultureInfo.InvariantCulture, "strings[{0}]", i)); + throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull($"strings[{i}]"); if (value == s) { index = i; @@ -650,7 +650,7 @@ public virtual string ReadContentAsString(XmlDictionaryString[] strings, out int { XmlDictionaryString value = strings[i]; if (value == null) - throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(string.Format(CultureInfo.InvariantCulture, "strings[{0}]", i)); + throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull($"strings[{i}]"); if (value.Value == s) { index = i; diff --git a/src/libraries/System.Private.Uri/src/System/IPv4AddressHelper.cs b/src/libraries/System.Private.Uri/src/System/IPv4AddressHelper.cs index ef18d5348c9b..1f751eeb5765 100644 --- a/src/libraries/System.Private.Uri/src/System/IPv4AddressHelper.cs +++ b/src/libraries/System.Private.Uri/src/System/IPv4AddressHelper.cs @@ -41,10 +41,11 @@ private static unsafe bool Parse(string name, byte* numbers, int start, int end) { fixed (char* ipString = name) { + // end includes ports, so changedEnd may be different from end int changedEnd = end; long result = IPv4AddressHelper.ParseNonCanonical(ipString, start, ref changedEnd, true); - // end includes ports, so changedEnd may be different from end - Debug.Assert(result != Invalid, "Failed to parse after already validated: " + name); + + Debug.Assert(result != Invalid, $"Failed to parse after already validated: {name}"); unchecked { diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index 4b7e1f4ceff0..dc687676f03f 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -4824,27 +4824,22 @@ private static string CombineUri(Uri basePart, string relativePart, UriFormat ur // For compatibility with V1.0 parser we restrict the compression scope to Unc Share, i.e. \\host\share\ if (basePart.IsUnc) { - string share = basePart.GetParts(UriComponents.Path | UriComponents.KeepDelimiter, - UriFormat.Unescaped); + ReadOnlySpan share = basePart.GetParts(UriComponents.Path | UriComponents.KeepDelimiter, UriFormat.Unescaped); for (int i = 1; i < share.Length; ++i) { if (share[i] == '/') { - share = share.Substring(0, i); + share = share.Slice(0, i); break; } } + if (basePart.IsImplicitFile) { - return @"\\" - + basePart.GetParts(UriComponents.Host, UriFormat.Unescaped) - + share - + relativePart; + return string.Concat(@"\\", basePart.GetParts(UriComponents.Host, UriFormat.Unescaped), share, relativePart); } - return "file://" - + basePart.GetParts(UriComponents.Host, uriFormat) - + share - + relativePart; + + return string.Concat("file://", basePart.GetParts(UriComponents.Host, uriFormat), share, relativePart); } // It's not obvious but we've checked (for this relativePart format) that baseUti is nor UNC nor DOS path // diff --git a/src/libraries/System.Private.Uri/tests/FunctionalTests/IriRelativeFileResolutionTest.cs b/src/libraries/System.Private.Uri/tests/FunctionalTests/IriRelativeFileResolutionTest.cs index befeda705c59..7715d7669f8f 100644 --- a/src/libraries/System.Private.Uri/tests/FunctionalTests/IriRelativeFileResolutionTest.cs +++ b/src/libraries/System.Private.Uri/tests/FunctionalTests/IriRelativeFileResolutionTest.cs @@ -123,8 +123,7 @@ public static int RelatavizeRestoreCompareImplicitVsExplicitFiles(string origina if (!(implicitValue.Equals(explicitValue) || (fileSchemePrefix + implicitValue).Equals(explicitValue))) { errorCount++; - testResults.Append("Property mismatch: " + info.Name + ", implicit value: " + implicitValue - + ", explicit value: " + explicitValue + "; "); + testResults.Append($"Property mismatch: {info.Name}, implicit value: {implicitValue}, explicit value: {explicitValue}; "); } } @@ -133,8 +132,7 @@ public static int RelatavizeRestoreCompareImplicitVsExplicitFiles(string origina if (!implicitString.Equals(explicitString)) { errorCount++; - testResults.Append("ToString mismatch; implicit value: " + implicitString - + ", explicit value: " + explicitString); + testResults.Append($"ToString mismatch; implicit value: {implicitString}, explicit value: {explicitString}"); } errors = testResults.ToString(); @@ -275,8 +273,7 @@ public static int RelatavizeRestoreCompareVsOriginal(string original, string bas if (!resultValue.Equals(startValue)) { errorCount++; - testResults.Append("Property mismatch: " + info.Name + ", result value: " - + resultValue + ", start value: " + startValue + "; "); + testResults.Append($"Property mismatch: {info.Name}, result value: {resultValue}, start value: {startValue}; "); } } @@ -285,8 +282,7 @@ public static int RelatavizeRestoreCompareVsOriginal(string original, string bas if (!resultString.Equals(startString)) { errorCount++; - testResults.Append("ToString mismatch; result value: " + resultString - + ", start value: " + startString); + testResults.Append($"ToString mismatch; result value: {resultString}, start value: {startString}"); } errors = testResults.ToString(); diff --git a/src/libraries/System.Private.Uri/tests/FunctionalTests/UriRelativeResolutionTest.cs b/src/libraries/System.Private.Uri/tests/FunctionalTests/UriRelativeResolutionTest.cs index 8a548c85abe0..fa4ee9299a83 100644 --- a/src/libraries/System.Private.Uri/tests/FunctionalTests/UriRelativeResolutionTest.cs +++ b/src/libraries/System.Private.Uri/tests/FunctionalTests/UriRelativeResolutionTest.cs @@ -686,12 +686,9 @@ public static string IriEscapeAll(string input) { var result = new StringBuilder(); - byte[] bytes = Encoding.UTF8.GetBytes(input); - - foreach (byte b in bytes) + foreach (byte b in Encoding.UTF8.GetBytes(input)) { - result.Append('%'); - result.Append(b.ToString("X2")); + result.Append($"%{b:X2}"); } return result.ToString(); @@ -710,12 +707,9 @@ public static string IriEscapeNonUcsChars(string input) continue; } - byte[] bytes = Encoding.UTF8.GetBytes(c.ToString()); - - foreach (byte b in bytes) + foreach (byte b in Encoding.UTF8.GetBytes(c.ToString())) { - result.Append('%'); - result.Append(b.ToString("X2")); + result.Append($"%{b:X2}"); } } diff --git a/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XNodeReader.cs b/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XNodeReader.cs index 2ee9abc08513..547c8196d58d 100644 --- a/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XNodeReader.cs +++ b/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XNodeReader.cs @@ -1388,8 +1388,7 @@ private bool IsDuplicateNamespaceAttributeInner(XAttribute candidateAttribute) /// The first attribute which is not a namespace attribute or null if the end of attributes has bean reached private XAttribute? GetFirstNonDuplicateNamespaceAttribute(XAttribute candidate) { - Debug.Assert(_omitDuplicateNamespaces, "This method should only be called if we're omitting duplicate namespace attribute." + - "For perf reason it's better to test this flag in the caller method."); + Debug.Assert(_omitDuplicateNamespaces, "This method should only be called if we're omitting duplicate namespace attribute. For perf reason it's better to test this flag in the caller method."); if (!IsDuplicateNamespaceAttribute(candidate)) { return candidate; diff --git a/src/libraries/System.Private.Xml.Linq/tests/XDocument.Common/ManagedNodeWriter.cs b/src/libraries/System.Private.Xml.Linq/tests/XDocument.Common/ManagedNodeWriter.cs index 0e4a7033b10d..f8773b85ffcc 100644 --- a/src/libraries/System.Private.Xml.Linq/tests/XDocument.Common/ManagedNodeWriter.cs +++ b/src/libraries/System.Private.Xml.Linq/tests/XDocument.Common/ManagedNodeWriter.cs @@ -218,7 +218,7 @@ public void PutByte() /// public void PutCData() { - _nodeQueue.Append(""); + _nodeQueue.Append($""); } /// @@ -226,7 +226,7 @@ public void PutCData() /// public void PutPI() { - _nodeQueue.Append(""); + _nodeQueue.Append($""); } /// @@ -234,7 +234,7 @@ public void PutPI() /// public void PutComment() { - _nodeQueue.Append(""); + _nodeQueue.Append($""); } /// diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Cache/XPathNode.cs b/src/libraries/System.Private.Xml/src/System/Xml/Cache/XPathNode.cs index 30e93b624291..a2ab5b21217f 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Cache/XPathNode.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Cache/XPathNode.cs @@ -369,8 +369,8 @@ public void Create(XPathNodeInfoAtom info, XPathNodeType xptyp, int idxParent) /// public void SetLineInfoOffsets(int lineNumOffset, int linePosOffset) { - Debug.Assert(lineNumOffset >= 0 && lineNumOffset <= MaxLineNumberOffset, "Line number offset too large or small: " + lineNumOffset); - Debug.Assert(linePosOffset >= 0 && linePosOffset <= MaxLinePositionOffset, "Line position offset too large or small: " + linePosOffset); + Debug.Assert(lineNumOffset >= 0 && lineNumOffset <= MaxLineNumberOffset, $"Line number offset too large or small: {lineNumOffset}"); + Debug.Assert(linePosOffset >= 0 && linePosOffset <= MaxLinePositionOffset, $"Line position offset too large or small: {linePosOffset}"); _props |= ((uint)lineNumOffset << LineNumberShift); _posOffset = (ushort)linePosOffset; } @@ -380,7 +380,7 @@ public void SetLineInfoOffsets(int lineNumOffset, int linePosOffset) /// public void SetCollapsedLineInfoOffset(int posOffset) { - Debug.Assert(posOffset >= 0 && posOffset <= MaxCollapsedPositionOffset, "Collapsed text line position offset too large or small: " + posOffset); + Debug.Assert(posOffset >= 0 && posOffset <= MaxCollapsedPositionOffset, $"Collapsed text line position offset too large or small: {posOffset}"); _props |= ((uint)posOffset << CollapsedPositionShift); } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/CharEntityEncoderFallback.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/CharEntityEncoderFallback.cs index cfbdc9e8a0d5..5cdccd480081 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/CharEntityEncoderFallback.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/CharEntityEncoderFallback.cs @@ -102,7 +102,7 @@ public override bool Fallback(char charUnknown, int index) if (_parent.CanReplaceAt(index)) { // Create the replacement character entity - _charEntity = string.Format(CultureInfo.InvariantCulture, "&#x{0:X};", new object[] { (int)charUnknown }); + _charEntity = string.Create(null, stackalloc char[64], $"&#x{(int)charUnknown:X};"); _charEntityIndex = 0; return true; } @@ -131,7 +131,7 @@ public override bool Fallback(char charUnknownHigh, char charUnknownLow, int ind if (_parent.CanReplaceAt(index)) { // Create the replacement character entity - _charEntity = string.Format(CultureInfo.InvariantCulture, "&#x{0:X};", new object[] { SurrogateCharToUtf32(charUnknownHigh, charUnknownLow) }); + _charEntity = string.Create(null, stackalloc char[64], $"&#x{SurrogateCharToUtf32(charUnknownHigh, charUnknownLow):X};"); _charEntityIndex = 0; return true; } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs index ffb19d21d520..c39f6155d3df 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs @@ -1186,7 +1186,7 @@ private void StartDocument(int standalone) _currentState = State.Prolog; StringBuilder bufBld = new StringBuilder(128); - bufBld.Append("version=" + _quoteChar + "1.0" + _quoteChar); + bufBld.Append($"version={_quoteChar}1.0{_quoteChar}"); if (_encoding != null) { bufBld.Append(" encoding="); diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/ContentValidator.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/ContentValidator.cs index 2e2baa402875..0a4ad3d346ee 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/ContentValidator.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/ContentValidator.cs @@ -354,7 +354,7 @@ public override bool IsNullable #if DEBUG public override void Dump(StringBuilder bb, SymbolsDictionary symbols, Positions positions) { - bb.Append("\"" + symbols.NameOf(positions[_pos].symbol) + "\""); + bb.Append($"\"{symbols.NameOf(positions[_pos].symbol)}\""); } #endif } @@ -427,7 +427,7 @@ public override bool IsNullable #if DEBUG public override void Dump(StringBuilder bb, SymbolsDictionary symbols, Positions positions) { - bb.Append("[" + namespaceList.ToString() + "]"); + bb.Append($"[{namespaceList}]"); } #endif } @@ -876,7 +876,7 @@ public override bool IsNullable { public override void Dump(StringBuilder bb, SymbolsDictionary symbols, Positions positions) { LeftChild.Dump(bb, symbols, positions); - bb.Append("{" + Convert.ToString(min, NumberFormatInfo.InvariantInfo) + ", " + Convert.ToString(max, NumberFormatInfo.InvariantInfo) + "}"); + bb.Append($"{{{Convert.ToString(min, NumberFormatInfo.InvariantInfo)}, {Convert.ToString(max, NumberFormatInfo.InvariantInfo)}}}"); } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/Inference/Infer.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/Inference/Infer.cs index 86fc6cd39535..9f2a8d5c0f7b 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/Inference/Infer.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/Inference/Infer.cs @@ -1782,7 +1782,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) //else case 'I': //try to match "INF" INF: - if (s.Substring(i) == "INF") + if (s.AsSpan(i).SequenceEqual("INF")) return TF_float | TF_double | TF_string; else return TF_string; case '.': //try to match ".9999" decimal/float/double diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/SchemaCollectionCompiler.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/SchemaCollectionCompiler.cs index 5312db2a96c1..615c405d31bc 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/SchemaCollectionCompiler.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/SchemaCollectionCompiler.cs @@ -2526,7 +2526,7 @@ private void DumpContentModelTo(StringBuilder sb, XmlSchemaParticle particle) } else { - sb.Append("{" + particle.MinOccurs.ToString(NumberFormatInfo.InvariantInfo) + ", " + particle.MaxOccurs.ToString(NumberFormatInfo.InvariantInfo) + "}"); + sb.Append($"{{{particle.MinOccurs.ToString(NumberFormatInfo.InvariantInfo)}, {particle.MaxOccurs.ToString(NumberFormatInfo.InvariantInfo)}}}"); } } #endif diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs index 717bc204e582..71f43082b5e2 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs @@ -191,7 +191,7 @@ public XsdDateTime(DateTime dateTime, XsdDateTimeFlags kinds) default: { - Debug.Assert(dateTime.Kind == DateTimeKind.Local, "Unknown DateTimeKind: " + dateTime.Kind); + Debug.Assert(dateTime.Kind == DateTimeKind.Local, $"Unknown DateTimeKind: {dateTime.Kind}"); TimeSpan utcOffset = TimeZoneInfo.Local.GetUtcOffset(dateTime); if (utcOffset.Ticks < 0) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReader.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReader.cs index 6ef46a0f6866..931735f60c6e 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReader.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReader.cs @@ -2464,7 +2464,7 @@ private string GenerateMembersElement(XmlMembersMapping xmlMembersMapping) { if (mappings[j].Name == member.ChoiceIdentifier.MemberName) { - choiceSource = "p[" + j.ToString(CultureInfo.InvariantCulture) + "]"; + choiceSource = $"p[{j}]"; break; } } @@ -2520,17 +2520,17 @@ private string GenerateLiteralMembersElement(XmlMembersMapping xmlMembersMapping for (int i = 0; i < mappings.Length; i++) { MemberMapping mapping = mappings[i]; - string source = "p[" + i.ToString(CultureInfo.InvariantCulture) + "]"; + string source = $"p[{i}]"; string arraySource = source; if (mapping.Xmlns != null) { - arraySource = "((" + mapping.TypeDesc!.CSharpName + ")" + source + ")"; + arraySource = $"(({mapping.TypeDesc!.CSharpName}){source})"; } string? choiceSource = GetChoiceIdentifierSource(mappings, mapping); Member member = new Member(this, source, arraySource, "a", i, mapping, choiceSource); Member anyMember = new Member(this, source, null, "a", i, mapping, choiceSource); if (!mapping.IsSequence) - member.ParamsReadSource = "paramsRead[" + i.ToString(CultureInfo.InvariantCulture) + "]"; + member.ParamsReadSource = $"paramsRead[{i}]"; if (mapping.CheckSpecified == SpecifiedAccessor.ReadWrite) { string nameSpecified = mapping.Name + "Specified"; @@ -2538,7 +2538,7 @@ private string GenerateLiteralMembersElement(XmlMembersMapping xmlMembersMapping { if (mappings[j].Name == nameSpecified) { - member.CheckSpecifiedSource = "p[" + j.ToString(CultureInfo.InvariantCulture) + "]"; + member.CheckSpecifiedSource = $"p[{j}]"; break; } } @@ -2700,15 +2700,15 @@ private string GenerateEncodedMembersElement(XmlMembersMapping xmlMembersMapping for (int i = 0; i < mappings.Length; i++) { MemberMapping mapping = mappings[i]; - string source = "p[" + i.ToString(CultureInfo.InvariantCulture) + "]"; + string source = $"p[{i}]"; string arraySource = source; if (mapping.Xmlns != null) { - arraySource = "((" + mapping.TypeDesc!.CSharpName + ")" + source + ")"; + arraySource = $"(({mapping.TypeDesc!.CSharpName}){source})"; } Member member = new Member(this, source, arraySource, "a", i, mapping); if (!mapping.IsSequence) - member.ParamsReadSource = "paramsRead[" + i.ToString(CultureInfo.InvariantCulture) + "]"; + member.ParamsReadSource = $"paramsRead[{i}]"; members[i] = member; if (mapping.CheckSpecified == SpecifiedAccessor.ReadWrite) @@ -2718,7 +2718,7 @@ private string GenerateEncodedMembersElement(XmlMembersMapping xmlMembersMapping { if (mappings[j].Name == nameSpecified) { - member.CheckSpecifiedSource = "p[" + j.ToString(CultureInfo.InvariantCulture) + "]"; + member.CheckSpecifiedSource = $"p[{j}]"; break; } } @@ -2826,15 +2826,11 @@ private string GenerateTypeElement(XmlTypeMapping xmlTypeMapping) return methodName; } - private string NextMethodName(string name) - { - return "Read" + (++NextMethodNumber).ToString(CultureInfo.InvariantCulture) + "_" + CodeIdentifier.MakeValidInternal(name); - } + private string NextMethodName(string name) => + string.Create(CultureInfo.InvariantCulture, $"Read{++NextMethodNumber}_{CodeIdentifier.MakeValidInternal(name)}"); - private string NextIdName(string name) - { - return "id" + (++_nextIdNumber).ToString(CultureInfo.InvariantCulture) + "_" + CodeIdentifier.MakeValidInternal(name); - } + private string NextIdName(string name) => + string.Create(CultureInfo.InvariantCulture, $"id{(++_nextIdNumber)}_{CodeIdentifier.MakeValidInternal(name)}"); private void WritePrimitive(TypeMapping mapping, string source) { @@ -2961,7 +2957,7 @@ private string WriteHashtable(EnumMapping mapping, string typeName) else { Writer.Write(", "); - Writer.Write(constants[i].Value.ToString(CultureInfo.InvariantCulture) + "L"); + Writer.Write(string.Create(CultureInfo.InvariantCulture, $"{constants[i].Value}L")); } Writer.WriteLine(");"); @@ -3336,7 +3332,7 @@ private void WriteLiteralStructMethod(StructMapping structMapping) string source = RaCodeGen.GetStringForMember("o", mapping.Name, structMapping.TypeDesc); Member member = new Member(this, source, "a", i, mapping, GetChoiceIdentifierSource(mapping, "o", structMapping.TypeDesc)); if (!mapping.IsSequence) - member.ParamsReadSource = "paramsRead[" + i.ToString(CultureInfo.InvariantCulture) + "]"; + member.ParamsReadSource = $"paramsRead[{i}]"; member.IsNullable = mapping.TypeDesc!.IsNullable; if (mapping.CheckSpecified == SpecifiedAccessor.ReadWrite) member.CheckSpecifiedSource = RaCodeGen.GetStringForMember("o", mapping.Name + "Specified", structMapping.TypeDesc); @@ -3478,7 +3474,7 @@ private void WriteEncodedStructMethod(StructMapping structMapping) if (mapping.CheckSpecified == SpecifiedAccessor.ReadWrite) member.CheckSpecifiedSource = RaCodeGen.GetStringForMember("o", mapping.Name + "Specified", structMapping.TypeDesc); if (!mapping.IsSequence) - member.ParamsReadSource = "paramsRead[" + i.ToString(CultureInfo.InvariantCulture) + "]"; + member.ParamsReadSource = $"paramsRead[{i}]"; members[i] = member; } @@ -3581,7 +3577,7 @@ private void WriteAddCollectionFixup(TypeDesc typeDesc, bool readOnly, string me CreateCollectionInfo? create = (CreateCollectionInfo?)_createMethods[typeDesc]; if (create == null) { - string createName = "create" + (++_nextCreateMethodNumber).ToString(CultureInfo.InvariantCulture) + "_" + typeDesc.Name; + string createName = string.Create(CultureInfo.InvariantCulture, $"create{++_nextCreateMethodNumber}_{typeDesc.Name}"); create = new CreateCollectionInfo(createName, typeDesc); _createMethods.Add(typeDesc, create); } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReaderILGen.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReaderILGen.cs index 010d537da38b..41c5c83506d7 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReaderILGen.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReaderILGen.cs @@ -373,7 +373,7 @@ private string GetChoiceIdentifierSource(MemberMapping[] mappings, MemberMapping { if (mappings[j].Name == member.ChoiceIdentifier.MemberName) { - choiceSource = "p[" + j.ToString(CultureInfo.InvariantCulture) + "]"; + choiceSource = $"p[{j}]"; break; } } @@ -447,17 +447,17 @@ private string GenerateLiteralMembersElement(XmlMembersMapping xmlMembersMapping for (int i = 0; i < mappings.Length; i++) { MemberMapping mapping = mappings[i]; - string source = "p[" + i.ToString(CultureInfo.InvariantCulture) + "]"; + string source = $"p[{i}]"; string arraySource = source; if (mapping.Xmlns != null) { - arraySource = "((" + mapping.TypeDesc!.CSharpName + ")" + source + ")"; + arraySource = $"(({mapping.TypeDesc!.CSharpName}){source})"; } string choiceSource = GetChoiceIdentifierSource(mappings, mapping); Member member = new Member(this, source, arraySource, "a", i, mapping, choiceSource); Member anyMember = new Member(this, source, null, "a", i, mapping, choiceSource); if (!mapping.IsSequence) - member.ParamsReadSource = "paramsRead[" + i.ToString(CultureInfo.InvariantCulture) + "]"; + member.ParamsReadSource = $"paramsRead[{i}]"; if (mapping.CheckSpecified == SpecifiedAccessor.ReadWrite) { string nameSpecified = mapping.Name + "Specified"; @@ -465,7 +465,7 @@ private string GenerateLiteralMembersElement(XmlMembersMapping xmlMembersMapping { if (mappings[j].Name == nameSpecified) { - member.CheckSpecifiedSource = "p[" + j.ToString(CultureInfo.InvariantCulture) + "]"; + member.CheckSpecifiedSource = $"p[{j}]"; break; } } @@ -685,15 +685,11 @@ private string GenerateTypeElement(XmlTypeMapping xmlTypeMapping) return methodName; } - private string NextMethodName(string name) - { - return "Read" + (++NextMethodNumber).ToString(CultureInfo.InvariantCulture) + "_" + CodeIdentifier.MakeValidInternal(name); - } + private string NextMethodName(string name) => + string.Create(CultureInfo.InvariantCulture, $"Read{++NextMethodNumber}_{CodeIdentifier.MakeValidInternal(name)}"); - private string NextIdName(string name) - { - return "id" + (++_nextIdNumber).ToString(CultureInfo.InvariantCulture) + "_" + CodeIdentifier.MakeValidInternal(name); - } + private string NextIdName(string name) => + string.Create(CultureInfo.InvariantCulture, $"id{++_nextIdNumber}_{CodeIdentifier.MakeValidInternal(name)}"); [RequiresUnreferencedCode("XmlSerializationReader methods have RequiresUnreferencedCode")] private void WritePrimitive(TypeMapping mapping, string source) @@ -1571,7 +1567,7 @@ private void WriteLiteralStructMethod(StructMapping structMapping) string source = RaCodeGen.GetStringForMember("o", mapping.Name, structMapping.TypeDesc); Member member = new Member(this, source, "a", i, mapping, GetChoiceIdentifierSource(mapping, "o", structMapping.TypeDesc)); if (!mapping.IsSequence) - member.ParamsReadSource = "paramsRead[" + i.ToString(CultureInfo.InvariantCulture) + "]"; + member.ParamsReadSource = $"paramsRead[{i}]"; member.IsNullable = mapping.TypeDesc!.IsNullable; if (mapping.CheckSpecified == SpecifiedAccessor.ReadWrite) member.CheckSpecifiedSource = RaCodeGen.GetStringForMember("o", mapping.Name + "Specified", structMapping.TypeDesc); diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs index 62dc75b1c994..8308e0de3864 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs @@ -1241,7 +1241,7 @@ private void WriteArray(string name, string? ns, object o, Type type) } else { - _w.WriteAttributeString("arrayType", Soap.Encoding, GetQualifiedName(typeName, typeNs) + "[" + arrayLength.ToString(CultureInfo.InvariantCulture) + "]"); + _w.WriteAttributeString("arrayType", Soap.Encoding, $"{GetQualifiedName(typeName, typeNs)}[{arrayLength}]"); } for (int i = 0; i < arrayLength; i++) { @@ -2605,7 +2605,7 @@ private string GenerateMembersElement(XmlMembersMapping xmlMembersMapping) int xmlnsMember = FindXmlnsIndex(mapping.Members!); if (xmlnsMember >= 0) { - string source = "((" + typeof(System.Xml.Serialization.XmlSerializerNamespaces).FullName + ")p[" + xmlnsMember.ToString(CultureInfo.InvariantCulture) + "])"; + string source = $"(({typeof(System.Xml.Serialization.XmlSerializerNamespaces).FullName})p[{xmlnsMember}])"; Writer.Write("if (pLength > "); Writer.Write(xmlnsMember.ToString(CultureInfo.InvariantCulture)); @@ -2623,7 +2623,7 @@ private string GenerateMembersElement(XmlMembersMapping xmlMembersMapping) if (member.Attribute != null && !member.Ignore) { string index = i.ToString(CultureInfo.InvariantCulture); - string source = "p[" + index + "]"; + string source = $"p[{index}]"; string? specifiedSource = null; int specifiedPosition = 0; @@ -2634,7 +2634,7 @@ private string GenerateMembersElement(XmlMembersMapping xmlMembersMapping) { if (mapping.Members[j].Name == memberNameSpecified) { - specifiedSource = "((bool) p[" + j.ToString(CultureInfo.InvariantCulture) + "])"; + specifiedSource = $"((bool) p[{j}])"; specifiedPosition = j; break; } @@ -2688,7 +2688,7 @@ private string GenerateMembersElement(XmlMembersMapping xmlMembersMapping) { if (mapping.Members[j].Name == memberNameSpecified) { - specifiedSource = "((bool) p[" + j.ToString(CultureInfo.InvariantCulture) + "])"; + specifiedSource = $"((bool) p[{j}])"; specifiedPosition = j; break; } @@ -2720,9 +2720,9 @@ private string GenerateMembersElement(XmlMembersMapping xmlMembersMapping) if (mapping.Members[j].Name == member.ChoiceIdentifier.MemberName) { if (member.ChoiceIdentifier.Mapping!.TypeDesc!.UseReflection) - enumSource = "p[" + j.ToString(CultureInfo.InvariantCulture) + "]"; + enumSource = $"p[{j}]"; else - enumSource = "((" + mapping.Members[j].TypeDesc!.CSharpName + ")p[" + j.ToString(CultureInfo.InvariantCulture) + "]" + ")"; + enumSource = $"(({mapping.Members[j].TypeDesc!.CSharpName })p[{j}])"; break; } } @@ -4427,12 +4427,12 @@ private string FindChoiceEnumValue(ElementAccessor element, EnumMapping choiceMa continue; } int colon = xmlName.LastIndexOf(':'); - string? choiceNs = colon < 0 ? choiceMapping.Namespace : xmlName.Substring(0, colon); - string choiceName = colon < 0 ? xmlName : xmlName.Substring(colon + 1); + ReadOnlySpan choiceNs = colon < 0 ? choiceMapping.Namespace : xmlName.AsSpan(0, colon); + ReadOnlySpan choiceName = colon < 0 ? xmlName : xmlName.AsSpan(colon + 1); - if (element.Name == choiceName) + if (choiceName.SequenceEqual(element.Name)) { - if ((element.Form == XmlSchemaForm.Unqualified && string.IsNullOrEmpty(choiceNs)) || element.Namespace == choiceNs) + if ((element.Form == XmlSchemaForm.Unqualified && choiceNs.IsEmpty) || choiceNs.SequenceEqual(element.Namespace)) { if (useReflection) enumValue = choiceMapping.Constants[i].Value.ToString(CultureInfo.InvariantCulture); diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriterILGen.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriterILGen.cs index f348fdfa9538..1bf9b7069b16 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriterILGen.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriterILGen.cs @@ -407,7 +407,7 @@ private string GenerateMembersElement(XmlMembersMapping xmlMembersMapping) int xmlnsMember = FindXmlnsIndex(mapping.Members!); if (xmlnsMember >= 0) { - string source = "((" + typeof(XmlSerializerNamespaces).FullName + ")p[" + xmlnsMember.ToString(CultureInfo.InvariantCulture) + "])"; + string source = string.Create(CultureInfo.InvariantCulture, $"(({typeof(XmlSerializerNamespaces).FullName})p[{xmlnsMember}])"); ilg.Ldloc(pLengthLoc); ilg.Ldc(xmlnsMember); @@ -422,7 +422,7 @@ private string GenerateMembersElement(XmlMembersMapping xmlMembersMapping) if (member.Attribute != null && !member.Ignore) { - SourceInfo source = new SourceInfo("p[" + i.ToString(CultureInfo.InvariantCulture) + "]", null, null, pLengthLoc.LocalType.GetElementType()!, ilg); + SourceInfo source = new SourceInfo($"p[{i}]", null, null, pLengthLoc.LocalType.GetElementType()!, ilg); SourceInfo? specifiedSource = null; int specifiedPosition = 0; @@ -433,7 +433,7 @@ private string GenerateMembersElement(XmlMembersMapping xmlMembersMapping) { if (mapping.Members[j].Name == memberNameSpecified) { - specifiedSource = new SourceInfo("((bool)p[" + j.ToString(CultureInfo.InvariantCulture) + "])", null, null, typeof(bool), ilg); + specifiedSource = new SourceInfo($"((bool)p[{j}])", null, null, typeof(bool), ilg); specifiedPosition = j; break; } @@ -489,7 +489,7 @@ private string GenerateMembersElement(XmlMembersMapping xmlMembersMapping) { if (mapping.Members[j].Name == memberNameSpecified) { - specifiedSource = new SourceInfo("((bool)p[" + j.ToString(CultureInfo.InvariantCulture) + "])", null, null, typeof(bool), ilg); + specifiedSource = new SourceInfo($"((bool)p[{j}])", null, null, typeof(bool), ilg); specifiedPosition = j; break; } @@ -515,7 +515,7 @@ private string GenerateMembersElement(XmlMembersMapping xmlMembersMapping) ilg.If(); } - string source = "p[" + i.ToString(CultureInfo.InvariantCulture) + "]"; + string source = $"p[{i}]"; string? enumSource = null; if (member.ChoiceIdentifier != null) { @@ -523,7 +523,7 @@ private string GenerateMembersElement(XmlMembersMapping xmlMembersMapping) { if (mapping.Members[j].Name == member.ChoiceIdentifier.MemberName) { - enumSource = "((" + mapping.Members[j].TypeDesc!.CSharpName + ")p[" + j.ToString(CultureInfo.InvariantCulture) + "]" + ")"; + enumSource = $"(({mapping.Members[j].TypeDesc!.CSharpName})p[{j}])"; break; } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs index b28f0d6eb8bb..c2f54c2715c0 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs @@ -237,13 +237,13 @@ public class XmlConvert int x = name[0]; int y = name[1]; int u = XmlCharType.CombineSurrogateChar(y, x); - bufBld.Append(u.ToString("X8", CultureInfo.InvariantCulture)); + bufBld.Append($"{u:X8}"); position++; copyPosition = 2; } else { - bufBld.Append(((int)name[0]).ToString("X4", CultureInfo.InvariantCulture)); + bufBld.Append($"{(int)name[0]:X4}"); copyPosition = 1; } @@ -282,13 +282,13 @@ public class XmlConvert int x = name[position]; int y = name[position + 1]; int u = XmlCharType.CombineSurrogateChar(y, x); - bufBld.Append(u.ToString("X8", CultureInfo.InvariantCulture)); + bufBld.Append($"{u:X8}"); copyPosition = position + 2; position++; } else { - bufBld.Append(((int)name[position]).ToString("X4", CultureInfo.InvariantCulture)); + bufBld.Append($"{(int)name[position]:X4}"); copyPosition = position + 1; } bufBld.Append('_'); diff --git a/src/libraries/System.Private.Xml/src/System/Xml/XmlException.cs b/src/libraries/System.Private.Xml/src/System/Xml/XmlException.cs index dde7f76f54a5..7d8276f0851b 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/XmlException.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/XmlException.cs @@ -234,9 +234,8 @@ internal static string[] BuildCharExceptionArgs(char invChar, char nextChar) if (XmlCharType.IsHighSurrogate(invChar) && nextChar != 0) { int combinedChar = XmlCharType.CombineSurrogateChar(nextChar, invChar); - ReadOnlySpan invAndNextChars = stackalloc char[] { invChar, nextChar }; - aStringList[0] = new string(invAndNextChars); - aStringList[1] = string.Format(CultureInfo.InvariantCulture, "0x{0:X2}", combinedChar); + aStringList[0] = new string(stackalloc char[] { invChar, nextChar }); + aStringList[1] = $"0x{combinedChar:X2}"; } else { @@ -249,7 +248,7 @@ internal static string[] BuildCharExceptionArgs(char invChar, char nextChar) { aStringList[0] = invChar.ToString(); } - aStringList[1] = string.Format(CultureInfo.InvariantCulture, "0x{0:X2}", (int)invChar); + aStringList[1] = $"0x{(int)invChar:X2}"; } return aStringList; } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/IteratorDescriptor.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/IteratorDescriptor.cs index cc7b1fcc8394..9e142049756c 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/IteratorDescriptor.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/IteratorDescriptor.cs @@ -109,7 +109,7 @@ public static StorageDescriptor Local(LocalBuilder loc, Type itemStorageType, bo public static StorageDescriptor Current(LocalBuilder locIter, MethodInfo currentMethod, Type itemStorageType) { Debug.Assert(currentMethod.ReturnType == itemStorageType, - "Type " + itemStorageType + " does not match type of Current property."); + $"Type {itemStorageType} does not match type of Current property."); StorageDescriptor storage = default; storage._location = ItemLocation.Current; diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/OptimizerPatterns.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/OptimizerPatterns.cs index dc3bd0d9a387..ab2c645e9a06 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/OptimizerPatterns.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/OptimizerPatterns.cs @@ -233,7 +233,7 @@ public object GetArgument(OptimizerPatternArgument argNum) case 2: arg = _arg2; break; } - Debug.Assert(arg != null, "There is no '" + argNum + "' argument."); + Debug.Assert(arg != null, $"There is no '{argNum}' argument."); return arg; } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/XmlILOptimizerVisitor.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/XmlILOptimizerVisitor.cs index 1c7ea589ad30..29145c1c7371 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/XmlILOptimizerVisitor.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/XmlILOptimizerVisitor.cs @@ -5440,7 +5440,7 @@ private QilNode FoldComparison(QilNodeType opType, QilNode left, QilNode right) { object litLeft, litRight; int cmp; - Debug.Assert(left.XmlType == right.XmlType, "Comparison is not defined between " + left.XmlType + " and " + right.XmlType); + Debug.Assert(left.XmlType == right.XmlType, $"Comparison is not defined between {left.XmlType} and {right.XmlType}"); // Extract objects that represent each literal value litLeft = ExtractLiteralValue(left); diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/XmlIlVisitor.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/XmlIlVisitor.cs index 7c5a4c78db48..fb15bb3ecc6c 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/XmlIlVisitor.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/XmlIlVisitor.cs @@ -1749,7 +1749,7 @@ private void Compare(QilBinary ndComp) } else { - Debug.Assert(code != XmlTypeCode.QName, "QName values do not support the " + relOp + " operation"); + Debug.Assert(code != XmlTypeCode.QName, $"QName values do not support the {relOp} operation"); // Push -1, 0, or 1 onto the stack depending upon the result of the comparison _helper.CallCompare(code); diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/EarlyBoundInfo.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/EarlyBoundInfo.cs index b63284285b45..d4f0fe916b9e 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/EarlyBoundInfo.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/EarlyBoundInfo.cs @@ -26,7 +26,7 @@ public EarlyBoundInfo(string namespaceUri, [DynamicallyAccessedMembers(Dynamical _namespaceUri = namespaceUri; _ebType = ebType; _constrInfo = ebType.GetConstructor(Type.EmptyTypes); - Debug.Assert(_constrInfo != null, "The early bound object type " + ebType.FullName + " must have a public default constructor"); + Debug.Assert(_constrInfo != null, $"The early bound object type {ebType.FullName} must have a public default constructor"); } /// diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/TreeIterators.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/TreeIterators.cs index 07a979858943..65c543543948 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/TreeIterators.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/TreeIterators.cs @@ -928,7 +928,7 @@ public bool MoveNext() return true; } - Debug.Assert(_state == IteratorState.NoNext, "Illegal state: " + _state); + Debug.Assert(_state == IteratorState.NoNext, $"Illegal state: {_state}"); return false; } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XmlQueryOutput.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XmlQueryOutput.cs index 83387e02926f..f33347623eeb 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XmlQueryOutput.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XmlQueryOutput.cs @@ -418,7 +418,7 @@ public override string XmlLang /// public void StartTree(XPathNodeType rootType) { - Debug.Assert(_xstate == XmlState.WithinSequence, "StartTree cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.WithinSequence, $"StartTree cannot be called in the {_xstate} state."); Writer = _seqwrt.StartTree(rootType, _nsmgr, _runtime.NameTable); _rootType = rootType; _xstate = (rootType == XPathNodeType.Attribute || rootType == XPathNodeType.Namespace) ? XmlState.EnumAttrs : XmlState.WithinContent; @@ -429,7 +429,7 @@ public void StartTree(XPathNodeType rootType) /// public void EndTree() { - Debug.Assert(_xstate == XmlState.EnumAttrs || _xstate == XmlState.WithinContent, "EndTree cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.EnumAttrs || _xstate == XmlState.WithinContent, $"EndTree cannot be called in the {_xstate} state."); _seqwrt.EndTree(); _xstate = XmlState.WithinSequence; Writer = null; @@ -445,7 +445,7 @@ public void EndTree() /// public void WriteStartElementUnchecked(string prefix, string localName, string ns) { - Debug.Assert(_xstate == XmlState.WithinContent, "WriteStartElement cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.WithinContent, $"WriteStartElement cannot be called in the {_xstate} state."); if (_nsmgr != null) _nsmgr.PushScope(); Writer.WriteStartElement(prefix, localName, ns); //reset when enter element @@ -468,7 +468,7 @@ public void WriteStartElementUnchecked(string localName) /// public void StartElementContentUnchecked() { - Debug.Assert(_xstate == XmlState.EnumAttrs, "StartElementContent cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.EnumAttrs, $"StartElementContent cannot be called in the {_xstate} state."); // Output any cached namespaces if (_cntNmsp != 0) @@ -483,7 +483,7 @@ public void StartElementContentUnchecked() /// public void WriteEndElementUnchecked(string prefix, string localName, string ns) { - Debug.Assert(_xstate == XmlState.EnumAttrs || _xstate == XmlState.WithinContent, "WriteEndElement cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.EnumAttrs || _xstate == XmlState.WithinContent, $"WriteEndElement cannot be called in the {_xstate} state."); Writer.WriteEndElement(prefix, localName, ns); _xstate = XmlState.WithinContent; _depth--; @@ -503,7 +503,7 @@ public void WriteEndElementUnchecked(string localName) /// public void WriteStartAttributeUnchecked(string prefix, string localName, string ns) { - Debug.Assert(_xstate == XmlState.EnumAttrs, "WriteStartAttribute cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.EnumAttrs, $"WriteStartAttribute cannot be called in the {_xstate} state."); Writer.WriteStartAttribute(prefix, localName, ns); _xstate = XmlState.WithinAttr; _depth++; @@ -522,7 +522,7 @@ public void WriteStartAttributeUnchecked(string localName) /// public void WriteEndAttributeUnchecked() { - Debug.Assert(_xstate == XmlState.WithinAttr, "WriteEndAttribute cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.WithinAttr, $"WriteEndAttribute cannot be called in the {_xstate} state."); Writer.WriteEndAttribute(); _xstate = XmlState.EnumAttrs; _depth--; @@ -538,7 +538,7 @@ public void WriteEndAttributeUnchecked() public void WriteNamespaceDeclarationUnchecked(string prefix, string ns) { Debug.Assert(prefix != null && ns != null); - Debug.Assert(_xstate == XmlState.EnumAttrs, "WriteNamespaceDeclaration cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.EnumAttrs, $"WriteNamespaceDeclaration cannot be called in the {_xstate} state."); // xmlns:foo="" is illegal Debug.Assert(prefix.Length == 0 || ns.Length != 0); @@ -571,7 +571,7 @@ public void WriteNamespaceDeclarationUnchecked(string prefix, string ns) /// public void WriteStringUnchecked(string text) { - Debug.Assert(_xstate != XmlState.WithinSequence && _xstate != XmlState.EnumAttrs, "WriteTextBlock cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate != XmlState.WithinSequence && _xstate != XmlState.EnumAttrs, $"WriteTextBlock cannot be called in the {_xstate} state."); Writer.WriteString(text); } @@ -580,7 +580,7 @@ public void WriteStringUnchecked(string text) /// public void WriteRawUnchecked(string text) { - Debug.Assert(_xstate != XmlState.WithinSequence && _xstate != XmlState.EnumAttrs, "WriteTextBlockNoEntities cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate != XmlState.WithinSequence && _xstate != XmlState.EnumAttrs, $"WriteTextBlockNoEntities cannot be called in the {_xstate} state."); Writer.WriteRaw(text); } @@ -761,7 +761,7 @@ public void WriteStartNamespace(string prefix) /// public void WriteNamespaceString(string text) { - Debug.Assert(_xstate == XmlState.WithinNmsp, "WriteNamespaceString cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.WithinNmsp, $"WriteNamespaceString cannot be called in the {_xstate} state."); _nodeText.ConcatNoDelimiter(text); } @@ -770,7 +770,7 @@ public void WriteNamespaceString(string text) /// public void WriteEndNamespace() { - Debug.Assert(_xstate == XmlState.WithinNmsp, "WriteEndNamespace cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.WithinNmsp, $"WriteEndNamespace cannot be called in the {_xstate} state."); _xstate = XmlState.EnumAttrs; _depth--; @@ -800,7 +800,7 @@ public void WriteStartComment() /// public void WriteCommentString(string text) { - Debug.Assert(_xstate == XmlState.WithinComment, "WriteCommentString cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.WithinComment, $"WriteCommentString cannot be called in the {_xstate} state."); _nodeText.ConcatNoDelimiter(text); } @@ -809,7 +809,7 @@ public void WriteCommentString(string text) /// public void WriteEndComment() { - Debug.Assert(_xstate == XmlState.WithinComment, "WriteEndComment cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.WithinComment, $"WriteEndComment cannot be called in the {_xstate} state."); Writer.WriteComment(_nodeText.GetResult()); @@ -843,7 +843,7 @@ public void WriteStartProcessingInstruction(string target) /// public void WriteProcessingInstructionString(string text) { - Debug.Assert(_xstate == XmlState.WithinPI, "WriteProcessingInstructionString cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.WithinPI, $"WriteProcessingInstructionString cannot be called in the {_xstate} state."); _nodeText.ConcatNoDelimiter(text); } @@ -852,7 +852,7 @@ public void WriteProcessingInstructionString(string text) /// public void WriteEndProcessingInstruction() { - Debug.Assert(_xstate == XmlState.WithinPI, "WriteEndProcessingInstruction cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.WithinPI, $"WriteEndProcessingInstruction cannot be called in the {_xstate} state."); Writer.WriteProcessingInstruction(_piTarget, _nodeText.GetResult()); @@ -1205,7 +1205,7 @@ private bool StartCopy(XPathNavigator navigator, bool callChk) private void EndCopy(XPathNavigator navigator, bool callChk) { Debug.Assert(navigator.NodeType == XPathNodeType.Element); - Debug.Assert(_xstate == XmlState.WithinContent, "EndCopy cannot be called in the " + _xstate + " state."); + Debug.Assert(_xstate == XmlState.WithinContent, $"EndCopy cannot be called in the {_xstate} state."); if (callChk) WriteEndElement(); diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XmlQueryRuntime.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XmlQueryRuntime.cs index 4a490b3128ff..4bdaf5451112 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XmlQueryRuntime.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XmlQueryRuntime.cs @@ -492,7 +492,7 @@ internal object ChangeTypeXsltArgument(XmlQueryType xmlType, object value, Type "Values passed to ChangeTypeXsltArgument should be in ILGen's default Clr representation."); Debug.Assert(destinationType == XsltConvert.ObjectType || !destinationType.IsAssignableFrom(value.GetType()), - "No need to call ChangeTypeXsltArgument since value is already assignable to destinationType " + destinationType); + $"No need to call ChangeTypeXsltArgument since value is already assignable to destinationType {destinationType}"); switch (xmlType.TypeCode) { @@ -566,7 +566,7 @@ internal object ChangeTypeXsltArgument(XmlQueryType xmlType, object value, Type } } - Debug.Assert(destinationType.IsAssignableFrom(value.GetType()), "ChangeType from type " + value.GetType().Name + " to type " + destinationType.Name + " failed"); + Debug.Assert(destinationType.IsAssignableFrom(value.GetType()), $"ChangeType from type {value.GetType().Name} to type {destinationType.Name} failed"); return value; } @@ -688,7 +688,7 @@ internal object ChangeTypeXsltResult(XmlQueryType xmlType, object value) } } - Debug.Assert(XmlILTypeHelper.GetStorageType(xmlType).IsAssignableFrom(value.GetType()), "Xml type " + xmlType + " is not represented in ILGen as " + value.GetType().Name); + Debug.Assert(XmlILTypeHelper.GetStorageType(xmlType).IsAssignableFrom(value.GetType()), $"Xml type {xmlType} is not represented in ILGen as {value.GetType().Name}"); return value; } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XslNumber.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XslNumber.cs index a0f455144764..e5b4d1a655ba 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XslNumber.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XslNumber.cs @@ -296,7 +296,7 @@ private void FormatItem(StringBuilder sb, XPathItem item, char startChar, int le } break; default: - Debug.Assert(CharUtil.IsDecimalDigitOne(startChar), "Unexpected startChar: " + startChar); + Debug.Assert(CharUtil.IsDecimalDigitOne(startChar), $"Unexpected startChar: {startChar}"); zero = (char)(startChar - 1); break; } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltConvert.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltConvert.cs index 26b902273ee7..a3d392d571c4 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltConvert.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltConvert.cs @@ -82,7 +82,7 @@ public static bool ToBoolean(XPathItem item) } else { - Debug.Assert(itemType == BooleanType, "Unexpected type of atomic sequence " + itemType.ToString()); + Debug.Assert(itemType == BooleanType, $"Unexpected type of atomic sequence {itemType}"); return item.ValueAsBoolean; } } @@ -126,7 +126,7 @@ public static double ToDouble(XPathItem item) } else { - Debug.Assert(itemType == BooleanType, "Unexpected type of atomic sequence " + itemType.ToString()); + Debug.Assert(itemType == BooleanType, $"Unexpected type of atomic sequence {itemType}"); return item.ValueAsBoolean ? 1d : 0d; } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs index 2d45c788d2f9..c227f2cf9d35 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs @@ -340,7 +340,7 @@ public static double MSNumber(IList value) } else { - Debug.Assert(itemType == XsltConvert.BooleanType, "Unexpected type of atomic value " + itemType.ToString()); + Debug.Assert(itemType == XsltConvert.BooleanType, $"Unexpected type of atomic value {itemType}"); return item.ValueAsBoolean ? 1d : 0d; } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs index 31c779d6abd6..c6b516c7fdbe 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs @@ -353,7 +353,7 @@ private static TypeCode GetTypeCode(XPathItem item) } else { - Debug.Assert(itemType == XsltConvert.BooleanType, "Unexpected type of atomic value " + itemType.ToString()); + Debug.Assert(itemType == XsltConvert.BooleanType, $"Unexpected type of atomic value {itemType}"); return TypeCode.Boolean; } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/XPath/XPathQilFactory.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/XPath/XPathQilFactory.cs index 879e8d997c7c..d27a7d3470e3 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/XPath/XPathQilFactory.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/XPath/XPathQilFactory.cs @@ -168,7 +168,7 @@ public QilNode InvokeRelationalOperator(QilNodeType op, QilNode left, QilNode ri [Conditional("DEBUG")] private void ExpectAny(QilNode n) { - Debug.Assert(IsAnyType(n), "Unexpected expression type: " + n.XmlType!.ToString()); + Debug.Assert(IsAnyType(n), $"Unexpected expression type: {n.XmlType}"); } public QilNode ConvertToType(XmlTypeCode requiredType, QilNode n) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/MatcherBuilder.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/MatcherBuilder.cs index 0df9d3b3a9d7..327bd70c9f66 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/MatcherBuilder.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/MatcherBuilder.cs @@ -569,7 +569,7 @@ public QilNode BuildMatcher(QilIterator it, IList actualArgs, QilNode o default : nodeType = null ; break; } - Debug.Assert(nodeType != null, "Unexpected nodeKind: " + nodeKind); + Debug.Assert(nodeType != null, $"Unexpected nodeKind: {nodeKind}"); QilNode typeNameCheck = f.IsType(it, nodeType); if (qname != null) { diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/XsltQilFactory.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/XsltQilFactory.cs index e8e8b9be0f38..d923e086c9be 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/XsltQilFactory.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/XsltQilFactory.cs @@ -35,7 +35,7 @@ public void CheckXsltType(QilNode n) Debug.Assert(IsDebug, "QName is reserved as the marker for missing values"); break; default: - Debug.Assert(xt.IsNode, "Unexpected expression type: " + xt.ToString()); + Debug.Assert(xt.IsNode, $"Unexpected expression type: {xt}"); break; } } diff --git a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs index 54d1fd9a7f87..568072314f34 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs +++ b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs @@ -28,14 +28,14 @@ public ReflectionTestCaseBase(ITestOutputHelper output) : base(output) public static MethodInfo GetInstanceMethod(Type type, string methName) { MethodInfo methInfo = type.GetMethod(methName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - Debug.Assert(methInfo != null, "Instance method " + type.Name + "." + methName + " not found"); + Debug.Assert(methInfo != null, $"Instance method {type.Name}.{methName} not found"); return methInfo; } public static MethodInfo GetStaticMethod(Type type, string methName) { MethodInfo methInfo = type.GetMethod(methName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); - Debug.Assert(methInfo != null, "Static method " + type.Name + "." + methName + " not found"); + Debug.Assert(methInfo != null, $"Static method {type.Name}.{methName} not found"); return methInfo; } diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Internal/StringHeap.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Internal/StringHeap.cs index ea0e38e3cde1..c2b0151b9ffc 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Internal/StringHeap.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Internal/StringHeap.cs @@ -113,7 +113,7 @@ private static void AssertFilled() { for (int i = 0; i < s_virtualValues!.Length; i++) { - Debug.Assert(s_virtualValues[i] != null, "Missing virtual value for StringHandle.VirtualIndex." + (StringHandle.VirtualIndex)i); + Debug.Assert(s_virtualValues[i] != null, $"Missing virtual value for StringHandle.VirtualIndex.{(StringHandle.VirtualIndex)i}"); } } diff --git a/src/libraries/System.Reflection.MetadataLoadContext/tests/src/TestUtils/TestUtils.cs b/src/libraries/System.Reflection.MetadataLoadContext/tests/src/TestUtils/TestUtils.cs index ac71eddcbaa2..5b6635cabfaf 100644 --- a/src/libraries/System.Reflection.MetadataLoadContext/tests/src/TestUtils/TestUtils.cs +++ b/src/libraries/System.Reflection.MetadataLoadContext/tests/src/TestUtils/TestUtils.cs @@ -123,7 +123,7 @@ internal static string ByteArrayToHex(this byte[] bytes) for (int i = 0; i < bytes.Length; i++) { - builder.Append(bytes[i].ToString("X2")); + builder.Append($"{bytes[i]:X2}"); } return builder.ToString(); diff --git a/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/CacheUsage.cs b/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/CacheUsage.cs index 4b6df845ef50..73ed9f30d3d5 100644 --- a/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/CacheUsage.cs +++ b/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/CacheUsage.cs @@ -717,7 +717,7 @@ internal int FlushUnderUsedItems(int maxFlush, bool force) if (_cEntriesInUse == 0) return 0; - Debug.Assert(maxFlush > 0, "maxFlush is not greater than 0, instead is " + maxFlush); + Debug.Assert(maxFlush > 0, $"maxFlush is not greater than 0, instead is {maxFlush}"); Debug.Assert(_cEntriesInFlush == 0, "_cEntriesInFlush == 0"); UsageEntryRef inFlushHead = UsageEntryRef.INVALID; diff --git a/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/HostFileChangeMonitor.cs b/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/HostFileChangeMonitor.cs index c80daaac7a8f..9d3cb73564bf 100644 --- a/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/HostFileChangeMonitor.cs +++ b/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/HostFileChangeMonitor.cs @@ -38,7 +38,7 @@ private void InitDisposableMembers() DateTimeOffset lastWrite; long fileSize; s_fcn.StartMonitoring(path, new OnChangedCallback(OnChanged), out _fcnState, out lastWrite, out fileSize); - uniqueId = path + lastWrite.UtcDateTime.Ticks.ToString("X", CultureInfo.InvariantCulture) + fileSize.ToString("X", CultureInfo.InvariantCulture); + uniqueId = $"{path}{lastWrite.UtcDateTime.Ticks:X}{fileSize:X}"; _lastModified = lastWrite; } else diff --git a/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCacheEntryChangeMonitor.cs b/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCacheEntryChangeMonitor.cs index b04afc5149e0..05d3aeca4b13 100644 --- a/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCacheEntryChangeMonitor.cs +++ b/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCacheEntryChangeMonitor.cs @@ -39,7 +39,7 @@ private void InitDisposableMembers(MemoryCache cache) MemoryCacheEntry entry = cache.GetEntry(k); DateTime utcCreated = s_DATETIME_MINVALUE_UTC; StartMonitoring(cache, entry, ref hasChanged, ref utcCreated); - uniqueId = k + utcCreated.Ticks.ToString("X", CultureInfo.InvariantCulture); + uniqueId = $"{k}{utcCreated.Ticks:X}"; _lastModified = utcCreated; } else @@ -56,7 +56,7 @@ private void InitDisposableMembers(MemoryCache cache) DateTime utcCreated = s_DATETIME_MINVALUE_UTC; StartMonitoring(cache, entry, ref hasChanged, ref utcCreated); sb.Append(key); - sb.Append(utcCreated.Ticks.ToString("X", CultureInfo.InvariantCulture)); + sb.Append($"{utcCreated.Ticks:X}"); if (utcCreated > _lastModified) { _lastModified = utcCreated; diff --git a/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/PhysicalMemoryMonitor.cs b/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/PhysicalMemoryMonitor.cs index 9d4ba7c85cdf..901754ba815c 100644 --- a/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/PhysicalMemoryMonitor.cs +++ b/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/PhysicalMemoryMonitor.cs @@ -91,11 +91,7 @@ internal override int GetPercentToTrim(DateTime lastTrimTime, int lastTrimPercen } #if PERF - Debug.WriteLine(string.Format("PhysicalMemoryMonitor.GetPercentToTrim: percent={0:N}, lastTrimPercent={1:N}, secondsSinceTrim={2:N}{3}", - percent, - lastTrimPercent, - ticksSinceTrim/TimeSpan.TicksPerSecond, - Environment.NewLine)); + Debug.WriteLine($"PhysicalMemoryMonitor.GetPercentToTrim: percent={percent:N}, lastTrimPercent={lastTrimPercent:N}, secondsSinceTrim={ticksSinceTrim/TimeSpan.TicksPerSecond:N}{Environment.NewLine}"); #endif } @@ -111,8 +107,7 @@ internal void SetLimit(int physicalMemoryLimitPercentage) } _pressureHigh = Math.Max(3, physicalMemoryLimitPercentage); _pressureLow = Math.Max(1, _pressureHigh - 9); - Dbg.Trace("MemoryCacheStats", "PhysicalMemoryMonitor.SetLimit: _pressureHigh=" + _pressureHigh + - ", _pressureLow=" + _pressureLow); + Dbg.Trace($"MemoryCacheStats", "PhysicalMemoryMonitor.SetLimit: _pressureHigh={_pressureHigh}, _pressureLow={_pressureLow}"); } } } diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs b/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs index 488cda85c351..da7a83aab1c2 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs @@ -25,7 +25,7 @@ public static void CompleteValueRange() for (int i = 0; i < values.Length; i++) { values[i] = (byte)i; - sb.Append(i.ToString("X2")); + sb.Append($"{i:X2}"); } TestSequence(values, sb.ToString()); diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs b/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs index 3b0986a46e41..b92685667dd5 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs @@ -24,7 +24,7 @@ public static void CompleteValueRange() for (int i = 0; i < values.Length; i++) { values[i] = (byte)i; - sb.Append(i.ToString("X2")); + sb.Append($"{i:X2}"); } Assert.Equal(sb.ToString(), Convert.ToHexString(values)); diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs index fbe995267afe..44f4bf6d282e 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs @@ -1949,7 +1949,7 @@ public static void BigMul() public static void BigMul128_Unsigned(ulong a, ulong b, string result) { ulong high = Math.BigMul(a, b, out ulong low); - Assert.Equal(result, high.ToString("X16") + low.ToString("X16")); + Assert.Equal(result, $"{high:X16}{low:X16}"); } [Theory] @@ -1968,7 +1968,7 @@ public static void BigMul128_Unsigned(ulong a, ulong b, string result) public static void BigMul128_Signed(long a, long b, string result) { long high = Math.BigMul(a, b, out long low); - Assert.Equal(result, high.ToString("X16") + low.ToString("X16")); + Assert.Equal(result, $"{high:X16}{low:X16}"); } [Theory] diff --git a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System.Runtime.InteropServices.RuntimeInformation.csproj b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System.Runtime.InteropServices.RuntimeInformation.csproj index 2ee0caf2d562..1e04c274aa81 100644 --- a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System.Runtime.InteropServices.RuntimeInformation.csproj +++ b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System.Runtime.InteropServices.RuntimeInformation.csproj @@ -40,6 +40,7 @@ + diff --git a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.Windows.cs b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.Windows.cs index 0177eec2317c..2cd38459dc7b 100644 --- a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.Windows.cs +++ b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.Windows.cs @@ -21,10 +21,11 @@ public static string OSDescription OperatingSystem os = Environment.OSVersion; Version v = os.Version; + Span stackBuffer = stackalloc char[256]; const string Version = "Microsoft Windows"; s_osDescription = osDescription = string.IsNullOrEmpty(os.ServicePack) ? - $"{Version} {(uint)v.Major}.{(uint)v.Minor}.{(uint)v.Build}" : - $"{Version} {(uint)v.Major}.{(uint)v.Minor}.{(uint)v.Build} {os.ServicePack}"; + string.Create(null, stackBuffer, $"{Version} {(uint)v.Major}.{(uint)v.Minor}.{(uint)v.Build}") : + string.Create(null, stackBuffer, $"{Version} {(uint)v.Major}.{(uint)v.Minor}.{(uint)v.Build} {os.ServicePack}"); } return osDescription; diff --git a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.cs b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.cs index df0fb53273d4..6e892ce52048 100644 --- a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.cs +++ b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.cs @@ -17,19 +17,16 @@ public static string FrameworkDescription { if (s_frameworkDescription == null) { - string? versionString = typeof(object).Assembly.GetCustomAttribute()?.InformationalVersion; + ReadOnlySpan versionString = typeof(object).Assembly.GetCustomAttribute()?.InformationalVersion; // Strip the git hash if there is one - if (versionString != null) + int plusIndex = versionString.IndexOf('+'); + if (plusIndex != -1) { - int plusIndex = versionString.IndexOf('+'); - if (plusIndex != -1) - { - versionString = versionString.Substring(0, plusIndex); - } + versionString = versionString.Slice(0, plusIndex); } - s_frameworkDescription = !string.IsNullOrWhiteSpace(versionString) ? $"{FrameworkName} {versionString}" : FrameworkName; + s_frameworkDescription = !versionString.Trim().IsEmpty ? $"{FrameworkName} {versionString}" : FrameworkName; } return s_frameworkDescription; diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/GetObjectForNativeVariantTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/GetObjectForNativeVariantTests.cs index a4fe32e9c023..d8a6bc398f5e 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/GetObjectForNativeVariantTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/GetObjectForNativeVariantTests.cs @@ -61,7 +61,7 @@ public struct Variant [FieldOffset(0)] public TypeUnion m_Variant; [FieldOffset(0)] public decimal m_decimal; - public override string ToString() => "0x" + m_Variant.vt.ToString("X"); + public override string ToString() => $"0x{m_Variant.vt:X}"; } // Taken from wtypes.h diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs index 060200698b2d..2bbc23476c50 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs @@ -1676,11 +1676,11 @@ private static string ConvertDecimalToHex(string input, bool upper, NumberFormat { if (upper) { - output = output + chars[start].ToString("X"); + output = $"{output}{chars[start]:X}"; } else { - output = output + chars[start].ToString("x"); + output = $"{output}{chars[start]:x}"; } } diff --git a/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.RuntimeOnly.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.RuntimeOnly.cs index de485ec10b57..a7cc9748babd 100644 --- a/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.RuntimeOnly.cs +++ b/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.RuntimeOnly.cs @@ -2387,7 +2387,7 @@ public override string ToString() sb.AppendLine("Family members:"); foreach (var member in this.Members) { - sb.AppendLine(" " + member); + sb.AppendLine($" {member}"); } return sb.ToString(); @@ -2404,7 +2404,7 @@ public override string ToString() sb.AppendLine("Family members:"); foreach (var member in this.Members) { - sb.AppendLine(" " + member); + sb.AppendLine($" {member}"); } return sb.ToString(); diff --git a/src/libraries/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.Unix.cs b/src/libraries/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.Unix.cs index 63862ea65b4d..cfef4e4d0969 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.Unix.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.Unix.cs @@ -30,7 +30,7 @@ private static bool ShouldUseCache(OidGroup oidGroup) case -1: /* OpenSSL internal error */ throw Interop.Crypto.CreateOpenSslCryptographicException(); default: - Debug.Assert(result == 0, "LookupFriendlyNameByOid returned unexpected result " + result); + Debug.Assert(result == 0, $"LookupFriendlyNameByOid returned unexpected result {result}"); // The lookup may have left errors in this case, clean up for precaution. Interop.Crypto.ErrClearError(); diff --git a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/CanonicalXmlAttribute.cs b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/CanonicalXmlAttribute.cs index ed06c365672b..761fc37439ad 100644 --- a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/CanonicalXmlAttribute.cs +++ b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/CanonicalXmlAttribute.cs @@ -25,7 +25,7 @@ public bool IsInNodeSet public void Write(StringBuilder strBuilder, DocPosition docPos, AncestralNamespaceContextManager anc) { - strBuilder.Append(" " + Name + "=\""); + strBuilder.Append($" {Name}=\""); strBuilder.Append(Utils.EscapeAttributeValue(Value)); strBuilder.Append('"'); } diff --git a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/CanonicalXmlElement.cs b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/CanonicalXmlElement.cs index fb1f9c599cfd..7d3174b07800 100644 --- a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/CanonicalXmlElement.cs +++ b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/CanonicalXmlElement.cs @@ -91,7 +91,7 @@ public void Write(StringBuilder strBuilder, DocPosition docPos, AncestralNamespa if (IsInNodeSet) { - strBuilder.Append(""); + strBuilder.Append($""); } } diff --git a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/CanonicalXmlProcessingInstruction.cs b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/CanonicalXmlProcessingInstruction.cs index 2bc2502eb9f6..de71302a1c02 100644 --- a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/CanonicalXmlProcessingInstruction.cs +++ b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/CanonicalXmlProcessingInstruction.cs @@ -33,7 +33,7 @@ public void Write(StringBuilder strBuilder, DocPosition docPos, AncestralNamespa strBuilder.Append(" 0)) - strBuilder.Append(" " + Value); + strBuilder.Append(' ').Append(Value); strBuilder.Append("?>"); if (docPos == DocPosition.BeforeRootElement) strBuilder.Append((char)10); diff --git a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/SignedXmlDebugLog.cs b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/SignedXmlDebugLog.cs index 5f7a5090097e..e70615426c2b 100644 --- a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/SignedXmlDebugLog.cs +++ b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/SignedXmlDebugLog.cs @@ -249,7 +249,7 @@ private static string GetObjectId(object o) { Debug.Assert(o != null, "o != null"); - return $"{o.GetType().Name}#{o.GetHashCode().ToString("x8", CultureInfo.InvariantCulture)}"; + return $"{o.GetType().Name}#{o.GetHashCode():x8}"; } /// diff --git a/src/libraries/System.Security.Cryptography.Xml/tests/XmlDsigXsltTransformTest.cs b/src/libraries/System.Security.Cryptography.Xml/tests/XmlDsigXsltTransformTest.cs index 906e96761653..1fa91a3c912f 100644 --- a/src/libraries/System.Security.Cryptography.Xml/tests/XmlDsigXsltTransformTest.cs +++ b/src/libraries/System.Security.Cryptography.Xml/tests/XmlDsigXsltTransformTest.cs @@ -111,7 +111,7 @@ private string Stream2Array(Stream s) int b = s.ReadByte(); while (b != -1) { - sb.Append(b.ToString("X2")); + sb.Append($"{b:X2}"); b = s.ReadByte(); } return sb.ToString(); diff --git a/src/libraries/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/FeedUtils.cs b/src/libraries/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/FeedUtils.cs index 7b20e996ad68..eacc8ca6861f 100644 --- a/src/libraries/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/FeedUtils.cs +++ b/src/libraries/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/FeedUtils.cs @@ -14,7 +14,7 @@ public static string AddLineInfo(XmlReader reader, string error) IXmlLineInfo lineInfo = reader as IXmlLineInfo; if (lineInfo != null && lineInfo.HasLineInfo()) { - error = string.Format(CultureInfo.InvariantCulture, "{0} {1}", SR.Format(SR.ErrorInLine, lineInfo.LineNumber, lineInfo.LinePosition), SR.Format(error)); + error = $"{SR.Format(SR.ErrorInLine, lineInfo.LineNumber, lineInfo.LinePosition)} {error}"; } return error; } diff --git a/src/libraries/System.ServiceModel.Syndication/tests/Utils/XmlDiffDocument.cs b/src/libraries/System.ServiceModel.Syndication/tests/Utils/XmlDiffDocument.cs index f06e87cda95a..be087593f9b1 100644 --- a/src/libraries/System.ServiceModel.Syndication/tests/Utils/XmlDiffDocument.cs +++ b/src/libraries/System.ServiceModel.Syndication/tests/Utils/XmlDiffDocument.cs @@ -1612,7 +1612,7 @@ public override void WriteTo(XmlWriter w) w.WriteString(Value); break; default: - Debug.Assert(false, "Wrong type for text-like node : " + _nodetype.ToString()); + Debug.Assert(false, $"Wrong type for text-like node : {_nodetype}"); break; } } diff --git a/src/libraries/System.Speech/src/Result/RecognizedPhrase.cs b/src/libraries/System.Speech/src/Result/RecognizedPhrase.cs index 446d744cae0a..ebcde98e50aa 100644 --- a/src/libraries/System.Speech/src/Result/RecognizedPhrase.cs +++ b/src/libraries/System.Speech/src/Result/RecognizedPhrase.cs @@ -1166,7 +1166,7 @@ internal RuleNode Find(uint firstElement, uint count) private string DisplayDebugInfo() { - return string.Format("'rule={0}", _rule); + return $"'rule={_rule}"; } internal Grammar _grammar; internal string _rule; diff --git a/src/libraries/System.Text.Encoding.CodePages/src/System/Text/BaseCodePageEncoding.cs b/src/libraries/System.Text.Encoding.CodePages/src/System/Text/BaseCodePageEncoding.cs index 01208d211838..ab66bca82c3d 100644 --- a/src/libraries/System.Text.Encoding.CodePages/src/System/Text/BaseCodePageEncoding.cs +++ b/src/libraries/System.Text.Encoding.CodePages/src/System/Text/BaseCodePageEncoding.cs @@ -337,7 +337,7 @@ internal static unsafe int GetCodePageByteSize(int codePage) if (pCodePageIndex->CodePage == codePage) { Debug.Assert(pCodePageIndex->ByteCount == 1 || pCodePageIndex->ByteCount == 2, - "[BaseCodePageEncoding] Code page (" + codePage + ") has invalid byte size (" + pCodePageIndex->ByteCount + ") in table"); + $"[BaseCodePageEncoding] Code page ({codePage}) has invalid byte size ({pCodePageIndex->ByteCount}) in table"); // Return what it says for byte count return pCodePageIndex->ByteCount; } diff --git a/src/libraries/System.Text.Encoding.CodePages/src/System/Text/DBCSCodePageEncoding.cs b/src/libraries/System.Text.Encoding.CodePages/src/System/Text/DBCSCodePageEncoding.cs index 531f26876242..5255ed67b97e 100644 --- a/src/libraries/System.Text.Encoding.CodePages/src/System/Text/DBCSCodePageEncoding.cs +++ b/src/libraries/System.Text.Encoding.CodePages/src/System/Text/DBCSCodePageEncoding.cs @@ -399,7 +399,7 @@ protected unsafe override void ReadBestFitTable() if (bOutOfOrder) { Debug.Assert((arrayTemp.Length / 2) < 20, - "[DBCSCodePageEncoding.ReadBestFitTable]Expected small best fit table < 20 for code page " + CodePage + ", not " + arrayTemp.Length / 2); + $"[DBCSCodePageEncoding.ReadBestFitTable]Expected small best fit table < 20 for code page {CodePage}, not {arrayTemp.Length / 2}"); for (int i = 0; i < arrayTemp.Length - 2; i += 2) { diff --git a/src/libraries/System.Text.Encoding.CodePages/src/System/Text/EncoderBestFitFallback.cs b/src/libraries/System.Text.Encoding.CodePages/src/System/Text/EncoderBestFitFallback.cs index 0d9bed68345d..e232be861680 100644 --- a/src/libraries/System.Text.Encoding.CodePages/src/System/Text/EncoderBestFitFallback.cs +++ b/src/libraries/System.Text.Encoding.CodePages/src/System/Text/EncoderBestFitFallback.cs @@ -84,7 +84,7 @@ public override bool Fallback(char charUnknown, int index) // If we had a buffer already we're being recursive, throw, it's probably at the suspect // character in our array. // Shouldn't be able to get here for all of our code pages, table would have to be messed up. - Debug.Assert(_iCount < 1, "[InternalEncoderBestFitFallbackBuffer.Fallback(non surrogate)] Fallback char " + ((int)_cBestFit).ToString("X4", CultureInfo.InvariantCulture) + " caused recursive fallback"); + Debug.Assert(_iCount < 1, $"[InternalEncoderBestFitFallbackBuffer.Fallback(non surrogate)] Fallback char {(int)_cBestFit:X4} caused recursive fallback"); _iCount = _iSize = 1; _cBestFit = TryBestFit(charUnknown); @@ -106,7 +106,7 @@ public override bool Fallback(char charUnknownHigh, char charUnknownLow, int ind // If we had a buffer already we're being recursive, throw, it's probably at the suspect // character in our array. 0 is processing last character, < 0 is not falling back // Shouldn't be able to get here, table would have to be messed up. - Debug.Assert(_iCount < 1, "[InternalEncoderBestFitFallbackBuffer.Fallback(surrogate)] Fallback char " + ((int)_cBestFit).ToString("X4", CultureInfo.InvariantCulture) + " caused recursive fallback"); + Debug.Assert(_iCount < 1, $"[InternalEncoderBestFitFallbackBuffer.Fallback(surrogate)] Fallback char {(int)_cBestFit:X4} caused recursive fallback"); // Go ahead and get our fallback, surrogates don't have best fit _cBestFit = '?'; diff --git a/src/libraries/System.Text.Encoding.CodePages/src/System/Text/GB18030Encoding.cs b/src/libraries/System.Text.Encoding.CodePages/src/System/Text/GB18030Encoding.cs index 2f4042f334c5..7a27288fd4ed 100644 --- a/src/libraries/System.Text.Encoding.CodePages/src/System/Text/GB18030Encoding.cs +++ b/src/libraries/System.Text.Encoding.CodePages/src/System/Text/GB18030Encoding.cs @@ -188,7 +188,7 @@ protected override unsafe void LoadManagedCodePage() // We should've read in GBLast4ByteCode 4 byte sequences Debug.Assert(count4Byte == GBLast4ByteCode + 1, - "[GB18030Encoding.LoadManagedCodePage] Expected 0x99FB to be last 4 byte offset, found 0x" + count4Byte.ToString("X4", CultureInfo.InvariantCulture)); + $"[GB18030Encoding.LoadManagedCodePage] Expected 0x99FB to be last 4 byte offset, found 0x{count4Byte:X4}"); } // Is4Byte @@ -242,7 +242,7 @@ public override unsafe int GetBytes(char* chars, int charCount, if (charLeftOver != 0) { Debug.Assert(char.IsHighSurrogate(charLeftOver), - "[GB18030Encoding.GetBytes] leftover character should be high surrogate, not 0x" + ((int)charLeftOver).ToString("X4", CultureInfo.InvariantCulture)); + $"[GB18030Encoding.GetBytes] leftover character should be high surrogate, not 0x{(int)charLeftOver:X4}"); // If our next char isn't a low surrogate, then we need to do fallback. if (!char.IsLowSurrogate(ch)) @@ -272,7 +272,7 @@ public override unsafe int GetBytes(char* chars, int charCount, byte byte2 = (byte)((offset % 0x0a) + 0x30); offset /= 0x0a; Debug.Assert(offset < 0x6f, - "[GB18030Encoding.GetBytes](1) Expected offset < 0x6f, not 0x" + offset.ToString("X2", CultureInfo.InvariantCulture)); + $"[GB18030Encoding.GetBytes](1) Expected offset < 0x6f, not 0x{offset:X2}"); charLeftOver = (char)0; if (!buffer.AddByte((byte)(offset + 0x90), byte2, byte3, byte4)) @@ -322,7 +322,7 @@ public override unsafe int GetBytes(char* chars, int charCount, byte byte2 = (byte)((iBytes % 0x0a) + 0x30); iBytes /= 0x0a; Debug.Assert(iBytes < 0x7e, - "[GB18030Encoding.GetBytes]Expected iBytes < 0x7e, not 0x" + iBytes.ToString("X2", CultureInfo.InvariantCulture)); + $"[GB18030Encoding.GetBytes]Expected iBytes < 0x7e, not 0x{iBytes:X2}"); if (!buffer.AddByte((byte)(iBytes + 0x81), byte2, byte3, byte4)) break; } diff --git a/src/libraries/System.Text.Encoding.CodePages/src/System/Text/ISCIIEncoding.cs b/src/libraries/System.Text.Encoding.CodePages/src/System/Text/ISCIIEncoding.cs index e630706877a3..204c45dbd390 100644 --- a/src/libraries/System.Text.Encoding.CodePages/src/System/Text/ISCIIEncoding.cs +++ b/src/libraries/System.Text.Encoding.CodePages/src/System/Text/ISCIIEncoding.cs @@ -58,7 +58,7 @@ public ISCIIEncoding(int codePage) : base(codePage) // Legal windows code pages are between Devanagari and Punjabi Debug.Assert(_defaultCodePage >= CodeDevanagari && _defaultCodePage <= CodePunjabi, - "[ISCIIEncoding] Code page (" + codePage + " isn't supported by ISCIIEncoding!"); + $"[ISCIIEncoding] Code page ({codePage} isn't supported by ISCIIEncoding!"); // This shouldn't really be possible if (_defaultCodePage < CodeDevanagari || _defaultCodePage > CodePunjabi) @@ -237,7 +237,7 @@ public override unsafe int GetBytes(char* chars, int charCount, byte* bytes, int // We only know how to map from Unicode to pages from Devanagari to Punjabi (2 to 11) Debug.Assert(currentCodePage >= CodeDevanagari && currentCodePage <= CodePunjabi, - "[ISCIIEncoding.GetBytes]Code page (" + currentCodePage + " shouldn't appear in ISCII from Unicode table!"); + $"[ISCIIEncoding.GetBytes]Code page ({currentCodePage} shouldn't appear in ISCII from Unicode table!"); } // Safe to add our byte now @@ -252,7 +252,7 @@ public override unsafe int GetBytes(char* chars, int charCount, byte* bytes, int { // This one needs another byte Debug.Assert((indicTwoBytes >> 12) > 0 && (indicTwoBytes >> 12) <= 3, - "[ISCIIEncoding.GetBytes]Expected indicTwoBytes from 1-3, not " + (indicTwoBytes >> 12)); + $"[ISCIIEncoding.GetBytes]Expected indicTwoBytes from 1-3, not {(indicTwoBytes >> 12)}"); // Already did buffer checking, but... if (!buffer.AddByte(s_SecondIndicByte[indicTwoBytes >> 12])) @@ -350,7 +350,7 @@ public override unsafe int GetChars(byte* bytes, int byteCount, // Get our current code page index (some code pages are dups) int currentCodePageIndex = -1; Debug.Assert(currentCodePage >= CodeDevanagari && currentCodePage <= CodePunjabi, - "[ISCIIEncoding.GetChars]Decoder code page must be >= Devanagari and <= Punjabi, not " + currentCodePage); + $"[ISCIIEncoding.GetChars]Decoder code page must be >= Devanagari and <= Punjabi, not {currentCodePage}"); if (currentCodePage >= CodeDevanagari && currentCodePage <= CodePunjabi) { diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCode.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCode.cs index dbfa9a5cdbdf..e76e3ea160aa 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCode.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCode.cs @@ -403,9 +403,9 @@ public override string ToString() { var sb = new StringBuilder(); - sb.AppendLine("Direction: " + (RightToLeft ? "right-to-left" : "left-to-right")); - sb.AppendLine("Anchor: " + RegexPrefixAnalyzer.AnchorDescription(LeadingAnchor)); - sb.AppendLine(""); + sb.AppendLine($"Direction: {(RightToLeft ? "right-to-left" : "left-to-right")}"); + sb.AppendLine($"Anchor: {RegexPrefixAnalyzer.AnchorDescription(LeadingAnchor)}"); + sb.AppendLine(); if (BoyerMoorePrefix != null) { diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs index 52bd5b89ec26..4c8fe4f91902 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs @@ -47,7 +47,7 @@ public RegexRunnerFactory FactoryInstanceFromCode(string pattern, RegexCode code _hasTimeout = hasTimeout; // Pick a unique number for the methods we generate. - object regexNum = (uint)Interlocked.Increment(ref s_regexCount); // object to box once instead of twice below + uint regexNum = (uint)Interlocked.Increment(ref s_regexCount); // Get a description of the regex to use in the name. This is helpful when profiling, and is opt-in. string description = string.Empty; diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexCharacterSetTests.cs b/src/libraries/System.Text.RegularExpressions/tests/RegexCharacterSetTests.cs index b11071d595e5..a029d679c8ba 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/RegexCharacterSetTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/RegexCharacterSetTests.cs @@ -82,7 +82,7 @@ public void SetExclusionsExpected(string set, RegexOptions options, char[] expec [InlineData('\u0100')] public void SingleExpected(char c) { - string s = @"\u" + ((int)c).ToString("X4"); + string s = $@"\u{(int)c:X4}"; var set = new HashSet() { c }; // One diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionsEtwProvider.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionsEtwProvider.cs index 0be2514b3449..5da12d241967 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionsEtwProvider.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionsEtwProvider.cs @@ -1168,16 +1168,14 @@ private void SetActivityId(string str) { // GUID with dash if (str.Length >= 36) { - string str_part1 = str.Substring(0, 36); - Guid.TryParse(str_part1, out guid); + Guid.TryParse(str.AsSpan(0, 36), out guid); } } else { if (str.Length >= 32) { - string str_part1 = str.Substring(0, 32); - Guid.TryParse(str_part1, out guid); + Guid.TryParse(str.AsSpan(0, 32), out guid); } } SetCurrentThreadActivityId(guid); diff --git a/src/libraries/System.ValueTuple/tests/ExtensionsTests.cs b/src/libraries/System.ValueTuple/tests/ExtensionsTests.cs index a4db2b40b5e8..758f7d8da622 100644 --- a/src/libraries/System.ValueTuple/tests/ExtensionsTests.cs +++ b/src/libraries/System.ValueTuple/tests/ExtensionsTests.cs @@ -674,26 +674,26 @@ public override string ToString() { var builder = new StringBuilder(); if (x1 > 0) { builder.Append(x1); } - if (x2 > 0) { builder.Append(" " + x2); } - if (x3 > 0) { builder.Append(" " + x3); } - if (x4 > 0) { builder.Append(" " + x4); } - if (x5 > 0) { builder.Append(" " + x5); } - if (x6 > 0) { builder.Append(" " + x6); } - if (x7 > 0) { builder.Append(" " + x7); } - if (x8 > 0) { builder.Append(" " + x8); } - if (x9 > 0) { builder.Append(" " + x9); } - if (x10 > 0) { builder.Append(" " + x10); } - if (x11 > 0) { builder.Append(" " + x11); } - if (x12 > 0) { builder.Append(" " + x12); } - if (x13 > 0) { builder.Append(" " + x13); } - if (x14 > 0) { builder.Append(" " + x14); } - if (x15 > 0) { builder.Append(" " + x15); } - if (x16 > 0) { builder.Append(" " + x16); } - if (x17 > 0) { builder.Append(" " + x17); } - if (x18 > 0) { builder.Append(" " + x18); } - if (x19 > 0) { builder.Append(" " + x19); } - if (x20 > 0) { builder.Append(" " + x20); } - if (x21 > 0) { builder.Append(" " + x21); } + if (x2 > 0) { builder.Append($" {x2}"); } + if (x3 > 0) { builder.Append($" {x3}"); } + if (x4 > 0) { builder.Append($" {x4}"); } + if (x5 > 0) { builder.Append($" {x5}"); } + if (x6 > 0) { builder.Append($" {x6}"); } + if (x7 > 0) { builder.Append($" {x7}"); } + if (x8 > 0) { builder.Append($" {x8}"); } + if (x9 > 0) { builder.Append($" {x9}"); } + if (x10 > 0) { builder.Append($" {x10}"); } + if (x11 > 0) { builder.Append($" {x11}"); } + if (x12 > 0) { builder.Append($" {x12}"); } + if (x13 > 0) { builder.Append($" {x13}"); } + if (x14 > 0) { builder.Append($" {x14}"); } + if (x15 > 0) { builder.Append($" {x15}"); } + if (x16 > 0) { builder.Append($" {x16}"); } + if (x17 > 0) { builder.Append($" {x17}"); } + if (x18 > 0) { builder.Append($" {x18}"); } + if (x19 > 0) { builder.Append($" {x19}"); } + if (x20 > 0) { builder.Append($" {x20}"); } + if (x21 > 0) { builder.Append($" {x21}"); } return builder.ToString(); } diff --git a/src/libraries/System.Web.HttpUtility/src/System/Web/HttpUtility.cs b/src/libraries/System.Web.HttpUtility/src/System/Web/HttpUtility.cs index b6b46d1fda9a..d991cc61d3f1 100644 --- a/src/libraries/System.Web.HttpUtility/src/System/Web/HttpUtility.cs +++ b/src/libraries/System.Web.HttpUtility/src/System/Web/HttpUtility.cs @@ -66,11 +66,11 @@ public override string ToString() { if (string.IsNullOrEmpty(keys[i])) { - sb.AppendFormat("{0}&", UrlEncode(value)); + sb.Append(UrlEncode(value)).Append('&'); } else { - sb.AppendFormat("{0}={1}&", keys[i], UrlEncode(value)); + sb.AppendFormat($"{keys[i]}={UrlEncode(value)}&"); } } } diff --git a/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs b/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs index 868418b22927..658665d3c666 100644 --- a/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs +++ b/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs @@ -14,8 +14,7 @@ internal static class HttpEncoder { private static void AppendCharAsUnicodeJavaScript(StringBuilder builder, char c) { - builder.Append("\\u"); - builder.Append(((int)c).ToString("x4", CultureInfo.InvariantCulture)); + builder.Append($"\\u{(int)c:x4}"); } private static bool CharRequiresJavaScriptEncoding(char c) => diff --git a/src/tasks/AppleAppBuilder/Xcode.cs b/src/tasks/AppleAppBuilder/Xcode.cs index 9c0af486b640..b79387da224f 100644 --- a/src/tasks/AppleAppBuilder/Xcode.cs +++ b/src/tasks/AppleAppBuilder/Xcode.cs @@ -222,7 +222,7 @@ public string GenerateXCode( if (!string.IsNullOrEmpty(DiagnosticPorts)) { - defines.AppendLine("\nadd_definitions(-DDIAGNOSTIC_PORTS=\"" + DiagnosticPorts + "\")"); + defines.AppendLine($"\nadd_definitions(-DDIAGNOSTIC_PORTS=\"{DiagnosticPorts}\")"); } cmakeLists = cmakeLists.Replace("%Defines%", defines.ToString()); @@ -272,7 +272,7 @@ public string GenerateXCode( .Append("-S.") .Append(" -B").Append(projectName) .Append(" -GXcode") - .Append(" -DCMAKE_SYSTEM_NAME=" + targetName) + .Append(" -DCMAKE_SYSTEM_NAME=").Append(targetName) .Append(deployTarget); File.WriteAllText(Path.Combine(binDir, "runtime.h"), @@ -334,30 +334,30 @@ public string BuildAppBundle( case TargetNames.iOS: sdk = "iphoneos"; args.Append(" -arch arm64") - .Append(" -sdk " + sdk); + .Append(" -sdk ").Append(sdk); break; case TargetNames.iOSsim: sdk = "iphonesimulator"; args.Append(" -arch arm64") - .Append(" -sdk " + sdk); + .Append(" -sdk ").Append(sdk); break; case TargetNames.tvOS: sdk = "appletvos"; args.Append(" -arch arm64") - .Append(" -sdk " + sdk); + .Append(" -sdk ").Append(sdk); break; case TargetNames.tvOSsim: sdk = "appletvsimulator"; args.Append(" -arch arm64") - .Append(" -sdk " + sdk); + .Append(" -sdk ").Append(sdk); break; default: sdk = "maccatalyst"; - args.Append(" -scheme \"" + Path.GetFileNameWithoutExtension(xcodePrjPath) + "\"") + args.Append(" -scheme \"").Append(Path.GetFileNameWithoutExtension(xcodePrjPath)).Append('"') .Append(" -destination \"generic/platform=macOS,name=Any Mac,variant=Mac Catalyst\"") .Append(" -UseModernBuildSystem=YES") - .Append(" -archivePath \"" + Path.GetDirectoryName(xcodePrjPath) + "\"") - .Append(" -derivedDataPath \"" + Path.GetDirectoryName(xcodePrjPath) + "\"") + .Append(" -archivePath \"").Append(Path.GetDirectoryName(xcodePrjPath)).Append('"') + .Append(" -derivedDataPath \"").Append(Path.GetDirectoryName(xcodePrjPath)).Append('"') .Append(" IPHONEOS_DEPLOYMENT_TARGET=14.2"); break; } @@ -369,20 +369,20 @@ public string BuildAppBundle( case TargetNames.iOSsim: sdk = "iphonesimulator"; args.Append(" -arch x86_64") - .Append(" -sdk " + sdk); + .Append(" -sdk ").Append(sdk); break; case TargetNames.tvOSsim: sdk = "appletvsimulator"; args.Append(" -arch x86_64") - .Append(" -sdk " + sdk); + .Append(" -sdk ").Append(sdk); break; default: sdk = "maccatalyst"; - args.Append(" -scheme \"" + Path.GetFileNameWithoutExtension(xcodePrjPath) + "\"") + args.Append(" -scheme \"").Append(Path.GetFileNameWithoutExtension(xcodePrjPath)).Append('"') .Append(" -destination \"generic/platform=macOS,name=Any Mac,variant=Mac Catalyst\"") .Append(" -UseModernBuildSystem=YES") - .Append(" -archivePath \"" + Path.GetDirectoryName(xcodePrjPath) + "\"") - .Append(" -derivedDataPath \"" + Path.GetDirectoryName(xcodePrjPath) + "\"") + .Append(" -archivePath \"").Append(Path.GetDirectoryName(xcodePrjPath)).Append('"') + .Append(" -derivedDataPath \"").Append(Path.GetDirectoryName(xcodePrjPath)).Append('"') .Append(" IPHONEOS_DEPLOYMENT_TARGET=13.5"); break; } diff --git a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs index 603fbcf17003..bdd84c295ee7 100644 --- a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs +++ b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs @@ -209,7 +209,7 @@ private void EmitNativeToInterp(StreamWriter w, List callbacks) // The signature of the interp entry function // This is a gsharedvt_in signature sb.Append("typedef void "); - sb.Append(" (*WasmInterpEntrySig_" + cb_index + ") ("); + sb.Append($" (*WasmInterpEntrySig_{cb_index}) ("); int pindex = 0; if (method.ReturnType.Name != "Void") { sb.Append("int"); @@ -247,13 +247,13 @@ private void EmitNativeToInterp(StreamWriter w, List callbacks) if (pindex > 0) sb.Append(','); sb.Append(MapType(method.GetParameters()[pindex].ParameterType)); - sb.Append(" arg" + pindex); + sb.Append($" arg{pindex}"); pindex++; } sb.Append(") { \n"); if (!is_void) sb.Append(MapType(method.ReturnType) + " res;\n"); - sb.Append("((WasmInterpEntrySig_" + cb_index + ")wasm_native_to_interp_ftndescs [" + cb_index + "].func) ("); + sb.Append($"((WasmInterpEntrySig_{cb_index})wasm_native_to_interp_ftndescs [{cb_index}].func) ("); pindex = 0; if (!is_void) { sb.Append("&res"); @@ -263,7 +263,7 @@ private void EmitNativeToInterp(StreamWriter w, List callbacks) foreach (var p in method.GetParameters()) { if (pindex > 0) sb.Append(", "); - sb.Append("&arg" + aindex); + sb.Append($"&arg{aindex}"); pindex++; aindex++; } From c6acf8dbeff5e2c54b055f496cb8dafc5d0b3e20 Mon Sep 17 00:00:00 2001 From: Dzmitry Konanka <85848222+DzmitryKN@users.noreply.github.com> Date: Tue, 13 Jul 2021 18:02:43 +0300 Subject: [PATCH 068/133] optimize adding unwind info entries by hashtable (#55398) --- src/mono/mono/mini/unwind.c | 101 ++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 34 deletions(-) diff --git a/src/mono/mono/mini/unwind.c b/src/mono/mono/mini/unwind.c index bcf9bd4682ef..bdcfe78604b7 100644 --- a/src/mono/mono/mini/unwind.c +++ b/src/mono/mono/mini/unwind.c @@ -30,14 +30,15 @@ typedef struct { typedef struct { guint32 len; - guint8 info [MONO_ZERO_LEN_ARRAY]; + guint8 *info; } MonoUnwindInfo; static mono_mutex_t unwind_mutex; -static MonoUnwindInfo **cached_info; +static MonoUnwindInfo *cached_info; static int cached_info_next, cached_info_size; static GSList *cached_info_list; +static GHashTable *cached_info_ht; /* Statistics */ static int unwind_info_size; @@ -729,6 +730,34 @@ mono_unwind_init (void) mono_counters_register ("Unwind info size", MONO_COUNTER_JIT | MONO_COUNTER_INT, &unwind_info_size); } +static guint +cached_info_hash(gconstpointer key) +{ + guint i, a; + const guint8 *info = cached_info [GPOINTER_TO_UINT (key)].info; + const guint len = cached_info [GPOINTER_TO_UINT (key)].len; + + for (i = a = 0; i != len; ++i) + a ^= (((guint)info [i]) << (i & 0xf)); + + return a; +} + +static gboolean +cached_info_eq(gconstpointer a, gconstpointer b) +{ + const guint32 lena = cached_info [GPOINTER_TO_UINT (a)].len; + const guint32 lenb = cached_info [GPOINTER_TO_UINT (b)].len; + if (lena == lenb) { + const guint8 *infoa = cached_info [GPOINTER_TO_UINT (a)].info; + const guint8 *infob = cached_info [GPOINTER_TO_UINT (b)].info; + if (memcmp (infoa, infob, lena) == 0) + return TRUE; + } + + return FALSE; +} + /* * mono_cache_unwind_info * @@ -742,42 +771,31 @@ mono_unwind_init (void) guint32 mono_cache_unwind_info (guint8 *unwind_info, guint32 unwind_info_len) { - int i; - MonoUnwindInfo *info; - + gpointer orig_key; + guint32 i; unwind_lock (); - if (cached_info == NULL) { - cached_info_size = 16; - cached_info = g_new0 (MonoUnwindInfo*, cached_info_size); - } - - for (i = 0; i < cached_info_next; ++i) { - MonoUnwindInfo *cached = cached_info [i]; - - if (cached->len == unwind_info_len && memcmp (cached->info, unwind_info, unwind_info_len) == 0) { - unwind_unlock (); - return i; - } - } + if (!cached_info_ht) + cached_info_ht = g_hash_table_new (cached_info_hash, cached_info_eq); - info = (MonoUnwindInfo *)g_malloc (sizeof (MonoUnwindInfo) + unwind_info_len); - info->len = unwind_info_len; - memcpy (&info->info, unwind_info, unwind_info_len); - - i = cached_info_next; - if (cached_info_next >= cached_info_size) { - MonoUnwindInfo **new_table; + MonoUnwindInfo *new_table; + int new_cached_info_size = cached_info_size ? cached_info_size * 2 : 16; + + /* ensure no integer overflow */ + g_assert (new_cached_info_size > cached_info_size); /* * Avoid freeing the old table so mono_get_cached_unwind_info () * doesn't need locks/hazard pointers. */ + new_table = g_new0 (MonoUnwindInfo, new_cached_info_size ); - new_table = g_new0 (MonoUnwindInfo*, cached_info_size * 2); + /* include array allocations into statistics of memory totally consumed by unwind info */ + unwind_info_size += sizeof (MonoUnwindInfo) * new_cached_info_size ; - memcpy (new_table, cached_info, cached_info_size * sizeof (MonoUnwindInfo*)); + if (cached_info_size) + memcpy (new_table, cached_info, sizeof (MonoUnwindInfo) * cached_info_size); mono_memory_barrier (); @@ -785,14 +803,32 @@ mono_cache_unwind_info (guint8 *unwind_info, guint32 unwind_info_len) cached_info = new_table; - cached_info_size *= 2; + cached_info_size = new_cached_info_size; } - cached_info [cached_info_next ++] = info; + i = cached_info_next; + + /* construct temporary element at array's edge without allocated info copy - it will be used for hashtable lookup */ + cached_info [i].len = unwind_info_len; + cached_info [i].info = unwind_info; - unwind_info_size += sizeof (MonoUnwindInfo) + unwind_info_len; + if (!g_hash_table_lookup_extended (cached_info_ht, GUINT_TO_POINTER (i), &orig_key, NULL) ) { + /* hashtable lookup didnt find match - now need to really add new element with allocated copy of unwind info */ + cached_info [i].info = g_new (guint8, unwind_info_len); + memcpy (cached_info [i].info, unwind_info, unwind_info_len); + + /* include allocated memory in stats, note that hashtable allocates struct of 3 pointers per each entry */ + unwind_info_size += sizeof (void *) * 3 + unwind_info_len; + g_hash_table_insert_replace (cached_info_ht, GUINT_TO_POINTER (i), NULL, TRUE); + + cached_info_next = i + 1; + + } else { + i = GPOINTER_TO_UINT (orig_key); + } unwind_unlock (); + return i; } @@ -802,7 +838,6 @@ mono_cache_unwind_info (guint8 *unwind_info, guint32 unwind_info_len) guint8* mono_get_cached_unwind_info (guint32 index, guint32 *unwind_info_len) { - MonoUnwindInfo **table; MonoUnwindInfo *info; guint8 *data; @@ -810,9 +845,7 @@ mono_get_cached_unwind_info (guint32 index, guint32 *unwind_info_len) * This doesn't need any locks/hazard pointers, * since new tables are copies of the old ones. */ - table = cached_info; - - info = table [index]; + info = &cached_info [index]; *unwind_info_len = info->len; data = info->info; From 53db2ec476e4b3b47168a1bd93f29351b5df23bd Mon Sep 17 00:00:00 2001 From: SingleAccretion <62474226+SingleAccretion@users.noreply.github.com> Date: Tue, 13 Jul 2021 18:10:39 +0300 Subject: [PATCH 069/133] Do not eliminate casts from FP types (#53667) An optimization in morph was deleting casts on the RHS of a narrow store if the cast-to-type was wider than the type being stored. This is only valid for casts from integral types, as the backend does not handle "STOREIND(byte*, double)", nor is there an instruction to go from an XMM register to a narrow memory location on x86/x64. The issue is not reproducible right now because fgMorphCast wraps the casts in question, but it is an invalid IR transformation nonetheless, and similar code in fgMorphSmpOpOptional guards against non-integral sources. --- src/coreclr/jit/morph.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index b6ee82d3b807..f0bc076f2356 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -12071,10 +12071,11 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) tree->AsOp()->gtOp1 = op1; } - /* If we are storing a small type, we might be able to omit a cast */ - if ((effectiveOp1->gtOper == GT_IND) && varTypeIsSmall(effectiveOp1->TypeGet())) + // If we are storing a small type, we might be able to omit a cast. + if (effectiveOp1->OperIs(GT_IND) && varTypeIsSmall(effectiveOp1)) { - if (!gtIsActiveCSE_Candidate(op2) && (op2->gtOper == GT_CAST) && !op2->gtOverflow()) + if (!gtIsActiveCSE_Candidate(op2) && op2->OperIs(GT_CAST) && + varTypeIsIntegral(op2->AsCast()->CastOp()) && !op2->gtOverflow()) { var_types castType = op2->CastToType(); @@ -12082,7 +12083,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) // castType is larger or the same as op1's type // then we can discard the cast. - if (varTypeIsSmall(castType) && (genTypeSize(castType) >= genTypeSize(effectiveOp1->TypeGet()))) + if (varTypeIsSmall(castType) && (genTypeSize(castType) >= genTypeSize(effectiveOp1))) { tree->AsOp()->gtOp2 = op2 = op2->AsCast()->CastOp(); } From b2a670b5421af99edad62f8cb5ae172cd8d05030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20K=C3=B6plinger?= Date: Tue, 13 Jul 2021 17:12:28 +0200 Subject: [PATCH 070/133] Improve resolving runtime pack in WasmApp targets (#55258) Use similar logic to `eng/targetingpacks.targets` in `WasmApp.InTree.targets` and `WasmApp.LocalBuild.targets`. Also set `UseMonoRuntime=true` to make sure we get the Mono-based runtime pack. --- eng/targetingpacks.targets | 3 ++- src/mono/wasm/build/WasmApp.InTree.targets | 17 +++++++++++------ src/mono/wasm/build/WasmApp.LocalBuild.targets | 13 +++++++------ 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/eng/targetingpacks.targets b/eng/targetingpacks.targets index f80b26fb9844..0f44b205eb6a 100644 --- a/eng/targetingpacks.targets +++ b/eng/targetingpacks.targets @@ -11,6 +11,7 @@ $(MicrosoftNetCoreAppFrameworkName) + true diff --git a/src/mono/wasm/build/WasmApp.InTree.targets b/src/mono/wasm/build/WasmApp.InTree.targets index f92f2abd62bc..1537277f087d 100644 --- a/src/mono/wasm/build/WasmApp.InTree.targets +++ b/src/mono/wasm/build/WasmApp.InTree.targets @@ -2,16 +2,21 @@ + + - - + + + + <_LocalMicrosoftNetCoreAppRuntimePackDir>$([MSBuild]::NormalizeDirectory($(ArtifactsBinDir), 'microsoft.netcore.app.runtime.browser-wasm', $(Configuration))) + - - $([MSBuild]::NormalizeDirectory($(ArtifactsBinDir), 'microsoft.netcore.app.runtime.browser-wasm', $(Configuration))) - + - + diff --git a/src/mono/wasm/build/WasmApp.LocalBuild.targets b/src/mono/wasm/build/WasmApp.LocalBuild.targets index fac5252a4ef3..b34d8f03bfa3 100644 --- a/src/mono/wasm/build/WasmApp.LocalBuild.targets +++ b/src/mono/wasm/build/WasmApp.LocalBuild.targets @@ -26,6 +26,7 @@ true link + true @@ -37,14 +38,14 @@ false - + + - - $(MicrosoftNetCoreAppRuntimePackLocationToUse) - - + - + From 4ff3762a2e08245a5f005beffeb42cca5c4b6c10 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 13 Jul 2021 08:59:20 -0700 Subject: [PATCH 071/133] Update spin-wait pause/yield normalization (#55295) Update spin-wait pause/yield normalization - Modified the measurement to use much less time and to remeasure periodically to reduce CPU usage during startup - Each measurement does a low-microsecond-level measurement of pause/yield times - Some small amount of history of recent measurements is retained and used to for now take the lowest measurement for normalization - Measurements are done lazily, and at most every few seconds another measurement is taken - Added a profiling event that includes info about a measurement and the established value from recent measurements that is used for normalization --- .../src/System/Threading/Thread.CoreCLR.cs | 23 +- src/coreclr/inc/yieldprocessornormalized.h | 120 +++++-- .../utilcode/yieldprocessornormalized.cpp | 16 +- src/coreclr/vm/CMakeLists.txt | 2 +- src/coreclr/vm/ClrEtwAll.man | 27 +- src/coreclr/vm/ClrEtwAllMeta.lst | 8 +- src/coreclr/vm/comsynchronizable.cpp | 17 +- src/coreclr/vm/comsynchronizable.h | 2 +- src/coreclr/vm/ecalllist.h | 2 +- src/coreclr/vm/eventtrace.cpp | 6 + src/coreclr/vm/finalizerthread.cpp | 5 - src/coreclr/vm/threads.cpp | 9 +- src/coreclr/vm/yieldprocessornormalized.cpp | 305 ++++++++++++++---- .../System/Threading/LowLevelSpinWaiter.cs | 4 - .../src/System/Threading/SpinWait.cs | 4 - 15 files changed, 412 insertions(+), 138 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index c1a968ed9f99..83be00cf5e03 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -322,33 +322,14 @@ public void DisableComObjectEagerCleanup() [MethodImpl(MethodImplOptions.InternalCall)] public extern bool Join(int millisecondsTimeout); - private static int s_optimalMaxSpinWaitsPerSpinIteration; - - [DllImport(RuntimeHelpers.QCall)] - private static extern int GetOptimalMaxSpinWaitsPerSpinIterationInternal(); - /// /// Max value to be passed into for optimal delaying. This value is normalized to be /// appropriate for the processor. /// internal static int OptimalMaxSpinWaitsPerSpinIteration { - get - { - int optimalMaxSpinWaitsPerSpinIteration = s_optimalMaxSpinWaitsPerSpinIteration; - return optimalMaxSpinWaitsPerSpinIteration != 0 ? optimalMaxSpinWaitsPerSpinIteration : CalculateOptimalMaxSpinWaitsPerSpinIteration(); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static int CalculateOptimalMaxSpinWaitsPerSpinIteration() - { - // This is done lazily because the first call to the function below in the process triggers a measurement that - // takes a nontrivial amount of time if the measurement has not already been done in the backgorund. - // See Thread::InitializeYieldProcessorNormalized(), which describes and calculates this value. - s_optimalMaxSpinWaitsPerSpinIteration = GetOptimalMaxSpinWaitsPerSpinIterationInternal(); - Debug.Assert(s_optimalMaxSpinWaitsPerSpinIteration > 0); - return s_optimalMaxSpinWaitsPerSpinIteration; + [MethodImpl(MethodImplOptions.InternalCall)] + get; } [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/src/coreclr/inc/yieldprocessornormalized.h b/src/coreclr/inc/yieldprocessornormalized.h index ba349bb83ad5..121e60b03335 100644 --- a/src/coreclr/inc/yieldprocessornormalized.h +++ b/src/coreclr/inc/yieldprocessornormalized.h @@ -12,14 +12,59 @@ FORCEINLINE void System_YieldProcessor() { YieldProcessor(); } #endif #define YieldProcessor Dont_Use_YieldProcessor -const unsigned int MinNsPerNormalizedYield = 37; // measured typically 37-46 on post-Skylake -const unsigned int NsPerOptimalMaxSpinIterationDuration = 272; // approx. 900 cycles, measured 281 on pre-Skylake, 263 on post-Skylake +#define DISABLE_COPY(T) \ + T(const T &) = delete; \ + T &operator =(const T &) = delete -extern unsigned int g_yieldsPerNormalizedYield; -extern unsigned int g_optimalMaxNormalizedYieldsPerSpinIteration; +#define DISABLE_CONSTRUCT_COPY(T) \ + T() = delete; \ + DISABLE_COPY(T) -void InitializeYieldProcessorNormalizedCrst(); -void EnsureYieldProcessorNormalizedInitialized(); +class YieldProcessorNormalization +{ +public: + static const unsigned int TargetNsPerNormalizedYield = 37; + static const unsigned int TargetMaxNsPerSpinIteration = 272; + + // These are maximums for the computed values for normalization based their calculation + static const unsigned int MaxYieldsPerNormalizedYield = TargetNsPerNormalizedYield * 10; + static const unsigned int MaxOptimalMaxNormalizedYieldsPerSpinIteration = + TargetMaxNsPerSpinIteration * 3 / (TargetNsPerNormalizedYield * 2) + 1; + +private: + static bool s_isMeasurementScheduled; + + static unsigned int s_yieldsPerNormalizedYield; + static unsigned int s_optimalMaxNormalizedYieldsPerSpinIteration; + +public: + static bool IsMeasurementScheduled() + { + return s_isMeasurementScheduled; + } + + static void PerformMeasurement(); + +private: + static void ScheduleMeasurementIfNecessary(); + +public: + static unsigned int GetOptimalMaxNormalizedYieldsPerSpinIteration() + { + return s_optimalMaxNormalizedYieldsPerSpinIteration; + } + + static void FireMeasurementEvents(); + +private: + static double AtomicLoad(double *valueRef); + static void AtomicStore(double *valueRef, double value); + + DISABLE_CONSTRUCT_COPY(YieldProcessorNormalization); + + friend class YieldProcessorNormalizationInfo; + friend void YieldProcessorNormalizedForPreSkylakeCount(unsigned int); +}; class YieldProcessorNormalizationInfo { @@ -30,12 +75,15 @@ class YieldProcessorNormalizationInfo public: YieldProcessorNormalizationInfo() - : yieldsPerNormalizedYield(g_yieldsPerNormalizedYield), - optimalMaxNormalizedYieldsPerSpinIteration(g_optimalMaxNormalizedYieldsPerSpinIteration), + : yieldsPerNormalizedYield(YieldProcessorNormalization::s_yieldsPerNormalizedYield), + optimalMaxNormalizedYieldsPerSpinIteration(YieldProcessorNormalization::s_optimalMaxNormalizedYieldsPerSpinIteration), optimalMaxYieldsPerSpinIteration(yieldsPerNormalizedYield * optimalMaxNormalizedYieldsPerSpinIteration) { + YieldProcessorNormalization::ScheduleMeasurementIfNecessary(); } + DISABLE_COPY(YieldProcessorNormalizationInfo); + friend void YieldProcessorNormalized(const YieldProcessorNormalizationInfo &); friend void YieldProcessorNormalized(const YieldProcessorNormalizationInfo &, unsigned int); friend void YieldProcessorNormalizedForPreSkylakeCount(const YieldProcessorNormalizationInfo &, unsigned int); @@ -98,9 +146,8 @@ FORCEINLINE void YieldProcessorNormalized(const YieldProcessorNormalizationInfo if (sizeof(SIZE_T) <= sizeof(unsigned int)) { - // On platforms with a small SIZE_T, prevent overflow on the multiply below. normalizationInfo.yieldsPerNormalizedYield - // is limited to MinNsPerNormalizedYield by InitializeYieldProcessorNormalized(). - const unsigned int MaxCount = UINT_MAX / MinNsPerNormalizedYield; + // On platforms with a small SIZE_T, prevent overflow on the multiply below + const unsigned int MaxCount = UINT_MAX / YieldProcessorNormalization::MaxYieldsPerNormalizedYield; if (count > MaxCount) { count = MaxCount; @@ -144,9 +191,8 @@ FORCEINLINE void YieldProcessorNormalizedForPreSkylakeCount( if (sizeof(SIZE_T) <= sizeof(unsigned int)) { - // On platforms with a small SIZE_T, prevent overflow on the multiply below. normalizationInfo.yieldsPerNormalizedYield - // is limited to MinNsPerNormalizedYield by InitializeYieldProcessorNormalized(). - const unsigned int MaxCount = UINT_MAX / MinNsPerNormalizedYield; + // On platforms with a small SIZE_T, prevent overflow on the multiply below + const unsigned int MaxCount = UINT_MAX / YieldProcessorNormalization::MaxYieldsPerNormalizedYield; if (preSkylakeCount > MaxCount) { preSkylakeCount = MaxCount; @@ -175,7 +221,35 @@ FORCEINLINE void YieldProcessorNormalizedForPreSkylakeCount( // } FORCEINLINE void YieldProcessorNormalizedForPreSkylakeCount(unsigned int preSkylakeCount) { - YieldProcessorNormalizedForPreSkylakeCount(YieldProcessorNormalizationInfo(), preSkylakeCount); + // This function does not forward to the one above because it is used by some code under utilcode, where + // YieldProcessorNormalizationInfo cannot be used since normalization does not happen in some of its consumers. So this + // version uses the fields in YieldProcessorNormalization directly. + + _ASSERTE(preSkylakeCount != 0); + + if (sizeof(SIZE_T) <= sizeof(unsigned int)) + { + // On platforms with a small SIZE_T, prevent overflow on the multiply below + const unsigned int MaxCount = UINT_MAX / YieldProcessorNormalization::MaxYieldsPerNormalizedYield; + if (preSkylakeCount > MaxCount) + { + preSkylakeCount = MaxCount; + } + } + + const unsigned int PreSkylakeCountToSkylakeCountDivisor = 8; + SIZE_T n = + (SIZE_T)preSkylakeCount * + YieldProcessorNormalization::s_yieldsPerNormalizedYield / + PreSkylakeCountToSkylakeCountDivisor; + if (n == 0) + { + n = 1; + } + do + { + System_YieldProcessor(); + } while (--n != 0); } // See YieldProcessorNormalized() for preliminary info. This function is to be used when there is a decent possibility that the @@ -193,15 +267,12 @@ FORCEINLINE void YieldProcessorWithBackOffNormalized( const YieldProcessorNormalizationInfo &normalizationInfo, unsigned int spinIteration) { - // normalizationInfo.optimalMaxNormalizedYieldsPerSpinIteration cannot exceed the value below based on calculations done in - // InitializeYieldProcessorNormalized() - const unsigned int MaxOptimalMaxNormalizedYieldsPerSpinIteration = - NsPerOptimalMaxSpinIterationDuration * 3 / (MinNsPerNormalizedYield * 2) + 1; - _ASSERTE(normalizationInfo.optimalMaxNormalizedYieldsPerSpinIteration <= MaxOptimalMaxNormalizedYieldsPerSpinIteration); - - // This shift value should be adjusted based on the asserted condition below + // This shift value should be adjusted based on the asserted conditions below const UINT8 MaxShift = 3; - static_assert_no_msg(((unsigned int)1 << (MaxShift + 1)) >= MaxOptimalMaxNormalizedYieldsPerSpinIteration); + static_assert_no_msg( + ((unsigned int)1 << MaxShift) <= YieldProcessorNormalization::MaxOptimalMaxNormalizedYieldsPerSpinIteration); + static_assert_no_msg( + ((unsigned int)1 << (MaxShift + 1)) > YieldProcessorNormalization::MaxOptimalMaxNormalizedYieldsPerSpinIteration); unsigned int n; if (spinIteration <= MaxShift && @@ -219,3 +290,6 @@ FORCEINLINE void YieldProcessorWithBackOffNormalized( System_YieldProcessor(); } while (--n != 0); } + +#undef DISABLE_CONSTRUCT_COPY +#undef DISABLE_COPY diff --git a/src/coreclr/utilcode/yieldprocessornormalized.cpp b/src/coreclr/utilcode/yieldprocessornormalized.cpp index 4242f82792b4..020d8d7cc79e 100644 --- a/src/coreclr/utilcode/yieldprocessornormalized.cpp +++ b/src/coreclr/utilcode/yieldprocessornormalized.cpp @@ -2,8 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "stdafx.h" +#include "yieldprocessornormalized.h" -// Defaults are for when InitializeYieldProcessorNormalized has not yet been called or when no measurement is done, and are -// tuned for Skylake processors -unsigned int g_yieldsPerNormalizedYield = 1; // current value is for Skylake processors, this is expected to be ~8 for pre-Skylake -unsigned int g_optimalMaxNormalizedYieldsPerSpinIteration = 7; +bool YieldProcessorNormalization::s_isMeasurementScheduled; + +// Defaults are for when normalization has not yet been done +unsigned int YieldProcessorNormalization::s_yieldsPerNormalizedYield = 1; +unsigned int YieldProcessorNormalization::s_optimalMaxNormalizedYieldsPerSpinIteration = + (unsigned int) + ( + (double)YieldProcessorNormalization::TargetMaxNsPerSpinIteration / + YieldProcessorNormalization::TargetNsPerNormalizedYield + + 0.5 + ); diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 9c2cb3df0b7e..f31e5a3ca12a 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -136,7 +136,6 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON versionresilienthashcode.cpp virtualcallstub.cpp win32threadpool.cpp - yieldprocessornormalized.cpp zapsig.cpp ) @@ -389,6 +388,7 @@ set(VM_SOURCES_WKS threadsuspend.cpp typeparse.cpp weakreferencenative.cpp + yieldprocessornormalized.cpp ${VM_SOURCES_GDBJIT} ) diff --git a/src/coreclr/vm/ClrEtwAll.man b/src/coreclr/vm/ClrEtwAll.man index 45895f16fce4..0eed049c17ba 100644 --- a/src/coreclr/vm/ClrEtwAll.man +++ b/src/coreclr/vm/ClrEtwAll.man @@ -438,7 +438,13 @@ - + + + + + @@ -2916,6 +2922,19 @@ + + @@ -3313,6 +3332,10 @@ keywords ="ThreadingKeyword" opcode="Wait" task="ThreadPoolWorkerThread" symbol="ThreadPoolWorkerThreadWait" message="$(string.RuntimePublisher.ThreadPoolWorkerThreadEventMessage)"/> + + + @@ -8334,6 +8358,7 @@ + diff --git a/src/coreclr/vm/ClrEtwAllMeta.lst b/src/coreclr/vm/ClrEtwAllMeta.lst index 4ac4fe405d9d..9c5738ef43db 100644 --- a/src/coreclr/vm/ClrEtwAllMeta.lst +++ b/src/coreclr/vm/ClrEtwAllMeta.lst @@ -134,9 +134,9 @@ nomac:GarbageCollection:::GCJoin_V2 nostack:Type:::BulkType -################### -# Threadpool events -################### +################################# +# Threading and Threadpool events +################################# nomac:WorkerThreadCreation:::WorkerThreadCreate noclrinstanceid:WorkerThreadCreation:::WorkerThreadCreate nomac:WorkerThreadCreation:::WorkerThreadTerminate @@ -170,6 +170,8 @@ nomac:ThreadPoolWorkerThreadAdjustment:::ThreadPoolWorkerThreadAdjustmentSample nostack:ThreadPoolWorkerThreadAdjustment:::ThreadPoolWorkerThreadAdjustmentSample nomac:ThreadPoolWorkerThreadAdjustment:::ThreadPoolWorkerThreadAdjustmentAdjustment nostack:ThreadPoolWorkerThreadAdjustment:::ThreadPoolWorkerThreadAdjustmentAdjustment +nomac:YieldProcessorMeasurement:::YieldProcessorMeasurement +nostack:YieldProcessorMeasurement:::YieldProcessorMeasurement ################## # Exception events diff --git a/src/coreclr/vm/comsynchronizable.cpp b/src/coreclr/vm/comsynchronizable.cpp index 39f00d067419..15a33c711e7a 100644 --- a/src/coreclr/vm/comsynchronizable.cpp +++ b/src/coreclr/vm/comsynchronizable.cpp @@ -1089,22 +1089,13 @@ FCIMPL1(void, ThreadNative::SetIsThreadpoolThread, ThreadBaseObject* thread) } FCIMPLEND -INT32 QCALLTYPE ThreadNative::GetOptimalMaxSpinWaitsPerSpinIteration() +FCIMPL0(INT32, ThreadNative::GetOptimalMaxSpinWaitsPerSpinIteration) { - QCALL_CONTRACT; - - INT32 optimalMaxNormalizedYieldsPerSpinIteration; - - BEGIN_QCALL; - - // RuntimeThread calls this function only once lazily and caches the result, so ensure initialization - EnsureYieldProcessorNormalizedInitialized(); - optimalMaxNormalizedYieldsPerSpinIteration = g_optimalMaxNormalizedYieldsPerSpinIteration; - - END_QCALL; + FCALL_CONTRACT; - return optimalMaxNormalizedYieldsPerSpinIteration; + return (INT32)YieldProcessorNormalization::GetOptimalMaxNormalizedYieldsPerSpinIteration(); } +FCIMPLEND FCIMPL1(void, ThreadNative::SpinWait, int iterations) { diff --git a/src/coreclr/vm/comsynchronizable.h b/src/coreclr/vm/comsynchronizable.h index e9968201b8bc..cfab18d90107 100644 --- a/src/coreclr/vm/comsynchronizable.h +++ b/src/coreclr/vm/comsynchronizable.h @@ -86,7 +86,7 @@ friend class ThreadBaseObject; UINT64 QCALLTYPE GetProcessDefaultStackSize(); static FCDECL1(INT32, GetManagedThreadId, ThreadBaseObject* th); - static INT32 QCALLTYPE GetOptimalMaxSpinWaitsPerSpinIteration(); + static FCDECL0(INT32, GetOptimalMaxSpinWaitsPerSpinIteration); static FCDECL1(void, SpinWait, int iterations); static BOOL QCALLTYPE YieldThread(); static FCDECL0(Object*, GetCurrentThread); diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index f77dc75c80b5..ea3f65d72917 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -602,7 +602,7 @@ FCFuncStart(gThreadFuncs) #endif // FEATURE_COMINTEROP FCFuncElement("Interrupt", ThreadNative::Interrupt) FCFuncElement("Join", ThreadNative::Join) - QCFuncElement("GetOptimalMaxSpinWaitsPerSpinIterationInternal", ThreadNative::GetOptimalMaxSpinWaitsPerSpinIteration) + FCFuncElement("get_OptimalMaxSpinWaitsPerSpinIteration", ThreadNative::GetOptimalMaxSpinWaitsPerSpinIteration) FCFuncElement("GetCurrentProcessorNumber", ThreadNative::GetCurrentProcessorNumber) FCFuncEnd() diff --git a/src/coreclr/vm/eventtrace.cpp b/src/coreclr/vm/eventtrace.cpp index ac7be2a94398..aded74deda61 100644 --- a/src/coreclr/vm/eventtrace.cpp +++ b/src/coreclr/vm/eventtrace.cpp @@ -4417,6 +4417,12 @@ VOID EtwCallbackCommon( { ETW::TypeSystemLog::OnKeywordsChanged(); } + + if (g_fEEStarted && !g_fEEShutDown) + { + // Emit the YieldProcessor measured values at the beginning of the trace + YieldProcessorNormalization::FireMeasurementEvents(); + } } // Individual callbacks for each EventPipe provider. diff --git a/src/coreclr/vm/finalizerthread.cpp b/src/coreclr/vm/finalizerthread.cpp index 1e4dbf913c89..e8370315e666 100644 --- a/src/coreclr/vm/finalizerthread.cpp +++ b/src/coreclr/vm/finalizerthread.cpp @@ -379,11 +379,6 @@ DWORD WINAPI FinalizerThread::FinalizerThreadStart(void *args) { GetFinalizerThread()->SetBackground(TRUE); - { - GCX_PREEMP(); - EnsureYieldProcessorNormalizedInitialized(); - } - while (!fQuitFinalizer) { // This will apply any policy for swallowing exceptions during normal diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 2c55f8770b01..c6485b86d59c 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -1150,8 +1150,6 @@ void InitThreadManager() } CONTRACTL_END; - InitializeYieldProcessorNormalizedCrst(); - // All patched helpers should fit into one page. // If you hit this assert on retail build, there is most likely problem with BBT script. _ASSERTE_ALL_BUILDS("clr/src/VM/threads.cpp", (BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart > (ptrdiff_t)0); @@ -7194,6 +7192,7 @@ BOOL Thread::HaveExtraWorkForFinalizer() || Thread::CleanupNeededForFinalizedThread() || (m_DetachCount > 0) || SystemDomain::System()->RequireAppDomainCleanup() + || YieldProcessorNormalization::IsMeasurementScheduled() || ThreadStore::s_pThreadStore->ShouldTriggerGCForDeadThreads(); } @@ -7240,6 +7239,12 @@ void Thread::DoExtraWorkForFinalizer() // If there were any TimerInfos waiting to be released, they'll get flushed now ThreadpoolMgr::FlushQueueOfTimerInfos(); + if (YieldProcessorNormalization::IsMeasurementScheduled()) + { + GCX_PREEMP(); + YieldProcessorNormalization::PerformMeasurement(); + } + ThreadStore::s_pThreadStore->TriggerGCForDeadThreadsIfNecessary(); } diff --git a/src/coreclr/vm/yieldprocessornormalized.cpp b/src/coreclr/vm/yieldprocessornormalized.cpp index 91547923310f..2c51e73b678d 100644 --- a/src/coreclr/vm/yieldprocessornormalized.cpp +++ b/src/coreclr/vm/yieldprocessornormalized.cpp @@ -2,17 +2,33 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "common.h" +#include "yieldprocessornormalized.h" -static Volatile s_isYieldProcessorNormalizedInitialized = false; -static CrstStatic s_initializeYieldProcessorNormalizedCrst; +#ifndef CROSSGEN_COMPILE -void InitializeYieldProcessorNormalizedCrst() +#include "finalizerthread.h" + +enum class NormalizationState : UINT8 { - WRAPPER_NO_CONTRACT; - s_initializeYieldProcessorNormalizedCrst.Init(CrstLeafLock); -} + Uninitialized, + Initialized, + Failed +}; + +static const int NsPerYieldMeasurementCount = 8; +static const unsigned int MeasurementPeriodMs = 4000; + +static const unsigned int NsPerS = 1000 * 1000 * 1000; + +static NormalizationState s_normalizationState = NormalizationState::Uninitialized; +static unsigned int s_previousNormalizationTimeMs; + +static UINT64 s_performanceCounterTicksPerS; +static double s_nsPerYieldMeasurements[NsPerYieldMeasurementCount]; +static int s_nextMeasurementIndex; +static double s_establishedNsPerYield = YieldProcessorNormalization::TargetNsPerNormalizedYield; -static void InitializeYieldProcessorNormalized() +static unsigned int DetermineMeasureDurationUs() { CONTRACTL { @@ -22,92 +38,271 @@ static void InitializeYieldProcessorNormalized() } CONTRACTL_END; - CrstHolder lock(&s_initializeYieldProcessorNormalizedCrst); + _ASSERTE(s_normalizationState != NormalizationState::Failed); - if (s_isYieldProcessorNormalizedInitialized) + // On some systems, querying the high performance counter has relatively significant overhead. Increase the measure duration + // if the overhead seems high relative to the measure duration. + unsigned int measureDurationUs = 1; + LARGE_INTEGER li; + QueryPerformanceCounter(&li); + UINT64 startTicks = li.QuadPart; + QueryPerformanceCounter(&li); + UINT64 elapsedTicks = li.QuadPart - startTicks; + if (elapsedTicks >= s_performanceCounterTicksPerS * measureDurationUs * (1000 / 4) / NsPerS) // elapsed >= 1/4 of the measure duration { - return; + measureDurationUs *= 4; + } + return measureDurationUs; +} + +static double MeasureNsPerYield(unsigned int measureDurationUs) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; } + CONTRACTL_END; - // Intel pre-Skylake processor: measured typically 14-17 cycles per yield - // Intel post-Skylake processor: measured typically 125-150 cycles per yield - const int MeasureDurationMs = 10; - const int NsPerSecond = 1000 * 1000 * 1000; + _ASSERTE(s_normalizationState != NormalizationState::Failed); + + int yieldCount = (int)(measureDurationUs * 1000 / s_establishedNsPerYield) + 1; + UINT64 ticksPerS = s_performanceCounterTicksPerS; + UINT64 measureDurationTicks = ticksPerS * measureDurationUs / (1000 * 1000); LARGE_INTEGER li; - if (!QueryPerformanceFrequency(&li) || (ULONGLONG)li.QuadPart < 1000 / MeasureDurationMs) + QueryPerformanceCounter(&li); + UINT64 startTicks = li.QuadPart; + + for (int i = 0; i < yieldCount; ++i) { - // High precision clock not available or clock resolution is too low, resort to defaults - s_isYieldProcessorNormalizedInitialized = true; - return; + System_YieldProcessor(); } - ULONGLONG ticksPerSecond = li.QuadPart; - // Measure the nanosecond delay per yield - ULONGLONG measureDurationTicks = ticksPerSecond / (1000 / MeasureDurationMs); - unsigned int yieldCount = 0; QueryPerformanceCounter(&li); - ULONGLONG startTicks = li.QuadPart; - ULONGLONG elapsedTicks; - do - { - // On some systems, querying the high performance counter has relatively significant overhead. Do enough yields to mask - // the timing overhead. Assuming one yield has a delay of MinNsPerNormalizedYield, 1000 yields would have a delay in the - // low microsecond range. - for (int i = 0; i < 1000; ++i) + UINT64 elapsedTicks = li.QuadPart - startTicks; + while (elapsedTicks < measureDurationTicks) + { + int nextYieldCount = + Max(4, + elapsedTicks == 0 + ? yieldCount / 4 + : (int)(yieldCount * (measureDurationTicks - elapsedTicks) / (double)elapsedTicks) + 1); + for (int i = 0; i < nextYieldCount; ++i) { System_YieldProcessor(); } - yieldCount += 1000; QueryPerformanceCounter(&li); - ULONGLONG nowTicks = li.QuadPart; - elapsedTicks = nowTicks - startTicks; - } while (elapsedTicks < measureDurationTicks); - double nsPerYield = (double)elapsedTicks * NsPerSecond / ((double)yieldCount * ticksPerSecond); - if (nsPerYield < 1) + elapsedTicks = li.QuadPart - startTicks; + yieldCount += nextYieldCount; + } + + // Limit the minimum to a reasonable value considering that on some systems a yield may be implemented as a no-op + const double MinNsPerYield = 0.1; + + // Measured values higher than this don't affect values calculated for normalization, and it's very unlikely for a yield to + // really take this long. Limit the maximum to keep the recorded values reasonable. + const double MaxNsPerYield = YieldProcessorNormalization::TargetMaxNsPerSpinIteration / 1.5 + 1; + + return Max(MinNsPerYield, Min((double)elapsedTicks * NsPerS / ((double)yieldCount * ticksPerS), MaxNsPerYield)); +} + +void YieldProcessorNormalization::PerformMeasurement() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + _ASSERTE(s_isMeasurementScheduled); + + double latestNsPerYield; + if (s_normalizationState == NormalizationState::Initialized) { - nsPerYield = 1; + if (GetTickCount() - s_previousNormalizationTimeMs < MeasurementPeriodMs) + { + return; + } + + int nextMeasurementIndex = s_nextMeasurementIndex; + latestNsPerYield = MeasureNsPerYield(DetermineMeasureDurationUs()); + AtomicStore(&s_nsPerYieldMeasurements[nextMeasurementIndex], latestNsPerYield); + if (++nextMeasurementIndex >= NsPerYieldMeasurementCount) + { + nextMeasurementIndex = 0; + } + s_nextMeasurementIndex = nextMeasurementIndex; } + else if (s_normalizationState == NormalizationState::Uninitialized) + { + LARGE_INTEGER li; + if (!QueryPerformanceFrequency(&li) || li.QuadPart < 1000 * 1000) + { + // High precision clock not available or clock resolution is too low, resort to defaults + s_normalizationState = NormalizationState::Failed; + return; + } + s_performanceCounterTicksPerS = li.QuadPart; + + unsigned int measureDurationUs = DetermineMeasureDurationUs(); + for (int i = 0; i < NsPerYieldMeasurementCount; ++i) + { + latestNsPerYield = MeasureNsPerYield(measureDurationUs); + AtomicStore(&s_nsPerYieldMeasurements[i], latestNsPerYield); + if (i == 0 || latestNsPerYield < s_establishedNsPerYield) + { + AtomicStore(&s_establishedNsPerYield, latestNsPerYield); + } - // Calculate the number of yields required to span the duration of a normalized yield. Since nsPerYield is at least 1, this - // value is naturally limited to MinNsPerNormalizedYield. - int yieldsPerNormalizedYield = (int)(MinNsPerNormalizedYield / nsPerYield + 0.5); - if (yieldsPerNormalizedYield < 1) + if (i < NsPerYieldMeasurementCount - 1) + { + FireEtwYieldProcessorMeasurement(GetClrInstanceId(), latestNsPerYield, s_establishedNsPerYield); + } + } + } + else { - yieldsPerNormalizedYield = 1; + _ASSERTE(s_normalizationState == NormalizationState::Failed); + return; } - _ASSERTE(yieldsPerNormalizedYield <= (int)MinNsPerNormalizedYield); + + double establishedNsPerYield = s_nsPerYieldMeasurements[0]; + for (int i = 1; i < NsPerYieldMeasurementCount; ++i) + { + double nsPerYield = s_nsPerYieldMeasurements[i]; + if (nsPerYield < establishedNsPerYield) + { + establishedNsPerYield = nsPerYield; + } + } + if (establishedNsPerYield != s_establishedNsPerYield) + { + AtomicStore(&s_establishedNsPerYield, establishedNsPerYield); + } + + FireEtwYieldProcessorMeasurement(GetClrInstanceId(), latestNsPerYield, s_establishedNsPerYield); + + // Calculate the number of yields required to span the duration of a normalized yield + unsigned int yieldsPerNormalizedYield = Max(1u, (unsigned int)(TargetNsPerNormalizedYield / establishedNsPerYield + 0.5)); + _ASSERTE(yieldsPerNormalizedYield <= MaxYieldsPerNormalizedYield); + s_yieldsPerNormalizedYield = yieldsPerNormalizedYield; // Calculate the maximum number of yields that would be optimal for a late spin iteration. Typically, we would not want to // spend excessive amounts of time (thousands of cycles) doing only YieldProcessor, as SwitchToThread/Sleep would do a // better job of allowing other work to run. - int optimalMaxNormalizedYieldsPerSpinIteration = - (int)(NsPerOptimalMaxSpinIterationDuration / (yieldsPerNormalizedYield * nsPerYield) + 0.5); - if (optimalMaxNormalizedYieldsPerSpinIteration < 1) + s_optimalMaxNormalizedYieldsPerSpinIteration = + Max(1u, (unsigned int)(TargetMaxNsPerSpinIteration / (yieldsPerNormalizedYield * establishedNsPerYield) + 0.5)); + _ASSERTE(s_optimalMaxNormalizedYieldsPerSpinIteration <= MaxOptimalMaxNormalizedYieldsPerSpinIteration); + + GCHeapUtilities::GetGCHeap()->SetYieldProcessorScalingFactor((float)yieldsPerNormalizedYield); + + s_previousNormalizationTimeMs = GetTickCount(); + s_normalizationState = NormalizationState::Initialized; + s_isMeasurementScheduled = false; +} + +#endif // !CROSSGEN_COMPILE + +void YieldProcessorNormalization::ScheduleMeasurementIfNecessary() +{ + CONTRACTL { - optimalMaxNormalizedYieldsPerSpinIteration = 1; + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; } + CONTRACTL_END; - g_yieldsPerNormalizedYield = yieldsPerNormalizedYield; - g_optimalMaxNormalizedYieldsPerSpinIteration = optimalMaxNormalizedYieldsPerSpinIteration; - s_isYieldProcessorNormalizedInitialized = true; +#ifndef CROSSGEN_COMPILE + NormalizationState normalizationState = VolatileLoadWithoutBarrier(&s_normalizationState); + if (normalizationState == NormalizationState::Initialized) + { + if (GetTickCount() - s_previousNormalizationTimeMs < MeasurementPeriodMs) + { + return; + } + } + else if (normalizationState == NormalizationState::Uninitialized) + { + } + else + { + _ASSERTE(normalizationState == NormalizationState::Failed); + return; + } - GCHeapUtilities::GetGCHeap()->SetYieldProcessorScalingFactor((float)yieldsPerNormalizedYield); + // !g_fEEStarted is required for FinalizerThread::EnableFinalization() below + if (s_isMeasurementScheduled || !g_fEEStarted) + { + return; + } + + s_isMeasurementScheduled = true; + FinalizerThread::EnableFinalization(); +#endif // !CROSSGEN_COMPILE } -void EnsureYieldProcessorNormalizedInitialized() +#ifndef CROSSGEN_COMPILE + +void YieldProcessorNormalization::FireMeasurementEvents() { CONTRACTL { NOTHROW; GC_NOTRIGGER; - MODE_PREEMPTIVE; + MODE_ANY; } CONTRACTL_END; - if (!s_isYieldProcessorNormalizedInitialized) + if (!EventEnabledYieldProcessorMeasurement()) { - InitializeYieldProcessorNormalized(); + return; } + + // This function may be called at any time to fire events about recorded measurements. There is no synchronization for the + // recorded information, so try to enumerate the array with some care. + double establishedNsPerYield = AtomicLoad(&s_establishedNsPerYield); + int nextIndex = VolatileLoadWithoutBarrier(&s_nextMeasurementIndex); + for (int i = 0; i < NsPerYieldMeasurementCount; ++i) + { + double nsPerYield = AtomicLoad(&s_nsPerYieldMeasurements[nextIndex]); + if (nsPerYield != 0) // the array may not be fully initialized yet + { + FireEtwYieldProcessorMeasurement(GetClrInstanceId(), nsPerYield, establishedNsPerYield); + } + + if (++nextIndex >= NsPerYieldMeasurementCount) + { + nextIndex = 0; + } + } +} + +double YieldProcessorNormalization::AtomicLoad(double *valueRef) +{ + WRAPPER_NO_CONTRACT; + +#ifdef TARGET_64BIT + return VolatileLoadWithoutBarrier(valueRef); +#else + return InterlockedCompareExchangeT(valueRef, 0.0, 0.0); +#endif } + +void YieldProcessorNormalization::AtomicStore(double *valueRef, double value) +{ + WRAPPER_NO_CONTRACT; + +#ifdef TARGET_64BIT + *valueRef = value; +#else + InterlockedExchangeT(valueRef, value); +#endif +} + +#endif // !CROSSGEN_COMPILE diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelSpinWaiter.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelSpinWaiter.cs index e1c0766b3f0d..8e8198de392b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelSpinWaiter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelSpinWaiter.cs @@ -71,10 +71,6 @@ public static void Wait(int spinIndex, int sleep0Threshold, int processorCount) // the equivalent of YieldProcessor(), as that that point SwitchToThread/Sleep(0) are more likely to be able to // allow other useful work to run. Long YieldProcessor() loops can help to reduce contention, but Sleep(1) is // usually better for that. - // - // Thread.OptimalMaxSpinWaitsPerSpinIteration: - // - See Thread::InitializeYieldProcessorNormalized(), which describes and calculates this value. - // int n = Thread.OptimalMaxSpinWaitsPerSpinIteration; if (spinIndex <= 30 && (1 << spinIndex) < n) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs index b45cc7d5d380..66b73f8be025 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs @@ -225,10 +225,6 @@ private void SpinOnceCore(int sleep1Threshold) // the equivalent of YieldProcessor(), as at that point SwitchToThread/Sleep(0) are more likely to be able to // allow other useful work to run. Long YieldProcessor() loops can help to reduce contention, but Sleep(1) is // usually better for that. - // - // Thread.OptimalMaxSpinWaitsPerSpinIteration: - // - See Thread::InitializeYieldProcessorNormalized(), which describes and calculates this value. - // int n = Thread.OptimalMaxSpinWaitsPerSpinIteration; if (_count <= 30 && (1 << _count) < n) { From 1904cfcbe436e73b3aef7f83bc36ee4c262b3196 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Tue, 13 Jul 2021 09:00:40 -0700 Subject: [PATCH 072/133] Rename TextMapPropagator to DistributedContextPropagator (#55539) --- ...em.Diagnostics.DiagnosticSourceActivity.cs | 10 +-- ...System.Diagnostics.DiagnosticSource.csproj | 2 +- ...tor.cs => DistributedContextPropagator.cs} | 18 ++--- .../System/Diagnostics/LegacyPropagator.cs | 4 +- .../System/Diagnostics/NoOutputPropagator.cs | 4 +- .../Diagnostics/PassThroughPropagator.cs | 4 +- .../tests/PropagatorTests.cs | 74 +++++++++---------- 7 files changed, 58 insertions(+), 58 deletions(-) rename src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/{TextMapPropagator.cs => DistributedContextPropagator.cs} (89%) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs index 7e50f732e421..68fa62b8be9b 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs @@ -254,7 +254,7 @@ public sealed class ActivityListener : IDisposable public System.Diagnostics.SampleActivity? Sample { get { throw null; } set { throw null; } } public void Dispose() { throw null; } } - public abstract class TextMapPropagator + public abstract class DistributedContextPropagator { public delegate void PropagatorGetterCallback(object? carrier, string fieldName, out string? fieldValue, out System.Collections.Generic.IEnumerable? fieldValues); public delegate void PropagatorSetterCallback(object? carrier, string fieldName, string fieldValue); @@ -262,10 +262,10 @@ public abstract class TextMapPropagator public abstract void Inject(Activity? activity, object? carrier, PropagatorSetterCallback? setter); public abstract void ExtractTraceIdAndState(object? carrier, PropagatorGetterCallback? getter, out string? traceId, out string? traceState); public abstract System.Collections.Generic.IEnumerable>? ExtractBaggage(object? carrier, PropagatorGetterCallback? getter); - public static TextMapPropagator Current { get; set; } - public static TextMapPropagator CreateDefaultPropagator() { throw null; } - public static TextMapPropagator CreatePassThroughPropagator() { throw null; } - public static TextMapPropagator CreateNoOutputPropagator() { throw null; } + public static DistributedContextPropagator Current { get; set; } + public static DistributedContextPropagator CreateDefaultPropagator() { throw null; } + public static DistributedContextPropagator CreatePassThroughPropagator() { throw null; } + public static DistributedContextPropagator CreateNoOutputPropagator() { throw null; } } } diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj index e4a9cbd28d7f..dab222792b1b 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj @@ -39,11 +39,11 @@ + - diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/TextMapPropagator.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DistributedContextPropagator.cs similarity index 89% rename from src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/TextMapPropagator.cs rename to src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DistributedContextPropagator.cs index 0dab622a2983..9c54baf9c251 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/TextMapPropagator.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DistributedContextPropagator.cs @@ -9,13 +9,13 @@ namespace System.Diagnostics { /// - /// An implementation of TextMapPropagator determines if and how distributed context information is encoded and decoded as it traverses the network. + /// An implementation of DistributedContextPropagator determines if and how distributed context information is encoded and decoded as it traverses the network. /// The encoding can be transported over any network protocol that supports string key-value pairs. For example when using HTTP, each key value pair is an HTTP header. - /// TextMapPropagator inject values into and extracts values from carriers as string key/value pairs. + /// DistributedContextPropagator inject values into and extracts values from carriers as string key/value pairs. /// - public abstract class TextMapPropagator + public abstract class DistributedContextPropagator { - private static TextMapPropagator s_current = CreateDefaultPropagator(); + private static DistributedContextPropagator s_current = CreateDefaultPropagator(); /// /// The callback that is used in propagators' extract methods. The callback is invoked to lookup the value of a named field. @@ -38,7 +38,7 @@ public abstract class TextMapPropagator /// /// The set of field names this propagator is likely to read or write. /// - /// Returns list of fields that will be used by the TextMapPropagator. + /// Returns list of fields that will be used by the DistributedContextPropagator. public abstract IReadOnlyCollection Fields { get; } /// @@ -69,7 +69,7 @@ public abstract class TextMapPropagator /// /// Get or set the process wide propagator object which used as the current selected propagator. /// - public static TextMapPropagator Current + public static DistributedContextPropagator Current { get { @@ -91,18 +91,18 @@ public static TextMapPropagator Current /// "traceparent" of the identifiers which are formatted as W3C trace parent, "Request-Id" of the identifiers which are formatted as a hierarchical identifier. /// The returned propagator can inject the baggage key-value pair list with header name "Correlation-Context" and it can extract the baggage values mapped to header names "Correlation-Context" and "baggage". /// - public static TextMapPropagator CreateDefaultPropagator() => LegacyPropagator.Instance; + public static DistributedContextPropagator CreateDefaultPropagator() => LegacyPropagator.Instance; /// /// Returns a propagator which attempts to act transparently, emitting the same data on outbound network requests that was received on the in-bound request. /// When encoding the outbound message, this propagator uses information from the request's root Activity, ignoring any intermediate Activities that may have been created while processing the request. /// - public static TextMapPropagator CreatePassThroughPropagator() => PassThroughPropagator.Instance; + public static DistributedContextPropagator CreatePassThroughPropagator() => PassThroughPropagator.Instance; /// /// Returns a propagator which does not transmit any distributed context information in outbound network messages. /// - public static TextMapPropagator CreateNoOutputPropagator() => NoOutputPropagator.Instance; + public static DistributedContextPropagator CreateNoOutputPropagator() => NoOutputPropagator.Instance; // internal stuff diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LegacyPropagator.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LegacyPropagator.cs index a469dd5b56b6..9a3c909bb177 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LegacyPropagator.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LegacyPropagator.cs @@ -7,9 +7,9 @@ namespace System.Diagnostics { - internal sealed class LegacyPropagator : TextMapPropagator + internal sealed class LegacyPropagator : DistributedContextPropagator { - internal static TextMapPropagator Instance { get; } = new LegacyPropagator(); + internal static DistributedContextPropagator Instance { get; } = new LegacyPropagator(); public override IReadOnlyCollection Fields { get; } = new ReadOnlyCollection(new[] { TraceParent, RequestId, TraceState, Baggage, CorrelationContext }); diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/NoOutputPropagator.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/NoOutputPropagator.cs index f9d503a8f1c9..1655ef466e00 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/NoOutputPropagator.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/NoOutputPropagator.cs @@ -5,9 +5,9 @@ namespace System.Diagnostics { - internal sealed class NoOutputPropagator : TextMapPropagator + internal sealed class NoOutputPropagator : DistributedContextPropagator { - internal static TextMapPropagator Instance { get; } = new NoOutputPropagator(); + internal static DistributedContextPropagator Instance { get; } = new NoOutputPropagator(); public override IReadOnlyCollection Fields { get; } = LegacyPropagator.Instance.Fields; diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/PassThroughPropagator.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/PassThroughPropagator.cs index 01bf821a5cbc..12515555fcf4 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/PassThroughPropagator.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/PassThroughPropagator.cs @@ -5,9 +5,9 @@ namespace System.Diagnostics { - internal sealed class PassThroughPropagator : TextMapPropagator + internal sealed class PassThroughPropagator : DistributedContextPropagator { - internal static TextMapPropagator Instance { get; } = new PassThroughPropagator(); + internal static DistributedContextPropagator Instance { get; } = new PassThroughPropagator(); public override IReadOnlyCollection Fields { get; } = LegacyPropagator.Instance.Fields; diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/PropagatorTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/PropagatorTests.cs index a0ade495467d..2a914f62ad53 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/PropagatorTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/PropagatorTests.cs @@ -21,89 +21,89 @@ public class PropagatorTests public void TestAllPropagators() { RemoteExecutor.Invoke(() => { - Assert.NotNull(TextMapPropagator.Current); + Assert.NotNull(DistributedContextPropagator.Current); // // Default Propagator // - Assert.Same(TextMapPropagator.CreateDefaultPropagator(), TextMapPropagator.Current); + Assert.Same(DistributedContextPropagator.CreateDefaultPropagator(), DistributedContextPropagator.Current); TestDefaultPropagatorUsingW3CActivity( - TextMapPropagator.Current, + DistributedContextPropagator.Current, "Legacy1=true", new List>() { new KeyValuePair(" LegacyKey1 ", " LegacyValue1 ") }); TestDefaultPropagatorUsingHierarchicalActivity( - TextMapPropagator.Current, + DistributedContextPropagator.Current, "Legacy2=true", new List>() { new KeyValuePair("LegacyKey2", "LegacyValue2") }); - TestFields(TextMapPropagator.Current); + TestFields(DistributedContextPropagator.Current); // // NoOutput Propagator // - TextMapPropagator.Current = TextMapPropagator.CreateNoOutputPropagator(); - Assert.NotNull(TextMapPropagator.Current); + DistributedContextPropagator.Current = DistributedContextPropagator.CreateNoOutputPropagator(); + Assert.NotNull(DistributedContextPropagator.Current); TestNoOutputPropagatorUsingHierarchicalActivity( - TextMapPropagator.Current, + DistributedContextPropagator.Current, "ActivityState=1", new List>() { new KeyValuePair("B1", "V1"), new KeyValuePair(" B2 ", " V2 ")}); TestNoOutputPropagatorUsingHierarchicalActivity( - TextMapPropagator.Current, + DistributedContextPropagator.Current, "ActivityState=2", null); TestNoOutputPropagatorUsingW3CActivity( - TextMapPropagator.Current, + DistributedContextPropagator.Current, "ActivityState=1", new List>() { new KeyValuePair(" B3 ", " V3"), new KeyValuePair(" B4 ", " V4 "), new KeyValuePair("B5", "V5")}); TestNoOutputPropagatorUsingW3CActivity( - TextMapPropagator.Current, + DistributedContextPropagator.Current, "ActivityState=2", null); - TestFields(TextMapPropagator.Current); + TestFields(DistributedContextPropagator.Current); // // Pass Through Propagator // - TextMapPropagator.Current = TextMapPropagator.CreatePassThroughPropagator(); - Assert.NotNull(TextMapPropagator.Current); + DistributedContextPropagator.Current = DistributedContextPropagator.CreatePassThroughPropagator(); + Assert.NotNull(DistributedContextPropagator.Current); TestPassThroughPropagatorUsingHierarchicalActivityWithParentChain( - TextMapPropagator.Current, + DistributedContextPropagator.Current, "PassThrough=true", new List>() { new KeyValuePair("PassThroughKey1", "PassThroughValue1"), new KeyValuePair("PassThroughKey2", "PassThroughValue2")}); TestPassThroughPropagatorUsingHierarchicalActivityWithParentId( - TextMapPropagator.Current, + DistributedContextPropagator.Current, "PassThrough1=true", new List>() { new KeyValuePair("PassThroughKey3", "PassThroughValue3"), new KeyValuePair(" PassThroughKey4 ", " PassThroughValue4 ")}); TestPassThroughPropagatorUsingW3CActivity( - TextMapPropagator.Current, + DistributedContextPropagator.Current, "PassThrough2=1", new List>() { new KeyValuePair(" PassThroughKey4 ", " PassThroughValue4 ") }); - TestPassThroughPropagatorWithNullCurrent(TextMapPropagator.Current); + TestPassThroughPropagatorWithNullCurrent(DistributedContextPropagator.Current); - TestFields(TextMapPropagator.Current); + TestFields(DistributedContextPropagator.Current); // // Test Current // - Assert.Throws(() => TextMapPropagator.Current = null); + Assert.Throws(() => DistributedContextPropagator.Current = null); }).Dispose(); } - private void TestDefaultPropagatorUsingW3CActivity(TextMapPropagator propagator, string state, IEnumerable> baggage) + private void TestDefaultPropagatorUsingW3CActivity(DistributedContextPropagator propagator, string state, IEnumerable> baggage) { using Activity a = CreateW3CActivity("LegacyW3C1", "LegacyW3CState=1", baggage); using Activity b = CreateW3CActivity("LegacyW3C2", "LegacyW3CState=2", baggage); @@ -117,7 +117,7 @@ private void TestDefaultPropagatorUsingW3CActivity(TextMapPropagator propagator, TestDefaultPropagatorUsing(Activity.Current, propagator, state, baggage); } - private void TestDefaultPropagatorUsingHierarchicalActivity(TextMapPropagator propagator, string state, IEnumerable> baggage) + private void TestDefaultPropagatorUsingHierarchicalActivity(DistributedContextPropagator propagator, string state, IEnumerable> baggage) { using Activity a = CreateHierarchicalActivity("LegacyHierarchical1", null, "LegacyHierarchicalState=1", baggage); using Activity b = CreateHierarchicalActivity("LegacyHierarchical2", null, "LegacyHierarchicalState=2", baggage); @@ -131,7 +131,7 @@ private void TestDefaultPropagatorUsingHierarchicalActivity(TextMapPropagator pr TestDefaultPropagatorUsing(Activity.Current, propagator, state, baggage); } - private void TestDefaultPropagatorUsing(Activity a, TextMapPropagator propagator, string state, IEnumerable> baggage) + private void TestDefaultPropagatorUsing(Activity a, DistributedContextPropagator propagator, string state, IEnumerable> baggage) { // Test with non-current propagator.Inject(a, null, (object carrier, string fieldName, string value) => @@ -167,7 +167,7 @@ private void TestDefaultPropagatorUsing(Activity a, TextMapPropagator propagator TestBaggageExtraction(propagator, a); } - private void TestNoOutputPropagatorUsingHierarchicalActivity(TextMapPropagator propagator, string state, IEnumerable> baggage) + private void TestNoOutputPropagatorUsingHierarchicalActivity(DistributedContextPropagator propagator, string state, IEnumerable> baggage) { using Activity a = CreateHierarchicalActivity("NoOutputHierarchical", null, state, baggage); @@ -181,7 +181,7 @@ private void TestNoOutputPropagatorUsingHierarchicalActivity(TextMapPropagator p TestBaggageExtraction(propagator, a); } - private void TestNoOutputPropagatorUsingW3CActivity(TextMapPropagator propagator, string state, IEnumerable> baggage) + private void TestNoOutputPropagatorUsingW3CActivity(DistributedContextPropagator propagator, string state, IEnumerable> baggage) { using Activity a = CreateW3CActivity("NoOutputW3C", state, baggage); @@ -195,7 +195,7 @@ private void TestNoOutputPropagatorUsingW3CActivity(TextMapPropagator propagator TestBaggageExtraction(propagator, a); } - private void TestPassThroughPropagatorUsingHierarchicalActivityWithParentChain(TextMapPropagator propagator, string state, IEnumerable> baggage) + private void TestPassThroughPropagatorUsingHierarchicalActivityWithParentChain(DistributedContextPropagator propagator, string state, IEnumerable> baggage) { using Activity a = CreateHierarchicalActivity("PassThrough", null, state, baggage); using Activity b = CreateHierarchicalActivity("PassThroughChild1", null, state + "1", new List>() { new KeyValuePair("Child1Key", "Child1Value") } ); @@ -239,7 +239,7 @@ private void TestPassThroughPropagatorUsingHierarchicalActivityWithParentChain(T TestBaggageExtraction(propagator, c); } - private void TestPassThroughPropagatorUsingHierarchicalActivityWithParentId(TextMapPropagator propagator, string state, IEnumerable> baggage) + private void TestPassThroughPropagatorUsingHierarchicalActivityWithParentId(DistributedContextPropagator propagator, string state, IEnumerable> baggage) { using Activity a = CreateHierarchicalActivity("PassThrough", "Parent1", state, baggage); using Activity b = CreateHierarchicalActivity("PassThroughChild1", "Parent2", state + "1", new List>() { new KeyValuePair("Child1Key", "Child1Value") } ); @@ -283,7 +283,7 @@ private void TestPassThroughPropagatorUsingHierarchicalActivityWithParentId(Text TestBaggageExtraction(propagator, c); } - private void TestPassThroughPropagatorUsingW3CActivity(TextMapPropagator propagator, string state, IEnumerable> baggage) + private void TestPassThroughPropagatorUsingW3CActivity(DistributedContextPropagator propagator, string state, IEnumerable> baggage) { using Activity a = CreateW3CActivity("PassThroughW3C", "PassThroughW3CState=1", baggage); @@ -314,7 +314,7 @@ private void TestPassThroughPropagatorUsingW3CActivity(TextMapPropagator propaga TestBaggageExtraction(propagator, a); } - private void TestPassThroughPropagatorWithNullCurrent(TextMapPropagator propagator) + private void TestPassThroughPropagatorWithNullCurrent(DistributedContextPropagator propagator) { Activity.Current = null; @@ -337,7 +337,7 @@ private void TestPassThroughPropagatorWithNullCurrent(TextMapPropagator propagat }); } - private void TestDefaultExtraction(TextMapPropagator propagator, Activity a) + private void TestDefaultExtraction(DistributedContextPropagator propagator, Activity a) { bool traceParentEncountered = false; @@ -388,7 +388,7 @@ private void TestDefaultExtraction(TextMapPropagator propagator, Activity a) Assert.Equal(a.TraceStateString, traceState); } - private void TestBaggageExtraction(TextMapPropagator propagator, Activity a) + private void TestBaggageExtraction(DistributedContextPropagator propagator, Activity a) { bool baggageEncountered = false; @@ -425,7 +425,7 @@ private void TestBaggageExtraction(TextMapPropagator propagator, Activity a) Assert.Equal(GetFormattedBaggage(a.Baggage, false, true), GetFormattedBaggage(b, true)); } - private void TestFields(TextMapPropagator propagator) + private void TestFields(DistributedContextPropagator propagator) { Assert.True(propagator.Fields.Contains(TraceParent)); Assert.True(propagator.Fields.Contains(RequestId)); @@ -532,14 +532,14 @@ public void TestCustomPropagator() { RemoteExecutor.Invoke(() => { - TextMapPropagator.Current = new CustomPropagator(); + DistributedContextPropagator.Current = new CustomPropagator(); using Activity a = CreateW3CActivity("CustomW3C1", "CustomW3CState=1", new List>() { new KeyValuePair(" CustomKey1 ", " CustomValue1 ") }); string traceParent = "x-" + a.Id ; string traceState = "x-" + a.TraceStateString; string baggageString = "x=y, " + GetFormattedBaggage(a.Baggage); - TextMapPropagator.Current.Inject(a, null, (object carrier, string fieldName, string value) => + DistributedContextPropagator.Current.Inject(a, null, (object carrier, string fieldName, string value) => { if (fieldName == CustomPropagator.XTraceParent) { @@ -562,7 +562,7 @@ public void TestCustomPropagator() Assert.False(true, $"Encountered wrong header name '{fieldName}' in the Custom Propagator"); }); - TextMapPropagator.Current.ExtractTraceIdAndState(null, (object carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => + DistributedContextPropagator.Current.ExtractTraceIdAndState(null, (object carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => { fieldValues = null; fieldValue = null; @@ -585,7 +585,7 @@ public void TestCustomPropagator() Assert.Equal(traceParent, traceId); Assert.Equal(traceState, state); - IEnumerable>? b = TextMapPropagator.Current.ExtractBaggage(null, (object carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => + IEnumerable>? b = DistributedContextPropagator.Current.ExtractBaggage(null, (object carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => { Assert.Null(carrier); fieldValue = null; @@ -607,7 +607,7 @@ public void TestCustomPropagator() }).Dispose(); } - internal class CustomPropagator : TextMapPropagator + internal class CustomPropagator : DistributedContextPropagator { internal const string XTraceParent = "x-traceparent"; internal const string XTraceState = "x-tracestate"; From d2e9f42c609453a1104afa1a006242ba69b524cf Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 13 Jul 2021 12:05:31 -0400 Subject: [PATCH 073/133] Augment tests for FileMode.Append (#55513) To validate that seeking and writing are valid in the region of the file since its initial length. --- .../tests/FileStream/Position.cs | 4 +++ .../tests/FileStream/Seek.cs | 3 ++ .../tests/FileStream/SetLength.cs | 6 ++++ .../tests/FileStream/ctor_str_fm.cs | 34 +++++++++++++++++-- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/Position.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/Position.cs index 8157f6cc91ef..32817dcee266 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/Position.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/Position.cs @@ -26,6 +26,10 @@ public void SetPositionAppendModify() fs.Position = length + 1; Assert.Equal(length + 1, fs.Position); + + fs.Write(TestBuffer); + fs.Position = length + 1; + Assert.Equal(length + 1, fs.Position); } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/Seek.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/Seek.cs index 6971eb49927e..963a1fddff57 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/Seek.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/Seek.cs @@ -32,6 +32,9 @@ public void SeekAppendModifyThrows() Assert.Equal(length, fs.Position); Assert.Throws(() => fs.Seek(-length, SeekOrigin.End)); Assert.Equal(length, fs.Position); + + fs.Write(TestBuffer); + Assert.Equal(length, fs.Seek(length, SeekOrigin.Begin)); } } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/SetLength.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/SetLength.cs index 67944a457716..3a3f547d070e 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/SetLength.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/SetLength.cs @@ -23,6 +23,12 @@ public void SetLengthAppendModifyThrows() Assert.Equal(length, fs.Length); Assert.Throws(() => fs.SetLength(0)); Assert.Equal(length, fs.Length); + + fs.Write(TestBuffer); + Assert.Equal(length + TestBuffer.Length, fs.Length); + + fs.SetLength(length); + Assert.Equal(length, fs.Length); } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm.cs index 60e3cd49ea71..47e3b4923731 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text; using Xunit; namespace System.IO.Tests @@ -213,11 +214,23 @@ public void FileModeTruncateExisting(string streamSpecifier) [Theory, MemberData(nameof(StreamSpecifiers))] public virtual void FileModeAppend(string streamSpecifier) { - using (FileStream fs = CreateFileStream(GetTestFilePath() + streamSpecifier, FileMode.Append)) + string fileName = GetTestFilePath() + streamSpecifier; + using (FileStream fs = CreateFileStream(fileName, FileMode.Append)) { Assert.False(fs.CanRead); Assert.True(fs.CanWrite); + + fs.Write(Encoding.ASCII.GetBytes("abcde")); + Assert.Equal(5, fs.Length); + Assert.Equal(5, fs.Position); + Assert.Equal(1, fs.Seek(1, SeekOrigin.Begin)); + + fs.Write(Encoding.ASCII.GetBytes("xyz")); + Assert.Equal(4, fs.Position); + Assert.Equal(5, fs.Length); } + + Assert.Equal("axyze", File.ReadAllText(fileName)); } [Theory, MemberData(nameof(StreamSpecifiers))] @@ -226,20 +239,35 @@ public virtual void FileModeAppendExisting(string streamSpecifier) string fileName = GetTestFilePath() + streamSpecifier; using (FileStream fs = CreateFileStream(fileName, FileMode.Create)) { - fs.WriteByte(0); + fs.WriteByte((byte)'s'); } + string initialContents = File.ReadAllText(fileName); using (FileStream fs = CreateFileStream(fileName, FileMode.Append)) { // Ensure that the file was re-opened and position set to end Assert.Equal(Math.Max(1L, InitialLength), fs.Length); - Assert.Equal(fs.Length, fs.Position); + + long position = fs.Position; + Assert.Equal(fs.Length, position); + Assert.False(fs.CanRead); Assert.True(fs.CanSeek); Assert.True(fs.CanWrite); + Assert.Throws(() => fs.Seek(-1, SeekOrigin.Current)); + Assert.Throws(() => fs.Seek(0, SeekOrigin.Begin)); Assert.Throws(() => fs.ReadByte()); + + fs.Write(Encoding.ASCII.GetBytes("abcde")); + Assert.Equal(position + 5, fs.Position); + + Assert.Equal(position, fs.Seek(position, SeekOrigin.Begin)); + Assert.Equal(position + 1, fs.Seek(1, SeekOrigin.Current)); + fs.Write(Encoding.ASCII.GetBytes("xyz")); } + + Assert.Equal(initialContents + "axyze", File.ReadAllText(fileName)); } } } From 62966bcaf01204f4afca5d550785f48a5aeb0ee9 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Tue, 13 Jul 2021 09:34:34 -0700 Subject: [PATCH 074/133] Expose nullability info (#54985) * Expose nullability info from Reflection --- .../System.Private.CoreLib.Shared.projitems | 4 +- .../src/System/Reflection/NullabilityInfo.cs | 64 ++ .../Reflection/NullabilityInfoContext.cs | 521 +++++++++ .../System.Runtime/ref/System.Runtime.cs | 26 + .../tests/System.Runtime.Tests.csproj | 3 + .../Reflection/NullabilityInfoContextTests.cs | 985 ++++++++++++++++++ 6 files changed, 1602 insertions(+), 1 deletion(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfo.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs create mode 100644 src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 23ffc05ed32a..4be53fea1973 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -615,6 +615,8 @@ + + @@ -2319,4 +2321,4 @@ - + \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfo.cs new file mode 100644 index 000000000000..2da593d76286 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfo.cs @@ -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.Collections.ObjectModel; + +namespace System.Reflection +{ + /// + /// A class that represents nullability info + /// + public sealed class NullabilityInfo + { + internal NullabilityInfo(Type type, NullabilityState readState, NullabilityState writeState, + NullabilityInfo? elementType, NullabilityInfo[] typeArguments) + { + Type = type; + ReadState = readState; + WriteState = writeState; + ElementType = elementType; + GenericTypeArguments = typeArguments; + } + + /// + /// The of the member or generic parameter + /// to which this NullabilityInfo belongs + /// + public Type Type { get; } + /// + /// The nullability read state of the member + /// + public NullabilityState ReadState { get; internal set; } + /// + /// The nullability write state of the member + /// + public NullabilityState WriteState { get; internal set; } + /// + /// If the member type is an array, gives the of the elements of the array, null otherwise + /// + public NullabilityInfo? ElementType { get; } + /// + /// If the member type is a generic type, gives the array of for each type parameter + /// + public NullabilityInfo[] GenericTypeArguments { get; } + } + + /// + /// An enum that represents nullability state + /// + public enum NullabilityState + { + /// + /// Nullability context not enabled (oblivious) + /// + Unknown, + /// + /// Non nullable value or reference type + /// + NotNull, + /// + /// Nullable value or reference type + /// + Nullable + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs new file mode 100644 index 000000000000..3d8b3766961c --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs @@ -0,0 +1,521 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; + +namespace System.Reflection +{ + /// + /// Provides APIs for populating nullability information/context from reflection members: + /// , , and . + /// + public sealed class NullabilityInfoContext + { + private const string CompilerServicesNameSpace = "System.Runtime.CompilerServices"; + private readonly Dictionary _publicOnlyModules = new(); + private readonly Dictionary _context = new(); + + [Flags] + private enum NotAnnotatedStatus + { + None = 0x0, // no restriction, all members annotated + Private = 0x1, // private members not annotated + Internal = 0x2 // internal members not annotated + } + + private NullabilityState GetNullableContext(MemberInfo? memberInfo) + { + while (memberInfo != null) + { + if (_context.TryGetValue(memberInfo, out NullabilityState state)) + { + return state; + } + + foreach (CustomAttributeData attribute in memberInfo.GetCustomAttributesData()) + { + if (attribute.AttributeType.Name == "NullableContextAttribute" && + attribute.AttributeType.Namespace == CompilerServicesNameSpace && + attribute.ConstructorArguments.Count == 1) + { + state = TranslateByte(attribute.ConstructorArguments[0].Value); + _context.Add(memberInfo, state); + return state; + } + } + + memberInfo = memberInfo.DeclaringType; + } + + return NullabilityState.Unknown; + } + + /// + /// Populates for the given . + /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's + /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state. + /// + /// The parameter which nullability info gets populated + /// If the parameterInfo parameter is null + /// + [RequiresUnreferencedCode("By default nullability attributes are trimmed by the trimmer")] + public NullabilityInfo Create(ParameterInfo parameterInfo) + { + if (parameterInfo is null) + { + throw new ArgumentNullException(nameof(parameterInfo)); + } + + if (parameterInfo.Member is MethodInfo method && IsPrivateOrInternalMethodAndAnnotationDisabled(method)) + { + return new NullabilityInfo(parameterInfo.ParameterType, NullabilityState.Unknown, NullabilityState.Unknown, null, Array.Empty()); + } + + IList attributes = parameterInfo.GetCustomAttributesData(); + NullabilityInfo nullability = GetNullabilityInfo(parameterInfo.Member, parameterInfo.ParameterType, attributes); + + if (nullability.ReadState != NullabilityState.Unknown) + { + CheckParameterMetadataType(parameterInfo, nullability); + } + + CheckNullabilityAttributes(nullability, attributes); + return nullability; + } + + private void CheckParameterMetadataType(ParameterInfo parameter, NullabilityInfo nullability) + { + if (parameter.Member is MethodInfo method) + { + MethodInfo metaMethod = GetMethodMetadataDefinition(method); + ParameterInfo? metaParameter = null; + if (string.IsNullOrEmpty(parameter.Name)) + { + metaParameter = metaMethod.ReturnParameter; + } + else + { + ParameterInfo[] parameters = metaMethod.GetParameters(); + for (int i = 0; i < parameters.Length; i++) + { + if (parameter.Position == i && + parameter.Name == parameters[i].Name) + { + metaParameter = parameters[i]; + break; + } + } + } + + if (metaParameter != null) + { + CheckGenericParameters(nullability, metaMethod, metaParameter.ParameterType); + } + } + } + + private static MethodInfo GetMethodMetadataDefinition(MethodInfo method) + { + if (method.IsGenericMethod && !method.IsGenericMethodDefinition) + { + method = method.GetGenericMethodDefinition(); + } + + return (MethodInfo)GetMemberMetadataDefinition(method); + } + + private void CheckNullabilityAttributes(NullabilityInfo nullability, IList attributes) + { + foreach (CustomAttributeData attribute in attributes) + { + if (attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis") + { + if (attribute.AttributeType.Name == "NotNullAttribute" && + nullability.ReadState == NullabilityState.Nullable) + { + nullability.ReadState = NullabilityState.NotNull; + break; + } + else if ((attribute.AttributeType.Name == "MaybeNullAttribute" || + attribute.AttributeType.Name == "MaybeNullWhenAttribute") && + nullability.ReadState == NullabilityState.NotNull && + !nullability.Type.IsValueType) + { + nullability.ReadState = NullabilityState.Nullable; + break; + } + + if (attribute.AttributeType.Name == "DisallowNullAttribute" && + nullability.WriteState == NullabilityState.Nullable) + { + nullability.WriteState = NullabilityState.NotNull; + break; + } + else if (attribute.AttributeType.Name == "AllowNullAttribute" && + nullability.WriteState == NullabilityState.NotNull && + !nullability.Type.IsValueType) + { + nullability.WriteState = NullabilityState.Nullable; + break; + } + } + } + } + + /// + /// Populates for the given . + /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's + /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state. + /// + /// The parameter which nullability info gets populated + /// If the propertyInfo parameter is null + /// + [RequiresUnreferencedCode("By default nullability attributes are trimmed by the trimmer")] + public NullabilityInfo Create(PropertyInfo propertyInfo) + { + if (propertyInfo is null) + { + throw new ArgumentNullException(nameof(propertyInfo)); + } + + NullabilityInfo nullability = GetNullabilityInfo(propertyInfo, propertyInfo.PropertyType, propertyInfo.GetCustomAttributesData()); + MethodInfo? getter = propertyInfo.GetGetMethod(true); + MethodInfo? setter = propertyInfo.GetSetMethod(true); + + if (getter != null) + { + if (IsPrivateOrInternalMethodAndAnnotationDisabled(getter)) + { + nullability.ReadState = NullabilityState.Unknown; + } + + CheckNullabilityAttributes(nullability, getter.ReturnParameter.GetCustomAttributesData()); + } + else + { + nullability.ReadState = NullabilityState.Unknown; + } + + if (setter != null) + { + if (IsPrivateOrInternalMethodAndAnnotationDisabled(setter)) + { + nullability.WriteState = NullabilityState.Unknown; + } + + CheckNullabilityAttributes(nullability, setter.GetParameters()[0].GetCustomAttributesData()); + } + else + { + nullability.WriteState = NullabilityState.Unknown; + } + + return nullability; + } + + private bool IsPrivateOrInternalMethodAndAnnotationDisabled(MethodInfo method) + { + if ((method.IsPrivate || method.IsFamilyAndAssembly || method.IsAssembly) && + IsPublicOnly(method.IsPrivate, method.IsFamilyAndAssembly, method.IsAssembly, method.Module)) + { + return true; + } + + return false; + } + + /// + /// Populates for the given . + /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's + /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state. + /// + /// The parameter which nullability info gets populated + /// If the eventInfo parameter is null + /// + [RequiresUnreferencedCode("By default nullability attributes are trimmed by the trimmer")] + public NullabilityInfo Create(EventInfo eventInfo) + { + if (eventInfo is null) + { + throw new ArgumentNullException(nameof(eventInfo)); + } + + return GetNullabilityInfo(eventInfo, eventInfo.EventHandlerType!, eventInfo.GetCustomAttributesData()); + } + + /// + /// Populates for the given + /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's + /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state. + /// + /// The parameter which nullability info gets populated + /// If the fieldInfo parameter is null + /// + [RequiresUnreferencedCode("By default nullability attributes are trimmed by the trimmer")] + public NullabilityInfo Create(FieldInfo fieldInfo) + { + if (fieldInfo is null) + { + throw new ArgumentNullException(nameof(fieldInfo)); + } + + if (IsPrivateOrInternalFieldAndAnnotationDisabled(fieldInfo)) + { + return new NullabilityInfo(fieldInfo.FieldType, NullabilityState.Unknown, NullabilityState.Unknown, null, Array.Empty()); + } + + IList attributes = fieldInfo.GetCustomAttributesData(); + NullabilityInfo nullability = GetNullabilityInfo(fieldInfo, fieldInfo.FieldType, attributes); + CheckNullabilityAttributes(nullability, attributes); + return nullability; + } + + private bool IsPrivateOrInternalFieldAndAnnotationDisabled(FieldInfo fieldInfo) + { + if ((fieldInfo.IsPrivate || fieldInfo.IsFamilyAndAssembly || fieldInfo.IsAssembly) && + IsPublicOnly(fieldInfo.IsPrivate, fieldInfo.IsFamilyAndAssembly, fieldInfo.IsAssembly, fieldInfo.Module)) + { + return true; + } + + return false; + } + + private bool IsPublicOnly(bool isPrivate, bool isFamilyAndAssembly, bool isAssembly, Module module) + { + if (!_publicOnlyModules.TryGetValue(module, out NotAnnotatedStatus value)) + { + value = PopulateAnnotationInfo(module.GetCustomAttributesData()); + _publicOnlyModules.Add(module, value); + } + + if (value == NotAnnotatedStatus.None) + { + return false; + } + + if ((isPrivate || isFamilyAndAssembly) && value.HasFlag(NotAnnotatedStatus.Private) || + isAssembly && value.HasFlag(NotAnnotatedStatus.Internal)) + { + return true; + } + + return false; + } + + private NotAnnotatedStatus PopulateAnnotationInfo(IList customAttributes) + { + foreach (CustomAttributeData attribute in customAttributes) + { + if (attribute.AttributeType.Name == "NullablePublicOnlyAttribute" && + attribute.AttributeType.Namespace == CompilerServicesNameSpace && + attribute.ConstructorArguments.Count == 1) + { + if (attribute.ConstructorArguments[0].Value is bool boolValue && boolValue) + { + return NotAnnotatedStatus.Internal | NotAnnotatedStatus.Private; + } + else + { + return NotAnnotatedStatus.Private; + } + } + } + + return NotAnnotatedStatus.None; + } + + private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, IList customAttributes) => + GetNullabilityInfo(memberInfo, type, customAttributes, 0); + + private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, IList customAttributes, int index) + { + NullabilityState state = NullabilityState.Unknown; + + if (type.IsValueType) + { + if (Nullable.GetUnderlyingType(type) != null) + { + state = NullabilityState.Nullable; + } + else + { + state = NullabilityState.NotNull; + } + + return new NullabilityInfo(type, state, state, null, Array.Empty()); + } + else + { + if (!ParseNullableState(customAttributes, index, ref state)) + { + state = GetNullableContext(memberInfo); + } + + NullabilityInfo? elementState = null; + NullabilityInfo[]? genericArgumentsState = null; + + if (type.IsArray) + { + elementState = GetNullabilityInfo(memberInfo, type.GetElementType()!, customAttributes, index + 1); + } + else if (type.IsGenericType) + { + Type[] genericArguments = type.GetGenericArguments(); + genericArgumentsState = new NullabilityInfo[genericArguments.Length]; + + for (int i = 0; i < genericArguments.Length; i++) + { + genericArgumentsState[i] = GetNullabilityInfo(memberInfo, genericArguments[i], customAttributes, i + 1); + } + } + + NullabilityInfo nullability = new NullabilityInfo(type, state, state, elementState, genericArgumentsState ?? Array.Empty()); + if (state != NullabilityState.Unknown) + { + TryLoadGenericMetaTypeNullability(memberInfo, nullability); + } + + return nullability; + } + } + + private static bool ParseNullableState(IList customAttributes, int index, ref NullabilityState state) + { + foreach (CustomAttributeData attribute in customAttributes) + { + if (attribute.AttributeType.Name == "NullableAttribute" && + attribute.AttributeType.Namespace == CompilerServicesNameSpace && + attribute.ConstructorArguments.Count == 1) + { + object? o = attribute.ConstructorArguments[0].Value; + + if (o is byte b) + { + state = TranslateByte(b); + return true; + } + else if (o is ReadOnlyCollection args && + index < args.Count && + args[index].Value is byte elementB) + { + state = TranslateByte(elementB); + return true; + } + + break; + } + } + + return false; + } + + private void TryLoadGenericMetaTypeNullability(MemberInfo memberInfo, NullabilityInfo nullability) + { + MemberInfo? metaMember = GetMemberMetadataDefinition(memberInfo); + Type? metaType = null; + if (metaMember is FieldInfo field) + { + metaType = field.FieldType; + } + else if (metaMember is PropertyInfo property) + { + metaType = GetPropertyMetaType(property); + } + + if (metaType != null) + { + CheckGenericParameters(nullability, metaMember!, metaType); + } + } + + private static MemberInfo GetMemberMetadataDefinition(MemberInfo member) + { + Type? type = member.DeclaringType; + if ((type != null) && type.IsGenericType && !type.IsGenericTypeDefinition) + { + return type.GetGenericTypeDefinition().GetMemberWithSameMetadataDefinitionAs(member); + } + + return member; + } + + private static Type GetPropertyMetaType(PropertyInfo property) + { + if (property.GetGetMethod(true) is MethodInfo method) + { + return method.ReturnType; + } + + return property.GetSetMethod(true)!.GetParameters()[0].ParameterType; + } + + private void CheckGenericParameters(NullabilityInfo nullability, MemberInfo metaMember, Type metaType) + { + if (metaType.IsGenericParameter) + { + NullabilityState state = nullability.ReadState; + + if (!ParseNullableState(metaType.GetCustomAttributesData(), 0, ref state)) + { + state = GetNullableContext(metaType); + } + + nullability.ReadState = state; + nullability.WriteState = state; + } + else if (metaType.ContainsGenericParameters) + { + if (nullability.GenericTypeArguments.Length > 0) + { + Type[] genericArguments = metaType.GetGenericArguments(); + + for (int i = 0; i < genericArguments.Length; i++) + { + if (genericArguments[i].IsGenericParameter) + { + NullabilityInfo n = GetNullabilityInfo(metaMember, genericArguments[i], genericArguments[i].GetCustomAttributesData(), i + 1); + nullability.GenericTypeArguments[i].ReadState = n.ReadState; + nullability.GenericTypeArguments[i].WriteState = n.WriteState; + } + else + { + UpdateGenericArrayElements(nullability.GenericTypeArguments[i].ElementType, metaMember, genericArguments[i]); + } + } + } + else + { + UpdateGenericArrayElements(nullability.ElementType, metaMember, metaType); + } + } + } + + private void UpdateGenericArrayElements(NullabilityInfo? elementState, MemberInfo metaMember, Type metaType) + { + if (metaType.IsArray && elementState != null + && metaType.GetElementType()!.IsGenericParameter) + { + Type elementType = metaType.GetElementType()!; + NullabilityInfo n = GetNullabilityInfo(metaMember, elementType, elementType.GetCustomAttributesData(), 0); + elementState.ReadState = n.ReadState; + elementState.WriteState = n.WriteState; + } + } + + private static NullabilityState TranslateByte(object? value) + { + return value is byte b ? TranslateByte(b) : NullabilityState.Unknown; + } + + private static NullabilityState TranslateByte(byte b) => + b switch + { + 1 => NullabilityState.NotNull, + 2 => NullabilityState.Nullable, + _ => NullabilityState.Unknown + }; + } +} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 43ca97b21d3d..2656da8c9996 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11933,6 +11933,32 @@ public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo public virtual System.Type ResolveType(int metadataToken, System.Type[]? genericTypeArguments, System.Type[]? genericMethodArguments) { throw null; } public override string ToString() { throw null; } } + public sealed class NullabilityInfoContext + { + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("By default nullability attributes are trimmed by the trimmer")] + public System.Reflection.NullabilityInfo Create(System.Reflection.EventInfo eventInfo) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("By default nullability attributes are trimmed by the trimmer")] + public System.Reflection.NullabilityInfo Create(System.Reflection.FieldInfo fieldInfo) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("By default nullability attributes are trimmed by the trimmer")] + public System.Reflection.NullabilityInfo Create(System.Reflection.ParameterInfo parameterInfo) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("By default nullability attributes are trimmed by the trimmer")] + public System.Reflection.NullabilityInfo Create(System.Reflection.PropertyInfo propertyInfo) { throw null; } + } + public sealed class NullabilityInfo + { + internal NullabilityInfo(System.Type type, System.Reflection.NullabilityState readState, System.Reflection.NullabilityState writeState, System.Reflection.NullabilityInfo? elementType, System.Reflection.NullabilityInfo[] genericTypeArguments) { } + public System.Type Type { get; } + public System.Reflection.NullabilityState ReadState { get; } + public System.Reflection.NullabilityState WriteState { get; } + public System.Reflection.NullabilityInfo? ElementType { get; } + public System.Reflection.NullabilityInfo[] GenericTypeArguments { get; } + } + public enum NullabilityState + { + Unknown, + NotNull, + Nullable + } public delegate System.Reflection.Module ModuleResolveEventHandler(object sender, System.ResolveEventArgs e); [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=false, Inherited=false)] public sealed partial class ObfuscateAssemblyAttribute : System.Attribute diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index 0ddefe7dcf06..405fd8b8701b 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -7,6 +7,8 @@ true $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser disable + + $(Features.Replace('nullablePublicOnly', '') $(DefineConstants);FEATURE_GENERIC_MATH @@ -123,6 +125,7 @@ + diff --git a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs new file mode 100644 index 000000000000..07f244e9172f --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs @@ -0,0 +1,985 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO.Enumeration; +using System.Text.RegularExpressions; +using Xunit; + +namespace System.Reflection.Tests +{ + public class NullabilityInfoContextTests + { + private static readonly NullabilityInfoContext nullabilityContext = new NullabilityInfoContext(); + private static readonly Type testType = typeof(TypeWithNotNullContext); + private static readonly Type genericType = typeof(GenericTest); + private static readonly Type stringType = typeof(string); + private static readonly BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; + + public static IEnumerable FieldTestData() + { + yield return new object[] { "FieldNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(string) }; + yield return new object[] { "FieldUnknown", NullabilityState.Unknown, NullabilityState.Unknown, typeof(TypeWithNotNullContext) }; + yield return new object[] { "FieldNonNullable", NullabilityState.NotNull, NullabilityState.NotNull, typeof(NullabilityInfoContextTests) }; + yield return new object[] { "FieldValueTypeUnknown", NullabilityState.NotNull, NullabilityState.NotNull, typeof(int) }; + yield return new object[] { "FieldValueTypeNotNull", NullabilityState.NotNull, NullabilityState.NotNull, typeof(double) }; + yield return new object[] { "FieldValueTypeNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(int?) }; + yield return new object[] { "FieldDisallowNull", NullabilityState.Nullable, NullabilityState.NotNull, typeof(string) }; + yield return new object[] { "FieldAllowNull", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; + yield return new object[] { "FieldDisallowNull2", NullabilityState.Nullable, NullabilityState.NotNull, typeof(string) }; + yield return new object[] { "FieldAllowNull2", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; + yield return new object[] { "FieldNotNull", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; + yield return new object[] { "FieldMaybeNull", NullabilityState.Nullable, NullabilityState.NotNull, typeof(string) }; + yield return new object[] { "FieldMaybeNull", NullabilityState.Nullable, NullabilityState.NotNull, typeof(string) }; + yield return new object[] { "FieldNotNull2", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; + } + + [Theory] + [MemberData(nameof(FieldTestData))] + public void FieldTest(string fieldName, NullabilityState readState, NullabilityState writeState, Type type) + { + FieldInfo field = testType.GetField(fieldName, flags); + NullabilityInfo nullability = nullabilityContext.Create(field); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + Assert.Equal(type, nullability.Type); + Assert.Empty(nullability.GenericTypeArguments); + Assert.Null(nullability.ElementType); + } + + public static IEnumerable EventTestData() + { + yield return new object[] { "EventNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(EventHandler) }; + yield return new object[] { "EventUnknown", NullabilityState.Unknown, NullabilityState.Unknown, typeof(EventHandler) }; + yield return new object[] { "EventNotNull", NullabilityState.NotNull, NullabilityState.NotNull, typeof(EventHandler) }; + } + + [Theory] + [MemberData(nameof(EventTestData))] + public void EventTest(string eventName, NullabilityState readState, NullabilityState writeState, Type type) + { + EventInfo @event = testType.GetEvent(eventName); + NullabilityInfo nullability = nullabilityContext.Create(@event); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + Assert.Equal(type, nullability.Type); + Assert.Empty(nullability.GenericTypeArguments); + Assert.Null(nullability.ElementType); + } + + public static IEnumerable PropertyTestData() + { + yield return new object[] { "PropertyNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(TypeWithNotNullContext) }; + yield return new object[] { "PropertyNullableReadOnly", NullabilityState.Nullable, NullabilityState.Unknown, typeof(TypeWithNotNullContext) }; + yield return new object[] { "PropertyUnknown", NullabilityState.Unknown, NullabilityState.Unknown, typeof(string) }; + yield return new object[] { "PropertyNonNullable", NullabilityState.NotNull, NullabilityState.NotNull, typeof(NullabilityInfoContextTests) }; + yield return new object[] { "PropertyValueTypeUnknown", NullabilityState.NotNull, NullabilityState.NotNull, typeof(short) }; + yield return new object[] { "PropertyValueType", NullabilityState.NotNull, NullabilityState.NotNull, typeof(float) }; + yield return new object[] { "PropertyValueTypeNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(long?) }; + yield return new object[] { "PropertyValueTypeDisallowNull", NullabilityState.Nullable, NullabilityState.NotNull, typeof(int?) }; + yield return new object[] { "PropertyValueTypeAllowNull", NullabilityState.NotNull, NullabilityState.NotNull, typeof(byte) }; + yield return new object[] { "PropertyValueTypeNotNull", NullabilityState.NotNull, NullabilityState.Nullable, typeof(int?) }; + yield return new object[] { "PropertyValueTypeMaybeNull", NullabilityState.NotNull, NullabilityState.NotNull, typeof(byte) }; + yield return new object[] { "PropertyDisallowNull", NullabilityState.Nullable, NullabilityState.NotNull, typeof(string) }; + yield return new object[] { "PropertyAllowNull", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; + yield return new object[] { "PropertyDisallowNull2", NullabilityState.Nullable, NullabilityState.NotNull, typeof(string) }; + yield return new object[] { "PropertyAllowNull2", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; + yield return new object[] { "PropertyNotNull", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; + yield return new object[] { "PropertyMaybeNull", NullabilityState.Nullable, NullabilityState.NotNull, typeof(string) }; + yield return new object[] { "PropertyMaybeNull2", NullabilityState.Nullable, NullabilityState.NotNull, typeof(string) }; + yield return new object[] { "PropertyNotNull2", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; + } + + [Theory] + [MemberData(nameof(PropertyTestData))] + public void PropertyTest(string propertyName, NullabilityState readState, NullabilityState writeState, Type type) + { + PropertyInfo property = testType.GetProperty(propertyName, flags); + NullabilityInfo nullability = nullabilityContext.Create(property); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(readState, nullabilityContext.Create(property.GetMethod.ReturnParameter).ReadState); + Assert.Equal(writeState, nullability.WriteState); + if (property.SetMethod != null) + { + Assert.Equal(writeState, nullabilityContext.Create(property.SetMethod.GetParameters()[0]).WriteState); + } + Assert.Equal(type, nullability.Type); + Assert.Empty(nullability.GenericTypeArguments); + Assert.Null(nullability.ElementType); + } + + public static IEnumerable ArrayPropertyTestData() + { + yield return new object[] { "PropertyArrayUnknown", NullabilityState.Unknown, NullabilityState.Unknown }; + yield return new object[] { "PropertyArrayNullNull", NullabilityState.Nullable, NullabilityState.Nullable }; + yield return new object[] { "PropertyArrayNullNon", NullabilityState.Nullable, NullabilityState.NotNull }; + yield return new object[] { "PropertyArrayNonNull", NullabilityState.NotNull, NullabilityState.Nullable }; + yield return new object[] { "PropertyArrayNonNon", NullabilityState.NotNull, NullabilityState.NotNull }; + } + + [Theory] + [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [MemberData(nameof(ArrayPropertyTestData))] + public void ArrayPropertyTest(string propertyName, NullabilityState elementState, NullabilityState propertyState) + { + PropertyInfo property = testType.GetProperty(propertyName, flags); + NullabilityInfo nullability = nullabilityContext.Create(property); + Assert.Equal(propertyState, nullability.ReadState); + Assert.NotNull(nullability.ElementType); + Assert.Equal(elementState, nullability.ElementType.ReadState); + Assert.Empty(nullability.GenericTypeArguments); + } + + public static IEnumerable GenericArrayPropertyTestData() + { + yield return new object[] { "PropertyArrayUnknown", NullabilityState.Unknown, NullabilityState.Unknown }; + yield return new object[] { "PropertyArrayNullNull", NullabilityState.Nullable, NullabilityState.Nullable }; // T?[]? PropertyArrayNullNull { get; set; } + yield return new object[] { "PropertyArrayNullNon", NullabilityState.Nullable, NullabilityState.NotNull }; // T?[] PropertyArrayNullNon { get; set; } + yield return new object[] { "PropertyArrayNonNull", NullabilityState.Nullable, NullabilityState.Nullable }; // T[]? PropertyArrayNonNull { get; set; } + yield return new object[] { "PropertyArrayNonNon", NullabilityState.Nullable, NullabilityState.NotNull }; // T[] PropertyArrayNonNon { get; set; } + } + + [Theory] + [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [MemberData(nameof(GenericArrayPropertyTestData))] + public void GenericArrayPropertyTest(string propertyName, NullabilityState elementState, NullabilityState propertyState) + { + PropertyInfo property = genericType.GetProperty(propertyName, flags); + NullabilityInfo nullability = nullabilityContext.Create(property); + Assert.Equal(propertyState, nullability.ReadState); + Assert.NotNull(nullability.ElementType); + Assert.Equal(elementState, nullability.ElementType.ReadState); + Assert.Empty(nullability.GenericTypeArguments); + } + + public static IEnumerable JaggedArrayPropertyTestData() + { + yield return new object[] { "PropertyJaggedArrayUnknown", NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown }; + yield return new object[] { "PropertyJaggedArrayNullNullNull", NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.Nullable }; + yield return new object[] { "PropertyJaggedArrayNullNullNon", NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull }; + yield return new object[] { "PropertyJaggedArrayNullNonNull", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; + yield return new object[] { "PropertyJaggedArrayNonNullNull", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.Nullable }; + yield return new object[] { "PropertyJaggedArrayNullNonNon", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull }; + yield return new object[] { "PropertyJaggedArrayNonNonNull", NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.Nullable }; + } + + [Theory] + [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [MemberData(nameof(JaggedArrayPropertyTestData))] + public void JaggedArrayPropertyTest(string propertyName, NullabilityState innermodtElementState, NullabilityState elementState, NullabilityState propertyState) + { + PropertyInfo property = testType.GetProperty(propertyName, flags); + NullabilityInfo nullability = nullabilityContext.Create(property); + Assert.Equal(propertyState, nullability.ReadState); + Assert.NotNull(nullability.ElementType); + Assert.Equal(elementState, nullability.ElementType.ReadState); + Assert.NotNull(nullability.ElementType.ElementType); + Assert.Equal(innermodtElementState, nullability.ElementType.ElementType.ReadState); + Assert.Empty(nullability.GenericTypeArguments); + } + + public static IEnumerable TuplePropertyTestData() + { + yield return new object[] { "PropertyTupleUnknown", NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown }; + yield return new object[] { "PropertyTupleNullNullNullNull", NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.Nullable }; + yield return new object[] { "PropertyTupleNonNullNonNon", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull }; + yield return new object[] { "PropertyTupleNullNonNullNull", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.Nullable }; + yield return new object[] { "PropertyTupleNonNullNonNull", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; + yield return new object[] { "PropertyTupleNonNonNonNon", NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.NotNull }; + } + + [Theory] + [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [MemberData(nameof(TuplePropertyTestData))] + public void TuplePropertyTest(string propertyName, NullabilityState genericParam1, NullabilityState genericParam2, NullabilityState genericParam3, NullabilityState propertyState) + { + PropertyInfo property = testType.GetProperty(propertyName, flags); + NullabilityInfo nullability = nullabilityContext.Create(property); + Assert.Equal(propertyState, nullability.ReadState); + Assert.NotEmpty(nullability.GenericTypeArguments); + Assert.Equal(genericParam1, nullability.GenericTypeArguments[0].ReadState); + Assert.Equal(genericParam2, nullability.GenericTypeArguments[1].ReadState); + Assert.Equal(genericParam3, nullability.GenericTypeArguments[2].ReadState); + Assert.Null(nullability.ElementType); + } + + public static IEnumerable GenericTuplePropertyTestData() + { + yield return new object[] { "PropertyTupleUnknown", NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown }; + yield return new object[] { "PropertyTupleNullNullNullNull", NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.Nullable }; // Tuple? + yield return new object[] { "PropertyTupleNonNullNonNon", NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull }; // Tuple + yield return new object[] { "PropertyTupleNullNonNullNull", NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.Nullable }; // Tuple? + yield return new object[] { "PropertyTupleNonNullNonNull", NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; // Tuple? + yield return new object[] { "PropertyTupleNonNonNonNon", NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull }; // Tuple + } + + [Theory] + [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [MemberData(nameof(GenericTuplePropertyTestData))] + public void GenericTuplePropertyTest(string propertyName, NullabilityState genericParam1, NullabilityState genericParam2, NullabilityState genericParam3, NullabilityState propertyState) + { + PropertyInfo property = genericType.GetProperty(propertyName, flags); + NullabilityInfo nullability = nullabilityContext.Create(property); + Assert.Equal(propertyState, nullability.ReadState); + Assert.NotEmpty(nullability.GenericTypeArguments); + Assert.Equal(genericParam1, nullability.GenericTypeArguments[0].ReadState); + Assert.Equal(genericParam2, nullability.GenericTypeArguments[1].ReadState); + Assert.Equal(genericParam3, nullability.GenericTypeArguments[2].ReadState); + Assert.Null(nullability.ElementType); + } + + public static IEnumerable DictionaryPropertyTestData() + { + yield return new object[] { "PropertyDictionaryUnknown", NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown }; + yield return new object[] { "PropertyDictionaryNullNullNullNon", NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull }; + yield return new object[] { "PropertyDictionaryNonNullNonNull", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; + yield return new object[] { "PropertyDictionaryNullNonNonNull", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.Nullable }; + yield return new object[] { "PropertyDictionaryNonNullNonNon", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull }; + yield return new object[] { "PropertyDictionaryNonNonNonNull", NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.Nullable }; + yield return new object[] { "PropertyDictionaryNonNonNonNon", NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.NotNull }; + } + + [Theory] + [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [MemberData(nameof(DictionaryPropertyTestData))] + public void DictionaryPropertyTest(string propertyName, NullabilityState keyState, NullabilityState valueElement, NullabilityState valueState, NullabilityState propertyState) + { + PropertyInfo property = testType.GetProperty(propertyName, flags); + NullabilityInfo nullability = nullabilityContext.Create(property); + Assert.Equal(propertyState, nullability.ReadState); + Assert.NotEmpty(nullability.GenericTypeArguments); + Assert.Equal(keyState, nullability.GenericTypeArguments[0].ReadState); + Assert.Equal(valueState, nullability.GenericTypeArguments[1].ReadState); + Assert.Equal(valueElement, nullability.GenericTypeArguments[1].ElementType.ReadState); + Assert.Null(nullability.ElementType); + } + + public static IEnumerable GenericDictionaryPropertyTestData() + { + yield return new object[] { "PropertyDictionaryUnknown", NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown }; + yield return new object[] { "PropertyDictionaryNullNullNullNon", NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull }; // IDictionary PropertyDictionaryNullNullNullNon { get; set; } + yield return new object[] { "PropertyDictionaryNonNullNonNull", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; // IDictionary? PropertyDictionaryNonNullNonNull + yield return new object[] { "PropertyDictionaryNullNonNonNull", NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; // IDictionary? PropertyDictionaryNullNonNonNull + yield return new object[] { "PropertyDictionaryNonNullNonNon", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull }; // IDictionary PropertyDictionaryNonNullNonNon + yield return new object[] { "PropertyDictionaryNonNonNonNull", NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; // IDictionary? PropertyDictionaryNonNonNonNull + yield return new object[] { "PropertyDictionaryNonNonNonNon", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.NotNull }; // IDictionary PropertyDictionaryNonNonNonNon + } + + [Theory] + [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [MemberData(nameof(GenericDictionaryPropertyTestData))] + public void GenericDictionaryPropertyTest(string propertyName, NullabilityState keyState, NullabilityState valueElement, NullabilityState valueState, NullabilityState propertyState) + { + PropertyInfo property = genericType.GetProperty(propertyName, flags); + NullabilityInfo nullability = nullabilityContext.Create(property); + Assert.Equal(propertyState, nullability.ReadState); + Assert.NotEmpty(nullability.GenericTypeArguments); + Assert.Equal(keyState, nullability.GenericTypeArguments[0].ReadState); + Assert.Equal(valueState, nullability.GenericTypeArguments[1].ReadState); + Assert.Equal(valueElement, nullability.GenericTypeArguments[1].ElementType.ReadState); + Assert.Null(nullability.ElementType); + } + + public static IEnumerable GenericPropertyReferenceTypeTestData() + { + yield return new object[] { "PropertyNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(TypeWithNotNullContext) }; + yield return new object[] { "PropertyUnknown", NullabilityState.Unknown, NullabilityState.Unknown, typeof(TypeWithNotNullContext) }; + yield return new object[] { "PropertyNonNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(TypeWithNotNullContext) }; + yield return new object[] { "PropertyDisallowNull", NullabilityState.Nullable, NullabilityState.NotNull, typeof(TypeWithNotNullContext) }; + yield return new object[] { "PropertyAllowNull", NullabilityState.Nullable, NullabilityState.Nullable, typeof(TypeWithNotNullContext) }; + yield return new object[] { "PropertyMaybeNull", NullabilityState.Nullable, NullabilityState.Nullable, typeof(TypeWithNotNullContext) }; + } + +#nullable enable + [Theory] + [MemberData(nameof(GenericPropertyReferenceTypeTestData))] + public void GenericPropertyReferenceTypeTest(string fieldName, NullabilityState readState, NullabilityState writeState, Type type) + { + PropertyInfo property = typeof(GenericTest).GetProperty(fieldName, flags)!; + NullabilityInfo nullability = nullabilityContext.Create(property); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + Assert.Equal(type, nullability.Type); + Assert.Empty(nullability.GenericTypeArguments); + Assert.Null(nullability.ElementType); + + property = typeof(GenericTest).GetProperty(fieldName, flags)!; + nullability = nullabilityContext.Create(property); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + Assert.Equal(type, nullability.Type); + + property = typeof(GenericTest<>).GetProperty(fieldName, flags)!; + nullability = nullabilityContext.Create(property); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + } + + public static IEnumerable GenericFieldReferenceTypeTestData() + { + yield return new object[] { "FieldNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(TypeWithNotNullContext) }; + yield return new object[] { "FieldUnknown", NullabilityState.Unknown, NullabilityState.Unknown, typeof(TypeWithNotNullContext) }; + yield return new object[] { "FieldNonNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(TypeWithNotNullContext) }; + yield return new object[] { "FieldDisallowNull", NullabilityState.Nullable, NullabilityState.NotNull, typeof(TypeWithNotNullContext) }; + yield return new object[] { "FieldAllowNull", NullabilityState.Nullable, NullabilityState.Nullable, typeof(TypeWithNotNullContext) }; + yield return new object[] { "FieldMaybeNull", NullabilityState.Nullable, NullabilityState.Nullable, typeof(TypeWithNotNullContext) }; + } + + [Theory] + [MemberData(nameof(GenericFieldReferenceTypeTestData))] + public void GenericFieldReferenceTypeTest(string fieldName, NullabilityState readState, NullabilityState writeState, Type type) + { + FieldInfo field = typeof(GenericTest).GetField(fieldName, flags)!; + NullabilityInfo nullability = nullabilityContext.Create(field); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + Assert.Equal(type, nullability.Type); + Assert.Empty(nullability.GenericTypeArguments); + Assert.Null(nullability.ElementType); + + field = typeof(GenericTest).GetField(fieldName, flags)!; + nullability = nullabilityContext.Create(field); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + Assert.Equal(type, nullability.Type); + + field = typeof(GenericTest<>).GetField(fieldName, flags)!; + nullability = nullabilityContext.Create(field); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + } + + public static IEnumerable GenericFieldValueTypeTestData() + { + yield return new object[] { "FieldNullable", typeof(int) }; + yield return new object[] { "FieldUnknown", typeof(int) }; + yield return new object[] { "FieldNonNullable", typeof(int) }; + yield return new object[] { "FieldDisallowNull", typeof(int) }; + yield return new object[] { "FieldAllowNull", typeof(int) }; + yield return new object[] { "FieldMaybeNull", typeof(int) }; + yield return new object[] { "FieldNotNull", typeof(int) }; + } + + [Theory] + [MemberData(nameof(GenericFieldValueTypeTestData))] + public void GenericFieldValueTypeTest(string fieldName, Type type) + { + FieldInfo field = typeof(GenericTest).GetField(fieldName, flags)!; + NullabilityInfo nullability = nullabilityContext.Create(field); + Assert.Equal(NullabilityState.NotNull, nullability.ReadState); + Assert.Equal(NullabilityState.NotNull, nullability.WriteState); + Assert.Equal(type, nullability.Type); + } + + public static IEnumerable GenericFieldNullableValueTypeTestData() + { + yield return new object[] { "FieldNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(int?) }; + yield return new object[] { "FieldUnknown", NullabilityState.Nullable, NullabilityState.Nullable, typeof(int?) }; + yield return new object[] { "FieldNonNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(int?) }; + yield return new object[] { "FieldDisallowNull", NullabilityState.Nullable, NullabilityState.NotNull, typeof(int?) }; + yield return new object[] { "FieldAllowNull", NullabilityState.Nullable, NullabilityState.Nullable, typeof(int?) }; + yield return new object[] { "FieldMaybeNull", NullabilityState.Nullable, NullabilityState.Nullable, typeof(int?) }; + yield return new object[] { "FieldNotNull", NullabilityState.NotNull, NullabilityState.Nullable, typeof(int?) }; + } + + [Theory] + [MemberData(nameof(GenericFieldNullableValueTypeTestData))] + public void GenericFieldNullableValueTypeTest(string fieldName, NullabilityState readState, NullabilityState writeState, Type type) + { + FieldInfo field = typeof(GenericTest).GetField(fieldName, flags)!; + NullabilityInfo nullability = nullabilityContext.Create(field); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + Assert.Equal(type, nullability.Type); + } + + public static IEnumerable GenericNotnullConstraintTestData() + { + yield return new object[] { "FieldNullable", NullabilityState.NotNull, NullabilityState.NotNull, typeof(string) }; + yield return new object[] { "FieldUnknown", NullabilityState.Unknown, NullabilityState.Unknown, typeof(string) }; + yield return new object[] { "FieldNullableEnabled", NullabilityState.NotNull, NullabilityState.NotNull, typeof(string) }; + } + + [Theory] + [MemberData(nameof(GenericNotnullConstraintTestData))] + public void GenericNotNullConstraintTest(string fieldName, NullabilityState readState, NullabilityState writeState, Type type) + { + FieldInfo field = typeof(GenericTestConstrainedNotNull).GetField(fieldName, flags)!; + NullabilityInfo nullability = nullabilityContext.Create(field); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + Assert.Equal(type, nullability.Type); + } + + public static IEnumerable GenericStructConstraintTestData() + { + yield return new object[] { "FieldNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(int?) }; + yield return new object[] { "FieldUnknown", NullabilityState.NotNull, NullabilityState.NotNull, typeof(int) }; + yield return new object[] { "FieldNullableEnabled", NullabilityState.NotNull, NullabilityState.NotNull, typeof(int) }; + } + + [Theory] + [MemberData(nameof(GenericStructConstraintTestData))] + public void GenericStructConstraintTest(string fieldName, NullabilityState readState, NullabilityState writeState, Type type) + { + FieldInfo field = typeof(GenericTestConstrainedStruct).GetField(fieldName, flags)!; + NullabilityInfo nullability = nullabilityContext.Create(field); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + Assert.Equal(type, nullability.Type); + } + + [Fact] + [SkipOnMono("Nullability attributes trimmed on Mono")] + public void GenericListTest() + { + Type listNullable = typeof(List); + MethodInfo addNullable = listNullable.GetMethod("Add")!; + NullabilityInfo nullability = nullabilityContext.Create(addNullable.GetParameters()[0]); + Assert.Equal(NullabilityState.Nullable, nullability.ReadState); + Assert.Equal(NullabilityState.Nullable, nullability.WriteState); + Assert.Equal(typeof(string), nullability.Type); + + Type lisNontNull = typeof(List); + MethodInfo addNotNull = lisNontNull.GetMethod("Add")!; + nullability = nullabilityContext.Create(addNotNull.GetParameters()[0]); + Assert.Equal(NullabilityState.Nullable, nullability.ReadState); + Assert.Equal(typeof(string), nullability.Type); + + Type listOpen = typeof(List<>); + MethodInfo addOpen = listOpen.GetMethod("Add")!; + nullability = nullabilityContext.Create(addOpen.GetParameters()[0]); + Assert.Equal(NullabilityState.Nullable, nullability.ReadState); + } + + [Fact] + [SkipOnMono("Nullability attributes trimmed on Mono")] + public void GenericListAndDictionaryFieldTest() + { + Type typeNullable = typeof(GenericTest); + FieldInfo listOfTNullable = typeNullable.GetField("FieldListOfT")!; + NullabilityInfo listNullability = nullabilityContext.Create(listOfTNullable); + Assert.Equal(NullabilityState.Nullable, listNullability.GenericTypeArguments[0].ReadState); + Assert.Equal(typeof(string), listNullability.GenericTypeArguments[0].Type); + + FieldInfo dictStringToTNullable = typeNullable.GetField("FieldDictionaryStringToT")!; + NullabilityInfo dictNullability = nullabilityContext.Create(dictStringToTNullable); + Assert.Equal(NullabilityState.NotNull, dictNullability.GenericTypeArguments[0].ReadState); + Assert.Equal(NullabilityState.Nullable, dictNullability.GenericTypeArguments[1].ReadState); + Assert.Equal(typeof(string), dictNullability.GenericTypeArguments[1].Type); + + Type typeNonNull = typeof(GenericTest); + FieldInfo listOfTNotNull = typeNonNull.GetField("FieldListOfT")!; + listNullability = nullabilityContext.Create(listOfTNotNull); + Assert.Equal(NullabilityState.Nullable, listNullability.GenericTypeArguments[0].ReadState); + Assert.Equal(typeof(string), listNullability.GenericTypeArguments[0].Type); + + FieldInfo dictStringToTNotNull = typeNonNull.GetField("FieldDictionaryStringToT")!; + dictNullability = nullabilityContext.Create(dictStringToTNotNull); + Assert.Equal(NullabilityState.NotNull, dictNullability.GenericTypeArguments[0].ReadState); + Assert.Equal(NullabilityState.Nullable, dictNullability.GenericTypeArguments[1].ReadState); + Assert.Equal(typeof(string), dictNullability.GenericTypeArguments[1].Type); + + Type typeOpen = typeof(GenericTest<>); + FieldInfo listOfTOpen = typeOpen.GetField("FieldListOfT")!; + listNullability = nullabilityContext.Create(listOfTOpen); + Assert.Equal(NullabilityState.Nullable, listNullability.GenericTypeArguments[0].ReadState); + // Assert.Equal(typeof(T), listNullability.TypeArguments[0].Type); + + FieldInfo dictStringToTOpen = typeOpen.GetField("FieldDictionaryStringToT")!; + dictNullability = nullabilityContext.Create(dictStringToTOpen); + Assert.Equal(NullabilityState.NotNull, dictNullability.GenericTypeArguments[0].ReadState); + Assert.Equal(NullabilityState.Nullable, dictNullability.GenericTypeArguments[1].ReadState); + } + + public static IEnumerable MethodReturnParameterTestData() + { + yield return new object[] { "MethodReturnsUnknown", NullabilityState.Unknown, NullabilityState.Unknown}; + yield return new object[] { "MethodReturnsNullNon", NullabilityState.Nullable, NullabilityState.NotNull }; + yield return new object[] { "MethodReturnsNullNull", NullabilityState.Nullable, NullabilityState.Nullable }; + yield return new object[] { "MethodReturnsNonNull", NullabilityState.NotNull, NullabilityState.Nullable }; + yield return new object[] { "MethodReturnsNonNotNull", NullabilityState.NotNull, NullabilityState.NotNull }; + yield return new object[] { "MethodReturnsNonMaybeNull", NullabilityState.NotNull, NullabilityState.Nullable }; + yield return new object[] { "MethodReturnsNonNon", NullabilityState.NotNull, NullabilityState.NotNull }; + } + + [Theory] + [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [MemberData(nameof(MethodReturnParameterTestData))] + public void MethodReturnParameterTest(string methodName, NullabilityState elementState, NullabilityState readState) + { + MethodInfo method = testType.GetMethod(methodName, flags)!; + NullabilityInfo nullability = nullabilityContext.Create(method.ReturnParameter); + Assert.Equal(readState, nullability.ReadState); + //Assert.Equal(readState, nullability.WriteState); + Assert.NotNull(nullability.ElementType); + Assert.Equal(elementState, nullability.ElementType!.ReadState); + Assert.Empty(nullability.GenericTypeArguments); + } + + public static IEnumerable MethodGenericReturnParameterTestData() + { + yield return new object[] { "MethodReturnsUnknown", NullabilityState.Unknown, NullabilityState.Unknown }; + yield return new object[] { "MethodReturnsGeneric", NullabilityState.Nullable, NullabilityState.Unknown }; + yield return new object[] { "MethodReturnsNullGeneric", NullabilityState.Nullable, NullabilityState.Unknown }; + yield return new object[] { "MethodReturnsGenericNotNull", NullabilityState.NotNull, NullabilityState.Unknown }; + yield return new object[] { "MethodReturnsGenericMaybeNull", NullabilityState.Nullable, NullabilityState.Unknown }; + yield return new object[] { "MethodNonNullListNullGeneric", NullabilityState.NotNull, NullabilityState.Nullable }; + yield return new object[] { "MethodNullListNonNullGeneric", NullabilityState.Nullable, NullabilityState.Nullable }; + } + + [Theory] + [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [MemberData(nameof(MethodGenericReturnParameterTestData))] + public void MethodGenericReturnParameterTest(string methodName, NullabilityState readState, NullabilityState elementState) + { + MethodInfo method = typeof(GenericTest).GetMethod(methodName, flags)!; + NullabilityInfo nullability = nullabilityContext.Create(method.ReturnParameter); + Assert.Equal(readState, nullability.ReadState); + if (nullability.GenericTypeArguments.Length > 0) + { + Assert.Equal(elementState, nullability.GenericTypeArguments[0].ReadState); + } + } + + public static IEnumerable MethodParametersTestData() + { + yield return new object[] { "MethodParametersUnknown", NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown }; + yield return new object[] { "MethodNullNonNullNonNon", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull }; + yield return new object[] { "MethodNonNullNonNullNotNull", NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull }; + yield return new object[] { "MethodNullNonNullNullNon", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull }; + yield return new object[] { "MethodAllowNullNonNonNonNull", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.Nullable }; + } + + [Theory] + [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [MemberData(nameof(MethodParametersTestData))] + public void MethodParametersTest(string methodName, NullabilityState stringState, NullabilityState dictKey, NullabilityState dictValueElement, NullabilityState dictValue, NullabilityState dictionaryState) + { + ParameterInfo[] parameters = testType.GetMethod(methodName, flags)!.GetParameters(); + NullabilityInfo stringNullability = nullabilityContext.Create(parameters[0]); + NullabilityInfo dictionaryNullability = nullabilityContext.Create(parameters[1]); + Assert.Equal(stringState, stringNullability.WriteState); + Assert.Equal(dictionaryState, dictionaryNullability.ReadState); + Assert.NotEmpty(dictionaryNullability.GenericTypeArguments); + Assert.Equal(dictKey, dictionaryNullability.GenericTypeArguments[0].ReadState); + Assert.Equal(dictValue, dictionaryNullability.GenericTypeArguments[1].ReadState); + Assert.Equal(dictValueElement, dictionaryNullability.GenericTypeArguments[1].ElementType!.ReadState); + Assert.Null(dictionaryNullability.ElementType); + } + + public static IEnumerable MethodGenericParametersTestData() + { + yield return new object[] { "MethodParametersUnknown", NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown}; + yield return new object[] { "MethodArgsNullGenericNullDictValueGeneric", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.Nullable }; + yield return new object[] { "MethodArgsGenericDictValueNullGeneric", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull }; + } + + [Theory] + [SkipOnMono("Disabling NullablePublicOnly feature not work for Mono tests")] + [MemberData(nameof(MethodGenericParametersTestData))] + public void MethodGenericParametersTest(string methodName, NullabilityState param1State, NullabilityState dictKey, NullabilityState dictValue, NullabilityState dictionaryState) + { + ParameterInfo[] parameters = typeof(GenericTest).GetMethod(methodName, flags)!.GetParameters(); + NullabilityInfo stringNullability = nullabilityContext.Create(parameters[0]); + NullabilityInfo dictionaryNullability = nullabilityContext.Create(parameters[1]); + Assert.Equal(param1State, stringNullability.WriteState); + Assert.Equal(dictionaryState, dictionaryNullability.ReadState); + Assert.NotEmpty(dictionaryNullability.GenericTypeArguments); + Assert.Equal(dictKey, dictionaryNullability.GenericTypeArguments[0].ReadState); + Assert.Equal(dictValue, dictionaryNullability.GenericTypeArguments[1].ReadState); + } + + public static IEnumerable StringTypeTestData() + { + yield return new object[] { "Format", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.Nullable, new Type[] { typeof(string), typeof(object), typeof(object) } }; + yield return new object[] { "ReplaceCore", NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown, new Type[] { typeof(string), typeof(string), typeof(CompareInfo), typeof(CompareOptions) } }; + yield return new object[] { "Join", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.NotNull, new Type[] { typeof(string), typeof(String?[]), typeof(int), typeof(int) } }; + } + + [Theory] + [SkipOnMono("Nullability attributes trimmed on Mono")] + [MemberData(nameof(StringTypeTestData))] + public void NullablePublicOnlyStringTypeTest(string methodName, NullabilityState param1State, NullabilityState param2State, NullabilityState param3State, Type[] types) + { + ParameterInfo[] parameters = stringType.GetMethod(methodName, flags, types)!.GetParameters(); + NullabilityInfo param1 = nullabilityContext.Create(parameters[0]); + NullabilityInfo param2 = nullabilityContext.Create(parameters[1]); + NullabilityInfo param3 = nullabilityContext.Create(parameters[2]); + Assert.Equal(param1State, param1.ReadState); + Assert.Equal(param2State, param2.ReadState); + Assert.Equal(param3State, param3.ReadState); + if (param2.ElementType != null) + { + Assert.Equal(NullabilityState.Nullable, param2.ElementType.ReadState); + } + } + + [Fact] + [SkipOnMono("Nullability attributes trimmed on Mono")] + public void NullablePublicOnlyOtherTypesTest() + { + Type type = typeof(Type); + FieldInfo privateNullableField = type.GetField("s_defaultBinder", flags)!; + NullabilityInfo info = nullabilityContext.Create(privateNullableField); + Assert.Equal(NullabilityState.Unknown, info.ReadState); + Assert.Equal(NullabilityState.Unknown, info.WriteState); + + MethodInfo internalNotNullableMethod = type.GetMethod("GetRootElementType", flags)!; + info = nullabilityContext.Create(internalNotNullableMethod.ReturnParameter); + Assert.Equal(NullabilityState.NotNull, info.ReadState); + Assert.Equal(NullabilityState.NotNull, info.WriteState); + + PropertyInfo publicNullableProperty = type.GetProperty("DeclaringType", flags)!; + info = nullabilityContext.Create(publicNullableProperty); + Assert.Equal(NullabilityState.Nullable, info.ReadState); + Assert.Equal(NullabilityState.Unknown, info.WriteState); + + PropertyInfo publicGetPrivateSetNullableProperty = typeof(FileSystemEntry).GetProperty("Directory", flags)!; + info = nullabilityContext.Create(publicGetPrivateSetNullableProperty); + Assert.Equal(NullabilityState.NotNull, info.ReadState); + Assert.Equal(NullabilityState.Unknown, info.WriteState); + + MethodInfo protectedNullableReturnMethod = type.GetMethod("GetPropertyImpl", flags)!; + info = nullabilityContext.Create(protectedNullableReturnMethod.ReturnParameter); + Assert.Equal(NullabilityState.Nullable, info.ReadState); + Assert.Equal(NullabilityState.Nullable, info.WriteState); + + MethodInfo privateValueTypeReturnMethod = type.GetMethod("BinarySearch", flags)!; + info = nullabilityContext.Create(privateValueTypeReturnMethod.ReturnParameter); + Assert.Equal(NullabilityState.Unknown, info.ReadState); + Assert.Equal(NullabilityState.Unknown, info.WriteState); + + Type regexType = typeof(Regex); + FieldInfo protectedInternalNullableField = regexType.GetField("pattern", flags)!; + info = nullabilityContext.Create(protectedInternalNullableField); + Assert.Equal(NullabilityState.Nullable, info.ReadState); + Assert.Equal(NullabilityState.Nullable, info.WriteState); + + privateNullableField = regexType.GetField("_code", flags)!; + info = nullabilityContext.Create(privateNullableField); + Assert.Equal(NullabilityState.Unknown, info.ReadState); + Assert.Equal(NullabilityState.Unknown, info.WriteState); + } + + public static IEnumerable DifferentContextTestData() + { + yield return new object[] { "PropertyDisabled", NullabilityState.Unknown, NullabilityState.Unknown, typeof(string) }; + yield return new object[] { "PropertyEnabledAllowNull", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; + yield return new object[] { "PropertyEnabledNotNull", NullabilityState.NotNull, NullabilityState.Nullable, typeof(string) }; + yield return new object[] { "PropertyEnabledMaybeNull", NullabilityState.Nullable, NullabilityState.NotNull, typeof(string) }; + yield return new object[] { "PropertyEnabledDisallowNull", NullabilityState.Nullable, NullabilityState.NotNull, typeof(string) }; + yield return new object[] { "PropertyEnabledNullable", NullabilityState.Nullable, NullabilityState.Nullable, typeof(string) }; + yield return new object[] { "PropertyEnabledNonNullable", NullabilityState.NotNull, NullabilityState.NotNull, typeof(string) }; + } + [Theory] + [MemberData(nameof(DifferentContextTestData))] + public void NullabilityDifferentContextTest(string propertyName, NullabilityState readState, NullabilityState writeState, Type type) + { + Type noContext = typeof(TypeWithNoContext); + PropertyInfo property = noContext.GetProperty(propertyName, flags)!; + NullabilityInfo nullability = nullabilityContext.Create(property); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + Assert.Equal(type, nullability.Type); + + Type nullableContext = typeof(TypeWithNullableContext); + property = nullableContext.GetProperty(propertyName, flags)!; + nullability = nullabilityContext.Create(property); + Assert.Equal(readState, nullability.ReadState); + Assert.Equal(writeState, nullability.WriteState); + Assert.Equal(type, nullability.Type); + } + + [Fact] + public void AttributedParametersTest() + { + Type type = typeof(TypeWithNullableContext); + + // bool NotNullWhenParameter([DisallowNull] string? disallowNull, [NotNullWhen(true)] ref string? notNullWhen, Type? nullableType); + ParameterInfo[] notNullWhenParameters = type.GetMethod("NotNullWhenParameter", flags)!.GetParameters(); + NullabilityInfo disallowNull = nullabilityContext.Create(notNullWhenParameters[0]); + NullabilityInfo notNullWhen = nullabilityContext.Create(notNullWhenParameters[1]); + Assert.Equal(NullabilityState.Nullable, disallowNull.ReadState); + Assert.Equal(NullabilityState.NotNull, disallowNull.WriteState); + Assert.Equal(NullabilityState.Nullable, notNullWhen.ReadState); + Assert.Equal(NullabilityState.Nullable, notNullWhen.WriteState); + Assert.Equal(NullabilityState.Nullable, nullabilityContext.Create(notNullWhenParameters[1]).ReadState); + + // bool MaybeNullParameters([MaybeNull] string maybeNull, [MaybeNullWhen(false)] out string maybeNullWhen, Type? nullableType) + ParameterInfo[] maybeNullParameters = type.GetMethod("MaybeNullParameters", flags)!.GetParameters(); + NullabilityInfo maybeNull = nullabilityContext.Create(maybeNullParameters[0]); + NullabilityInfo maybeNullWhen = nullabilityContext.Create(maybeNullParameters[1]); + Assert.Equal(NullabilityState.Nullable, maybeNull.ReadState); + Assert.Equal(NullabilityState.NotNull, maybeNull.WriteState); + Assert.Equal(NullabilityState.Nullable, maybeNullWhen.ReadState); + Assert.Equal(NullabilityState.NotNull, maybeNullWhen.WriteState); + Assert.Equal(NullabilityState.Nullable, nullabilityContext.Create(maybeNullParameters[1]).ReadState); + + // string? AllowNullParameter([AllowNull] string allowNull, [NotNullIfNotNull("allowNull")] string? notNullIfNotNull) + ParameterInfo[] allowNullParameter = type.GetMethod("AllowNullParameter", flags)!.GetParameters(); + NullabilityInfo allowNull = nullabilityContext.Create(allowNullParameter[0]); + NullabilityInfo notNullIfNotNull = nullabilityContext.Create(allowNullParameter[1]); + Assert.Equal(NullabilityState.NotNull, allowNull.ReadState); + Assert.Equal(NullabilityState.Nullable, allowNull.WriteState); + Assert.Equal(NullabilityState.Nullable, notNullIfNotNull.ReadState); + Assert.Equal(NullabilityState.Nullable, notNullIfNotNull.WriteState); + Assert.Equal(NullabilityState.Nullable, nullabilityContext.Create(allowNullParameter[1]).ReadState); + + // [return: NotNullIfNotNull("nullable")] public string? NullablNotNullIfNotNullReturn(string? nullable, [NotNull] ref string? readNotNull) + ParameterInfo[] nullablNotNullIfNotNullReturn = type.GetMethod("NullablNotNullIfNotNullReturn", flags)!.GetParameters(); + NullabilityInfo returnNotNullIfNotNull = nullabilityContext.Create(type.GetMethod("NullablNotNullIfNotNullReturn", flags)!.ReturnParameter); + NullabilityInfo readNotNull = nullabilityContext.Create(nullablNotNullIfNotNullReturn[1]); + Assert.Equal(NullabilityState.Nullable, returnNotNullIfNotNull.ReadState); + Assert.Equal(NullabilityState.Nullable, returnNotNullIfNotNull.WriteState); + Assert.Equal(NullabilityState.NotNull, readNotNull.ReadState); + Assert.Equal(NullabilityState.Nullable, readNotNull.WriteState); + Assert.Equal(NullabilityState.Nullable, nullabilityContext.Create(nullablNotNullIfNotNullReturn[0]).ReadState); + + // public bool TryGetOutParameters(string id, [NotNullWhen(true)] out string? value, [MaybeNullWhen(false)] out string value2) + ParameterInfo[] tryGetOutParameters = type.GetMethod("TryGetOutParameters", flags)!.GetParameters(); + NullabilityInfo notNullWhenParam = nullabilityContext.Create(tryGetOutParameters[1]); + NullabilityInfo maybeNullWhenParam = nullabilityContext.Create(tryGetOutParameters[2]); + Assert.Equal(NullabilityState.Nullable, notNullWhenParam.ReadState); + Assert.Equal(NullabilityState.Nullable, notNullWhenParam.WriteState); + Assert.Equal(NullabilityState.Nullable, maybeNullWhenParam.ReadState); + Assert.Equal(NullabilityState.NotNull, maybeNullWhenParam.WriteState); + Assert.Equal(NullabilityState.NotNull, nullabilityContext.Create(tryGetOutParameters[0]).ReadState); + } + + public static IEnumerable RefReturnData() + { + yield return new object[] { "RefReturnUnknown", NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown }; + // [return: MaybeNull] public ref string RefReturnMaybeNull([DisallowNull] ref string? id) + yield return new object[] { "RefReturnMaybeNull", NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull }; + // public ref string RefReturnNotNullable([MaybeNull] ref string id) + yield return new object[] { "RefReturnNotNullable", NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull }; + // [return: NotNull]public ref string? RefReturnNotNull([NotNull] ref string? id) + yield return new object[] { "RefReturnNotNull", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; + // publiic ref string? RefReturnNullable([AllowNull] ref string id) + yield return new object[] { "RefReturnNullable", NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; + } + + [Theory] + [MemberData(nameof(RefReturnData))] + public void RefReturnTestTest(string methodName, NullabilityState retReadState, NullabilityState retWriteState, NullabilityState paramReadState, NullabilityState paramWriteState) + { + MethodInfo method = typeof(TypeWithNullableContext).GetMethod(methodName, flags)!; + NullabilityInfo returnNullability = nullabilityContext.Create(method.ReturnParameter); + NullabilityInfo paramNullability = nullabilityContext.Create(method.GetParameters()[0]); + Assert.Equal(retReadState, returnNullability.ReadState); + Assert.Equal(retWriteState, returnNullability.WriteState); + Assert.Equal(paramReadState, paramNullability.ReadState); + Assert.Equal(paramWriteState, paramNullability.WriteState); + } + } + +#pragma warning disable CS0649, CS0067, CS0414 + public class TypeWithNullableContext + { +#nullable disable + public string PropertyDisabled { get; set; } + public ref string RefReturnUnknown(ref string id) { return ref id; } +#nullable enable + [AllowNull] public string PropertyEnabledAllowNull { get; set; } + [NotNull] public string? PropertyEnabledNotNull { get; set; } = null!; + [DisallowNull] public string? PropertyEnabledDisallowNull { get; set; } = null!; + [MaybeNull] public string PropertyEnabledMaybeNull { get; set; } + public string? PropertyEnabledNullable { get; set; } + public string PropertyEnabledNonNullable { get; set; } = null!; + bool NotNullWhenParameter([DisallowNull] string? disallowNull, [NotNullWhen(true)] ref string? notNullWhen, Type? nullableType) { return false; } + public bool MaybeNullParameters([MaybeNull] string maybeNull, [MaybeNullWhen(false)] out string maybeNullWhen, Type? nullableType) { maybeNullWhen = null; return false; } + public string? AllowNullParameter([AllowNull] string allowNull, [NotNullIfNotNull("allowNull")] string? notNullIfNotNull) { return null; } + [return: NotNullIfNotNull("nullable")] public string? NullablNotNullIfNotNullReturn(string? nullable, [NotNull] ref string? readNotNull) { readNotNull = string.Empty; return null!; } + public ref string? RefReturnNullable([AllowNull] ref string id) { return ref id!; } + [return: MaybeNull] public ref string RefReturnMaybeNull([DisallowNull] ref string? id) { return ref id; } + [return: NotNull] public ref string? RefReturnNotNull([NotNull] ref string? id) { id = string.Empty; return ref id!; } + public ref string RefReturnNotNullable([MaybeNull] ref string id) { return ref id; } + public bool TryGetOutParameters(string id, [NotNullWhen(true)] out string? value, [MaybeNullWhen(false)] out string value2) { value = null; value2 = null; return false; } + } + + public class TypeWithNoContext + { +#nullable disable + [AllowNull] public string PropertyDisabledAllowNull { get; set; } + [MaybeNull] public string PropertyDisabledMaybeNull { get; set; } + public string PropertyDisabled { get; set; } +#nullable enable + [AllowNull] public string PropertyEnabledAllowNull { get; set; } + [NotNull] public string? PropertyEnabledNotNull { get; set; } = null!; + [DisallowNull] public string? PropertyEnabledDisallowNull { get; set; } = null!; + [MaybeNull] public string PropertyEnabledMaybeNull { get; set; } + public string? PropertyEnabledNullable { get; set; } + public string PropertyEnabledNonNullable { get; set; } = null!; +#nullable disable + [return: NotNull, MaybeNull] + public string MethodNullableDisabled([AllowNull] string value, string ret) { return null; } + } + + public class TypeWithNotNullContext + { + public string PropertyUnknown { get; set; } + short PropertyValueTypeUnknown { get; set; } + public string[] PropertyArrayUnknown { get; set; } + private string[][] PropertyJaggedArrayUnknown { get; set; } + protected Tuple PropertyTupleUnknown { get; set; } + protected internal IDictionary PropertyDictionaryUnknown { get; set; } + + internal TypeWithNotNullContext FieldUnknown; + public int FieldValueTypeUnknown; + + public event EventHandler EventUnknown; + public string[] MethodReturnsUnknown() => null!; + public void MethodParametersUnknown(string s, IDictionary dict) { } +#nullable enable + public TypeWithNotNullContext? PropertyNullable { get; set; } + public TypeWithNotNullContext? PropertyNullableReadOnly { get; } + private NullabilityInfoContextTests PropertyNonNullable { get; set; } = null!; + internal float PropertyValueType { get; set; } + protected long? PropertyValueTypeNullable { get; set; } + [DisallowNull] public int? PropertyValueTypeDisallowNull { get; set; } + [NotNull] protected int? PropertyValueTypeNotNull { get; set; } + [MaybeNull] public byte PropertyValueTypeMaybeNull { get; set; } + [AllowNull] public byte PropertyValueTypeAllowNull { get; set; } + [DisallowNull] public string? PropertyDisallowNull { get; set; } + [AllowNull] public string PropertyAllowNull { get; set; } + [NotNull] public string? PropertyNotNull { get; set; } + [MaybeNull] public string PropertyMaybeNull { get; set; } + // only AllowNull matter + [AllowNull, DisallowNull] public string PropertyAllowNull2 { get; set; } + // only DisallowNull matter + [AllowNull, DisallowNull] public string? PropertyDisallowNull2 { get; set; } + // only NotNull matter + [NotNull, MaybeNull] public string? PropertyNotNull2 { get; set; } + // only MaybeNull matter + [NotNull, MaybeNull] public string PropertyMaybeNull2 { get; set; } + private protected string?[]?[]? PropertyJaggedArrayNullNullNull { get; set; } + public static string?[]?[] PropertyJaggedArrayNullNullNon { get; set; } = null!; + public string?[][]? PropertyJaggedArrayNullNonNull { get; set; } + public static string[]?[]? PropertyJaggedArrayNonNullNull { get; set; } + public string?[][] PropertyJaggedArrayNullNonNon { get; set; } = null!; + private static string[][]? PropertyJaggedArrayNonNonNull { get; set; } + public string?[]? PropertyArrayNullNull { get; set; } + static string?[] PropertyArrayNullNon { get; set; } = null!; + public string[]? PropertyArrayNonNull { get; } = null; + public string[] PropertyArrayNonNon { get; set; } = null!; + public Tuple? PropertyTupleNullNullNullNull { get; set; } + public Tuple PropertyTupleNonNullNonNon { get; set; } = null!; + internal Tuple? PropertyTupleNullNonNullNull { get; set; } + public Tuple? PropertyTupleNonNullNonNull { get; set; } + protected Tuple PropertyTupleNonNonNonNon { get; set; } = null!; + public IDictionary PropertyDictionaryNullNullNullNon { get; set; } = null!; + public IDictionary? PropertyDictionaryNonNullNonNull { get; set; } + IDictionary? PropertyDictionaryNullNonNonNull { get; set; } + public IDictionary PropertyDictionaryNonNullNonNon { get; set; } = null!; + private IDictionary? PropertyDictionaryNonNonNonNull { get; set; } + public IDictionary PropertyDictionaryNonNonNonNon { get; set; } = null!; + + private const string? FieldNullable = null; + protected static NullabilityInfoContextTests FieldNonNullable = null!; + public static double FieldValueTypeNotNull; + public readonly int? FieldValueTypeNullable; + [DisallowNull] public string? FieldDisallowNull; + [AllowNull] public string FieldAllowNull; + [NotNull] string? FieldNotNull = null; + [MaybeNull] public string FieldMaybeNull; + [AllowNull, DisallowNull] public string FieldAllowNull2; + [AllowNull, DisallowNull] public string? FieldDisallowNull2; + [NotNull, MaybeNull] internal string? FieldNotNull2; + [NotNull, MaybeNull] public string FieldMaybeNull2; + + public event EventHandler? EventNullable; + public event EventHandler EventNotNull = null!; + public string?[] MethodReturnsNullNon() => null!; + public string?[]? MethodReturnsNullNull() => null; + public string[]? MethodReturnsNonNull() => null; + [return: NotNull, MaybeNull] public string[]? MethodReturnsNonNotNull() => null!; // only NotNull is applicable + [return: MaybeNull] public string[] MethodReturnsNonMaybeNull() => null; + public string[] MethodReturnsNonNon() => null!; + public Tuple? MethodTupleNullNonNull() => null; + public IEnumerable?> MethodEnumerableNonNonNullUnknownNullNonNullNon() => null!; + public void MethodNullNonNullNonNon(string? s, IDictionary dict) { } + public void MethodNonNullNonNullNotNull(string s, [NotNull] IDictionary? dict) { dict = new Dictionary(); } + public void MethodNullNonNullNullNon(string? s, IDictionary dict) { } + public void MethodAllowNullNonNonNonNull([AllowNull] string s, IDictionary? dict) { } + } + + internal class GenericTest + { +#nullable disable + public T PropertyUnknown { get; set; } + protected T[] PropertyArrayUnknown { get; set; } + public Tuple PropertyTupleUnknown { get; set; } + private IDictionary PropertyDictionaryUnknown { get; set; } + public T FieldUnknown; + public T MethodReturnsUnknown() => default!; + public void MethodParametersUnknown(T s, IDictionary dict) { } +#nullable enable + + public T PropertyNonNullable { get; set; } = default!; + public T? PropertyNullable { get; set; } + [DisallowNull] public T PropertyDisallowNull { get; set; } = default!; + [NotNull] public T PropertyNotNull { get; set; } = default!; + [MaybeNull] public T PropertyMaybeNull { get; set; } + [AllowNull] public T PropertyAllowNull { get; set; } + internal T?[]? PropertyArrayNullNull { get; set; } + public T?[] PropertyArrayNullNon { get; set; } = null!; + T[]? PropertyArrayNonNull { get; set; } + public T[] PropertyArrayNonNon { get; set; } = null!; + public Tuple? PropertyTupleNullNullNullNull { get; set; } + public Tuple PropertyTupleNonNullNonNon { get; set; } = null!; + Tuple? PropertyTupleNullNonNullNull { get; set; } + public Tuple? PropertyTupleNonNullNonNull { get; set; } + public Tuple PropertyTupleNonNonNonNon { get; set; } = null!; + private IDictionary PropertyDictionaryNullNullNullNon { get; set; } = null!; + static IDictionary? PropertyDictionaryNonNullNonNull { get; set; } + public static IDictionary? PropertyDictionaryNullNonNonNull { get; set; } + public IDictionary PropertyDictionaryNonNullNonNon { get; set; } = null!; + protected IDictionary? PropertyDictionaryNonNonNonNull { get; set; } + public IDictionary PropertyDictionaryNonNonNonNon { get; set; } = null!; + + static T? FieldNullable = default; + public T FieldNonNullable = default!; + [DisallowNull] public T? FieldDisallowNull; + [AllowNull] protected T FieldAllowNull; + [NotNull] public T? FieldNotNull = default; + [MaybeNull] protected internal T FieldMaybeNull = default!; + public List FieldListOfT = default!; + public Dictionary FieldDictionaryStringToT = default!; + + public T MethodReturnsGeneric() => default!; + public T? MethodReturnsNullGeneric() => default; + [return: NotNull] public T MethodReturnsGenericNotNull() => default!; + [return: MaybeNull] public T MethodReturnsGenericMaybeNull() => default; + public List MethodNonNullListNullGeneric() => null!; + public List? MethodNullListNonNullGeneric() => null; + public void MethodArgsNullGenericNullDictValueGeneric(T? s, IDictionary? dict) { } + public void MethodArgsGenericDictValueNullGeneric(T s, IDictionary dict) { } + } + + internal class GenericTestConstrainedNotNull where T : notnull + { +#nullable disable + public T FieldUnknown; + public T PropertyUnknown { get; set; } +#nullable enable + + public T FieldNullableEnabled = default!; + public T? FieldNullable; + public T PropertyNullableEnabled { get; set; } = default!; + } + + internal class GenericTestConstrainedStruct where T : struct + { +#nullable disable + public T FieldUnknown; + public T PropertyUnknown { get; set; } +#nullable enable + + public T FieldNullableEnabled; + public T? FieldNullable; + public T PropertyNullableEnabled { get; set; } + } +} From 1c3d401d329c3305ca9b8fdbc36cb4d5ceba1a63 Mon Sep 17 00:00:00 2001 From: SingleAccretion <62474226+SingleAccretion@users.noreply.github.com> Date: Tue, 13 Jul 2021 19:39:21 +0300 Subject: [PATCH 075/133] Import `cgt.un(op, 0)` as `NE(op, 0)` (#54539) * Some minor code modernization Use "genActualType" directly as it is now a templated method. Don't create casts to TYP_ULONG - they are identical to casts to TYP_LONG. TYP_ULONG is only relevant for checked casts. Add a TODO on addressing the duplicated logic that upcasts to native int from int32 on 64 bit. Use modern comments. Zero diffs. * Normalize GT_UN(op, 0) early in importer Normalizing this idiom helps downstream optimizations. * Solve most of the regressions In morph, when narrowing the AND operand, only insert casts if necessary - prefer to use "optNarrowTree". Otherwise we end up with redundant register shuffling. --- src/coreclr/jit/importer.cpp | 29 ++++++++++------ src/coreclr/jit/morph.cpp | 64 +++++++++++++++++++++--------------- 2 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index d49a12ffd13d..c30e9a177846 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -13075,27 +13075,36 @@ void Compiler::impImportBlockCode(BasicBlock* block) op2 = impPopStack().val; op1 = impPopStack().val; + // Recognize the IL idiom of CGT_UN(op1, 0) and normalize + // it so that downstream optimizations don't have to. + if ((opcode == CEE_CGT_UN) && op2->IsIntegralConst(0)) + { + oper = GT_NE; + uns = false; + } + #ifdef TARGET_64BIT - if (varTypeIsI(op1->TypeGet()) && (genActualType(op2->TypeGet()) == TYP_INT)) + // TODO-Casts: create a helper that upcasts int32 -> native int when necessary. + // See also identical code in impGetByRefResultType and STSFLD import. + if (varTypeIsI(op1) && (genActualType(op2) == TYP_INT)) { - op2 = gtNewCastNode(TYP_I_IMPL, op2, uns, uns ? TYP_U_IMPL : TYP_I_IMPL); + op2 = gtNewCastNode(TYP_I_IMPL, op2, uns, TYP_I_IMPL); } - else if (varTypeIsI(op2->TypeGet()) && (genActualType(op1->TypeGet()) == TYP_INT)) + else if (varTypeIsI(op2) && (genActualType(op1) == TYP_INT)) { - op1 = gtNewCastNode(TYP_I_IMPL, op1, uns, uns ? TYP_U_IMPL : TYP_I_IMPL); + op1 = gtNewCastNode(TYP_I_IMPL, op1, uns, TYP_I_IMPL); } #endif // TARGET_64BIT - assertImp(genActualType(op1->TypeGet()) == genActualType(op2->TypeGet()) || - (varTypeIsI(op1->TypeGet()) && varTypeIsI(op2->TypeGet())) || - (varTypeIsFloating(op1->gtType) && varTypeIsFloating(op2->gtType))); + assertImp(genActualType(op1) == genActualType(op2) || (varTypeIsI(op1) && varTypeIsI(op2)) || + (varTypeIsFloating(op1) && varTypeIsFloating(op2))); - /* Create the comparison node */ + // Create the comparison node. op1 = gtNewOperNode(oper, TYP_INT, op1, op2); - /* TODO: setting both flags when only one is appropriate */ - if (opcode == CEE_CGT_UN || opcode == CEE_CLT_UN) + // TODO: setting both flags when only one is appropriate. + if (uns) { op1->gtFlags |= GTF_RELOP_NAN_UN | GTF_UNSIGNED; } diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index f0bc076f2356..354b1e4a3574 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -12465,44 +12465,54 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) noway_assert(op1->TypeGet() == TYP_LONG && op1->OperGet() == GT_AND); - /* Is the result of the mask effectively an INT ? */ - - GenTree* andMask; - andMask = op1->AsOp()->gtOp2; - if (andMask->gtOper != GT_CNS_NATIVELONG) - { - goto COMPARE; - } - if ((andMask->AsIntConCommon()->LngValue() >> 32) != 0) + // The transform below cannot preserve VNs. + if (fgGlobalMorph) { - goto COMPARE; - } + // Is the result of the mask effectively an INT ? - /* Now we know that we can cast AsOp()->gtOp1 of AND to int */ + GenTree* andMask = op1->AsOp()->gtOp2; + + if (andMask->gtOper != GT_CNS_NATIVELONG) + { + goto COMPARE; + } + if ((andMask->AsIntConCommon()->LngValue() >> 32) != 0) + { + goto COMPARE; + } - op1->AsOp()->gtOp1 = gtNewCastNode(TYP_INT, op1->AsOp()->gtOp1, false, TYP_INT); + // Now we narrow AsOp()->gtOp1 of AND to int. + if (optNarrowTree(op1->AsOp()->gtGetOp1(), TYP_LONG, TYP_INT, ValueNumPair(), false)) + { + optNarrowTree(op1->AsOp()->gtGetOp1(), TYP_LONG, TYP_INT, ValueNumPair(), true); + } + else + { + op1->AsOp()->gtOp1 = gtNewCastNode(TYP_INT, op1->AsOp()->gtGetOp1(), false, TYP_INT); + } - /* now replace the mask node (AsOp()->gtOp2 of AND node) */ + // now replace the mask node (AsOp()->gtOp2 of AND node). - noway_assert(andMask == op1->AsOp()->gtOp2); + noway_assert(andMask == op1->AsOp()->gtOp2); - ival1 = (int)andMask->AsIntConCommon()->LngValue(); - andMask->SetOper(GT_CNS_INT); - andMask->gtType = TYP_INT; - andMask->AsIntCon()->gtIconVal = ival1; + ival1 = (int)andMask->AsIntConCommon()->LngValue(); + andMask->SetOper(GT_CNS_INT); + andMask->gtType = TYP_INT; + andMask->AsIntCon()->gtIconVal = ival1; - /* now change the type of the AND node */ + // now change the type of the AND node. - op1->gtType = TYP_INT; + op1->gtType = TYP_INT; - /* finally we replace the comparand */ + // finally we replace the comparand. - ival2 = (int)cns2->AsIntConCommon()->LngValue(); - cns2->SetOper(GT_CNS_INT); - cns2->gtType = TYP_INT; + ival2 = (int)cns2->AsIntConCommon()->LngValue(); + cns2->SetOper(GT_CNS_INT); + cns2->gtType = TYP_INT; - noway_assert(cns2 == op2); - cns2->AsIntCon()->gtIconVal = ival2; + noway_assert(cns2 == op2); + cns2->AsIntCon()->gtIconVal = ival2; + } goto COMPARE; From 430d87f67d29bb2001ce9b53509488db754deab4 Mon Sep 17 00:00:00 2001 From: Natalia Kondratyeva Date: Tue, 13 Jul 2021 18:49:47 +0200 Subject: [PATCH 076/133] QUIC read pipeline changes (#55505) This brings changes to read states and behavior done initially in #52929 with my fixes to it to make all tests work --- .../Implementations/MsQuic/MsQuicStream.cs | 312 ++++++++++++------ .../tests/FunctionalTests/QuicStreamTests.cs | 122 ++++++- .../tests/FunctionalTests/QuicTestBase.cs | 54 ++- 3 files changed, 368 insertions(+), 120 deletions(-) diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs index 437f81ce7759..585aca851b3f 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs @@ -36,11 +36,20 @@ private sealed class State public string TraceId = null!; // set in ctor. public ReadState ReadState; + + // set when ReadState.Aborted: public long ReadErrorCode = -1; - public readonly List ReceiveQuicBuffers = new List(); - // Resettable completions to be used for multiple calls to receive. - public readonly ResettableCompletionSource ReceiveResettableCompletionSource = new ResettableCompletionSource(); + // filled when ReadState.BuffersAvailable: + public QuicBuffer[] ReceiveQuicBuffers = Array.Empty(); + public int ReceiveQuicBuffersCount; + public int ReceiveQuicBuffersTotalBytes; + + // set when ReadState.PendingRead: + public Memory ReceiveUserBuffer; + public CancellationTokenRegistration ReceiveCancellationRegistration; + public MsQuicStream? RootedReceiveStream; // roots the stream in the pinned state to prevent GC during an async read I/O. + public readonly ResettableCompletionSource ReceiveResettableCompletionSource = new ResettableCompletionSource(); public SendState SendState; public long SendErrorCode = -1; @@ -340,7 +349,7 @@ private void HandleWriteFailedState() } } - internal override async ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) + internal override ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) { ThrowIfDisposed(); @@ -349,109 +358,151 @@ internal override async ValueTask ReadAsync(Memory destination, Cance throw new InvalidOperationException(SR.net_quic_reading_notallowed); } - if (cancellationToken.IsCancellationRequested) - { - lock (_state) - { - if (_state.ReadState == ReadState.None) - { - _state.ReadState = ReadState.Aborted; - } - } - - throw new OperationCanceledException(cancellationToken); - } - if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(_state, $"{TraceId()} Stream reading into Memory of '{destination.Length}' bytes."); } + ReadState readState; + long abortError = -1; + bool canceledSynchronously = false; + lock (_state) { - if (_state.ReadState == ReadState.ReadsCompleted) - { - return 0; - } - else if (_state.ReadState == ReadState.Aborted) + readState = _state.ReadState; + abortError = _state.ReadErrorCode; + + if (readState != ReadState.PendingRead && cancellationToken.IsCancellationRequested) { - throw ThrowHelper.GetStreamAbortedException(_state.ReadErrorCode); + readState = ReadState.Aborted; + _state.ReadState = ReadState.Aborted; + canceledSynchronously = true; } - else if (_state.ReadState == ReadState.ConnectionClosed) + else if (readState == ReadState.None) { - throw GetConnectionAbortedException(_state); - } - } + Debug.Assert(_state.RootedReceiveStream is null); - using CancellationTokenRegistration registration = cancellationToken.UnsafeRegister(static (s, token) => - { - var state = (State)s!; - bool shouldComplete = false; - lock (state) - { - if (state.ReadState == ReadState.None) + _state.ReceiveUserBuffer = destination; + _state.RootedReceiveStream = this; + _state.ReadState = ReadState.PendingRead; + + if (cancellationToken.CanBeCanceled) { - shouldComplete = true; + _state.ReceiveCancellationRegistration = cancellationToken.UnsafeRegister(static (obj, token) => + { + var state = (State)obj!; + bool completePendingRead; + + lock (state) + { + completePendingRead = state.ReadState == ReadState.PendingRead; + state.RootedReceiveStream = null; + state.ReceiveUserBuffer = null; + state.ReadState = ReadState.Aborted; + } + + if (completePendingRead) + { + state.ReceiveResettableCompletionSource.CompleteException(ExceptionDispatchInfo.SetCurrentStackTrace(new OperationCanceledException(token))); + } + }, _state); + } + else + { + _state.ReceiveCancellationRegistration = default; } - state.ReadState = ReadState.Aborted; - } - if (shouldComplete) - { - state.ReceiveResettableCompletionSource.CompleteException( - ExceptionDispatchInfo.SetCurrentStackTrace(new OperationCanceledException("Read was canceled", token))); + return _state.ReceiveResettableCompletionSource.GetValueTask(); } - }, _state); - - // TODO there could potentially be a perf gain by storing the buffer from the initial read - // This reduces the amount of async calls, however it makes it so MsQuic holds onto the buffers - // longer than it needs to. We will need to benchmark this. - int length = (int)await _state.ReceiveResettableCompletionSource.GetValueTask().ConfigureAwait(false); + else if (readState == ReadState.IndividualReadComplete) + { + _state.ReadState = ReadState.None; - int actual = Math.Min(length, destination.Length); + int taken = CopyMsQuicBuffersToUserBuffer(_state.ReceiveQuicBuffers.AsSpan(0, _state.ReceiveQuicBuffersCount), destination.Span); + ReceiveComplete(taken); - static unsafe void CopyToBuffer(Span destinationBuffer, List sourceBuffers) - { - Span slicedBuffer = destinationBuffer; - for (int i = 0; i < sourceBuffers.Count; i++) - { - QuicBuffer nativeBuffer = sourceBuffers[i]; - int length = Math.Min((int)nativeBuffer.Length, slicedBuffer.Length); - new Span(nativeBuffer.Buffer, length).CopyTo(slicedBuffer); - if (length < nativeBuffer.Length) + if (taken != _state.ReceiveQuicBuffersTotalBytes) { - // The buffer passed in was larger that the received data, return - return; + // Need to re-enable receives because MsQuic will pause them when we don't consume the entire buffer. + EnableReceive(); } - slicedBuffer = slicedBuffer.Slice(length); + + return new ValueTask(taken); } } - CopyToBuffer(destination.Span, _state.ReceiveQuicBuffers); + Exception? ex = null; - lock (_state) + switch (readState) { - if (_state.ReadState == ReadState.IndividualReadComplete) - { - _state.ReceiveQuicBuffers.Clear(); - ReceiveComplete(actual); - EnableReceive(); - _state.ReadState = ReadState.None; - } + case ReadState.ReadsCompleted: + return new ValueTask(0); + case ReadState.PendingRead: + ex = new InvalidOperationException("Only one read is supported at a time."); + break; + case ReadState.Aborted: + ex = + canceledSynchronously ? new OperationCanceledException(cancellationToken) : // aborted by token being canceled before the async op started. + abortError == -1 ? new QuicOperationAbortedException() : // aborted by user via some other operation. + new QuicStreamAbortedException(abortError); // aborted by peer. + + break; + case ReadState.ConnectionClosed: + default: + Debug.Assert(readState == ReadState.ConnectionClosed, $"{nameof(ReadState)} of '{readState}' is unaccounted for in {nameof(ReadAsync)}."); + ex = GetConnectionAbortedException(_state); + break; } - return actual; + return ValueTask.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(ex!)); + } + + /// The number of bytes copied. + private static unsafe int CopyMsQuicBuffersToUserBuffer(ReadOnlySpan sourceBuffers, Span destinationBuffer) + { + Debug.Assert(sourceBuffers.Length != 0); + + int originalDestinationLength = destinationBuffer.Length; + QuicBuffer nativeBuffer; + int takeLength = 0; + int i = 0; + + do + { + nativeBuffer = sourceBuffers[i]; + takeLength = Math.Min((int)nativeBuffer.Length, destinationBuffer.Length); + + new Span(nativeBuffer.Buffer, takeLength).CopyTo(destinationBuffer); + destinationBuffer = destinationBuffer.Slice(takeLength); + } + while (destinationBuffer.Length != 0 && ++i < sourceBuffers.Length); + + return originalDestinationLength - destinationBuffer.Length; } - // TODO do we want this to be a synchronization mechanism to cancel a pending read - // If so, we need to complete the read here as well. internal override void AbortRead(long errorCode) { ThrowIfDisposed(); + bool shouldComplete = false; lock (_state) { - _state.ReadState = ReadState.Aborted; + if (_state.ReadState == ReadState.PendingRead) + { + shouldComplete = true; + _state.RootedReceiveStream = null; + _state.ReceiveUserBuffer = null; + } + if (_state.ReadState < ReadState.ReadsCompleted) + { + _state.ReadState = ReadState.Aborted; + } + } + + if (shouldComplete) + { + _state.ReceiveResettableCompletionSource.CompleteException( + ExceptionDispatchInfo.SetCurrentStackTrace(new QuicOperationAbortedException("Read was aborted"))); } StartShutdown(QUIC_STREAM_SHUTDOWN_FLAGS.ABORT_RECEIVE, errorCode); @@ -702,7 +753,8 @@ private void Dispose(bool disposing) private void EnableReceive() { - MsQuicApi.Api.StreamReceiveSetEnabledDelegate(_state.Handle, enabled: true); + uint status = MsQuicApi.Api.StreamReceiveSetEnabledDelegate(_state.Handle, enabled: true); + QuicExceptionHelpers.ThrowIfFailed(status, "StreamReceiveSetEnabled failed."); } private static uint NativeCallbackHandler( @@ -776,31 +828,80 @@ private static uint HandleEvent(State state, ref StreamEvent evt) private static unsafe uint HandleEventRecv(State state, ref StreamEvent evt) { - StreamEventDataReceive receiveEvent = evt.Data.Receive; - for (int i = 0; i < receiveEvent.BufferCount; i++) + ref StreamEventDataReceive receiveEvent = ref evt.Data.Receive; + + if (receiveEvent.BufferCount == 0) { - state.ReceiveQuicBuffers.Add(receiveEvent.Buffers[i]); + // This is a 0-length receive that happens once reads are finished (via abort or otherwise). + // State changes for this are handled elsewhere. + return MsQuicStatusCodes.Success; } + int readLength; + bool shouldComplete = false; lock (state) { - if (state.ReadState == ReadState.None) + switch (state.ReadState) { - shouldComplete = true; - } - if (state.ReadState != ReadState.ConnectionClosed) - { - state.ReadState = ReadState.IndividualReadComplete; + case ReadState.None: + // ReadAsync() hasn't been called yet. Stash the buffer so the next ReadAsync call completes synchronously. + + if ((uint)state.ReceiveQuicBuffers.Length < receiveEvent.BufferCount) + { + QuicBuffer[] oldReceiveBuffers = state.ReceiveQuicBuffers; + state.ReceiveQuicBuffers = ArrayPool.Shared.Rent((int)receiveEvent.BufferCount); + + if (oldReceiveBuffers.Length != 0) // don't return Array.Empty. + { + ArrayPool.Shared.Return(oldReceiveBuffers); + } + } + + for (uint i = 0; i < receiveEvent.BufferCount; ++i) + { + state.ReceiveQuicBuffers[i] = receiveEvent.Buffers[i]; + } + + state.ReceiveQuicBuffersCount = (int)receiveEvent.BufferCount; + state.ReceiveQuicBuffersTotalBytes = checked((int)receiveEvent.TotalBufferLength); + state.ReadState = ReadState.IndividualReadComplete; + return MsQuicStatusCodes.Pending; + case ReadState.PendingRead: + // There is a pending ReadAsync(). + + state.ReceiveCancellationRegistration.Unregister(); + shouldComplete = true; + state.RootedReceiveStream = null; + state.ReadState = ReadState.None; + + readLength = CopyMsQuicBuffersToUserBuffer(new ReadOnlySpan(receiveEvent.Buffers, (int)receiveEvent.BufferCount), state.ReceiveUserBuffer.Span); + state.ReceiveUserBuffer = null; + break; + default: + Debug.Assert(state.ReadState is ReadState.Aborted or ReadState.ConnectionClosed, $"Unexpected {nameof(ReadState)} '{state.ReadState}' in {nameof(HandleEventRecv)}."); + + // There was a race between a user aborting the read stream and the callback being ran. + // This will eat any received data. + return MsQuicStatusCodes.Success; } } + // We're completing a pending read. if (shouldComplete) { - state.ReceiveResettableCompletionSource.Complete((uint)receiveEvent.TotalBufferLength); + state.ReceiveResettableCompletionSource.Complete(readLength); } - return MsQuicStatusCodes.Pending; + // Returning Success when the entire buffer hasn't been consumed will cause MsQuic to disable further receive events until EnableReceive() is called. + // Returning Continue will cause a second receive event to fire immediately after this returns, but allows MsQuic to clean up its buffers. + + uint ret = (uint)readLength == receiveEvent.TotalBufferLength + ? MsQuicStatusCodes.Success + : MsQuicStatusCodes.Continue; + + receiveEvent.TotalBufferLength = (uint)readLength; + return ret; } private static uint HandleEventPeerRecvAborted(State state, ref StreamEvent evt) @@ -871,12 +972,13 @@ private static uint HandleEventShutdownComplete(State state, ref StreamEvent evt // This event won't occur within the middle of a receive. if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(state, $"{state.TraceId} Stream completing resettable event source."); - if (state.ReadState == ReadState.None) + if (state.ReadState == ReadState.PendingRead) { shouldReadComplete = true; + state.RootedReceiveStream = null; + state.ReceiveUserBuffer = null; } - - if (state.ReadState != ReadState.ConnectionClosed && state.ReadState != ReadState.Aborted) + if (state.ReadState < ReadState.ReadsCompleted) { state.ReadState = ReadState.ReadsCompleted; } @@ -924,9 +1026,11 @@ private static uint HandleEventPeerSendAborted(State state, ref StreamEvent evt) bool shouldComplete = false; lock (state) { - if (state.ReadState == ReadState.None) + if (state.ReadState == ReadState.PendingRead) { shouldComplete = true; + state.RootedReceiveStream = null; + state.ReceiveUserBuffer = null; } state.ReadState = ReadState.Aborted; state.ReadErrorCode = (long)evt.Data.PeerSendAborted.ErrorCode; @@ -950,12 +1054,13 @@ private static uint HandleEventPeerSendShutdown(State state) // This event won't occur within the middle of a receive. if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(state, $"{state.TraceId} Stream completing resettable event source."); - if (state.ReadState == ReadState.None) + if (state.ReadState == ReadState.PendingRead) { shouldComplete = true; + state.RootedReceiveStream = null; + state.ReceiveUserBuffer = null; } - - if (state.ReadState != ReadState.ConnectionClosed) + if (state.ReadState < ReadState.ReadsCompleted) { state.ReadState = ReadState.ReadsCompleted; } @@ -1249,11 +1354,11 @@ private static uint HandleEventConnectionClose(State state) lock (state) { - if (state.ReadState == ReadState.None) + shouldCompleteRead = state.ReadState == ReadState.PendingRead; + if (state.ReadState < ReadState.ReadsCompleted) { - shouldCompleteRead = true; + state.ReadState = ReadState.ConnectionClosed; } - state.ReadState = ReadState.ConnectionClosed; if (state.SendState == SendState.None || state.SendState == SendState.Pending) { @@ -1304,6 +1409,16 @@ private static uint HandleEventConnectionClose(State state) private static Exception GetConnectionAbortedException(State state) => ThrowHelper.GetConnectionAbortedException(state.ConnectionState.AbortErrorCode); + // Read state transitions: + // + // None --(data arrives in event RECV)-> IndividualReadComplete --(user calls ReadAsync() & completes syncronously)-> None + // None --(user calls ReadAsync() & waits)-> PendingRead --(data arrives in event RECV & completes user's ReadAsync())-> None + // Any non-final state --(event PEER_SEND_SHUTDOWN or SHUTDOWN_COMPLETED with ConnectionClosed=false)-> ReadsCompleted + // Any non-final state --(event PEER_SEND_ABORT)-> Aborted + // Any non-final state --(user calls AbortRead())-> Aborted + // Any state --(CancellationToken's cancellation for ReadAsync())-> Aborted (TODO: should it be only for non-final as others?) + // Any non-final state --(event SHUTDOWN_COMPLETED with ConnectionClosed=true)-> ConnectionClosed + // Closed - no transitions, set for Unidirectional write-only streams private enum ReadState { /// @@ -1316,6 +1431,13 @@ private enum ReadState /// IndividualReadComplete, + /// + /// User called ReadAsync() + /// + PendingRead, + + // following states are final: + /// /// The peer has gracefully shutdown their sends / our receives; the stream's reads are complete. /// diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs index b21135000aca..4890ebea5f35 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs @@ -408,32 +408,122 @@ from writeSize in sizes } [Fact] - public async Task Read_StreamAborted_Throws() + public async Task Read_WriteAborted_Throws() { const int ExpectedErrorCode = 0xfffffff; - await Task.Run(async () => + using SemaphoreSlim sem = new SemaphoreSlim(0); + + await RunBidirectionalClientServer( + async clientStream => + { + await clientStream.WriteAsync(new byte[1]); + + await sem.WaitAsync(); + clientStream.AbortWrite(ExpectedErrorCode); + }, + async serverStream => + { + int received = await serverStream.ReadAsync(new byte[1]); + Assert.Equal(1, received); + + sem.Release(); + + byte[] buffer = new byte[100]; + QuicStreamAbortedException ex = await Assert.ThrowsAsync(() => serverStream.ReadAsync(buffer).AsTask()); + Assert.Equal(ExpectedErrorCode, ex.ErrorCode); + }); + } + + [Fact] + public async Task Read_SynchronousCompletion_Success() + { + using SemaphoreSlim sem = new SemaphoreSlim(0); + + await RunBidirectionalClientServer( + async clientStream => + { + await clientStream.WriteAsync(new byte[1]); + sem.Release(); + clientStream.Shutdown(); + sem.Release(); + }, + async serverStream => + { + await sem.WaitAsync(); + await Task.Delay(1000); + + ValueTask task = serverStream.ReadAsync(new byte[1]); + Assert.True(task.IsCompleted); + + int received = await task; + Assert.Equal(1, received); + + await sem.WaitAsync(); + await Task.Delay(1000); + + task = serverStream.ReadAsync(new byte[1]); + Assert.True(task.IsCompleted); + + received = await task; + Assert.Equal(0, received); + }); + } + + [Fact] + public async Task ReadOutstanding_ReadAborted_Throws() + { + // aborting doesn't work properly on mock + if (typeof(T) == typeof(MockProviderFactory)) { - using QuicListener listener = CreateQuicListener(); - ValueTask serverConnectionTask = listener.AcceptConnectionAsync(); + return; + } + + const int ExpectedErrorCode = 0xfffffff; + + using SemaphoreSlim sem = new SemaphoreSlim(0); + + await RunBidirectionalClientServer( + async clientStream => + { + await sem.WaitAsync(); + }, + async serverStream => + { + Task exTask = Assert.ThrowsAsync(() => serverStream.ReadAsync(new byte[1]).AsTask()); - using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint); - await clientConnection.ConnectAsync(); + Assert.False(exTask.IsCompleted); - using QuicConnection serverConnection = await serverConnectionTask; + serverStream.AbortRead(ExpectedErrorCode); - await using QuicStream clientStream = clientConnection.OpenBidirectionalStream(); - await clientStream.WriteAsync(new byte[1]); + await exTask; - await using QuicStream serverStream = await serverConnection.AcceptStreamAsync(); - await serverStream.ReadAsync(new byte[1]); + sem.Release(); + }); + } - clientStream.AbortWrite(ExpectedErrorCode); + [Fact] + public async Task Read_ConcurrentReads_Throws() + { + using SemaphoreSlim sem = new SemaphoreSlim(0); - byte[] buffer = new byte[100]; - QuicStreamAbortedException ex = await Assert.ThrowsAsync(() => serverStream.ReadAsync(buffer).AsTask()); - Assert.Equal(ExpectedErrorCode, ex.ErrorCode); - }).WaitAsync(TimeSpan.FromSeconds(15)); + await RunBidirectionalClientServer( + async clientStream => + { + await sem.WaitAsync(); + }, + async serverStream => + { + ValueTask readTask = serverStream.ReadAsync(new byte[1]); + Assert.False(readTask.IsCompleted); + + await Assert.ThrowsAsync(async () => await serverStream.ReadAsync(new byte[1])); + + sem.Release(); + + int res = await readTask; + Assert.Equal(0, res); + }); } [Fact] diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs index 803b2d407059..da2cfb37f412 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Xunit; +using System.Diagnostics.Tracing; namespace System.Net.Quic.Tests { @@ -114,22 +115,19 @@ internal async Task RunClientServer(Func clientFunction, F { using QuicListener listener = CreateQuicListener(); - var serverFinished = new ManualResetEventSlim(); - var clientFinished = new ManualResetEventSlim(); + using var serverFinished = new SemaphoreSlim(0); + using var clientFinished = new SemaphoreSlim(0); for (int i = 0; i < iterations; ++i) { - serverFinished.Reset(); - clientFinished.Reset(); - await new[] { Task.Run(async () => { using QuicConnection serverConnection = await listener.AcceptConnectionAsync(); await serverFunction(serverConnection); - serverFinished.Set(); - clientFinished.Wait(); + serverFinished.Release(); + await clientFinished.WaitAsync(); await serverConnection.CloseAsync(0); }), Task.Run(async () => @@ -137,14 +135,52 @@ await new[] using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint); await clientConnection.ConnectAsync(); await clientFunction(clientConnection); - clientFinished.Set(); - serverFinished.Wait(); + clientFinished.Release(); + await serverFinished.WaitAsync(); await clientConnection.CloseAsync(0); }) }.WhenAllOrAnyFailed(millisecondsTimeout); } } + internal async Task RunStreamClientServer(Func clientFunction, Func serverFunction, bool bidi, int iterations, int millisecondsTimeout) + { + byte[] buffer = new byte[1] { 42 }; + + await RunClientServer( + clientFunction: async connection => + { + await using QuicStream stream = bidi ? connection.OpenBidirectionalStream() : connection.OpenUnidirectionalStream(); + // Open(Bi|Uni)directionalStream only allocates ID. We will force stream opening + // by Writing there and receiving data on the other side. + await stream.WriteAsync(buffer); + + await clientFunction(stream); + + stream.Shutdown(); + await stream.ShutdownCompleted(); + }, + serverFunction: async connection => + { + await using QuicStream stream = await connection.AcceptStreamAsync(); + Assert.Equal(1, await stream.ReadAsync(buffer)); + + await serverFunction(stream); + + stream.Shutdown(); + await stream.ShutdownCompleted(); + }, + iterations, + millisecondsTimeout + ); + } + + internal Task RunBidirectionalClientServer(Func clientFunction, Func serverFunction, int iterations = 1, int millisecondsTimeout = 10_000) + => RunStreamClientServer(clientFunction, serverFunction, bidi: true, iterations, millisecondsTimeout); + + internal Task RunUnirectionalClientServer(Func clientFunction, Func serverFunction, int iterations = 1, int millisecondsTimeout = 10_000) + => RunStreamClientServer(clientFunction, serverFunction, bidi: false, iterations, millisecondsTimeout); + internal static async Task ReadAll(QuicStream stream, byte[] buffer) { Memory memory = buffer; From cf7394339937535dd97ed1082bb6d0d30f34eab0 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Tue, 13 Jul 2021 19:57:45 +0300 Subject: [PATCH 077/133] Add AttributeTargets.Interface to JsonConverterAttribute (#54922) * Allow JsonConverterAttribute usage on interfaces. Fix #33112 * update ApiCompat baseline --- .../System.Text.Json/ref/System.Text.Json.cs | 2 +- .../Attributes/JsonConverterAttribute.cs | 2 +- .../CustomConverterTests.Interface.cs | 60 +++++++++++++++++++ .../System.Text.Json.Tests.csproj | 1 + .../ApiCompatBaseline.PreviousNetCoreApp.txt | 3 +- 5 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Interface.cs diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index 8e87154e8035..a7d5a44feb61 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -763,7 +763,7 @@ public abstract partial class JsonConverter internal JsonConverter() { } public abstract bool CanConvert(System.Type typeToConvert); } - [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Enum | System.AttributeTargets.Field | System.AttributeTargets.Property | System.AttributeTargets.Struct, AllowMultiple=false)] + [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Interface | System.AttributeTargets.Enum | System.AttributeTargets.Field | System.AttributeTargets.Property | System.AttributeTargets.Struct, AllowMultiple=false)] public partial class JsonConverterAttribute : System.Text.Json.Serialization.JsonAttribute { protected JsonConverterAttribute() { } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonConverterAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonConverterAttribute.cs index 098b1e175e01..e456a009178d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonConverterAttribute.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonConverterAttribute.cs @@ -15,7 +15,7 @@ namespace System.Text.Json.Serialization /// or there is another on a property or field /// of the same type. /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] public class JsonConverterAttribute : JsonAttribute { /// diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Interface.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Interface.cs new file mode 100644 index 000000000000..da56c49591f2 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Interface.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using System.Collections.Generic; +using Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public static partial class CustomConverterTests + { + [JsonConverter(typeof(MyInterfaceConverter))] + private interface IMyInterface + { + int IntValue { get; set; } + string StringValue { get; set; } + } + + // A custom converter that writes and reads the string property as a top-level value + private class MyInterfaceConverter : JsonConverter + { + public override IMyInterface Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => new MyClass + { + IntValue = 42, + StringValue = reader.GetString() + }; + + public override void Write(Utf8JsonWriter writer, IMyInterface value, JsonSerializerOptions options) => writer.WriteStringValue(value.StringValue); + } + + private class MyClass : IMyInterface + { + public int IntValue { get; set; } + public string StringValue { get; set; } + } + + [Fact] + public static void CustomInterfaceConverter_Serialization() + { + IMyInterface value = new MyClass { IntValue = 11, StringValue = "myString" }; + + string expectedJson = "\"myString\""; + string actualJson = JsonSerializer.Serialize(value); + Assert.Equal(expectedJson, actualJson); + } + + [Fact] + public static void CustomInterfaceConverter_Deserialization() + { + string json = "\"myString\""; + + IMyInterface result = JsonSerializer.Deserialize(json); + + Assert.IsType(result); + Assert.Equal("myString", result.StringValue); + Assert.Equal(42, result.IntValue); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj index 62b646631196..41a5ec38d5a9 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj @@ -119,6 +119,7 @@ + diff --git a/src/libraries/shims/ApiCompatBaseline.PreviousNetCoreApp.txt b/src/libraries/shims/ApiCompatBaseline.PreviousNetCoreApp.txt index 23ae45228999..54945167821c 100644 --- a/src/libraries/shims/ApiCompatBaseline.PreviousNetCoreApp.txt +++ b/src/libraries/shims/ApiCompatBaseline.PreviousNetCoreApp.txt @@ -184,4 +184,5 @@ CannotChangeAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatfo CannotChangeAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' on 'System.Security.Cryptography.AesGcm' changed from '[UnsupportedOSPlatformAttribute("browser")]' in the contract to '[UnsupportedOSPlatformAttribute("browser")]' in the implementation. CannotChangeAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' on 'System.Security.Cryptography.AesCcm' changed from '[UnsupportedOSPlatformAttribute("browser")]' in the contract to '[UnsupportedOSPlatformAttribute("browser")]' in the implementation. CannotChangeAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' on 'System.Security.Cryptography.AesGcm' changed from '[UnsupportedOSPlatformAttribute("browser")]' in the contract to '[UnsupportedOSPlatformAttribute("browser")]' in the implementation. -Total Issues: 170 +CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Text.Json.Serialization.JsonConverterAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Struct, AllowMultiple=false)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Interface | AttributeTargets.Property | AttributeTargets.Struct, AllowMultiple=false)]' in the implementation. +Total Issues: 171 From 9a9b105bfa8e6803534333a096cb5af8490b5340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marie=20P=C3=ADchov=C3=A1?= <11718369+ManickaP@users.noreply.github.com> Date: Tue, 13 Jul 2021 19:18:26 +0200 Subject: [PATCH 078/133] H/3 Server Cert validation callback exception fix (#55526) * Fix and test. * MsQuicConnection now call the cert validation callback only once, removed code duplication --- .../Http/SocketsHttpHandler/ConnectHelper.cs | 13 +++-- .../SocketsHttpHandler/HttpConnectionPool.cs | 2 +- .../HttpClientHandlerTest.Http3.cs | 58 +++++++++++++++++++ .../MsQuic/MsQuicConnection.cs | 4 ++ 4 files changed, 71 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs index 97e8478fa748..84d6ff8b345b 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs @@ -33,7 +33,7 @@ public CertificateCallbackMapper(Func EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, bool async, Stream stream, CancellationToken cancellationToken) + private static SslClientAuthenticationOptions SetUpRemoteCertificateValidationCallback(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request) { // If there's a cert validation callback, and if it came from HttpClientHandler, // wrap the original delegate in order to change the sender to be the request message (expected by HttpClientHandler's delegate). @@ -52,12 +52,13 @@ public static ValueTask EstablishSslConnectionAsync(SslClientAuthenti }; } - // Create the SslStream, authenticate, and return it. - return EstablishSslConnectionAsyncCore(async, stream, sslOptions, cancellationToken); + return sslOptions; } - private static async ValueTask EstablishSslConnectionAsyncCore(bool async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken) + public static async ValueTask EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, bool async, Stream stream, CancellationToken cancellationToken) { + sslOptions = SetUpRemoteCertificateValidationCallback(sslOptions, request); + SslStream sslStream = new SslStream(stream); try @@ -104,8 +105,10 @@ private static async ValueTask EstablishSslConnectionAsyncCore(bool a [SupportedOSPlatform("windows")] [SupportedOSPlatform("linux")] [SupportedOSPlatform("macos")] - public static async ValueTask ConnectQuicAsync(QuicImplementationProvider quicImplementationProvider, DnsEndPoint endPoint, SslClientAuthenticationOptions? clientAuthenticationOptions, CancellationToken cancellationToken) + public static async ValueTask ConnectQuicAsync(HttpRequestMessage request, QuicImplementationProvider quicImplementationProvider, DnsEndPoint endPoint, SslClientAuthenticationOptions clientAuthenticationOptions, CancellationToken cancellationToken) { + clientAuthenticationOptions = SetUpRemoteCertificateValidationCallback(clientAuthenticationOptions, request); + QuicConnection con = new QuicConnection(quicImplementationProvider, endPoint, clientAuthenticationOptions); try { diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index e84f21dce3b4..480724dd8eb5 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -725,7 +725,7 @@ private async ValueTask GetHttp3ConnectionAsync(HttpRequestMess QuicConnection quicConnection; try { - quicConnection = await ConnectHelper.ConnectQuicAsync(Settings._quicImplementationProvider ?? QuicImplementationProviders.Default, new DnsEndPoint(authority.IdnHost, authority.Port), _sslOptionsHttp3, cancellationToken).ConfigureAwait(false); + quicConnection = await ConnectHelper.ConnectQuicAsync(request, Settings._quicImplementationProvider ?? QuicImplementationProviders.Default, new DnsEndPoint(authority.IdnHost, authority.Port), _sslOptionsHttp3!, cancellationToken).ConfigureAwait(false); } catch { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs index d4d806f68813..b3551199a299 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs @@ -314,6 +314,64 @@ public async Task ReservedFrameType_Throws() await new[] { clientTask, serverTask }.WhenAllOrAnyFailed(20_000); } + [Fact] + public async Task ServerCertificateCustomValidationCallback_Succeeds() + { + // Mock doesn't make use of cart validation callback. + if (UseQuicImplementationProvider == QuicImplementationProviders.Mock) + { + return; + } + + HttpRequestMessage? callbackRequest = null; + int invocationCount = 0; + + var httpClientHandler = CreateHttpClientHandler(); + httpClientHandler.ServerCertificateCustomValidationCallback = (request, _, _, _) => + { + callbackRequest = request; + ++invocationCount; + return true; + }; + + using Http3LoopbackServer server = CreateHttp3LoopbackServer(); + using HttpClient client = CreateHttpClient(httpClientHandler); + + Task serverTask = Task.Run(async () => + { + using Http3LoopbackConnection connection = (Http3LoopbackConnection)await server.EstablishGenericConnectionAsync(); + using Http3LoopbackStream stream = await connection.AcceptRequestStreamAsync(); + await stream.HandleRequestAsync(); + using Http3LoopbackStream stream2 = await connection.AcceptRequestStreamAsync(); + await stream2.HandleRequestAsync(); + }); + + var request = new HttpRequestMessage(HttpMethod.Get, server.Address); + request.Version = HttpVersion.Version30; + request.VersionPolicy = HttpVersionPolicy.RequestVersionExact; + + var response = await client.SendAsync(request); + + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version30, response.Version); + Assert.Same(request, callbackRequest); + Assert.Equal(1, invocationCount); + + // Second request, the callback shouldn't be hit at all. + callbackRequest = null; + + request = new HttpRequestMessage(HttpMethod.Get, server.Address); + request.Version = HttpVersion.Version30; + request.VersionPolicy = HttpVersionPolicy.RequestVersionExact; + + response = await client.SendAsync(request); + + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version30, response.Version); + Assert.Null(callbackRequest); + Assert.Equal(1, invocationCount); + } + [OuterLoop] [ConditionalTheory(nameof(IsMsQuicSupported))] [MemberData(nameof(InteropUris))] diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs index ec1710316c12..3eea4d19c692 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs @@ -418,6 +418,10 @@ private static uint HandleEventPeerCertificateReceived(State state, ref Connecti if (connection._remoteCertificateValidationCallback != null) { bool success = connection._remoteCertificateValidationCallback(connection, certificate, chain, sslPolicyErrors); + // Unset the callback to prevent multiple invocations of the callback per a single connection. + // Return the same value as the custom callback just did. + connection._remoteCertificateValidationCallback = (_, _, _, _) => success; + if (!success && NetEventSource.Log.IsEnabled()) NetEventSource.Error(state, $"{state.TraceId} Remote certificate rejected by verification callback"); return success ? MsQuicStatusCodes.Success : MsQuicStatusCodes.HandshakeFailure; From a490a3417adb78dbc36891624e67720ebdca919f Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Tue, 13 Jul 2021 10:26:42 -0700 Subject: [PATCH 079/133] Add ConfigurationManager(#55338) * ConfigurationManager : IConfigurationRoot, IConfigurationBuilder --- .../ref/Microsoft.Extensions.Configuration.cs | 15 + .../src/ConfigurationManager.cs | 353 +++++ .../src/ConfigurationRoot.cs | 56 +- .../tests/ConfigurationManagerTest.cs | 1201 +++++++++++++++++ 4 files changed, 1599 insertions(+), 26 deletions(-) create mode 100644 src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs create mode 100644 src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationManagerTest.cs diff --git a/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs b/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs index c3cc43f7eff5..feef64fc2dde 100644 --- a/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs +++ b/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs @@ -28,6 +28,21 @@ public ChainedConfigurationSource() { } public bool ShouldDisposeConfiguration { get { throw null; } set { } } public Microsoft.Extensions.Configuration.IConfigurationProvider Build(Microsoft.Extensions.Configuration.IConfigurationBuilder builder) { throw null; } } + public sealed partial class ConfigurationManager : Microsoft.Extensions.Configuration.IConfigurationBuilder, Microsoft.Extensions.Configuration.IConfigurationRoot, System.IDisposable + { + public ConfigurationManager() { } + public string this[string key] { get { throw null; } set { throw null; } } + public IConfigurationSection GetSection(string key) { throw null; } + public System.Collections.Generic.IEnumerable GetChildren() { throw null; } + public void Dispose() { throw null; } + System.Collections.Generic.IDictionary IConfigurationBuilder.Properties { get { throw null; } } + System.Collections.Generic.IList IConfigurationBuilder.Sources { get { throw null; } } + Microsoft.Extensions.Configuration.IConfigurationBuilder IConfigurationBuilder.Add(Microsoft.Extensions.Configuration.IConfigurationSource source) { throw null; } + Microsoft.Extensions.Configuration.IConfigurationRoot IConfigurationBuilder.Build() { throw null; } + System.Collections.Generic.IEnumerable IConfigurationRoot.Providers { get { throw null; } } + void IConfigurationRoot.Reload() { throw null; } + Primitives.IChangeToken IConfiguration.GetReloadToken() { throw null; } + } public partial class ConfigurationBuilder : Microsoft.Extensions.Configuration.IConfigurationBuilder { public ConfigurationBuilder() { } diff --git a/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs b/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs new file mode 100644 index 000000000000..9578f3c334ac --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs @@ -0,0 +1,353 @@ +// 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; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.Extensions.Configuration +{ + /// + /// Configuration is mutable configuration object. It is both an and an . + /// As sources are added, it updates its current view of configuration. Once Build is called, configuration is frozen. + /// + public sealed class ConfigurationManager : IConfigurationBuilder, IConfigurationRoot, IDisposable + { + private readonly ConfigurationSources _sources; + private readonly ConfigurationBuilderProperties _properties; + + private readonly object _providerLock = new(); + private readonly List _providers = new(); + private readonly List _changeTokenRegistrations = new(); + private ConfigurationReloadToken _changeToken = new(); + + /// + /// Creates an empty mutable configuration object that is both an and an . + /// + public ConfigurationManager() + { + _sources = new ConfigurationSources(this); + _properties = new ConfigurationBuilderProperties(this); + + // Make sure there's some default storage since there are no default providers. + this.AddInMemoryCollection(); + + AddSource(_sources[0]); + } + + /// + public string this[string key] + { + get + { + lock (_providerLock) + { + return ConfigurationRoot.GetConfiguration(_providers, key); + } + } + set + { + lock (_providerLock) + { + ConfigurationRoot.SetConfiguration(_providers, key, value); + } + } + } + + /// + public IConfigurationSection GetSection(string key) => new ConfigurationSection(this, key); + + /// + public IEnumerable GetChildren() + { + lock (_providerLock) + { + // ToList() to eagerly evaluate inside lock. + return this.GetChildrenImplementation(null).ToList(); + } + } + + IDictionary IConfigurationBuilder.Properties => _properties; + + IList IConfigurationBuilder.Sources => _sources; + + IEnumerable IConfigurationRoot.Providers + { + get + { + lock (_providerLock) + { + return new List(_providers); + } + } + } + + /// + public void Dispose() + { + lock (_providerLock) + { + DisposeRegistrationsAndProvidersUnsynchronized(); + } + } + + IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source) + { + _sources.Add(source ?? throw new ArgumentNullException(nameof(source))); + return this; + } + + IConfigurationRoot IConfigurationBuilder.Build() => this; + + IChangeToken IConfiguration.GetReloadToken() => _changeToken; + + void IConfigurationRoot.Reload() + { + lock (_providerLock) + { + foreach (var provider in _providers) + { + provider.Load(); + } + } + + RaiseChanged(); + } + + private void RaiseChanged() + { + var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken()); + previousToken.OnReload(); + } + + // Don't rebuild and reload all providers in the common case when a source is simply added to the IList. + private void AddSource(IConfigurationSource source) + { + lock (_providerLock) + { + var provider = source.Build(this); + _providers.Add(provider); + + provider.Load(); + _changeTokenRegistrations.Add(ChangeToken.OnChange(() => provider.GetReloadToken(), () => RaiseChanged())); + } + + RaiseChanged(); + } + + // Something other than Add was called on IConfigurationBuilder.Sources or IConfigurationBuilder.Properties has changed. + private void ReloadSources() + { + lock (_providerLock) + { + DisposeRegistrationsAndProvidersUnsynchronized(); + + _changeTokenRegistrations.Clear(); + _providers.Clear(); + + foreach (var source in _sources) + { + _providers.Add(source.Build(this)); + } + + foreach (var p in _providers) + { + p.Load(); + _changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged())); + } + } + + RaiseChanged(); + } + + private void DisposeRegistrationsAndProvidersUnsynchronized() + { + // dispose change token registrations + foreach (var registration in _changeTokenRegistrations) + { + registration.Dispose(); + } + + // dispose providers + foreach (var provider in _providers) + { + (provider as IDisposable)?.Dispose(); + } + } + + private class ConfigurationSources : IList + { + private readonly List _sources = new(); + private readonly ConfigurationManager _config; + + public ConfigurationSources(ConfigurationManager config) + { + _config = config; + } + + public IConfigurationSource this[int index] + { + get => _sources[index]; + set + { + _sources[index] = value; + _config.ReloadSources(); + } + } + + public int Count => _sources.Count; + + public bool IsReadOnly => false; + + public void Add(IConfigurationSource source) + { + _sources.Add(source); + _config.AddSource(source); + } + + public void Clear() + { + _sources.Clear(); + _config.ReloadSources(); + } + + public bool Contains(IConfigurationSource source) + { + return _sources.Contains(source); + } + + public void CopyTo(IConfigurationSource[] array, int arrayIndex) + { + _sources.CopyTo(array, arrayIndex); + } + + public IEnumerator GetEnumerator() + { + return _sources.GetEnumerator(); + } + + public int IndexOf(IConfigurationSource source) + { + return _sources.IndexOf(source); + } + + public void Insert(int index, IConfigurationSource source) + { + _sources.Insert(index, source); + _config.ReloadSources(); + } + + public bool Remove(IConfigurationSource source) + { + var removed = _sources.Remove(source); + _config.ReloadSources(); + return removed; + } + + public void RemoveAt(int index) + { + _sources.RemoveAt(index); + _config.ReloadSources(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + private class ConfigurationBuilderProperties : IDictionary + { + private readonly Dictionary _properties = new(); + private readonly ConfigurationManager _config; + + public ConfigurationBuilderProperties(ConfigurationManager config) + { + _config = config; + } + + public object this[string key] + { + get => _properties[key]; + set + { + _properties[key] = value; + _config.ReloadSources(); + } + } + + public ICollection Keys => _properties.Keys; + + public ICollection Values => _properties.Values; + + public int Count => _properties.Count; + + public bool IsReadOnly => false; + + public void Add(string key, object value) + { + _properties.Add(key, value); + _config.ReloadSources(); + } + + public void Add(KeyValuePair item) + { + ((IDictionary)_properties).Add(item); + _config.ReloadSources(); + } + + public void Clear() + { + _properties.Clear(); + _config.ReloadSources(); + } + + public bool Contains(KeyValuePair item) + { + return _properties.Contains(item); + } + + public bool ContainsKey(string key) + { + return _properties.ContainsKey(key); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((IDictionary)_properties).CopyTo(array, arrayIndex); + } + + public IEnumerator> GetEnumerator() + { + return _properties.GetEnumerator(); + } + + public bool Remove(string key) + { + var wasRemoved = _properties.Remove(key); + _config.ReloadSources(); + return wasRemoved; + } + + public bool Remove(KeyValuePair item) + { + var wasRemoved = ((IDictionary)_properties).Remove(item); + _config.ReloadSources(); + return wasRemoved; + } + + public bool TryGetValue(string key, out object value) + { + return _properties.TryGetValue(key, out value); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _properties.GetEnumerator(); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationRoot.cs b/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationRoot.cs index 455efdb4176b..cfe56a20978e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationRoot.cs +++ b/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationRoot.cs @@ -49,32 +49,8 @@ public ConfigurationRoot(IList providers) /// The configuration value. public string this[string key] { - get - { - for (int i = _providers.Count - 1; i >= 0; i--) - { - IConfigurationProvider provider = _providers[i]; - - if (provider.TryGet(key, out string value)) - { - return value; - } - } - - return null; - } - set - { - if (_providers.Count == 0) - { - throw new InvalidOperationException(SR.Error_NoSources); - } - - foreach (IConfigurationProvider provider in _providers) - { - provider.Set(key, value); - } - } + get => GetConfiguration(_providers, key); + set => SetConfiguration(_providers, key, value); } /// @@ -134,5 +110,33 @@ public void Dispose() (provider as IDisposable)?.Dispose(); } } + + internal static string GetConfiguration(IList providers, string key) + { + for (int i = providers.Count - 1; i >= 0; i--) + { + IConfigurationProvider provider = providers[i]; + + if (provider.TryGet(key, out string value)) + { + return value; + } + } + + return null; + } + + internal static void SetConfiguration(IList providers, string key, string value) + { + if (providers.Count == 0) + { + throw new InvalidOperationException(SR.Error_NoSources); + } + + foreach (IConfigurationProvider provider in providers) + { + provider.Set(key, value); + } + } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationManagerTest.cs b/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationManagerTest.cs new file mode 100644 index 000000000000..6e7a4919b25e --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationManagerTest.cs @@ -0,0 +1,1201 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Configuration.Memory; +using Microsoft.Extensions.Primitives; +using Moq; +using Xunit; + +namespace Microsoft.Extensions.Configuration.Test +{ + public class ConfigurationManagerTest + { + [Fact] + public void AutoUpdates() + { + var config = new ConfigurationManager(); + + config.AddInMemoryCollection(new Dictionary + { + { "TestKey", "TestValue" }, + }); + + Assert.Equal("TestValue", config["TestKey"]); + } + + [Fact] + public void TriggersReloadTokenOnSourceAddition() + { + var config = new ConfigurationManager(); + + var reloadToken = ((IConfiguration)config).GetReloadToken(); + + Assert.False(reloadToken.HasChanged); + + config.AddInMemoryCollection(new Dictionary + { + { "TestKey", "TestValue" }, + }); + + Assert.True(reloadToken.HasChanged); + } + + [Fact] + public void SettingValuesWorksWithoutManuallyAddingSource() + { + var config = new ConfigurationManager + { + ["TestKey"] = "TestValue", + }; + + Assert.Equal("TestValue", config["TestKey"]); + } + + [Fact] + public void SettingConfigValuesDoesNotTriggerReloadToken() + { + var config = new ConfigurationManager(); + var reloadToken = ((IConfiguration)config).GetReloadToken(); + + config["TestKey"] = "TestValue"; + + Assert.Equal("TestValue", config["TestKey"]); + + // ConfigurationRoot doesn't fire the token today when the setter is called. + Assert.False(reloadToken.HasChanged); + } + + [Fact] + public void SettingIConfigurationBuilderPropertiesReloadsSources() + { + var config = new ConfigurationManager(); + IConfigurationBuilder configBuilder = config; + + config["PreReloadTestConfigKey"] = "PreReloadTestConfigValue"; + + var reloadToken1 = ((IConfiguration)config).GetReloadToken(); + // Changing Properties causes all the IConfigurationSources to be reload. + configBuilder.Properties["TestPropertyKey"] = "TestPropertyValue"; + + var reloadToken2 = ((IConfiguration)config).GetReloadToken(); + config["PostReloadTestConfigKey"] = "PostReloadTestConfigValue"; + + Assert.Equal("TestPropertyValue", configBuilder.Properties["TestPropertyKey"]); + Assert.Null(config["TestPropertyKey"]); + + // Changes before the reload are lost by the MemoryConfigurationSource. + Assert.Null(config["PreReloadTestConfigKey"]); + Assert.Equal("PostReloadTestConfigValue", config["PostReloadTestConfigKey"]); + + Assert.True(reloadToken1.HasChanged); + Assert.False(reloadToken2.HasChanged); + } + + [Fact] + public void DisposesProvidersOnDispose() + { + var provider1 = new TestConfigurationProvider("foo", "foo-value"); + var provider2 = new DisposableTestConfigurationProvider("bar", "bar-value"); + var provider3 = new TestConfigurationProvider("baz", "baz-value"); + var provider4 = new DisposableTestConfigurationProvider("qux", "qux-value"); + var provider5 = new DisposableTestConfigurationProvider("quux", "quux-value"); + + var config = new ConfigurationManager(); + IConfigurationBuilder builder = config; + + builder.Add(new TestConfigurationSource(provider1)); + builder.Add(new TestConfigurationSource(provider2)); + builder.Add(new TestConfigurationSource(provider3)); + builder.Add(new TestConfigurationSource(provider4)); + builder.Add(new TestConfigurationSource(provider5)); + + Assert.Equal("foo-value", config["foo"]); + Assert.Equal("bar-value", config["bar"]); + Assert.Equal("baz-value", config["baz"]); + Assert.Equal("qux-value", config["qux"]); + Assert.Equal("quux-value", config["quux"]); + + config.Dispose(); + + Assert.True(provider2.IsDisposed); + Assert.True(provider4.IsDisposed); + Assert.True(provider5.IsDisposed); + } + + [Fact] + public void DisposesProvidersOnRemoval() + { + var provider1 = new TestConfigurationProvider("foo", "foo-value"); + var provider2 = new DisposableTestConfigurationProvider("bar", "bar-value"); + var provider3 = new TestConfigurationProvider("baz", "baz-value"); + var provider4 = new DisposableTestConfigurationProvider("qux", "qux-value"); + var provider5 = new DisposableTestConfigurationProvider("quux", "quux-value"); + + var source1 = new TestConfigurationSource(provider1); + var source2 = new TestConfigurationSource(provider2); + var source3 = new TestConfigurationSource(provider3); + var source4 = new TestConfigurationSource(provider4); + var source5 = new TestConfigurationSource(provider5); + + var config = new ConfigurationManager(); + IConfigurationBuilder builder = config; + + builder.Add(source1); + builder.Add(source2); + builder.Add(source3); + builder.Add(source4); + builder.Add(source5); + + Assert.Equal("foo-value", config["foo"]); + Assert.Equal("bar-value", config["bar"]); + Assert.Equal("baz-value", config["baz"]); + Assert.Equal("qux-value", config["qux"]); + Assert.Equal("quux-value", config["quux"]); + + builder.Sources.Remove(source2); + builder.Sources.Remove(source4); + + // While only provider2 and provider4 need to be disposed here, we do not assert provider5 is not disposed + // because even though it's unnecessary, Configuration disposes all providers on removal and rebuilds + // all the sources. While not optimal, this should be a pretty rare scenario. + Assert.True(provider2.IsDisposed); + Assert.True(provider4.IsDisposed); + + config.Dispose(); + + Assert.True(provider2.IsDisposed); + Assert.True(provider4.IsDisposed); + Assert.True(provider5.IsDisposed); + } + + [Fact] + public void DisposesChangeTokenRegistrationsOnDispose() + { + var changeToken = new TestChangeToken(); + var providerMock = new Mock(); + providerMock.Setup(p => p.GetReloadToken()).Returns(changeToken); + + var config = new ConfigurationManager(); + + ((IConfigurationBuilder)config).Add(new TestConfigurationSource(providerMock.Object)); + + Assert.NotEmpty(changeToken.Callbacks); + + config.Dispose(); + + Assert.Empty(changeToken.Callbacks); + } + + [Fact] + public void DisposesChangeTokenRegistrationsOnRemoval() + { + var changeToken = new TestChangeToken(); + var providerMock = new Mock(); + providerMock.Setup(p => p.GetReloadToken()).Returns(changeToken); + + var source = new TestConfigurationSource(providerMock.Object); + + var config = new ConfigurationManager(); + IConfigurationBuilder builder = config; + + builder.Add(source); + + Assert.NotEmpty(changeToken.Callbacks); + + builder.Sources.Remove(source); + + Assert.Empty(changeToken.Callbacks); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ChainedConfigurationIsDisposedOnDispose(bool shouldDispose) + { + var provider = new DisposableTestConfigurationProvider("foo", "foo-value"); + var chainedConfig = new ConfigurationRoot(new IConfigurationProvider[] { + provider + }); + + var config = new ConfigurationManager(); + + config.AddConfiguration(chainedConfig, shouldDisposeConfiguration: shouldDispose); + + Assert.False(provider.IsDisposed); + + config.Dispose(); + + Assert.Equal(shouldDispose, provider.IsDisposed); + } + + [Fact] + public void LoadAndCombineKeyValuePairsFromDifferentConfigurationProviders() + { + // Arrange + var dic1 = new Dictionary() + { + {"Mem1:KeyInMem1", "ValueInMem1"} + }; + var dic2 = new Dictionary() + { + {"Mem2:KeyInMem2", "ValueInMem2"} + }; + var dic3 = new Dictionary() + { + {"Mem3:KeyInMem3", "ValueInMem3"} + }; + var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 }; + var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 }; + var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 }; + + var config = new ConfigurationManager(); + IConfigurationBuilder configurationBuilder = config; + + // Act + configurationBuilder.Add(memConfigSrc1); + configurationBuilder.Add(memConfigSrc2); + configurationBuilder.Add(memConfigSrc3); + + var memVal1 = config["mem1:keyinmem1"]; + var memVal2 = config["Mem2:KeyInMem2"]; + var memVal3 = config["MEM3:KEYINMEM3"]; + + // Assert + Assert.Contains(memConfigSrc1, configurationBuilder.Sources); + Assert.Contains(memConfigSrc2, configurationBuilder.Sources); + Assert.Contains(memConfigSrc3, configurationBuilder.Sources); + + Assert.Equal("ValueInMem1", memVal1); + Assert.Equal("ValueInMem2", memVal2); + Assert.Equal("ValueInMem3", memVal3); + + Assert.Equal("ValueInMem1", config["mem1:keyinmem1"]); + Assert.Equal("ValueInMem2", config["Mem2:KeyInMem2"]); + Assert.Equal("ValueInMem3", config["MEM3:KEYINMEM3"]); + Assert.Null(config["NotExist"]); + } + + [Fact] + public void CanChainConfiguration() + { + // Arrange + var dic1 = new Dictionary() + { + {"Mem1:KeyInMem1", "ValueInMem1"} + }; + var dic2 = new Dictionary() + { + {"Mem2:KeyInMem2", "ValueInMem2"} + }; + var dic3 = new Dictionary() + { + {"Mem3:KeyInMem3", "ValueInMem3"} + }; + var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 }; + var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 }; + var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 }; + + var config = new ConfigurationManager(); + IConfigurationBuilder configurationBuilder = config; + + // Act + configurationBuilder.Add(memConfigSrc1); + configurationBuilder.Add(memConfigSrc2); + configurationBuilder.Add(memConfigSrc3); + + var chained = new ConfigurationManager(); + chained.AddConfiguration(config); + var memVal1 = chained["mem1:keyinmem1"]; + var memVal2 = chained["Mem2:KeyInMem2"]; + var memVal3 = chained["MEM3:KEYINMEM3"]; + + // Assert + + Assert.Equal("ValueInMem1", memVal1); + Assert.Equal("ValueInMem2", memVal2); + Assert.Equal("ValueInMem3", memVal3); + + Assert.Null(chained["NotExist"]); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ChainedAsEnumerateFlattensIntoDictionaryTest(bool removePath) + { + // Arrange + var dic1 = new Dictionary() + { + {"Mem1", "Value1"}, + {"Mem1:", "NoKeyValue1"}, + {"Mem1:KeyInMem1", "ValueInMem1"}, + {"Mem1:KeyInMem1:Deep1", "ValueDeep1"} + }; + var dic2 = new Dictionary() + { + {"Mem2", "Value2"}, + {"Mem2:", "NoKeyValue2"}, + {"Mem2:KeyInMem2", "ValueInMem2"}, + {"Mem2:KeyInMem2:Deep2", "ValueDeep2"} + }; + var dic3 = new Dictionary() + { + {"Mem3", "Value3"}, + {"Mem3:", "NoKeyValue3"}, + {"Mem3:KeyInMem3", "ValueInMem3"}, + {"Mem3:KeyInMem3:Deep3", "ValueDeep3"} + }; + var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 }; + var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 }; + var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 }; + + var config1 = new ConfigurationManager(); + IConfigurationBuilder configurationBuilder = config1; + + // Act + configurationBuilder.Add(memConfigSrc1); + configurationBuilder.Add(memConfigSrc2); + + var config2 = new ConfigurationManager(); + + config2 + .AddConfiguration(config1) + .Add(memConfigSrc3); + + var dict = config2.AsEnumerable(makePathsRelative: removePath).ToDictionary(k => k.Key, v => v.Value); + + // Assert + Assert.Equal("Value1", dict["Mem1"]); + Assert.Equal("NoKeyValue1", dict["Mem1:"]); + Assert.Equal("ValueDeep1", dict["Mem1:KeyInMem1:Deep1"]); + Assert.Equal("ValueInMem2", dict["Mem2:KeyInMem2"]); + Assert.Equal("Value2", dict["Mem2"]); + Assert.Equal("NoKeyValue2", dict["Mem2:"]); + Assert.Equal("ValueDeep2", dict["Mem2:KeyInMem2:Deep2"]); + Assert.Equal("Value3", dict["Mem3"]); + Assert.Equal("NoKeyValue3", dict["Mem3:"]); + Assert.Equal("ValueInMem3", dict["Mem3:KeyInMem3"]); + Assert.Equal("ValueDeep3", dict["Mem3:KeyInMem3:Deep3"]); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void AsEnumerateFlattensIntoDictionaryTest(bool removePath) + { + // Arrange + var dic1 = new Dictionary() + { + {"Mem1", "Value1"}, + {"Mem1:", "NoKeyValue1"}, + {"Mem1:KeyInMem1", "ValueInMem1"}, + {"Mem1:KeyInMem1:Deep1", "ValueDeep1"} + }; + var dic2 = new Dictionary() + { + {"Mem2", "Value2"}, + {"Mem2:", "NoKeyValue2"}, + {"Mem2:KeyInMem2", "ValueInMem2"}, + {"Mem2:KeyInMem2:Deep2", "ValueDeep2"} + }; + var dic3 = new Dictionary() + { + {"Mem3", "Value3"}, + {"Mem3:", "NoKeyValue3"}, + {"Mem3:KeyInMem3", "ValueInMem3"}, + {"Mem3:KeyInMem3:Deep3", "ValueDeep3"} + }; + var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 }; + var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 }; + var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 }; + + var config = new ConfigurationManager(); + IConfigurationBuilder configurationBuilder = config; + + // Act + configurationBuilder.Add(memConfigSrc1); + configurationBuilder.Add(memConfigSrc2); + configurationBuilder.Add(memConfigSrc3); + var dict = config.AsEnumerable(makePathsRelative: removePath).ToDictionary(k => k.Key, v => v.Value); + + // Assert + Assert.Equal("Value1", dict["Mem1"]); + Assert.Equal("NoKeyValue1", dict["Mem1:"]); + Assert.Equal("ValueDeep1", dict["Mem1:KeyInMem1:Deep1"]); + Assert.Equal("ValueInMem2", dict["Mem2:KeyInMem2"]); + Assert.Equal("Value2", dict["Mem2"]); + Assert.Equal("NoKeyValue2", dict["Mem2:"]); + Assert.Equal("ValueDeep2", dict["Mem2:KeyInMem2:Deep2"]); + Assert.Equal("Value3", dict["Mem3"]); + Assert.Equal("NoKeyValue3", dict["Mem3:"]); + Assert.Equal("ValueInMem3", dict["Mem3:KeyInMem3"]); + Assert.Equal("ValueDeep3", dict["Mem3:KeyInMem3:Deep3"]); + } + + [Fact] + public void AsEnumerateStripsKeyFromChildren() + { + // Arrange + var dic1 = new Dictionary() + { + {"Mem1", "Value1"}, + {"Mem1:", "NoKeyValue1"}, + {"Mem1:KeyInMem1", "ValueInMem1"}, + {"Mem1:KeyInMem1:Deep1", "ValueDeep1"} + }; + var dic2 = new Dictionary() + { + {"Mem2", "Value2"}, + {"Mem2:", "NoKeyValue2"}, + {"Mem2:KeyInMem2", "ValueInMem2"}, + {"Mem2:KeyInMem2:Deep2", "ValueDeep2"} + }; + var dic3 = new Dictionary() + { + {"Mem3", "Value3"}, + {"Mem3:", "NoKeyValue3"}, + {"Mem3:KeyInMem3", "ValueInMem3"}, + {"Mem3:KeyInMem4", "ValueInMem4"}, + {"Mem3:KeyInMem3:Deep3", "ValueDeep3"}, + {"Mem3:KeyInMem3:Deep4", "ValueDeep4"} + }; + var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 }; + var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 }; + var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 }; + + var config = new ConfigurationManager(); + IConfigurationBuilder configurationBuilder = config; + + // Act + configurationBuilder.Add(memConfigSrc1); + configurationBuilder.Add(memConfigSrc2); + configurationBuilder.Add(memConfigSrc3); + + var dict = config.GetSection("Mem1").AsEnumerable(makePathsRelative: true).ToDictionary(k => k.Key, v => v.Value); + Assert.Equal(3, dict.Count); + Assert.Equal("NoKeyValue1", dict[""]); + Assert.Equal("ValueInMem1", dict["KeyInMem1"]); + Assert.Equal("ValueDeep1", dict["KeyInMem1:Deep1"]); + + var dict2 = config.GetSection("Mem2").AsEnumerable(makePathsRelative: true).ToDictionary(k => k.Key, v => v.Value); + Assert.Equal(3, dict2.Count); + Assert.Equal("NoKeyValue2", dict2[""]); + Assert.Equal("ValueInMem2", dict2["KeyInMem2"]); + Assert.Equal("ValueDeep2", dict2["KeyInMem2:Deep2"]); + + var dict3 = config.GetSection("Mem3").AsEnumerable(makePathsRelative: true).ToDictionary(k => k.Key, v => v.Value); + Assert.Equal(5, dict3.Count); + Assert.Equal("NoKeyValue3", dict3[""]); + Assert.Equal("ValueInMem3", dict3["KeyInMem3"]); + Assert.Equal("ValueInMem4", dict3["KeyInMem4"]); + Assert.Equal("ValueDeep3", dict3["KeyInMem3:Deep3"]); + Assert.Equal("ValueDeep4", dict3["KeyInMem3:Deep4"]); + } + + [Fact] + public void NewConfigurationProviderOverridesOldOneWhenKeyIsDuplicated() + { + // Arrange + var dic1 = new Dictionary() + { + {"Key1:Key2", "ValueInMem1"} + }; + var dic2 = new Dictionary() + { + {"Key1:Key2", "ValueInMem2"} + }; + var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 }; + var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 }; + + var config = new ConfigurationManager(); + IConfigurationBuilder configurationBuilder = config; + + // Act + configurationBuilder.Add(memConfigSrc1); + configurationBuilder.Add(memConfigSrc2); + + // Assert + Assert.Equal("ValueInMem2", config["Key1:Key2"]); + } + + [Fact] + public void NewConfigurationRootMayBeBuiltFromExistingWithDuplicateKeys() + { + var configurationRoot = new ConfigurationManager(); + + configurationRoot.AddInMemoryCollection(new Dictionary + { + {"keya:keyb", "valueA"}, + }); + configurationRoot.AddInMemoryCollection(new Dictionary + { + {"KEYA:KEYB", "valueB"}, + }); + + var newConfigurationRoot = new ConfigurationManager(); + + newConfigurationRoot.AddInMemoryCollection(configurationRoot.AsEnumerable()); + + Assert.Equal("valueB", newConfigurationRoot["keya:keyb"]); + } + + [Fact] + public void SettingValueUpdatesAllConfigurationProviders() + { + // Arrange + var dict = new Dictionary() + { + {"Key1", "Value1"}, + {"Key2", "Value2"} + }; + + var memConfigSrc1 = new TestMemorySourceProvider(dict); + var memConfigSrc2 = new TestMemorySourceProvider(dict); + var memConfigSrc3 = new TestMemorySourceProvider(dict); + + var config = new ConfigurationManager(); + IConfigurationBuilder configurationBuilder = config; + + configurationBuilder.Add(memConfigSrc1); + configurationBuilder.Add(memConfigSrc2); + configurationBuilder.Add(memConfigSrc3); + + // Act + config["Key1"] = "NewValue1"; + config["Key2"] = "NewValue2"; + + var memConfigProvider1 = memConfigSrc1.Build(configurationBuilder); + var memConfigProvider2 = memConfigSrc2.Build(configurationBuilder); + var memConfigProvider3 = memConfigSrc3.Build(configurationBuilder); + + // Assert + Assert.Equal("NewValue1", config["Key1"]); + Assert.Equal("NewValue1", Get(memConfigProvider1, "Key1")); + Assert.Equal("NewValue1", Get(memConfigProvider2, "Key1")); + Assert.Equal("NewValue1", Get(memConfigProvider3, "Key1")); + Assert.Equal("NewValue2", config["Key2"]); + Assert.Equal("NewValue2", Get(memConfigProvider1, "Key2")); + Assert.Equal("NewValue2", Get(memConfigProvider2, "Key2")); + Assert.Equal("NewValue2", Get(memConfigProvider3, "Key2")); + } + + [Fact] + public void CanGetConfigurationSection() + { + // Arrange + var dic1 = new Dictionary() + { + {"Data:DB1:Connection1", "MemVal1"}, + {"Data:DB1:Connection2", "MemVal2"} + }; + var dic2 = new Dictionary() + { + {"DataSource:DB2:Connection", "MemVal3"} + }; + var dic3 = new Dictionary() + { + {"Data", "MemVal4"} + }; + var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 }; + var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 }; + var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 }; + + var config = new ConfigurationManager(); + IConfigurationBuilder configurationBuilder = config; + + configurationBuilder.Add(memConfigSrc1); + configurationBuilder.Add(memConfigSrc2); + configurationBuilder.Add(memConfigSrc3); + + // Act + var configFocus = config.GetSection("Data"); + + var memVal1 = configFocus["DB1:Connection1"]; + var memVal2 = configFocus["DB1:Connection2"]; + var memVal3 = configFocus["DB2:Connection"]; + var memVal4 = configFocus["Source:DB2:Connection"]; + var memVal5 = configFocus.Value; + + // Assert + Assert.Equal("MemVal1", memVal1); + Assert.Equal("MemVal2", memVal2); + Assert.Equal("MemVal4", memVal5); + + Assert.Equal("MemVal1", configFocus["DB1:Connection1"]); + Assert.Equal("MemVal2", configFocus["DB1:Connection2"]); + Assert.Null(configFocus["DB2:Connection"]); + Assert.Null(configFocus["Source:DB2:Connection"]); + Assert.Equal("MemVal4", configFocus.Value); + } + + [Fact] + public void CanGetConnectionStrings() + { + // Arrange + var dic1 = new Dictionary() + { + {"ConnectionStrings:DB1:Connection1", "MemVal1"}, + {"ConnectionStrings:DB1:Connection2", "MemVal2"} + }; + var dic2 = new Dictionary() + { + {"ConnectionStrings:DB2:Connection", "MemVal3"} + }; + var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 }; + var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 }; + + var config = new ConfigurationManager(); + IConfigurationBuilder configurationBuilder = config; + + configurationBuilder.Add(memConfigSrc1); + configurationBuilder.Add(memConfigSrc2); + + // Act + var memVal1 = config.GetConnectionString("DB1:Connection1"); + var memVal2 = config.GetConnectionString("DB1:Connection2"); + var memVal3 = config.GetConnectionString("DB2:Connection"); + + // Assert + Assert.Equal("MemVal1", memVal1); + Assert.Equal("MemVal2", memVal2); + Assert.Equal("MemVal3", memVal3); + } + + [Fact] + public void CanGetConfigurationChildren() + { + // Arrange + var dic1 = new Dictionary() + { + {"Data:DB1:Connection1", "MemVal1"}, + {"Data:DB1:Connection2", "MemVal2"} + }; + var dic2 = new Dictionary() + { + {"Data:DB2Connection", "MemVal3"} + }; + var dic3 = new Dictionary() + { + {"DataSource:DB3:Connection", "MemVal4"} + }; + var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 }; + var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 }; + var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 }; + + var config = new ConfigurationManager(); + IConfigurationBuilder configurationBuilder = config; + + configurationBuilder.Add(memConfigSrc1); + configurationBuilder.Add(memConfigSrc2); + configurationBuilder.Add(memConfigSrc3); + + // Act + var configSections = config.GetSection("Data").GetChildren().ToList(); + + // Assert + Assert.Equal(2, configSections.Count()); + Assert.Equal("MemVal1", configSections.FirstOrDefault(c => c.Key == "DB1")["Connection1"]); + Assert.Equal("MemVal2", configSections.FirstOrDefault(c => c.Key == "DB1")["Connection2"]); + Assert.Equal("MemVal3", configSections.FirstOrDefault(c => c.Key == "DB2Connection").Value); + Assert.False(configSections.Exists(c => c.Key == "DB3")); + Assert.False(configSections.Exists(c => c.Key == "DB3")); + } + + [Fact] + public void SourcesReturnsAddedConfigurationProviders() + { + // Arrange + var dict = new Dictionary() + { + {"Mem:KeyInMem", "MemVal"} + }; + var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dict }; + var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dict }; + var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dict }; + + var config = new ConfigurationManager(); + IConfigurationBuilder configurationBuilder = config; + + // Act + + // A MemoryConfigurationSource is added by default, so there will be no error unless we clear it + configurationBuilder.Sources.Clear(); + configurationBuilder.Add(memConfigSrc1); + configurationBuilder.Add(memConfigSrc2); + configurationBuilder.Add(memConfigSrc3); + + // Assert + Assert.Equal(new[] { memConfigSrc1, memConfigSrc2, memConfigSrc3 }, configurationBuilder.Sources); + } + + [Fact] + public void SetValueThrowsExceptionNoSourceRegistered() + { + // Arrange + var config = new ConfigurationManager(); + + // A MemoryConfigurationSource is added by default, so there will be no error unless we clear it + config["Title"] = "Welcome"; + + ((IConfigurationBuilder)config).Sources.Clear(); + + // Act + var ex = Assert.Throws(() => config["Title"] = "Welcome"); + + // Assert + Assert.Equal(SR.Error_NoSources, ex.Message); + } + + [Fact] + public void SameReloadTokenIsReturnedRepeatedly() + { + // Arrange + IConfiguration config = new ConfigurationManager(); + + // Act + var token1 = config.GetReloadToken(); + var token2 = config.GetReloadToken(); + + // Assert + Assert.Same(token1, token2); + } + + [Fact] + public void DifferentReloadTokenReturnedAfterReloading() + { + // Arrange + IConfigurationRoot config = new ConfigurationManager(); + + // Act + var token1 = config.GetReloadToken(); + var token2 = config.GetReloadToken(); + config.Reload(); + var token3 = config.GetReloadToken(); + var token4 = config.GetReloadToken(); + + // Assert + Assert.Same(token1, token2); + Assert.Same(token3, token4); + Assert.NotSame(token1, token3); + } + + [Fact] + public void TokenTriggeredWhenReloadOccurs() + { + // Arrange + IConfigurationRoot config = new ConfigurationManager(); + + // Act + var token1 = config.GetReloadToken(); + var hasChanged1 = token1.HasChanged; + config.Reload(); + var hasChanged2 = token1.HasChanged; + + // Assert + Assert.False(hasChanged1); + Assert.True(hasChanged2); + } + + [Fact] + public void MultipleCallbacksCanBeRegisteredToReload() + { + // Arrange + IConfigurationRoot config = new ConfigurationManager(); + + // Act + var token1 = config.GetReloadToken(); + var called1 = 0; + token1.RegisterChangeCallback(_ => called1++, state: null); + var called2 = 0; + token1.RegisterChangeCallback(_ => called2++, state: null); + + // Assert + Assert.Equal(0, called1); + Assert.Equal(0, called2); + + config.Reload(); + Assert.Equal(1, called1); + Assert.Equal(1, called2); + + var token2 = config.GetReloadToken(); + var cleanup1 = token2.RegisterChangeCallback(_ => called1++, state: null); + token2.RegisterChangeCallback(_ => called2++, state: null); + + cleanup1.Dispose(); + + config.Reload(); + Assert.Equal(1, called1); + Assert.Equal(2, called2); + } + + [Fact] + public void NewTokenAfterReloadIsNotChanged() + { + // Arrange + IConfigurationRoot config = new ConfigurationManager(); + + // Act + var token1 = config.GetReloadToken(); + var hasChanged1 = token1.HasChanged; + config.Reload(); + var hasChanged2 = token1.HasChanged; + var token2 = config.GetReloadToken(); + var hasChanged3 = token2.HasChanged; + + // + // Assert + Assert.False(hasChanged1); + Assert.True(hasChanged2); + Assert.False(hasChanged3); + Assert.NotSame(token1, token2); + } + + [Fact] + public void KeyStartingWithColonMeansFirstSectionHasEmptyName() + { + // Arrange + var dict = new Dictionary + { + [":Key2"] = "value" + }; + var config = new ConfigurationManager(); + config.AddInMemoryCollection(dict); + + // Act + var children = config.GetChildren().ToArray(); + + // Assert + Assert.Single(children); + Assert.Equal(string.Empty, children.First().Key); + Assert.Single(children.First().GetChildren()); + Assert.Equal("Key2", children.First().GetChildren().First().Key); + } + + [Fact] + public void KeyWithDoubleColonHasSectionWithEmptyName() + { + // Arrange + var dict = new Dictionary + { + ["Key1::Key3"] = "value" + }; + + var config = new ConfigurationManager(); + ((IConfigurationBuilder)config).AddInMemoryCollection(dict); + + // Act + var children = config.GetChildren().ToArray(); + + // Assert + Assert.Single(children); + Assert.Equal("Key1", children.First().Key); + Assert.Single(children.First().GetChildren()); + Assert.Equal(string.Empty, children.First().GetChildren().First().Key); + Assert.Single(children.First().GetChildren().First().GetChildren()); + Assert.Equal("Key3", children.First().GetChildren().First().GetChildren().First().Key); + } + + [Fact] + public void KeyEndingWithColonMeansLastSectionHasEmptyName() + { + // Arrange + var dict = new Dictionary + { + ["Key1:"] = "value" + }; + + var config = new ConfigurationManager(); + ((IConfigurationBuilder)config).AddInMemoryCollection(dict); + + // Act + var children = config.GetChildren().ToArray(); + + // Assert + Assert.Single(children); + Assert.Equal("Key1", children.First().Key); + Assert.Single(children.First().GetChildren()); + Assert.Equal(string.Empty, children.First().GetChildren().First().Key); + } + + [Fact] + public void SectionWithValueExists() + { + // Arrange + var dict = new Dictionary() + { + {"Mem1", "Value1"}, + {"Mem1:KeyInMem1", "ValueInMem1"}, + {"Mem1:KeyInMem1:Deep1", "ValueDeep1"} + }; + + var config = new ConfigurationManager(); + ((IConfigurationBuilder)config).AddInMemoryCollection(dict); + + // Act + var sectionExists1 = config.GetSection("Mem1").Exists(); + var sectionExists2 = config.GetSection("Mem1:KeyInMem1").Exists(); + var sectionNotExists = config.GetSection("Mem2").Exists(); + + // Assert + Assert.True(sectionExists1); + Assert.True(sectionExists2); + Assert.False(sectionNotExists); + } + + [Fact] + public void SectionGetRequiredSectionSuccess() + { + // Arrange + var dict = new Dictionary() + { + {"Mem1", "Value1"}, + {"Mem1:KeyInMem1", "ValueInMem1"}, + {"Mem1:KeyInMem1:Deep1", "ValueDeep1"} + }; + + var config = new ConfigurationManager(); + ((IConfigurationBuilder)config).AddInMemoryCollection(dict); + + // Act + var sectionExists1 = config.GetRequiredSection("Mem1").Exists(); + var sectionExists2 = config.GetRequiredSection("Mem1:KeyInMem1").Exists(); + + // Assert + Assert.True(sectionExists1); + Assert.True(sectionExists2); + } + + [Fact] + public void SectionGetRequiredSectionMissingThrowException() + { + // Arrange + var dict = new Dictionary() + { + {"Mem1", "Value1"}, + {"Mem1:Deep1", "Value1"}, + }; + + var config = new ConfigurationManager(); + ((IConfigurationBuilder)config).AddInMemoryCollection(dict); + + Assert.Throws(() => config.GetRequiredSection("Mem2")); + Assert.Throws(() => config.GetRequiredSection("Mem1:Deep2")); + } + + [Fact] + public void SectionWithChildrenExists() + { + // Arrange + var dict = new Dictionary() + { + {"Mem1:KeyInMem1", "ValueInMem1"}, + {"Mem1:KeyInMem1:Deep1", "ValueDeep1"}, + {"Mem2:KeyInMem2:Deep1", "ValueDeep2"} + }; + + var config = new ConfigurationManager(); + ((IConfigurationBuilder)config).AddInMemoryCollection(dict); + + // Act + var sectionExists1 = config.GetSection("Mem1").Exists(); + var sectionExists2 = config.GetSection("Mem2").Exists(); + var sectionNotExists = config.GetSection("Mem3").Exists(); + + // Assert + Assert.True(sectionExists1); + Assert.True(sectionExists2); + Assert.False(sectionNotExists); + } + + [Theory] + [InlineData("Value1")] + [InlineData("")] + public void KeyWithValueAndWithoutChildrenExistsAsSection(string value) + { + // Arrange + var dict = new Dictionary() + { + {"Mem1", value} + }; + + var config = new ConfigurationManager(); + ((IConfigurationBuilder)config).AddInMemoryCollection(dict); + + // Act + var sectionExists = config.GetSection("Mem1").Exists(); + + // Assert + Assert.True(sectionExists); + } + + [Fact] + public void KeyWithNullValueAndWithoutChildrenIsASectionButNotExists() + { + // Arrange + var dict = new Dictionary() + { + {"Mem1", null} + }; + + var config = new ConfigurationManager(); + ((IConfigurationBuilder)config).AddInMemoryCollection(dict); + + // Act + var sections = config.GetChildren(); + var sectionExists = config.GetSection("Mem1").Exists(); + var sectionChildren = config.GetSection("Mem1").GetChildren(); + + // Assert + Assert.Single(sections, section => section.Key == "Mem1"); + Assert.False(sectionExists); + Assert.Empty(sectionChildren); + } + + [Fact] + public void SectionWithChildrenHasNullValue() + { + // Arrange + var dict = new Dictionary() + { + {"Mem1:KeyInMem1", "ValueInMem1"}, + }; + + + var config = new ConfigurationManager(); + ((IConfigurationBuilder)config).AddInMemoryCollection(dict); + + // Act + var sectionValue = config.GetSection("Mem1").Value; + + // Assert + Assert.Null(sectionValue); + } + + [Fact] + public void ProviderWithNullReloadToken() + { + // Arrange + var config = new ConfigurationManager(); + IConfigurationBuilder builder = config; + + // Assert + Assert.NotNull(builder.Build()); + } + + [Fact] + public void BuildReturnsThis() + { + // Arrange + var config = new ConfigurationManager(); + + // Assert + Assert.Same(config, ((IConfigurationBuilder)config).Build()); + } + + private static string Get(IConfigurationProvider provider, string key) + { + string value; + + if (!provider.TryGet(key, out value)) + { + throw new InvalidOperationException("Key not found"); + } + + return value; + } + + private class TestConfigurationSource : IConfigurationSource + { + private readonly IConfigurationProvider _provider; + + public TestConfigurationSource(IConfigurationProvider provider) + { + _provider = provider; + } + + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + return _provider; + } + } + + private class TestConfigurationProvider : ConfigurationProvider + { + public TestConfigurationProvider(string key, string value) + => Data.Add(key, value); + } + + private class DisposableTestConfigurationProvider : ConfigurationProvider, IDisposable + { + public bool IsDisposed { get; set; } + + public DisposableTestConfigurationProvider(string key, string value) + => Data.Add(key, value); + + public void Dispose() + => IsDisposed = true; + } + + private class TestChangeToken : IChangeToken + { + public List<(Action, object)> Callbacks { get; } = new List<(Action, object)>(); + + public bool HasChanged => false; + + public bool ActiveChangeCallbacks => true; + + public IDisposable RegisterChangeCallback(Action callback, object state) + { + var item = (callback, state); + Callbacks.Add(item); + return new DisposableAction(() => Callbacks.Remove(item)); + } + + private class DisposableAction : IDisposable + { + private Action _action; + + public DisposableAction(Action action) + { + _action = action; + } + + public void Dispose() + { + var a = _action; + if (a != null) + { + _action = null; + a(); + } + } + } + } + + private class TestMemorySourceProvider : MemoryConfigurationProvider, IConfigurationSource + { + public TestMemorySourceProvider(Dictionary initialData) + : base(new MemoryConfigurationSource { InitialData = initialData }) + { } + + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + return this; + } + } + + private class NullReloadTokenConfigSource : IConfigurationSource, IConfigurationProvider + { + public IEnumerable GetChildKeys(IEnumerable earlierKeys, string parentPath) => throw new NotImplementedException(); + public IChangeToken GetReloadToken() => null; + public void Load() { } + public void Set(string key, string value) => throw new NotImplementedException(); + public bool TryGet(string key, out string value) => throw new NotImplementedException(); + public IConfigurationProvider Build(IConfigurationBuilder builder) => this; + } + + } +} From 2c275e87c0033da3830bae29059d9ee5b663cf58 Mon Sep 17 00:00:00 2001 From: Jan Jahoda Date: Tue, 13 Jul 2021 19:45:59 +0200 Subject: [PATCH 080/133] Delayed client certificate (#54692) * initial prototype * Restore TLS 1.2 renegotiation * First windows functionality merge * reenable client certificate * Add more renegotiate tests * Remove client certificates * Cleanup * add test log * Apply PR comments * Add Data frame test * Add drain buffer test * Fix tls 1.3 incomming app data frame * Restore verify callback * Remove debug log * Remove keylog callback and unused method * Fix test build * Attempt to fix openssl version api difference * Sort shim * fix build * CI log * Restore mac tests * Add logs * fix test runs on old openssl * fix tests * fix w7 condition * feedback from review Co-authored-by: wfurt --- .../Interop.OpenSsl.cs | 14 ++ .../Interop.Ssl.cs | 3 + .../apibridge.c | 7 + .../apibridge.h | 1 + .../entrypoints.c | 1 + .../opensslshim.h | 12 +- .../osslcompat_111.h | 2 + .../pal_ssl.c | 28 +++ .../pal_ssl.h | 7 + .../src/Resources/Strings.resx | 3 + .../Net/Security/SslStream.Implementation.cs | 179 ++++++++++-------- .../src/System/Net/Security/SslStream.cs | 2 +- .../System/Net/Security/SslStreamPal.Unix.cs | 26 ++- .../SslStreamNetworkStreamTest.cs | 21 +- .../FunctionalTests/TestConfiguration.cs | 2 +- .../Fakes/FakeSslStream.Implementation.cs | 2 +- 16 files changed, 215 insertions(+), 95 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs index 47a9312266e4..9dd310326b0a 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; +using System.Net; using System.Net.Security; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -223,6 +224,19 @@ internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX50 return context; } + internal static SecurityStatusPal SslRenegotiate(SafeSslHandle sslContext, out byte[]? outputBuffer) + { + int ret = Interop.Ssl.SslRenegotiate(sslContext); + + outputBuffer = Array.Empty(); + if (ret != 1) + { + GetSslError(sslContext, ret, out Exception? exception); + return new SecurityStatusPal(SecurityStatusPalErrorCode.InternalError, exception); + } + return new SecurityStatusPal(SecurityStatusPalErrorCode.OK); + } + internal static bool DoSslHandshake(SafeSslHandle context, ReadOnlySpan input, out byte[]? sendBuf, out int sendCount) { sendBuf = null; diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs index d080cf2f0d7b..29154b77da63 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs @@ -74,6 +74,9 @@ internal static partial class Ssl [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslRead", SetLastError = true)] internal static extern int SslRead(SafeSslHandle ssl, ref byte buf, int num); + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslRenegotiate")] + internal static extern int SslRenegotiate(SafeSslHandle ssl); + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_IsSslRenegotiatePending")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool IsSslRenegotiatePending(SafeSslHandle ssl); diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/apibridge.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/apibridge.c index daf13002b118..0414eb2b2710 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/apibridge.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/apibridge.c @@ -785,6 +785,13 @@ unsigned long local_SSL_CTX_set_options(SSL_CTX* ctx, unsigned long options) return (unsigned long)SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, (long)options, NULL); } +unsigned long local_SSL_set_options(SSL* ssl, unsigned long options) +{ + // SSL_ctrl is signed long in and signed long out; but SSL_set_options, + // which was a macro call to SSL_ctrl in 1.0, is unsigned/unsigned. + return (unsigned long)SSL_ctrl(ssl, SSL_CTRL_OPTIONS, (long)options, NULL); +} + int local_SSL_session_reused(SSL* ssl) { return (int)SSL_ctrl(ssl, SSL_CTRL_GET_SESSION_REUSED, 0, NULL); diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/apibridge.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/apibridge.h index 1b866bc4474d..968e12dad157 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/apibridge.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/apibridge.h @@ -32,6 +32,7 @@ int32_t local_RSA_pkey_ctx_ctrl(EVP_PKEY_CTX* ctx, int32_t optype, int32_t cmd, int32_t local_SSL_is_init_finished(const SSL* ssl); int32_t local_SSL_CTX_config(SSL_CTX* ctx, const char* name); unsigned long local_SSL_CTX_set_options(SSL_CTX* ctx, unsigned long options); +unsigned long local_SSL_set_options(SSL* ssl, unsigned long options); void local_SSL_CTX_set_security_level(SSL_CTX* ctx, int32_t level); int local_SSL_session_reused(SSL* ssl); int32_t local_X509_check_host(X509* x509, const char* name, size_t namelen, unsigned int flags, char** peername); diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c index 996921777f6f..99db84bfc3cc 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c @@ -286,6 +286,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_BioWrite) DllImportEntry(CryptoNative_EnsureLibSslInitialized) DllImportEntry(CryptoNative_GetOpenSslCipherSuiteName) + DllImportEntry(CryptoNative_SslRenegotiate) DllImportEntry(CryptoNative_IsSslRenegotiatePending) DllImportEntry(CryptoNative_IsSslStateOK) DllImportEntry(CryptoNative_SetCiphers) diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h index 050cdc251407..510d319a11de 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h @@ -458,7 +458,6 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); REQUIRED_FUNCTION(SSL_CTX_new) \ LIGHTUP_FUNCTION(SSL_CTX_set_alpn_protos) \ LIGHTUP_FUNCTION(SSL_CTX_set_alpn_select_cb) \ - REQUIRED_FUNCTION(SSL_CTX_set_cert_verify_callback) \ REQUIRED_FUNCTION(SSL_CTX_set_cipher_list) \ LIGHTUP_FUNCTION(SSL_CTX_set_ciphersuites) \ REQUIRED_FUNCTION(SSL_CTX_set_client_cert_cb) \ @@ -484,12 +483,16 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); LEGACY_FUNCTION(SSL_library_init) \ LEGACY_FUNCTION(SSL_load_error_strings) \ REQUIRED_FUNCTION(SSL_new) \ + REQUIRED_FUNCTION(SSL_peek) \ REQUIRED_FUNCTION(SSL_read) \ + REQUIRED_FUNCTION(SSL_renegotiate) \ REQUIRED_FUNCTION(SSL_renegotiate_pending) \ FALLBACK_FUNCTION(SSL_session_reused) \ REQUIRED_FUNCTION(SSL_set_accept_state) \ REQUIRED_FUNCTION(SSL_set_bio) \ REQUIRED_FUNCTION(SSL_set_connect_state) \ + FALLBACK_FUNCTION(SSL_set_options) \ + REQUIRED_FUNCTION(SSL_set_verify) \ REQUIRED_FUNCTION(SSL_shutdown) \ LEGACY_FUNCTION(SSL_state) \ LEGACY_FUNCTION(SSLeay) \ @@ -895,7 +898,6 @@ FOR_ALL_OPENSSL_FUNCTIONS #define SSL_CTX_new SSL_CTX_new_ptr #define SSL_CTX_set_alpn_protos SSL_CTX_set_alpn_protos_ptr #define SSL_CTX_set_alpn_select_cb SSL_CTX_set_alpn_select_cb_ptr -#define SSL_CTX_set_cert_verify_callback SSL_CTX_set_cert_verify_callback_ptr #define SSL_CTX_set_cipher_list SSL_CTX_set_cipher_list_ptr #define SSL_CTX_set_ciphersuites SSL_CTX_set_ciphersuites_ptr #define SSL_CTX_set_client_cert_cb SSL_CTX_set_client_cert_cb_ptr @@ -922,12 +924,18 @@ FOR_ALL_OPENSSL_FUNCTIONS #define SSL_library_init SSL_library_init_ptr #define SSL_load_error_strings SSL_load_error_strings_ptr #define SSL_new SSL_new_ptr +#define SSL_peek SSL_peek_ptr +#define SSL_state_string_long SSL_state_string_long_ptr #define SSL_read SSL_read_ptr +#define ERR_print_errors_fp ERR_print_errors_fp_ptr +#define SSL_renegotiate SSL_renegotiate_ptr #define SSL_renegotiate_pending SSL_renegotiate_pending_ptr #define SSL_session_reused SSL_session_reused_ptr #define SSL_set_accept_state SSL_set_accept_state_ptr #define SSL_set_bio SSL_set_bio_ptr #define SSL_set_connect_state SSL_set_connect_state_ptr +#define SSL_set_options SSL_set_options_ptr +#define SSL_set_verify SSL_set_verify_ptr #define SSL_shutdown SSL_shutdown_ptr #define SSL_state SSL_state_ptr #define SSLeay SSLeay_ptr diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/osslcompat_111.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/osslcompat_111.h index e9a1b4939bab..6791e306019b 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/osslcompat_111.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/osslcompat_111.h @@ -7,6 +7,7 @@ #include "pal_types.h" #undef SSL_CTX_set_options +#undef SSL_set_options #undef SSL_session_reused typedef struct ossl_init_settings_st OPENSSL_INIT_SETTINGS; @@ -56,6 +57,7 @@ int SSL_CTX_config(SSL_CTX* ctx, const char* name); unsigned long SSL_CTX_set_options(SSL_CTX* ctx, unsigned long options); void SSL_CTX_set_security_level(SSL_CTX* ctx, int32_t level); int32_t SSL_is_init_finished(SSL* ssl); +unsigned long SSL_set_options(SSL* ctx, unsigned long options); int SSL_session_reused(SSL* ssl); const SSL_METHOD* TLS_method(void); const ASN1_TIME* X509_CRL_get0_nextUpdate(const X509_CRL* crl); diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.c index 524179cdcb20..acdc977b5f32 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.c @@ -368,8 +368,36 @@ int32_t CryptoNative_SslRead(SSL* ssl, void* buf, int32_t num) return SSL_read(ssl, buf, num); } +static int verify_callback(int preverify_ok, X509_STORE_CTX* store) +{ + (void)preverify_ok; + (void)store; + // We don't care. Real verification happens in managed code. + return 1; +} + +int32_t CryptoNative_SslRenegotiate(SSL* ssl) +{ + // The openssl context is destroyed so we can't use ticket or session resumption. + SSL_set_options(ssl, SSL_OP_NO_TICKET | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + int pending = SSL_renegotiate_pending(ssl); + if (!pending) + { + SSL_set_verify(ssl, SSL_VERIFY_PEER, verify_callback); + int ret = SSL_renegotiate(ssl); + if(ret != 1) + return ret; + + return SSL_do_handshake(ssl); + } + + return 0; +} + int32_t CryptoNative_IsSslRenegotiatePending(SSL* ssl) { + SSL_peek(ssl, NULL, 0); return SSL_renegotiate_pending(ssl) != 0; } diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.h index d7b995c5a19e..79c6cbe22f95 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.h @@ -213,6 +213,13 @@ when an error is encountered. */ PALEXPORT int32_t CryptoNative_SslRead(SSL* ssl, void* buf, int32_t num); +/* +Shims the SSL_renegotiate method. + +Returns 1 when renegotiation started; 0 on error. +*/ +PALEXPORT int32_t CryptoNative_SslRenegotiate(SSL* ssl); + /* Shims the SSL_renegotiate_pending method. diff --git a/src/libraries/System.Net.Security/src/Resources/Strings.resx b/src/libraries/System.Net.Security/src/Resources/Strings.resx index 513a54ccc2a0..e33b4122a7d5 100644 --- a/src/libraries/System.Net.Security/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Security/src/Resources/Strings.resx @@ -455,6 +455,9 @@ Received data during renegotiation. + + Client stream needs to be drained before renegotiation. + Setting an SNI hostname is not supported on this API level. diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs index 2ecc2a3932ab..f0f0b954d4a8 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs @@ -19,6 +19,7 @@ public partial class SslStream private SslAuthenticationOptions? _sslAuthenticationOptions; private int _nestedAuth; + private bool _isRenego; private enum Framing { @@ -296,7 +297,8 @@ private async Task ReplyOnReAuthenticationAsync(TIOAdapter adapter, } // This will initiate renegotiation or PHA for Tls1.3 - private async Task RenegotiateAsync(CancellationToken cancellationToken) + private async Task RenegotiateAsync(TIOAdapter adapter) + where TIOAdapter : IReadWriteAdapter { if (Interlocked.Exchange(ref _nestedAuth, 1) == 1) { @@ -314,13 +316,19 @@ private async Task RenegotiateAsync(CancellationToken cancellationToken) throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, nameof(WriteAsync), "write")); } + if (_decryptedBytesCount is not 0) + { + throw new InvalidOperationException(SR.net_ssl_renegotiate_buffer); + } + _sslAuthenticationOptions!.RemoteCertRequired = true; - IReadWriteAdapter adapter = new AsyncReadWriteAdapter(InnerStream, cancellationToken); + _isRenego = true; try { SecurityStatusPal status = _context!.Renegotiate(out byte[]? nextmsg); - if (nextmsg?.Length > 0) + + if (nextmsg is {} && nextmsg.Length > 0) { await adapter.WriteAsync(nextmsg, 0, nextmsg.Length).ConfigureAwait(false); await adapter.FlushAsync().ConfigureAwait(false); @@ -330,20 +338,39 @@ private async Task RenegotiateAsync(CancellationToken cancellationToken) { if (status.ErrorCode == SecurityStatusPalErrorCode.NoRenegotiation) { - // peer does not want to renegotiate. That should keep session usable. + // Peer does not want to renegotiate. That should keep session usable. return; } throw SslStreamPal.GetException(status); } - // Issue empty read to get renegotiation going. - await ReadAsyncInternal(adapter, Memory.Empty, renegotiation: true).ConfigureAwait(false); + _handshakeBuffer = new ArrayBuffer(InitialHandshakeBufferSize); + ProtocolToken message = null!; + do { + message = await ReceiveBlobAsync(adapter).ConfigureAwait(false); + if (message.Size > 0) + { + await adapter.WriteAsync(message.Payload!, 0, message.Size).ConfigureAwait(false); + await adapter.FlushAsync().ConfigureAwait(false); + } + } while (message.Status.ErrorCode == SecurityStatusPalErrorCode.ContinueNeeded); + + if (_handshakeBuffer.ActiveLength > 0) + { + // If we read more than we needed for handshake, move it to input buffer for further processing. + ResetReadBuffer(); + _handshakeBuffer.ActiveSpan.CopyTo(_internalBuffer); + _internalBufferCount = _handshakeBuffer.ActiveLength; + } + + CompleteHandshake(_sslAuthenticationOptions!); } finally { _nestedRead = 0; _nestedWrite = 0; + _isRenego = false; // We will not release _nestedAuth at this point to prevent another renegotiation attempt. } } @@ -452,25 +479,7 @@ private async Task ForceAuthenticationAsync(TIOAdapter adapter, bool _internalBufferCount = _handshakeBuffer.ActiveLength; } - ProtocolToken? alertToken = null; - if (!CompleteHandshake(ref alertToken, out SslPolicyErrors sslPolicyErrors, out X509ChainStatusFlags chainStatus)) - { - if (_sslAuthenticationOptions!.CertValidationDelegate != null) - { - // there may be some chain errors but the decision was made by custom callback. Details should be tracing if enabled. - SendAuthResetSignal(alertToken, ExceptionDispatchInfo.Capture(new AuthenticationException(SR.net_ssl_io_cert_custom_validation, null))); - } - else if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors && chainStatus != X509ChainStatusFlags.NoError) - { - // We failed only because of chain and we have some insight. - SendAuthResetSignal(alertToken, ExceptionDispatchInfo.Capture(new AuthenticationException(SR.Format(SR.net_ssl_io_cert_chain_validation, chainStatus), null))); - } - else - { - // Simple add sslPolicyErrors as crude info. - SendAuthResetSignal(alertToken, ExceptionDispatchInfo.Capture(new AuthenticationException(SR.Format(SR.net_ssl_io_cert_validation, sslPolicyErrors), null))); - } - } + CompleteHandshake(_sslAuthenticationOptions!); } finally { @@ -478,6 +487,7 @@ private async Task ForceAuthenticationAsync(TIOAdapter adapter, bool if (reAuthenticationData == null) { _nestedAuth = 0; + _isRenego = false; } } @@ -534,51 +544,60 @@ private async ValueTask ReceiveBlobAsync(TIOAdapter a } // At this point, we have at least one TLS frame. - if (_lastFrame.Header.Type == TlsContentType.Alert) + switch (_lastFrame.Header.Type) { - if (TlsFrameHelper.TryGetFrameInfo(_handshakeBuffer.ActiveReadOnlySpan, ref _lastFrame)) - { - if (NetEventSource.Log.IsEnabled() && _lastFrame.AlertDescription != TlsAlertDescription.CloseNotify) NetEventSource.Error(this, $"Received TLS alert {_lastFrame.AlertDescription}"); - } - } - else if (_lastFrame.Header.Type == TlsContentType.Handshake) - { - if (_handshakeBuffer.ActiveReadOnlySpan[TlsFrameHelper.HeaderSize] == (byte)TlsHandshakeType.ClientHello && - (_sslAuthenticationOptions!.ServerCertSelectionDelegate != null || - _sslAuthenticationOptions!.ServerOptionDelegate != null)) - { - TlsFrameHelper.ProcessingOptions options = NetEventSource.Log.IsEnabled() ? - TlsFrameHelper.ProcessingOptions.All : - TlsFrameHelper.ProcessingOptions.ServerName; - - // Process SNI from Client Hello message - if (!TlsFrameHelper.TryGetFrameInfo(_handshakeBuffer.ActiveReadOnlySpan, ref _lastFrame, options)) + case TlsContentType.Alert: + if (TlsFrameHelper.TryGetFrameInfo(_handshakeBuffer.ActiveReadOnlySpan, ref _lastFrame)) { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"Failed to parse TLS hello."); + if (NetEventSource.Log.IsEnabled() && _lastFrame.AlertDescription != TlsAlertDescription.CloseNotify) NetEventSource.Error(this, $"Received TLS alert {_lastFrame.AlertDescription}"); } - - if (_lastFrame.HandshakeType == TlsHandshakeType.ClientHello) + break; + case TlsContentType.Handshake: + if (!_isRenego && _handshakeBuffer.ActiveReadOnlySpan[TlsFrameHelper.HeaderSize] == (byte)TlsHandshakeType.ClientHello && + (_sslAuthenticationOptions!.ServerCertSelectionDelegate != null || + _sslAuthenticationOptions!.ServerOptionDelegate != null)) { - // SNI if it exist. Even if we could not parse the hello, we can fall-back to default certificate. - if (_lastFrame.TargetName != null) + TlsFrameHelper.ProcessingOptions options = NetEventSource.Log.IsEnabled() ? + TlsFrameHelper.ProcessingOptions.All : + TlsFrameHelper.ProcessingOptions.ServerName; + + // Process SNI from Client Hello message + if (!TlsFrameHelper.TryGetFrameInfo(_handshakeBuffer.ActiveReadOnlySpan, ref _lastFrame, options)) { - _sslAuthenticationOptions!.TargetHost = _lastFrame.TargetName; + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"Failed to parse TLS hello."); } - if (_sslAuthenticationOptions.ServerOptionDelegate != null) + if (_lastFrame.HandshakeType == TlsHandshakeType.ClientHello) { - SslServerAuthenticationOptions userOptions = - await _sslAuthenticationOptions.ServerOptionDelegate(this, new SslClientHelloInfo(_sslAuthenticationOptions.TargetHost, _lastFrame.SupportedVersions), - _sslAuthenticationOptions.UserState, adapter.CancellationToken).ConfigureAwait(false); - _sslAuthenticationOptions.UpdateOptions(userOptions); + // SNI if it exist. Even if we could not parse the hello, we can fall-back to default certificate. + if (_lastFrame.TargetName != null) + { + _sslAuthenticationOptions!.TargetHost = _lastFrame.TargetName; + } + + if (_sslAuthenticationOptions.ServerOptionDelegate != null) + { + SslServerAuthenticationOptions userOptions = + await _sslAuthenticationOptions.ServerOptionDelegate(this, new SslClientHelloInfo(_sslAuthenticationOptions.TargetHost, _lastFrame.SupportedVersions), + _sslAuthenticationOptions.UserState, adapter.CancellationToken).ConfigureAwait(false); + _sslAuthenticationOptions.UpdateOptions(userOptions); + } } - } - if (NetEventSource.Log.IsEnabled()) + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Log.ReceivedFrame(this, _lastFrame); + } + } + break; + case TlsContentType.AppData: + // TLS1.3 it is not possible to distinguish between late Handshake and Application Data + if (_isRenego && SslProtocol != SslProtocols.Tls13) { - NetEventSource.Log.ReceivedFrame(this, _lastFrame); + throw new InvalidOperationException(SR.net_ssl_renegotiate_data); } - } + break; + } return ProcessBlob(frameSize); @@ -674,6 +693,29 @@ private bool CompleteHandshake(ref ProtocolToken? alertToken, out SslPolicyError return true; } + private void CompleteHandshake(SslAuthenticationOptions sslAuthenticationOptions) + { + ProtocolToken? alertToken = null; + if (!CompleteHandshake(ref alertToken, out SslPolicyErrors sslPolicyErrors, out X509ChainStatusFlags chainStatus)) + { + if (sslAuthenticationOptions!.CertValidationDelegate != null) + { + // there may be some chain errors but the decision was made by custom callback. Details should be tracing if enabled. + SendAuthResetSignal(alertToken, ExceptionDispatchInfo.Capture(new AuthenticationException(SR.net_ssl_io_cert_custom_validation, null))); + } + else if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors && chainStatus != X509ChainStatusFlags.NoError) + { + // We failed only because of chain and we have some insight. + SendAuthResetSignal(alertToken, ExceptionDispatchInfo.Capture(new AuthenticationException(SR.Format(SR.net_ssl_io_cert_chain_validation, chainStatus), null))); + } + else + { + // Simple add sslPolicyErrors as crude info. + SendAuthResetSignal(alertToken, ExceptionDispatchInfo.Capture(new AuthenticationException(SR.Format(SR.net_ssl_io_cert_validation, sslPolicyErrors), null))); + } + } + } + private async ValueTask WriteAsyncChunked(TIOAdapter writeAdapter, ReadOnlyMemory buffer) where TIOAdapter : struct, IReadWriteAdapter { @@ -916,15 +958,12 @@ private SecurityStatusPal DecryptData(int frameSize) return status; } - private async ValueTask ReadAsyncInternal(TIOAdapter adapter, Memory buffer, bool renegotiation = false) + private async ValueTask ReadAsyncInternal(TIOAdapter adapter, Memory buffer) where TIOAdapter : IReadWriteAdapter { - if (!renegotiation) + if (Interlocked.Exchange(ref _nestedRead, 1) == 1) { - if (Interlocked.Exchange(ref _nestedRead, 1) == 1) - { - throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, nameof(SslStream.ReadAsync), "read")); - } + throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, nameof(SslStream.ReadAsync), "read")); } ThrowIfExceptionalOrNotAuthenticated(); @@ -937,11 +976,6 @@ private async ValueTask ReadAsyncInternal(TIOAdapter adapter, M { if (_decryptedBytesCount != 0) { - if (renegotiation) - { - throw new InvalidOperationException(SR.net_ssl_renegotiate_data); - } - processedLength = CopyDecryptedData(buffer); if (processedLength == buffer.Length || !HaveFullTlsFrame(out payloadBytes)) { @@ -1006,11 +1040,6 @@ private async ValueTask ReadAsyncInternal(TIOAdapter adapter, M throw new IOException(SR.net_ssl_io_renego); } await ReplyOnReAuthenticationAsync(adapter, extraBuffer).ConfigureAwait(false); - if (renegotiation) - { - // if we received data frame instead, we would not be here but we would decrypt data and hit check above. - return 0; - } // Loop on read. continue; } @@ -1064,7 +1093,7 @@ private async ValueTask ReadAsyncInternal(TIOAdapter adapter, M } catch (Exception e) { - if (e is IOException || (e is OperationCanceledException && adapter.CancellationToken.IsCancellationRequested) || renegotiation) + if (e is IOException || (e is OperationCanceledException && adapter.CancellationToken.IsCancellationRequested)) { throw; } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.cs index 75c2938d444a..276652dbcb94 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.cs @@ -697,7 +697,7 @@ public virtual Task NegotiateClientCertificateAsync(CancellationToken cancellati throw new InvalidOperationException(SR.net_ssl_certificate_exist); } - return RenegotiateAsync(cancellationToken); + return RenegotiateAsync(new AsyncReadWriteAdapter(InnerStream, cancellationToken)); } protected override void Dispose(bool disposing) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs index 020456131702..ed0bcaaccd1b 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs @@ -36,11 +36,6 @@ public static SecurityStatusPal InitializeSecurityContext(ref SafeFreeCredential return HandshakeInternal(credential!, ref context, inputBuffer, ref outputBuffer, sslAuthenticationOptions); } - public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentialsHandle, ref SafeDeleteSslContext? context, SslAuthenticationOptions sslAuthenticationOptions, out byte[]? outputBuffer) - { - throw new PlatformNotSupportedException(); - } - public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateContext? certificateContext, SslProtocols protocols, EncryptionPolicy policy, bool isServer) { @@ -120,6 +115,19 @@ Interop.Ssl.SslErrorCode.SSL_ERROR_NONE or return bindingHandle; } + public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentialsHandle, ref SafeDeleteSslContext? securityContext, SslAuthenticationOptions sslAuthenticationOptions, out byte[]? outputBuffer) + { + var sslContext = ((SafeDeleteSslContext)securityContext!).SslContext; + SecurityStatusPal status = Interop.OpenSsl.SslRenegotiate(sslContext, out outputBuffer); + + outputBuffer = Array.Empty(); + if (status.ErrorCode != SecurityStatusPalErrorCode.OK) + { + return status; + } + return HandshakeInternal(credentialsHandle!, ref securityContext, null, ref outputBuffer, sslAuthenticationOptions); + } + public static void QueryContextStreamSizes(SafeDeleteContext? securityContext, out StreamSizes streamSizes) { streamSizes = StreamSizes.Default; @@ -150,7 +158,13 @@ private static SecurityStatusPal HandshakeInternal(SafeFreeCredentials credentia context = new SafeDeleteSslContext((credential as SafeFreeSslCredentials)!, sslAuthenticationOptions); } - bool done = Interop.OpenSsl.DoSslHandshake(context.SslContext, inputBuffer, out output, out outputSize); + bool done = Interop.OpenSsl.DoSslHandshake(((SafeDeleteSslContext)context).SslContext, inputBuffer, out output, out outputSize); + // sometimes during renegotiation processing messgae does not yield new output. + // That seems to be flaw in OpenSSL state machine and we have workaround to peek it and try it again. + if (outputSize == 0 && Interop.Ssl.IsSslRenegotiatePending(((SafeDeleteSslContext)context).SslContext)) + { + done = Interop.OpenSsl.DoSslHandshake(((SafeDeleteSslContext)context).SslContext, ReadOnlySpan.Empty, out output, out outputSize); + } // When the handshake is done, and the context is server, check if the alpnHandle target was set to null during ALPN. // If it was, then that indicates ALPN failed, send failure. diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs index 6ff3afb23f3a..b3a5f5e21f46 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs @@ -42,6 +42,8 @@ public void Dispose() public class SslStreamNetworkStreamTest : IClassFixture { + private static bool SupportsRenegotiation => TestConfiguration.SupportsRenegotiation; + readonly ITestOutputHelper _output; readonly CertificateSetup certificates; @@ -172,10 +174,10 @@ public async Task SslStream_NetworkStream_Renegotiation_Succeeds(bool useSync) } } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [ConditionalTheory(nameof(SupportsRenegotiation))] [InlineData(true)] [InlineData(false)] - [PlatformSpecific(TestPlatforms.Windows)] + [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.Linux)] public async Task SslStream_NegotiateClientCertificateAsync_Succeeds(bool sendClientCertificate) { bool negotiateClientCertificateCalled = false; @@ -214,6 +216,7 @@ public async Task SslStream_NegotiateClientCertificateAsync_Succeeds(bool sendCl return true; }; + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( client.AuthenticateAsClientAsync(clientOptions, cts.Token), server.AuthenticateAsServerAsync(serverOptions, cts.Token)); @@ -234,19 +237,21 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( { Assert.Null(server.RemoteCertificate); } + // Finish the client's read await server.WriteAsync(TestHelper.s_ping, cts.Token); await t; + // verify that the session is usable with or without client's certificate await TestHelper.PingPong(client, server, cts.Token); await TestHelper.PingPong(server, client, cts.Token); } } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [ConditionalTheory(nameof(SupportsRenegotiation))] [InlineData(true)] [InlineData(false)] - [PlatformSpecific(TestPlatforms.Windows)] + [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.Linux)] public async Task SslStream_NegotiateClientCertificateAsyncNoRenego_Succeeds(bool sendClientCertificate) { bool negotiateClientCertificateCalled = false; @@ -316,8 +321,7 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [PlatformSpecific(TestPlatforms.Windows)] - [ActiveIssue("https://github.com/dotnet/runtime/pull/54692")] + [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.Linux)] public async Task SslStream_NegotiateClientCertificateAsync_ClientWriteData() { using CancellationTokenSource cts = new CancellationTokenSource(); @@ -344,7 +348,6 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( Assert.Null(server.RemoteCertificate); - var t = server.NegotiateClientCertificateAsync(cts.Token); // Send application data instead of Client hello. @@ -354,8 +357,8 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [PlatformSpecific(TestPlatforms.Windows)] + [ConditionalFact(nameof(SupportsRenegotiation))] + [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.Linux)] public async Task SslStream_NegotiateClientCertificateAsync_ServerDontDrainClientData() { using CancellationTokenSource cts = new CancellationTokenSource(); diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/TestConfiguration.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/TestConfiguration.cs index a7dbdc9451ea..4bd1b0ee30f9 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/TestConfiguration.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/TestConfiguration.cs @@ -27,8 +27,8 @@ internal static class TestConfiguration public const string NtlmUserFilePath = "/var/tmp/ntlm_user_file"; public static bool SupportsNullEncryption { get { return s_supportsNullEncryption.Value; } } - public static bool SupportsHandshakeAlerts { get { return OperatingSystem.IsLinux() || OperatingSystem.IsWindows(); } } + public static bool SupportsRenegotiation { get { return (OperatingSystem.IsWindows() && !PlatformDetection.IsWindows7) || ((OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD()) && PlatformDetection.OpenSslVersion >= new Version(1, 1, 1)); } } public static Task WhenAllOrAnyFailedWithTimeout(params Task[] tasks) => tasks.WhenAllOrAnyFailed(PassingTestTimeoutMilliseconds); diff --git a/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeSslStream.Implementation.cs b/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeSslStream.Implementation.cs index 4e146153626b..ee160631b400 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeSslStream.Implementation.cs +++ b/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeSslStream.Implementation.cs @@ -59,7 +59,7 @@ private Task ProcessAuthentication(bool isAsync = false, bool isApm = false, Can return Task.Run(() => {}); } - private Task RenegotiateAsync(CancellationToken cancellationToken) => throw new PlatformNotSupportedException(); + private Task RenegotiateAsync(AsyncReadWriteAdapter adapter) => throw new PlatformNotSupportedException(); private void ReturnReadBufferIfEmpty() { From 04429ca36aa2a4075a1638a6025d2df74792741d Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 13 Jul 2021 12:57:48 -0500 Subject: [PATCH 081/133] Remove C# dynamic support from JsonNode (#55430) --- .../System.Text.Json/ref/System.Text.Json.cs | 3 +- .../ref/System.Text.Json.csproj | 2 - .../ILLink.Suppressions.LibraryBuild.xml | 12 - .../src/System.Text.Json.csproj | 5 - .../Text/Json/Nodes/JsonNode.Dynamic.cs | 27 -- .../Text/Json/Nodes/JsonObject.Dynamic.cs | 55 --- .../src/System/Text/Json/Nodes/MetaDynamic.cs | 438 ------------------ .../Collection/StackOfTConverter.cs | 1 + .../Converters/Node/JsonNodeConverter.cs | 6 +- .../Converters/Node/JsonObjectConverter.cs | 2 +- .../JsonNode/DynamicTests.cs | 282 ----------- .../JsonNode/JsonArrayTests.cs | 42 ++ .../System.Text.Json.Tests.csproj | 1 - .../TrimmingTests/Collections/StackOfT.cs | 14 +- 14 files changed, 56 insertions(+), 834 deletions(-) delete mode 100644 src/libraries/System.Text.Json/src/ILLink/ILLink.Suppressions.LibraryBuild.xml delete mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.Dynamic.cs delete mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.Dynamic.cs delete mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Nodes/MetaDynamic.cs delete mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/DynamicTests.cs diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index a7d5a44feb61..26b72dd6493a 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -547,7 +547,7 @@ public void RemoveAt(int index) { } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } public override void WriteTo(System.Text.Json.Utf8JsonWriter writer, System.Text.Json.JsonSerializerOptions? options = null) { } } - public abstract partial class JsonNode : System.Dynamic.IDynamicMetaObjectProvider + public abstract partial class JsonNode { internal JsonNode() { } public System.Text.Json.Nodes.JsonNode? this[int index] { get { throw null; } set { } } @@ -646,7 +646,6 @@ internal JsonNode() { } public static System.Text.Json.Nodes.JsonNode? Parse(System.ReadOnlySpan utf8Json, System.Text.Json.Nodes.JsonNodeOptions? nodeOptions = default(System.Text.Json.Nodes.JsonNodeOptions?), System.Text.Json.JsonDocumentOptions documentOptions = default(System.Text.Json.JsonDocumentOptions)) { throw null; } public static System.Text.Json.Nodes.JsonNode? Parse(string json, System.Text.Json.Nodes.JsonNodeOptions? nodeOptions = default(System.Text.Json.Nodes.JsonNodeOptions?), System.Text.Json.JsonDocumentOptions documentOptions = default(System.Text.Json.JsonDocumentOptions)) { throw null; } public static System.Text.Json.Nodes.JsonNode? Parse(ref System.Text.Json.Utf8JsonReader reader, System.Text.Json.Nodes.JsonNodeOptions? nodeOptions = default(System.Text.Json.Nodes.JsonNodeOptions?)) { throw null; } - System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter) { throw null; } public string ToJsonString(System.Text.Json.JsonSerializerOptions? options = null) { throw null; } public override string ToString() { throw null; } public abstract void WriteTo(System.Text.Json.Utf8JsonWriter writer, System.Text.Json.JsonSerializerOptions? options = null); diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.csproj b/src/libraries/System.Text.Json/ref/System.Text.Json.csproj index dc0bb21c008c..f015d29c355e 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.csproj @@ -13,7 +13,6 @@ - @@ -21,7 +20,6 @@ - diff --git a/src/libraries/System.Text.Json/src/ILLink/ILLink.Suppressions.LibraryBuild.xml b/src/libraries/System.Text.Json/src/ILLink/ILLink.Suppressions.LibraryBuild.xml deleted file mode 100644 index b12ccebb9c92..000000000000 --- a/src/libraries/System.Text.Json/src/ILLink/ILLink.Suppressions.LibraryBuild.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - ILLink - IL2026 - member - M:System.Text.Json.Nodes.JsonNode.System#Dynamic#IDynamicMetaObjectProvider#GetMetaObject(System.Linq.Expressions.Expression) - System.Text.Json's integration with dynamic is not trim compatible. However, there isn't a direct API developers call. Instead they use the 'dynamic' keyword, which gets compiled into calls to Microsoft.CSharp. Microsoft.CSharp looks for IDynamicMetaObjectProvider implementations. Leaving this warning in the product so developers get a warning stating that using dynamic JsonValue code may be broken in trimmed apps. - - - diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 195c640c85aa..3206cbd6de1f 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -59,20 +59,17 @@ - - - @@ -298,7 +295,6 @@ - @@ -315,7 +311,6 @@ - diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.Dynamic.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.Dynamic.cs deleted file mode 100644 index 457af37eb0f2..000000000000 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.Dynamic.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; -using System.Dynamic; -using System.Linq.Expressions; -using System.Reflection; - -namespace System.Text.Json.Nodes -{ - public partial class JsonNode : IDynamicMetaObjectProvider - { - internal virtual MethodInfo? TryGetMemberMethodInfo => null; - internal virtual MethodInfo? TrySetMemberMethodInfo - { - [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] - get => null; - } - - DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) => - CreateDynamicObject(parameter, this); - - [RequiresUnreferencedCode("Using JsonNode instances as dynamic types is not compatible with trimming. It can result in non-primitive types being serialized, which may have their members trimmed.")] - private static DynamicMetaObject CreateDynamicObject(Expression parameter, JsonNode node) => - new MetaDynamic(parameter, node); - } -} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.Dynamic.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.Dynamic.cs deleted file mode 100644 index b343b70d3fa5..000000000000 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.Dynamic.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; -using System.Dynamic; -using System.Reflection; - -namespace System.Text.Json.Nodes -{ - public partial class JsonObject - { - private bool TryGetMemberCallback(GetMemberBinder binder, out object? result) - { - if (TryGetPropertyValue(binder.Name, out JsonNode? node)) - { - result = node; - return true; - } - - // Return null for missing properties. - result = null; - return true; - } - - [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] - private bool TrySetMemberCallback(SetMemberBinder binder, object? value) - { - JsonNode? node = null; - if (value != null) - { - node = value as JsonNode; - if (node == null) - { - node = new JsonValueNotTrimmable(value, Options); - } - } - - this[binder.Name] = node; - return true; - } - - private const BindingFlags MemberInfoBindingFlags = BindingFlags.Instance | BindingFlags.NonPublic; - - private static MethodInfo? s_TryGetMember; - internal override MethodInfo? TryGetMemberMethodInfo => - s_TryGetMember ??= typeof(JsonObject).GetMethod(nameof(TryGetMemberCallback), MemberInfoBindingFlags); - - private static MethodInfo? s_TrySetMember; - internal override MethodInfo? TrySetMemberMethodInfo - { - [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] - get => s_TrySetMember ??= typeof(JsonObject).GetMethod(nameof(TrySetMemberCallback), MemberInfoBindingFlags); - } - } -} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/MetaDynamic.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/MetaDynamic.cs deleted file mode 100644 index 8806fe303528..000000000000 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/MetaDynamic.cs +++ /dev/null @@ -1,438 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Dynamic; -using System.Linq.Expressions; -using System.Reflection; -using System.Runtime.CompilerServices; - -namespace System.Text.Json.Nodes -{ - // The bulk of this code was pulled from src/libraries/System.Linq.Expressions/src/System/Dynamic/DynamicObject.cs - // and then refactored. - internal sealed class MetaDynamic : DynamicMetaObject - { - private static readonly ConstantExpression NullExpression = Expression.Constant(null); - private static readonly DefaultExpression EmptyExpression = Expression.Empty(); - private static readonly ConstantExpression Int1Expression = Expression.Constant((object)1); - - private JsonNode Dynamic { get; } - - [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] - internal MetaDynamic(Expression expression, JsonNode dynamicObject) - : base(expression, BindingRestrictions.Empty, dynamicObject) - { - Dynamic = dynamicObject; - } - - public override DynamicMetaObject BindGetMember(GetMemberBinder binder) - { - MethodInfo? methodInfo = Dynamic.TryGetMemberMethodInfo; - if (methodInfo == null) - { - return base.BindGetMember(binder); - } - - return CallMethodWithResult( - methodInfo, - binder, - s_noArgs, - (MetaDynamic @this, GetMemberBinder b, DynamicMetaObject? e) => b.FallbackGetMember(@this, e) - ); - } - - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "The ctor is marked with RequiresUnreferencedCode.")] - public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) - { - MethodInfo? methodInfo = Dynamic.TrySetMemberMethodInfo; - if (methodInfo == null) - { - return base.BindSetMember(binder, value); - } - - DynamicMetaObject localValue = value; - - return CallMethodReturnLast( - methodInfo, - binder, - s_noArgs, - value.Expression, - (MetaDynamic @this, SetMemberBinder b, DynamicMetaObject? e) => b.FallbackSetMember(@this, localValue, e) - ); - } - - private delegate DynamicMetaObject Fallback(MetaDynamic @this, TBinder binder, DynamicMetaObject? errorSuggestion); - -#pragma warning disable CA1825 // used in reference comparison, requires unique object identity - private static readonly Expression[] s_noArgs = new Expression[0]; -#pragma warning restore CA1825 - - private static ReadOnlyCollection GetConvertedArgs(params Expression[] args) - { - var paramArgs = new Expression[args.Length]; - - for (int i = 0; i < args.Length; i++) - { - paramArgs[i] = Expression.Convert(args[i], typeof(object)); - } - - return new ReadOnlyCollection(paramArgs); - } - - /// - /// Helper method for generating expressions that assign byRef call - /// parameters back to their original variables. - /// - private static Expression ReferenceArgAssign(Expression callArgs, Expression[] args) - { - ReadOnlyCollectionBuilder? block = null; - - for (int i = 0; i < args.Length; i++) - { - ParameterExpression variable = (ParameterExpression)args[i]; - - if (variable.IsByRef) - { - if (block == null) - block = new ReadOnlyCollectionBuilder(); - - block.Add( - Expression.Assign( - variable, - Expression.Convert( - Expression.ArrayIndex( - callArgs, - Int1Expression - ), - variable.Type - ) - ) - ); - } - } - - if (block != null) - return Expression.Block(block); - else - return EmptyExpression; - } - - /// - /// Helper method for generating arguments for calling methods - /// on DynamicObject. parameters is either a list of ParameterExpressions - /// to be passed to the method as an object[], or NoArgs to signify that - /// the target method takes no object[] parameter. - /// - private static Expression[] BuildCallArgs(TBinder binder, Expression[] parameters, Expression arg0, Expression? arg1) - where TBinder : DynamicMetaObjectBinder - { - if (!ReferenceEquals(parameters, s_noArgs)) - return arg1 != null ? new Expression[] { Constant(binder), arg0, arg1 } : new Expression[] { Constant(binder), arg0 }; - else - return arg1 != null ? new Expression[] { Constant(binder), arg1 } : new Expression[] { Constant(binder) }; - } - - private static ConstantExpression Constant(TBinder binder) - { - return Expression.Constant(binder, typeof(TBinder)); - } - - /// - /// Helper method for generating a MetaObject which calls a - /// specific method on Dynamic that returns a result - /// - private DynamicMetaObject CallMethodWithResult(MethodInfo method, TBinder binder, Expression[] args, Fallback fallback) - where TBinder : DynamicMetaObjectBinder - { - return CallMethodWithResult(method, binder, args, fallback, null); - } - - /// - /// Helper method for generating a MetaObject which calls a - /// specific method on Dynamic that returns a result - /// - private DynamicMetaObject CallMethodWithResult(MethodInfo method, TBinder binder, Expression[] args, Fallback fallback, Fallback? fallbackInvoke) - where TBinder : DynamicMetaObjectBinder - { - // - // First, call fallback to do default binding - // This produces either an error or a call to a .NET member - // - DynamicMetaObject fallbackResult = fallback(this, binder, null); - - DynamicMetaObject callDynamic = BuildCallMethodWithResult(method, binder, args, fallbackResult, fallbackInvoke); - - // - // Now, call fallback again using our new MO as the error - // When we do this, one of two things can happen: - // 1. Binding will succeed, and it will ignore our call to - // the dynamic method, OR - // 2. Binding will fail, and it will use the MO we created - // above. - // - return fallback(this, binder, callDynamic); - } - - private DynamicMetaObject BuildCallMethodWithResult(MethodInfo method, TBinder binder, Expression[] args, DynamicMetaObject fallbackResult, Fallback? fallbackInvoke) - where TBinder : DynamicMetaObjectBinder - { - ParameterExpression result = Expression.Parameter(typeof(object), null); - ParameterExpression callArgs = Expression.Parameter(typeof(object[]), null); - ReadOnlyCollection callArgsValue = GetConvertedArgs(args); - - var resultMO = new DynamicMetaObject(result, BindingRestrictions.Empty); - - // Need to add a conversion if calling TryConvert - if (binder.ReturnType != typeof(object)) - { - Debug.Assert(binder is ConvertBinder && fallbackInvoke == null); - - UnaryExpression convert = Expression.Convert(resultMO.Expression, binder.ReturnType); - // will always be a cast or unbox - Debug.Assert(convert.Method == null); - - // Prepare a good exception message in case the convert will fail - string convertFailed = SR.Format(SR.NodeDynamicObjectResultNotAssignable, - "{0}", - this.Value.GetType(), - binder.GetType(), - binder.ReturnType - ); - - Expression condition; - // If the return type can not be assigned null then just check for type assignability otherwise allow null. - if (binder.ReturnType.IsValueType && Nullable.GetUnderlyingType(binder.ReturnType) == null) - { - condition = Expression.TypeIs(resultMO.Expression, binder.ReturnType); - } - else - { - condition = Expression.OrElse( - Expression.Equal(resultMO.Expression, NullExpression), - Expression.TypeIs(resultMO.Expression, binder.ReturnType)); - } - - Expression checkedConvert = Expression.Condition( - condition, - convert, - Expression.Throw( - Expression.New( - CachedReflectionInfo.InvalidCastException_Ctor_String, - new TrueReadOnlyCollection( - Expression.Call( - CachedReflectionInfo.String_Format_String_ObjectArray, - Expression.Constant(convertFailed), - Expression.NewArrayInit( - typeof(object), - new TrueReadOnlyCollection( - Expression.Condition( - Expression.Equal(resultMO.Expression, NullExpression), - Expression.Constant("null"), - Expression.Call( - resultMO.Expression, - CachedReflectionInfo.Object_GetType - ), - typeof(object) - ) - ) - ) - ) - ) - ), - binder.ReturnType - ), - binder.ReturnType - ); - - resultMO = new DynamicMetaObject(checkedConvert, resultMO.Restrictions); - } - - if (fallbackInvoke != null) - { - resultMO = fallbackInvoke(this, binder, resultMO); - } - - var callDynamic = new DynamicMetaObject( - Expression.Block( - new TrueReadOnlyCollection(result, callArgs), - new TrueReadOnlyCollection( - Expression.Assign(callArgs, Expression.NewArrayInit(typeof(object), callArgsValue)), - Expression.Condition( - Expression.Call( - GetLimitedSelf(), - method, - BuildCallArgs( - binder, - args, - callArgs, - result - ) - ), - Expression.Block( - ReferenceArgAssign(callArgs, args), - resultMO.Expression - ), - fallbackResult.Expression, - binder.ReturnType - ) - ) - ), - GetRestrictions().Merge(resultMO.Restrictions).Merge(fallbackResult.Restrictions) - ); - return callDynamic; - } - - private DynamicMetaObject CallMethodReturnLast(MethodInfo method, TBinder binder, Expression[] args, Expression value, Fallback fallback) - where TBinder : DynamicMetaObjectBinder - { - // - // First, call fallback to do default binding - // This produces either an error or a call to a .NET member - // - DynamicMetaObject fallbackResult = fallback(this, binder, null); - - // - // Build a new expression like: - // { - // object result; - // TrySetMember(payload, result = value) ? result : fallbackResult - // } - // - - ParameterExpression result = Expression.Parameter(typeof(object), null); - ParameterExpression callArgs = Expression.Parameter(typeof(object[]), null); - ReadOnlyCollection callArgsValue = GetConvertedArgs(args); - - var callDynamic = new DynamicMetaObject( - Expression.Block( - new TrueReadOnlyCollection(result, callArgs), - new TrueReadOnlyCollection( - Expression.Assign(callArgs, Expression.NewArrayInit(typeof(object), callArgsValue)), - Expression.Condition( - Expression.Call( - GetLimitedSelf(), - method, - BuildCallArgs( - binder, - args, - callArgs, - Expression.Assign(result, Expression.Convert(value, typeof(object))) - ) - ), - Expression.Block( - ReferenceArgAssign(callArgs, args), - result - ), - fallbackResult.Expression, - typeof(object) - ) - ) - ), - GetRestrictions().Merge(fallbackResult.Restrictions) - ); - - // - // Now, call fallback again using our new MO as the error - // When we do this, one of two things can happen: - // 1. Binding will succeed, and it will ignore our call to - // the dynamic method, OR - // 2. Binding will fail, and it will use the MO we created - // above. - // - return fallback(this, binder, callDynamic); - } - - /// - /// Returns a Restrictions object which includes our current restrictions merged - /// with a restriction limiting our type - /// - private BindingRestrictions GetRestrictions() - { - Debug.Assert(Restrictions == BindingRestrictions.Empty, "We don't merge, restrictions are always empty"); - - return GetTypeRestriction(this); - } - - /// - /// Returns our Expression converted to DynamicObject - /// - private Expression GetLimitedSelf() - { - // Convert to DynamicObject rather than LimitType, because - // the limit type might be non-public. - if (AreEquivalent(Expression.Type, Value.GetType())) - { - return Expression; - } - return Expression.Convert(Expression, Value.GetType()); - } - - private static bool AreEquivalent(Type? t1, Type? t2) => t1 != null && t1.IsEquivalentTo(t2); - - private new object Value => base.Value!; - - // It is okay to throw NotSupported from this binder. This object - // is only used by DynamicObject.GetMember--it is not expected to - // (and cannot) implement binding semantics. It is just so the DO - // can use the Name and IgnoreCase properties. - private sealed class GetBinderAdapter : GetMemberBinder - { - internal GetBinderAdapter(InvokeMemberBinder binder) - : base(binder.Name, binder.IgnoreCase) - { - } - - public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject? errorSuggestion) - { - throw new NotSupportedException(); - } - } - - private sealed class TrueReadOnlyCollection : ReadOnlyCollection - { - /// - /// Creates instance of TrueReadOnlyCollection, wrapping passed in array. - /// !!! DOES NOT COPY THE ARRAY !!! - /// - public TrueReadOnlyCollection(params T[] list) - : base(list) - { - } - } - - internal static BindingRestrictions GetTypeRestriction(DynamicMetaObject obj) - { - Debug.Assert(obj != null); - if (obj.Value == null && obj.HasValue) - { - return BindingRestrictions.GetInstanceRestriction(obj.Expression, null); - } - else - { - return BindingRestrictions.GetTypeRestriction(obj.Expression, obj.LimitType); - } - } - - internal static partial class CachedReflectionInfo - { - private static MethodInfo? s_String_Format_String_ObjectArray; - public static MethodInfo String_Format_String_ObjectArray => - s_String_Format_String_ObjectArray ?? - (s_String_Format_String_ObjectArray = typeof(string).GetMethod(nameof(string.Format), new Type[] { typeof(string), typeof(object[]) })!); - - private static ConstructorInfo? s_InvalidCastException_Ctor_String; - public static ConstructorInfo InvalidCastException_Ctor_String => - s_InvalidCastException_Ctor_String ?? - (s_InvalidCastException_Ctor_String = typeof(InvalidCastException).GetConstructor(new Type[] { typeof(string) })!); - - private static MethodInfo? s_Object_GetType; - public static MethodInfo Object_GetType => - s_Object_GetType ?? - (s_Object_GetType = typeof(object).GetMethod(nameof(object.GetType))!); - } - } -} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/StackOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/StackOfTConverter.cs index df483b6986ec..7e1badfb9687 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/StackOfTConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/StackOfTConverter.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace System.Text.Json.Serialization.Converters { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs index 1e1856ccdaa1..07da2c99ebff 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs @@ -11,7 +11,7 @@ namespace System.Text.Json.Serialization.Converters /// Converter for JsonNode-derived types. The {T} value must be Object and not JsonNode /// since we allow Object-declared members\variables to deserialize as {JsonNode}. /// - internal sealed class JsonNodeConverter : JsonConverter + internal sealed class JsonNodeConverter : JsonConverter { private static JsonNodeConverter? s_nodeConverter; private static JsonArrayConverter? s_arrayConverter; @@ -23,7 +23,7 @@ internal sealed class JsonNodeConverter : JsonConverter public static JsonObjectConverter ObjectConverter => s_objectConverter ??= new JsonObjectConverter(); public static JsonValueConverter ValueConverter => s_valueConverter ??= new JsonValueConverter(); - public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, JsonNode? value, JsonSerializerOptions options) { if (value == null) { @@ -47,7 +47,7 @@ public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerO } } - public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override JsonNode? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { switch (reader.TokenType) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonObjectConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonObjectConverter.cs index da3be52fde06..b25f544d3c91 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonObjectConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonObjectConverter.cs @@ -20,7 +20,7 @@ internal override void ReadElementAndSetProperty( JsonSerializerOptions options, ref ReadStack state) { - bool success = JsonNodeConverter.Instance.TryRead(ref reader, typeof(JsonNode), options, ref state, out object? value); + bool success = JsonNodeConverter.Instance.TryRead(ref reader, typeof(JsonNode), options, ref state, out JsonNode? value); Debug.Assert(success); // Node converters are not resumable. Debug.Assert(obj is JsonObject); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/DynamicTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/DynamicTests.cs deleted file mode 100644 index 63afb3920b03..000000000000 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/DynamicTests.cs +++ /dev/null @@ -1,282 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Linq; -using System.Text.Json.Serialization; -using Microsoft.CSharp.RuntimeBinder; -using Xunit; - -namespace System.Text.Json.Nodes.Tests -{ - public static class DynamicTests - { - [Fact] - public static void ImplicitOperators() - { - dynamic jObj = new JsonObject(); - - // Dynamic objects do not support object initializers. - - // Primitives - jObj.MyString = "Hello!"; - Assert.IsAssignableFrom(jObj.MyString); - - jObj.MyNull = null; - jObj.MyBoolean = false; - - // Nested array - jObj.MyArray = new JsonArray(2, 3, 42); - - // Additional primitives - jObj.MyInt = 43; - jObj.MyDateTime = new DateTime(2020, 7, 8); - jObj.MyGuid = new Guid("ed957609-cdfe-412f-88c1-02daca1b4f51"); - - // Nested objects - jObj.MyObject = new JsonObject(); - jObj.MyObject.MyString = "Hello!!"; - - jObj.Child = new JsonObject(); - jObj.Child.ChildProp = 1; - - var options = new JsonSerializerOptions(); - options.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode; - - string json = jObj.ToJsonString(options); - JsonTestHelper.AssertJsonEqual(JsonNodeTests.ExpectedDomJson, json); - } - - private enum MyCustomEnum - { - Default = 0, - FortyTwo = 42, - Hello = 77 - } - - [Fact] - public static void Primitives_UnknownTypeHandling() - { - var options = new JsonSerializerOptions(); - options.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode; - options.Converters.Add(new JsonStringEnumConverter()); - - dynamic obj = JsonSerializer.Deserialize(Serialization.Tests.DynamicTests.Json, options); - Assert.IsAssignableFrom(obj); - - // JsonValue created from a JSON string. - Assert.IsAssignableFrom(obj.MyString); - Assert.Equal("Hello", (string)obj.MyString); - - // Verify other string-based types. - // Even though a custom converter was used, an explicit deserialize needs to be done. - Assert.Equal(42, (int)obj.MyInt); - Assert.ThrowsAny(() => (MyCustomEnum)obj.MyInt); - // Perform the explicit deserialize on the enum. - Assert.Equal(MyCustomEnum.FortyTwo, JsonSerializer.Deserialize(obj.MyInt.ToJsonString())); - - Assert.Equal(Serialization.Tests.DynamicTests.MyDateTime, (DateTime)obj.MyDateTime); - Assert.Equal(Serialization.Tests.DynamicTests.MyGuid, (Guid)obj.MyGuid); - - // JsonValue created from a JSON bool. - Assert.IsAssignableFrom(obj.MyBoolean); - bool b = (bool)obj.MyBoolean; - Assert.True(b); - - // Numbers must specify the type through a cast or assignment. - Assert.IsAssignableFrom(obj.MyInt); - Assert.ThrowsAny(() => obj.MyInt == 42L); - Assert.Equal(42L, (long)obj.MyInt); - Assert.Equal((byte)42, (byte)obj.MyInt); - - // Verify floating point. - obj = JsonSerializer.Deserialize("4.2", options); - Assert.IsAssignableFrom(obj); - - double dbl = (double)obj; - Assert.Equal(4.2, dbl); - } - - [Fact] - public static void Array_UnknownTypeHandling() - { - var options = new JsonSerializerOptions(); - options.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode; - - dynamic obj = JsonSerializer.Deserialize(Serialization.Tests.DynamicTests.Json, options); - Assert.IsAssignableFrom(obj); - Assert.IsAssignableFrom(obj.MyArray); - - Assert.Equal(2, obj.MyArray.Count); - Assert.Equal(1, (int)obj.MyArray[0]); - Assert.Equal(2, (int)obj.MyArray[1]); - - int count = 0; - foreach (object value in obj.MyArray) - { - count++; - } - Assert.Equal(2, count); - Assert.Equal(2, obj.MyArray.Count); - - obj.MyArray[0] = 10; - Assert.IsAssignableFrom(obj.MyArray[0]); - - Assert.Equal(10, (int)obj.MyArray[0]); - } - - [Fact] - public static void CreateDom_UnknownTypeHandling() - { - var options = new JsonSerializerOptions(); - options.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode; - - string GuidJson = $"{Serialization.Tests.DynamicTests.MyGuid.ToString("D")}"; - - // We can't convert an unquoted string to a Guid - dynamic dynamicString = JsonValue.Create(GuidJson); - InvalidOperationException ex = Assert.Throws(() => (Guid)dynamicString); - // "A value of type 'System.String' cannot be converted to a 'System.Guid'." - Assert.Contains(typeof(string).ToString(), ex.Message); - Assert.Contains(typeof(Guid).ToString(), ex.Message); - - string json; - - // Number (JsonElement) - using (JsonDocument doc = JsonDocument.Parse($"{decimal.MaxValue}")) - { - dynamic dynamicNumber = JsonValue.Create(doc.RootElement); - Assert.Equal(decimal.MaxValue, (decimal)dynamicNumber); - json = dynamicNumber.ToJsonString(options); - Assert.Equal(decimal.MaxValue.ToString(), json); - } - - // Boolean - dynamic dynamicBool = JsonValue.Create(true); - Assert.True((bool)dynamicBool); - json = dynamicBool.ToJsonString(options); - Assert.Equal("true", json); - - // Array - dynamic arr = new JsonArray(); - arr.Add(1); - arr.Add(2); - json = arr.ToJsonString(options); - Assert.Equal("[1,2]", json); - - // Object - dynamic dynamicObject = new JsonObject(); - dynamicObject.One = 1; - dynamicObject.Two = 2; - - json = dynamicObject.ToJsonString(options); - JsonTestHelper.AssertJsonEqual("{\"One\":1,\"Two\":2}", json); - } - - /// - /// Use a mutable DOM with the 'dynamic' keyword. - /// - [Fact] - public static void UnknownTypeHandling_Object() - { - var options = new JsonSerializerOptions(); - options.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode; - - dynamic obj = JsonSerializer.Deserialize(Serialization.Tests.DynamicTests.Json, options); - Assert.IsAssignableFrom(obj); - - // Change some primitives. - obj.MyString = "Hello!"; - obj.MyBoolean = false; - obj.MyInt = 43; - - // Add nested objects. - // Use JsonObject; ExpandoObject should not be used since it doesn't have the same semantics including - // null handling and case-sensitivity that respects JsonSerializerOptions.PropertyNameCaseInsensitive. - dynamic myObject = new JsonObject(); - myObject.MyString = "Hello!!"; - obj.MyObject = myObject; - - dynamic child = new JsonObject(); - child.ChildProp = 1; - obj.Child = child; - - // Modify number elements. - dynamic arr = obj.MyArray; - arr[0] = (int)arr[0] + 1; - arr[1] = (int)arr[1] + 1; - - // Add an element. - arr.Add(42); - - string json = obj.ToJsonString(options); - JsonTestHelper.AssertJsonEqual(JsonNodeTests.ExpectedDomJson, json); - } - - [Fact] - public static void ConvertJsonArrayToIListOfJsonNode() - { - dynamic obj = JsonSerializer.Deserialize("[42]"); - Assert.Equal(42, (int)obj[0]); - - IList ilist = obj; - Assert.NotNull(ilist); - Assert.Equal(42, (int)ilist[0]); - } - - [Fact] - public static void UnknownTypeHandling_CaseSensitivity() - { - var options = new JsonSerializerOptions(); - options.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode; - dynamic obj = JsonSerializer.Deserialize("{\"MyProperty\":42}", options); - - Assert.IsType(obj); - Assert.IsAssignableFrom(obj.MyProperty); - - Assert.Equal(42, (int)obj.MyProperty); - Assert.Null(obj.myProperty); - Assert.Null(obj.MYPROPERTY); - - options = new JsonSerializerOptions(); - options.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode; - options.PropertyNameCaseInsensitive = true; - obj = JsonSerializer.Deserialize("{\"MyProperty\":42}", options); - - Assert.Equal(42, (int)obj.MyProperty); - Assert.Equal(42, (int)obj.myproperty); - Assert.Equal(42, (int)obj.MYPROPERTY); - } - - [Fact] - public static void MissingProperty_UnknownTypeHandling() - { - var options = new JsonSerializerOptions(); - options.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode; - dynamic obj = JsonSerializer.Deserialize("{}", options); - Assert.Equal(null, obj.NonExistingProperty); - } - - [Fact] - public static void Linq_UnknownTypeHandling() - { - var options = new JsonSerializerOptions(); - options.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode; - - IEnumerable allOrders = JsonSerializer.Deserialize>(JsonNodeTests.Linq_Query_Json, options); - IEnumerable orders = allOrders.Where(o => ((string)o.Customer.City) == "Fargo"); - - Assert.Equal(2, orders.Count()); - Assert.Equal(100, (int)orders.ElementAt(0).OrderId); - Assert.Equal(300, (int)orders.ElementAt(1).OrderId); - Assert.Equal("Customer1", (string)orders.ElementAt(0).Customer.Name); - Assert.Equal("Customer3", (string)orders.ElementAt(1).Customer.Name); - - // Verify methods can be called as well. - Assert.Equal(100, orders.ElementAt(0).OrderId.GetValue()); - Assert.Equal(300, orders.ElementAt(1).OrderId.GetValue()); - Assert.Equal("Customer1", orders.ElementAt(0).Customer.Name.GetValue()); - Assert.Equal("Customer3", orders.ElementAt(1).Customer.Name.GetValue()); - } - } -} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonArrayTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonArrayTests.cs index b451ff0bc224..e5b1c18cf57d 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonArrayTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonArrayTests.cs @@ -196,6 +196,48 @@ public static void CopyTo() Assert.Throws(() => jArray.CopyTo(arr, -1)); } + [Fact] + public static void ConvertJSONArrayToIListOfJsonNode() + { + dynamic obj = JsonSerializer.Deserialize("[42]"); + Assert.Equal(42, (int)obj[0]); + + IList ilist = obj; + Assert.NotNull(ilist); + Assert.Equal(42, (int)ilist[0]); + } + + [Fact] + public static void ConvertJSONArrayToJsonArray() + { + JsonArray nodes = JsonSerializer.Deserialize("[1,1.1,\"Hello\"]"); + Assert.Equal(1, (long)nodes[0]); + Assert.Equal(1.1, (double)nodes[1]); + Assert.Equal("Hello", (string)nodes[2]); + } + + [Fact] + public static void ConvertJSONArrayToJsonNodeArray() + { + // Instead of JsonArray, use array of JsonNodes + JsonNode[] nodes = JsonSerializer.Deserialize("[1,1.1,\"Hello\"]"); + Assert.Equal(1, (long)nodes[0]); + Assert.Equal(1.1, (double)nodes[1]); + Assert.Equal("Hello", (string)nodes[2]); + } + + [Fact] + public static void ConvertJSONArrayToObjectArray() + { + // Instead of JsonArray, use array of objects + JsonSerializerOptions options = new(); + options.UnknownTypeHandling = Serialization.JsonUnknownTypeHandling.JsonNode; + object[] nodes = JsonSerializer.Deserialize("[1,1.1,\"Hello\"]", options); + Assert.Equal(1, (long)(JsonNode)nodes[0]); + Assert.Equal(1.1, (double)(JsonNode)nodes[1]); + Assert.Equal("Hello", (string)(JsonNode)nodes[2]); + } + [Fact] public static void ReAddSameNode_Throws() { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj index 41a5ec38d5a9..20d919ca164e 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj @@ -53,7 +53,6 @@ - diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/TrimmingTests/Collections/StackOfT.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/TrimmingTests/Collections/StackOfT.cs index 48435095e57f..c408c57cf903 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/TrimmingTests/Collections/StackOfT.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/TrimmingTests/Collections/StackOfT.cs @@ -13,12 +13,14 @@ internal class Program { static int Main(string[] args) { - string json = "[1]"; - object obj = JsonSerializer.Deserialize(json, typeof(Stack)); - if (!(TestHelper.AssertCollectionAndSerialize>(obj, json))) - { - return -1; - } + // Test is currently disabled until issue #53393 is addressed. + + //string json = "[1]"; + //object obj = JsonSerializer.Deserialize(json, typeof(Stack)); + //if (!(TestHelper.AssertCollectionAndSerialize>(obj, json))) + //{ + // return -1; + //} return 100; } From 59ab54cd8c4af11f64f8a42615437e44f4712fb1 Mon Sep 17 00:00:00 2001 From: Dong-Heon Jung Date: Wed, 14 Jul 2021 03:01:19 +0900 Subject: [PATCH 082/133] Fix Tizen ARMEL Build Failure (#55545) * Fix Tizen ARMEL Build Failure * Check with MFD_CLOEXEC instead of ARM_SOFTFP --- src/coreclr/minipal/Unix/doublemapping.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/coreclr/minipal/Unix/doublemapping.cpp b/src/coreclr/minipal/Unix/doublemapping.cpp index a50b326861aa..6e0278e3ccd6 100644 --- a/src/coreclr/minipal/Unix/doublemapping.cpp +++ b/src/coreclr/minipal/Unix/doublemapping.cpp @@ -14,10 +14,11 @@ #include #include #include -#ifdef TARGET_LINUX +#if defined(TARGET_LINUX) && !defined(MFD_CLOEXEC) #include #include // __NR_memfd_create -#endif // TARGET_LINUX +#define memfd_create(...) syscall(__NR_memfd_create, __VA_ARGS__) +#endif // TARGET_LINUX && !MFD_CLOEXEC #include "minipal.h" #if defined(TARGET_OSX) && defined(TARGET_AMD64) @@ -32,10 +33,6 @@ static const off_t MaxDoubleMappedSize = 2048ULL*1024*1024*1024; static const off_t MaxDoubleMappedSize = UINT_MAX; #endif -#ifdef TARGET_LINUX -#define memfd_create(...) syscall(__NR_memfd_create, __VA_ARGS__) -#endif // TARGET_LINUX - #endif // TARGET_OSX bool VMToOSInterface::CreateDoubleMemoryMapper(void** pHandle, size_t *pMaxExecutableCodeSize) From 2d3acc6a3dff03f89cc3d08d5f1fb6f5fb3077fc Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Tue, 13 Jul 2021 14:01:31 -0400 Subject: [PATCH 083/133] Add one-shot CFB encrypt/decrypt to SymmetricAlgorithm Adds the CFB versions similar to the already-added ECB and CBC versions. Co-authored-by: Jeremy Barton --- .../src/Internal/Cryptography/Helpers.cs | 9 +- .../AES/AesCipherOneShotTests.cs | 605 +++++++++++++++++- .../AES/AesCipherTests.cs | 8 +- .../DES/DESCipherOneShotTests.cs | 299 ++++++++- .../DES/DESCipherTests.cs | 27 +- .../DES/DESFactory.cs | 7 +- .../RC2/RC2CipherOneShotTests.cs | 20 + .../Symmetric/SymmetricOneShotBase.cs | 77 ++- .../TripleDES/TripleDESCipherOneShotTests.cs | 583 ++++++++++++++++- .../TripleDES/TripleDESCipherTests.cs | 8 +- .../Cryptography/AesImplementation.cs | 52 ++ .../Cryptography/DesImplementation.cs | 52 ++ .../Cryptography/RC2Implementation.cs | 22 + .../Cryptography/TripleDesImplementation.cs | 52 ++ .../tests/DESProvider.cs | 6 +- .../Cryptography/CngSymmetricAlgorithmCore.cs | 33 +- .../Cryptography/ICngSymmetricAlgorithm.cs | 2 +- .../src/Resources/Strings.resx | 3 + .../System/Security/Cryptography/AesCng.cs | 82 ++- .../Security/Cryptography/TripleDESCng.cs | 82 ++- .../tests/AesCngTests.cs | 24 +- .../tests/SymmetricCngTestHelpers.cs | 49 +- .../tests/TripleDESCngTests.cs | 18 +- .../tests/DESCryptoServiceProviderProvider.cs | 6 +- .../tests/ShimHelpers.cs | 2 + ...System.Security.Cryptography.Primitives.cs | 10 + .../Cryptography/SymmetricAlgorithm.cs | 545 ++++++++++++++++ .../tests/SymmetricAlgorithmTests.cs | 350 ++++++++++ 28 files changed, 2857 insertions(+), 176 deletions(-) diff --git a/src/libraries/Common/src/Internal/Cryptography/Helpers.cs b/src/libraries/Common/src/Internal/Cryptography/Helpers.cs index 2ee5ffc39c4f..6614b3c6d00b 100644 --- a/src/libraries/Common/src/Internal/Cryptography/Helpers.cs +++ b/src/libraries/Common/src/Internal/Cryptography/Helpers.cs @@ -30,14 +30,9 @@ internal static partial class Helpers return (byte[])(src.Clone()); } - public static int GetPaddingSize(this SymmetricAlgorithm algorithm, CipherMode mode, int feedbackSizeBits) + public static int GetPaddingSize(this SymmetricAlgorithm algorithm, CipherMode mode, int feedbackSizeInBits) { - // CFB8 does not require any padding at all - // otherwise, it is always required to pad for block size - if (mode == CipherMode.CFB && feedbackSizeBits == 8) - return 1; - - return algorithm.BlockSize / 8; + return (mode == CipherMode.CFB ? feedbackSizeInBits : algorithm.BlockSize) / 8; } internal static bool TryCopyToDestination(ReadOnlySpan source, Span destination, out int bytesWritten) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherOneShotTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherOneShotTests.cs index 77d5e0cf7782..bc109f9bd03d 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherOneShotTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherOneShotTests.cs @@ -22,68 +22,88 @@ public class AesCipherOneShotTests : SymmetricOneShotBase [Theory] [MemberData(nameof(TestCases))] - public void OneShotRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - OneShotRoundtripTest(plaintext, ciphertext, padding, mode); + public void OneShotRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + OneShotRoundtripTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void DecryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - DecryptOneShot_SpanTest(plaintext, ciphertext, padding, mode); + public void DecryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + DecryptOneShot_SpanTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void EncryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - EncryptOneShot_SpanTest(plaintext, ciphertext, padding, mode); + public void EncryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + EncryptOneShot_SpanTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void DecryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - DecryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode); + public void DecryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + DecryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void EncryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - EncryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode); + public void EncryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + EncryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode, feedbackSize); + + [Fact] + public void EncryptOneShot_CfbFeedbackSizeNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryEncryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _, feedbackSizeInBits: 120)); + } + } + + [Fact] + public void DecryptOneShot_CfbFeedbackSizeNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryDecryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _, feedbackSizeInBits: 120)); + } + } public static IEnumerable TestCases { @@ -91,6 +111,7 @@ public static IEnumerable TestCases { yield return new object[] { + // plaintext new byte[] { @@ -568,6 +589,538 @@ public static IEnumerable TestCases PaddingMode.PKCS7, CipherMode.ECB, }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + 0xD2, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + }, + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + 0xD2, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + 0xD2, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, 0x97, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, + }, + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, 0x97, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, 0x97, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + new byte[] + { + 0x02, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + // CFB128 is not supported on Windows 7. + if (PlatformDetection.IsNotWindows7) + { + yield return new object[] + { + + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, + 0x2B, 0x63, 0xD4, 0x34, 0x86, 0x05, 0x9B, 0x52, + 0x20, 0x46, 0x65, 0xD5, 0xBC, 0xA1, 0xED, 0x11, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, + }, + + PaddingMode.None, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, + 0x3B, 0x73, 0xC4, 0x24, 0x96, 0x15, 0x8B, 0x42, + 0x30, 0x56, 0x75, 0xC5, 0xAC, 0xB1, 0xFD, 0x11, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, + 0x3E, 0x5D, 0xED, 0x96, 0x51, 0x93, 0xF0, 0x12, + 0x95, 0x98, 0x51, 0x29, 0xB6, 0xF8, 0x84, 0x11, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, + 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, + 0x0C, 0xD0, 0xD4, 0xF1, 0x60, 0x93, 0xD0, 0x20, + 0x91, 0x11, 0xD8, 0xF6, 0x27, 0xE3, 0xAF, 0x0F, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, + 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, + 0x0C, 0xDF, 0xDB, 0xFE, 0x6F, 0x9C, 0xDF, 0x2F, + 0x9E, 0x1E, 0xD7, 0xF9, 0x28, 0xEC, 0xA0, 0x00, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, + 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, + 0x0C, 0xDF, 0xDB, 0xFE, 0x6F, 0x9C, 0xDF, 0x2F, + 0x9E, 0x1E, 0xD7, 0xF9, 0x28, 0xEC, 0xA0, 0x0F, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, + 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, + 0x0C, 0x0C, 0x39, 0x31, 0x1C, 0xAA, 0x41, 0x45, + 0x78, 0xD0, 0x9F, 0x0F, 0x44, 0xD9, 0x37, 0x0F, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + Array.Empty(), + + // ciphertext + new byte[] + { + 0x13, 0x47, 0x4B, 0xA9, 0x1C, 0x31, 0xE1, 0xFE, + 0x23, 0x69, 0x61, 0xE6, 0x27, 0x01, 0xBE, 0xAA, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.Zeros, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.None, + CipherMode.CFB, + 128, + }; + } } } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs index 465958943f8c..2144dad5aaf1 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs @@ -1124,6 +1124,7 @@ private static void TestAesTransformDirectKey( { aes.Mode = cipherMode; aes.Padding = paddingMode; + aes.Key = key; if (feedbackSize.HasValue) { @@ -1135,16 +1136,19 @@ private static void TestAesTransformDirectKey( if (cipherMode == CipherMode.ECB) { - aes.Key = key; liveOneShotDecryptBytes = aes.DecryptEcb(cipherBytes, paddingMode); liveOneShotEncryptBytes = aes.EncryptEcb(plainBytes, paddingMode); } else if (cipherMode == CipherMode.CBC) { - aes.Key = key; liveOneShotDecryptBytes = aes.DecryptCbc(cipherBytes, iv, paddingMode); liveOneShotEncryptBytes = aes.EncryptCbc(plainBytes, iv, paddingMode); } + else if (cipherMode == CipherMode.CFB) + { + liveOneShotDecryptBytes = aes.DecryptCfb(cipherBytes, iv, paddingMode, feedbackSizeInBits: feedbackSize.Value); + liveOneShotEncryptBytes = aes.EncryptCfb(plainBytes, iv, paddingMode, feedbackSizeInBits: feedbackSize.Value); + } } Assert.Equal(cipherBytes, liveEncryptBytes); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherOneShotTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherOneShotTests.cs index d775ffb9f128..0a050f842736 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherOneShotTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherOneShotTests.cs @@ -26,68 +26,88 @@ public class DesCipherOneShotTests : SymmetricOneShotBase [Theory] [MemberData(nameof(TestCases))] - public void OneShotRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - OneShotRoundtripTest(plaintext, ciphertext, padding, mode); + public void OneShotRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + OneShotRoundtripTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void DecryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - DecryptOneShot_SpanTest(plaintext, ciphertext, padding, mode); + public void DecryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + DecryptOneShot_SpanTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void EncryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - EncryptOneShot_SpanTest(plaintext, ciphertext, padding, mode); + public void EncryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + EncryptOneShot_SpanTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void DecryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - DecryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode); + public void DecryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + DecryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void EncryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - EncryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode); + public void EncryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + EncryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode, feedbackSize); + + [Fact] + public void EncryptOneShot_CfbFeedbackSizeNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryEncryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _, feedbackSizeInBits: 56)); + } + } + + [Fact] + public void DecryptOneShot_CfbFeedbackSizeNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryDecryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _, feedbackSizeInBits: 56)); + } + } public static IEnumerable TestCases { @@ -553,6 +573,233 @@ public static IEnumerable TestCases PaddingMode.PKCS7, CipherMode.ECB, }; + + // Windows 7 does not support CFB8 + if (PlatformDetection.IsNotWindows7) + { + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x6F, 0xF8, 0x24, 0x52, 0x68, 0x8E, 0x53, 0x97, + 0x2A, 0x6B, 0x8A, 0x5E, 0xBE, 0x98, 0x84, 0x28, + 0x39, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x6F, 0xF8, 0x24, 0x52, 0x68, 0x8E, 0x53, 0x97, + 0x2A, 0x6B, 0x8A, 0x5E, 0xBE, 0x98, 0x84, 0x28, + }, + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x6F, 0xF8, 0x24, 0x52, 0x68, 0x8E, 0x53, 0x97, + 0x2A, 0x6B, 0x8A, 0x5E, 0xBE, 0x98, 0x84, 0x28, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x6F, 0xF8, 0x24, 0x52, 0x68, 0x8E, 0x53, 0x97, + 0x2A, 0x6B, 0x8A, 0x5E, 0xBE, 0x98, 0x84, 0x28, + 0x39, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x6F, 0xF8, 0x24, 0x52, 0x68, 0x8E, 0x53, 0x97, + 0x2A, 0x6B, 0x8A, 0x5E, 0xBE, 0x98, 0x84, 0x28, + 0x39, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0xA6, 0x51, 0xB4, 0xF2, 0x2B, 0xFA, 0x22, 0xF5, + 0x15, 0x1E, 0x5E, 0x65, 0x39, 0xFD, 0x84, 0x4F, + 0xE1, 0x7E, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0xA6, 0x51, 0xB4, 0xF2, 0x2B, 0xFA, 0x22, 0xF5, + 0x15, 0x1E, 0x5E, 0x65, 0x39, 0xFD, 0x84, 0x4F, + 0xE1, + }, + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0xA6, 0x51, 0xB4, 0xF2, 0x2B, 0xFA, 0x22, 0xF5, + 0x15, 0x1E, 0x5E, 0x65, 0x39, 0xFD, 0x84, 0x4F, + 0xE1, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0xA6, 0x51, 0xB4, 0xF2, 0x2B, 0xFA, 0x22, 0xF5, + 0x15, 0x1E, 0x5E, 0x65, 0x39, 0xFD, 0x84, 0x4F, + 0xE1, 0x7E, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0xA6, 0x51, 0xB4, 0xF2, 0x2B, 0xFA, 0x22, 0xF5, + 0x15, 0x1E, 0x5E, 0x65, 0x39, 0xFD, 0x84, 0x4F, + 0xE1, 0x7E, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; + } } } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.cs index d23a48a2851c..707db864be13 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.cs @@ -531,6 +531,7 @@ private static void TestDESTransformDirectKey( { des.Mode = cipherMode; des.Padding = paddingMode; + des.Key = key; if (feedbackSize.HasValue) { @@ -540,17 +541,23 @@ private static void TestDESTransformDirectKey( liveEncryptBytes = DESEncryptDirectKey(des, key, iv, plainBytes); liveDecryptBytes = DESDecryptDirectKey(des, key, iv, cipherBytes); - if (cipherMode == CipherMode.ECB) - { - des.Key = key; - liveOneShotDecryptBytes = des.DecryptEcb(cipherBytes, paddingMode); - liveOneShotEncryptBytes = des.EncryptEcb(plainBytes, paddingMode); - } - else if (cipherMode == CipherMode.CBC) + if (DESFactory.OneShotSupported) { - des.Key = key; - liveOneShotDecryptBytes = des.DecryptCbc(cipherBytes, iv, paddingMode); - liveOneShotEncryptBytes = des.EncryptCbc(plainBytes, iv, paddingMode); + if (cipherMode == CipherMode.ECB) + { + liveOneShotDecryptBytes = des.DecryptEcb(cipherBytes, paddingMode); + liveOneShotEncryptBytes = des.EncryptEcb(plainBytes, paddingMode); + } + else if (cipherMode == CipherMode.CBC) + { + liveOneShotDecryptBytes = des.DecryptCbc(cipherBytes, iv, paddingMode); + liveOneShotEncryptBytes = des.EncryptCbc(plainBytes, iv, paddingMode); + } + else if (cipherMode == CipherMode.CFB) + { + liveOneShotDecryptBytes = des.DecryptCfb(cipherBytes, iv, paddingMode, feedbackSizeInBits: feedbackSize.Value); + liveOneShotEncryptBytes = des.EncryptCfb(plainBytes, iv, paddingMode, feedbackSizeInBits: feedbackSize.Value); + } } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESFactory.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESFactory.cs index 5b191ca5ea05..fd7a2f626e41 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESFactory.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESFactory.cs @@ -6,13 +6,12 @@ namespace System.Security.Cryptography.Encryption.Des.Tests public interface IDESProvider { DES Create(); + bool OneShotSupported { get; } } public static partial class DESFactory { - public static DES Create() - { - return s_provider.Create(); - } + public static DES Create() => s_provider.Create(); + public static bool OneShotSupported => s_provider.OneShotSupported; } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherOneShotTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherOneShotTests.cs index 411aadaf347e..24d2befc125c 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherOneShotTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherOneShotTests.cs @@ -90,6 +90,26 @@ public void DecryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMod public void EncryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => EncryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode); + [Fact] + public void EncryptOneShot_CfbNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryEncryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _)); + } + } + + [Fact] + public void DecryptOneShot_CfbNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryDecryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _)); + } + } + public static IEnumerable TestCases { get diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs index 89e6b5d285de..42a9b584366d 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs @@ -17,10 +17,11 @@ public abstract class SymmetricOneShotBase protected abstract byte[] IV { get; } protected abstract SymmetricAlgorithm CreateAlgorithm(); - protected void OneShotRoundtripTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void OneShotRoundtripTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { + int paddingSizeBytes = mode == CipherMode.CFB ? feedbackSize / 8 : alg.BlockSize / 8; alg.Key = Key; // Set the instance to use a different mode and padding than what will be used @@ -33,37 +34,41 @@ protected void OneShotRoundtripTest(byte[] plaintext, byte[] ciphertext, Padding { CipherMode.ECB => alg.EncryptEcb(plaintext, padding), CipherMode.CBC => alg.EncryptCbc(plaintext, IV, padding), + CipherMode.CFB => alg.EncryptCfb(plaintext, IV, padding, feedbackSize), _ => throw new NotImplementedException(), }; byte[] decrypted = mode switch { CipherMode.ECB => alg.DecryptEcb(encrypted, padding), CipherMode.CBC => alg.DecryptCbc(encrypted, IV, padding), + CipherMode.CFB => alg.DecryptCfb(encrypted, IV, padding, feedbackSize), _ => throw new NotImplementedException(), }; AssertPlaintexts(plaintext, decrypted, padding); - AssertCiphertexts(encrypted, ciphertext, padding, alg.BlockSize / 8); + AssertCiphertexts(encrypted, ciphertext, padding, paddingSizeBytes); decrypted = mode switch { CipherMode.ECB => alg.DecryptEcb(ciphertext, padding), CipherMode.CBC => alg.DecryptCbc(ciphertext, IV, padding), + CipherMode.CFB => alg.DecryptCfb(ciphertext, IV, padding, feedbackSize), _ => throw new NotImplementedException(), }; encrypted = mode switch { CipherMode.ECB => alg.EncryptEcb(decrypted, padding), CipherMode.CBC => alg.EncryptCbc(decrypted, IV, padding), + CipherMode.CFB => alg.EncryptCfb(decrypted, IV, padding, feedbackSize), _ => throw new NotImplementedException(), }; AssertPlaintexts(plaintext, decrypted, padding); - AssertCiphertexts(ciphertext, encrypted, padding, alg.BlockSize / 8); + AssertCiphertexts(ciphertext, encrypted, padding, paddingSizeBytes); } } - protected void TryDecryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryDecryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { if (plaintext.Length == 0) { @@ -82,6 +87,7 @@ protected void TryDecryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[ { CipherMode.ECB => alg.TryDecryptEcb(ciphertext, destinationBuffer, padding, out bytesWritten), CipherMode.CBC => alg.TryDecryptCbc(ciphertext, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryDecryptCfb(ciphertext, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), _ => throw new NotImplementedException(), }; @@ -90,7 +96,7 @@ protected void TryDecryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[ } } - protected void TryEncryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryEncryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { if (ciphertext.Length == 0) { @@ -109,6 +115,7 @@ protected void TryEncryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[ { CipherMode.ECB => alg.TryEncryptEcb(plaintext, destinationBuffer, padding, out bytesWritten), CipherMode.CBC => alg.TryEncryptCbc(plaintext, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryEncryptCfb(plaintext, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), _ => throw new NotImplementedException(), }; Assert.False(result, "TryEncrypt"); @@ -116,7 +123,7 @@ protected void TryEncryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[ } } - protected void TryDecryptOneShot_DestinationJustRightTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryDecryptOneShot_DestinationJustRightTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { @@ -127,11 +134,12 @@ protected void TryDecryptOneShot_DestinationJustRightTest(byte[] plaintext, byte int bytesWritten; bool result = mode switch - { - CipherMode.ECB => alg.TryDecryptEcb(ciphertext, destinationBuffer, padding, out bytesWritten), - CipherMode.CBC => alg.TryDecryptCbc(ciphertext, IV, destinationBuffer, out bytesWritten, padding), - _ => throw new NotImplementedException(), - }; + { + CipherMode.ECB => alg.TryDecryptEcb(ciphertext, destinationBuffer, padding, out bytesWritten), + CipherMode.CBC => alg.TryDecryptCbc(ciphertext, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryDecryptCfb(ciphertext, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), + _ => throw new NotImplementedException(), + }; Assert.True(result, "TryDecrypt"); Assert.Equal(destinationBuffer.Length, bytesWritten); @@ -139,16 +147,18 @@ protected void TryDecryptOneShot_DestinationJustRightTest(byte[] plaintext, byte } } - protected void TryEncryptOneShot_DestinationJustRightTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryEncryptOneShot_DestinationJustRightTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { + int paddingSizeBytes = mode == CipherMode.CFB ? feedbackSize / 8 : alg.BlockSize / 8; alg.Key = Key; int expectedCiphertextSize = mode switch { CipherMode.ECB => alg.GetCiphertextLengthEcb(plaintext.Length, padding), CipherMode.CBC => alg.GetCiphertextLengthCbc(plaintext.Length, padding), + CipherMode.CFB => alg.GetCiphertextLengthCfb(plaintext.Length, padding, feedbackSize), _ => throw new NotImplementedException(), }; Span destinationBuffer = new byte[expectedCiphertextSize]; @@ -158,16 +168,17 @@ protected void TryEncryptOneShot_DestinationJustRightTest(byte[] plaintext, byte { CipherMode.ECB => alg.TryEncryptEcb(plaintext, destinationBuffer, padding, out bytesWritten), CipherMode.CBC => alg.TryEncryptCbc(plaintext, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryEncryptCfb(plaintext, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), _ => throw new NotImplementedException(), }; Assert.True(result, "TryEncrypt"); Assert.Equal(expectedCiphertextSize, bytesWritten); - AssertCiphertexts(ciphertext, destinationBuffer, padding, alg.BlockSize / 8); + AssertCiphertexts(ciphertext, destinationBuffer, padding, paddingSizeBytes); } } - protected void TryDecryptOneShot_DestinationLargerTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryDecryptOneShot_DestinationLargerTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { @@ -183,6 +194,7 @@ protected void TryDecryptOneShot_DestinationLargerTest(byte[] plaintext, byte[] { CipherMode.ECB => alg.TryDecryptEcb(ciphertext, destinationBuffer, padding, out bytesWritten), CipherMode.CBC => alg.TryDecryptCbc(ciphertext, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryDecryptCfb(ciphertext, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), _ => throw new NotImplementedException(), }; @@ -196,10 +208,11 @@ protected void TryDecryptOneShot_DestinationLargerTest(byte[] plaintext, byte[] } } - protected void TryEncryptOneShot_DestinationLargerTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryEncryptOneShot_DestinationLargerTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { + int paddingSizeBytes = mode == CipherMode.CFB ? feedbackSize / 8 : alg.BlockSize / 8; alg.Key = Key; Span largeBuffer = new byte[ciphertext.Length + 10]; @@ -211,18 +224,19 @@ protected void TryEncryptOneShot_DestinationLargerTest(byte[] plaintext, byte[] { CipherMode.ECB => alg.TryEncryptEcb(plaintext, destinationBuffer, padding, out bytesWritten), CipherMode.CBC => alg.TryEncryptCbc(plaintext, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryEncryptCfb(plaintext, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), _ => throw new NotImplementedException(), }; Assert.True(result, "TryEncrypt"); Assert.Equal(destinationBuffer.Length, bytesWritten); - AssertCiphertexts(ciphertext, destinationBuffer, padding, alg.BlockSize / 8); + AssertCiphertexts(ciphertext, destinationBuffer, padding, paddingSizeBytes); AssertExtensions.FilledWith(0xCC, largeBuffer.Slice(ciphertext.Length)); } } - protected void TryDecryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryDecryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { (int plaintextOffset, int ciphertextOffset)[] offsets = { @@ -247,6 +261,7 @@ protected void TryDecryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertex { CipherMode.ECB => alg.TryDecryptEcb(ciphertextBuffer, destinationBuffer, padding, out bytesWritten), CipherMode.CBC => alg.TryDecryptCbc(ciphertextBuffer, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryDecryptCfb(ciphertextBuffer, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), _ => throw new NotImplementedException(), }; Assert.True(result, "TryDecrypt"); @@ -258,7 +273,7 @@ protected void TryDecryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertex } } - protected void TryEncryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryEncryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { (int plaintextOffset, int ciphertextOffset)[] offsets = { @@ -269,6 +284,7 @@ protected void TryEncryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertex { using (SymmetricAlgorithm alg = CreateAlgorithm()) { + int paddingSizeBytes = mode == CipherMode.CFB ? feedbackSize / 8 : alg.BlockSize / 8; alg.Key = Key; int destinationSize = ciphertext.Length + Math.Max(plaintextOffset, ciphertextOffset); @@ -282,18 +298,19 @@ protected void TryEncryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertex { CipherMode.ECB => alg.TryEncryptEcb(plaintextBuffer, destinationBuffer, padding, out bytesWritten), CipherMode.CBC => alg.TryEncryptCbc(plaintextBuffer, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryEncryptCfb(plaintextBuffer, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), _ => throw new NotImplementedException(), }; Assert.True(result, "TryEncrypt"); Assert.Equal(destinationBuffer.Length, bytesWritten); - AssertCiphertexts(ciphertext, destinationBuffer, padding, alg.BlockSize / 8); + AssertCiphertexts(ciphertext, destinationBuffer, padding, paddingSizeBytes); Assert.True(destinationBuffer.Overlaps(plaintextBuffer) || plaintext.Length == 0 || ciphertext.Length == 0); } } } - protected void DecryptOneShot_SpanTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void DecryptOneShot_SpanTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { @@ -302,6 +319,7 @@ protected void DecryptOneShot_SpanTest(byte[] plaintext, byte[] ciphertext, Padd { CipherMode.ECB => alg.DecryptEcb(ciphertext.AsSpan(), padding), CipherMode.CBC => alg.DecryptCbc(ciphertext.AsSpan(), IV.AsSpan(), padding), + CipherMode.CFB => alg.DecryptCfb(ciphertext.AsSpan(), IV.AsSpan(), padding, feedbackSize), _ => throw new NotImplementedException(), }; @@ -309,23 +327,25 @@ protected void DecryptOneShot_SpanTest(byte[] plaintext, byte[] ciphertext, Padd } } - protected void EncryptOneShot_SpanTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void EncryptOneShot_SpanTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { alg.Key = Key; + int paddingSizeBytes = mode == CipherMode.CFB ? feedbackSize / 8 : alg.BlockSize / 8; byte[] encrypted = mode switch { CipherMode.ECB => alg.EncryptEcb(plaintext.AsSpan(), padding), CipherMode.CBC => alg.EncryptCbc(plaintext.AsSpan(), IV.AsSpan(), padding), + CipherMode.CFB => alg.EncryptCfb(plaintext.AsSpan(), IV.AsSpan(), padding, feedbackSize), _ => throw new NotImplementedException(), }; - AssertCiphertexts(ciphertext, encrypted, padding, alg.BlockSize / 8); + AssertCiphertexts(ciphertext, encrypted, padding, paddingSizeBytes); } } - protected void DecryptOneShot_ArrayTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void DecryptOneShot_ArrayTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { @@ -334,6 +354,7 @@ protected void DecryptOneShot_ArrayTest(byte[] plaintext, byte[] ciphertext, Pad { CipherMode.ECB => alg.DecryptEcb(ciphertext, padding), CipherMode.CBC => alg.DecryptCbc(ciphertext, IV, padding), + CipherMode.CFB => alg.DecryptCfb(ciphertext, IV, padding, feedbackSize), _ => throw new NotImplementedException(), }; @@ -341,19 +362,21 @@ protected void DecryptOneShot_ArrayTest(byte[] plaintext, byte[] ciphertext, Pad } } - protected void EncryptOneShot_ArrayTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void EncryptOneShot_ArrayTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { alg.Key = Key; + int paddingSizeBytes = mode == CipherMode.CFB ? feedbackSize / 8 : alg.BlockSize / 8; byte[] encrypted = mode switch { CipherMode.ECB => alg.EncryptEcb(plaintext, padding), CipherMode.CBC => alg.EncryptCbc(plaintext, IV, padding), + CipherMode.CFB => alg.EncryptCfb(plaintext, IV, padding, feedbackSize), _ => throw new NotImplementedException(), }; - AssertCiphertexts(ciphertext, encrypted, padding, alg.BlockSize / 8); + AssertCiphertexts(ciphertext, encrypted, padding, paddingSizeBytes); } } @@ -398,12 +421,12 @@ private static void AssertPlaintexts(ReadOnlySpan expected, ReadOnlySpan expected, ReadOnlySpan actual, PaddingMode padding, int blockSizeBytes) + private static void AssertCiphertexts(ReadOnlySpan expected, ReadOnlySpan actual, PaddingMode padding, int paddingSizeBytes) { if (padding == PaddingMode.ISO10126) { // The padding is random, so we can't check the exact ciphertext. - AssertExtensions.SequenceEqual(expected[..^blockSizeBytes], actual[..^blockSizeBytes]); + AssertExtensions.SequenceEqual(expected[..^paddingSizeBytes], actual[..^paddingSizeBytes]); } else { diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherOneShotTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherOneShotTests.cs index d5706706858c..1d9e69c0a87e 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherOneShotTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherOneShotTests.cs @@ -28,68 +28,88 @@ public class TripleDESCipherOneShotTests : SymmetricOneShotBase [Theory] [MemberData(nameof(TestCases))] - public void OneShotRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - OneShotRoundtripTest(plaintext, ciphertext, padding, mode); + public void OneShotRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + OneShotRoundtripTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void DecryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - DecryptOneShot_SpanTest(plaintext, ciphertext, padding, mode); + public void DecryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + DecryptOneShot_SpanTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void EncryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - EncryptOneShot_SpanTest(plaintext, ciphertext, padding, mode); + public void EncryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + EncryptOneShot_SpanTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void DecryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - DecryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode); + public void DecryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + DecryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void EncryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - EncryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode); + public void EncryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + EncryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode, feedbackSize); + + [Fact] + public void EncryptOneShot_CfbFeedbackSizeNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryEncryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _, feedbackSizeInBits: 48)); + } + } + + [Fact] + public void DecryptOneShot_CfbFeedbackSizeNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryDecryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _, feedbackSizeInBits: 48)); + } + } public static IEnumerable TestCases { @@ -557,6 +577,517 @@ public static IEnumerable TestCases PaddingMode.PKCS7, CipherMode.ECB, }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x85, 0x45, 0x43, 0x3E, 0xD9, 0x40, 0xAF, + 0x16, 0xBE, 0xC5, 0xEF, 0xD9, 0x12, 0xFE, 0x07, + 0x66, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x85, 0x45, 0x43, 0x3E, 0xD9, 0x40, 0xAF, + 0x16, 0xBE, 0xC5, 0xEF, 0xD9, 0x12, 0xFE, 0x07, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x85, 0x45, 0x43, 0x3E, 0xD9, 0x40, 0xAF, + 0x16, 0xBE, 0xC5, 0xEF, 0xD9, 0x12, 0xFE, 0x07, + }, + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x85, 0x45, 0x43, 0x3E, 0xD9, 0x40, 0xAF, + 0x16, 0xBE, 0xC5, 0xEF, 0xD9, 0x12, 0xFE, 0x07, + 0x66, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x85, 0x45, 0x43, 0x3E, 0xD9, 0x40, 0xAF, + 0x16, 0xBE, 0xC5, 0xEF, 0xD9, 0x12, 0xFE, 0x07, + 0x66, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xD6, 0x1C, 0xF3, 0xAF, 0x0D, 0x3C, 0x98, + 0xCF, 0x29, 0x20, 0xB3, 0xF3, 0xFC, 0x34, 0xF0, + 0x38, 0xA0, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xD6, 0x1C, 0xF3, 0xAF, 0x0D, 0x3C, 0x98, + 0xCF, 0x29, 0x20, 0xB3, 0xF3, 0xFC, 0x34, 0xF0, + 0x38, + }, + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xD6, 0x1C, 0xF3, 0xAF, 0x0D, 0x3C, 0x98, + 0xCF, 0x29, 0x20, 0xB3, 0xF3, 0xFC, 0x34, 0xF0, + 0x38, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xD6, 0x1C, 0xF3, 0xAF, 0x0D, 0x3C, 0x98, + 0xCF, 0x29, 0x20, 0xB3, 0xF3, 0xFC, 0x34, 0xF0, + 0x38, 0xA0, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xD6, 0x1C, 0xF3, 0xAF, 0x0D, 0x3C, 0x98, + 0xCF, 0x29, 0x20, 0xB3, 0xF3, 0xFC, 0x34, 0xF0, + 0x38, 0xA0, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + new byte[] + { + 0x17, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + // 3DES CFB64 is not supported on Windows 7. + if (PlatformDetection.IsNotWindows7) + { + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x82, 0xB5, 0xFC, 0x6A, 0xD3, 0x92, 0xF9, + 0xB1, 0xF6, 0xD9, 0xF3, 0xBB, 0x8D, 0x57, 0xE5, + 0xFF, 0x66, 0x88, 0x3C, 0x53, 0xC4, 0x5A, 0xC6, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x82, 0xB5, 0xFC, 0x6A, 0xD3, 0x92, 0xF9, + 0xB1, 0xF6, 0xD9, 0xF3, 0xBB, 0x8D, 0x57, 0xE5, + }, + + PaddingMode.None, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x82, 0xB5, 0xFC, 0x6A, 0xD3, 0x92, 0xF9, + 0xB1, 0xF6, 0xD9, 0xF3, 0xBB, 0x8D, 0x57, 0xE5, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x82, 0xB5, 0xFC, 0x6A, 0xD3, 0x92, 0xF9, + 0xB1, 0xF6, 0xD9, 0xF3, 0xBB, 0x8D, 0x57, 0xE5, + 0xF7, 0x6E, 0x80, 0x34, 0x5B, 0xCC, 0x52, 0xC6, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x82, 0xB5, 0xFC, 0x6A, 0xD3, 0x92, 0xF9, + 0xB1, 0xF6, 0xD9, 0xF3, 0xBB, 0x8D, 0x57, 0xE5, + 0x47, 0x0F, 0x9A, 0x12, 0x6F, 0x92, 0xB4, 0xC6, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xBA, 0xCF, 0x4A, 0x91, 0x84, 0x52, 0xB8, + 0xFF, 0x69, 0xE0, 0xAD, 0x2C, 0xEC, 0xE4, 0x8F, + 0xE0, 0x50, 0x64, 0xD5, 0xA3, 0x32, 0x38, 0xA9, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xBA, 0xCF, 0x4A, 0x91, 0x84, 0x52, 0xB8, + 0xFF, 0x69, 0xE0, 0xAD, 0x2C, 0xEC, 0xE4, 0x8F, + 0xE0, 0x57, 0x63, 0xD2, 0xA4, 0x35, 0x3F, 0xAE, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xBA, 0xCF, 0x4A, 0x91, 0x84, 0x52, 0xB8, + 0xFF, 0x69, 0xE0, 0xAD, 0x2C, 0xEC, 0xE4, 0x8F, + 0xE0, 0x57, 0x63, 0xD2, 0xA4, 0x35, 0x3F, 0xA9, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xBA, 0xCF, 0x4A, 0x91, 0x84, 0x52, 0xB8, + 0xFF, 0x69, 0xE0, 0xAD, 0x2C, 0xEC, 0xE4, 0x8F, + 0xE0, 0xE7, 0xF6, 0x44, 0xBE, 0xDD, 0x3D, 0xA9, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + new byte[] + { + 0x1E, 0xE2, 0xAF, 0x50, 0x3D, 0xD3, 0x52, 0x78, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.None, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.Zeros, + CipherMode.CFB, + 64, + }; + } } } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.cs index 64b3203b4b7c..fc627effbc78 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.cs @@ -499,6 +499,7 @@ private static void TestTripleDESTransformDirectKey( { tdes.Mode = cipherMode; tdes.Padding = paddingMode; + tdes.Key = key; if (feedbackSize.HasValue) { @@ -510,16 +511,19 @@ private static void TestTripleDESTransformDirectKey( if (cipherMode == CipherMode.ECB) { - tdes.Key = key; liveOneShotDecryptBytes = tdes.DecryptEcb(cipherBytes, paddingMode); liveOneShotEncryptBytes = tdes.EncryptEcb(plainBytes, paddingMode); } else if (cipherMode == CipherMode.CBC) { - tdes.Key = key; liveOneShotDecryptBytes = tdes.DecryptCbc(cipherBytes, iv, paddingMode); liveOneShotEncryptBytes = tdes.EncryptCbc(plainBytes, iv, paddingMode); } + else if (cipherMode == CipherMode.CFB) + { + liveOneShotDecryptBytes = tdes.DecryptCfb(cipherBytes, iv, paddingMode, feedbackSizeInBits: feedbackSize.Value); + liveOneShotEncryptBytes = tdes.EncryptCfb(plainBytes, iv, paddingMode, feedbackSizeInBits: feedbackSize.Value); + } if (liveOneShotDecryptBytes is not null) { diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.cs index b08732a5aeaf..dee729afd93d 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.cs @@ -133,6 +133,58 @@ protected override bool TryDecryptCbcCore( } } + protected override bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = CreateTransformCore( + CipherMode.CFB, + paddingMode, + Key, + iv: iv.ToArray(), + blockSize: BlockSize / BitsPerByte, + paddingSize: feedbackSizeInBits / BitsPerByte, + feedbackSizeInBits / BitsPerByte, + encrypting: false); + + using (transform) + { + return transform.TransformOneShot(ciphertext, destination, out bytesWritten); + } + } + + protected override bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = CreateTransformCore( + CipherMode.CFB, + paddingMode, + Key, + iv: iv.ToArray(), + blockSize: BlockSize / BitsPerByte, + paddingSize: feedbackSizeInBits / BitsPerByte, + feedbackSizeInBits / BitsPerByte, + encrypting: true); + + using (transform) + { + return transform.TransformOneShot(plaintext, destination, out bytesWritten); + } + } + private ICryptoTransform CreateTransform(byte[] rgbKey, byte[]? rgbIV, bool encrypting) { // note: rbgIV is guaranteed to be cloned before this method, so no need to clone it again diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.cs index d0b960fc4ae8..e2fbafdbd15d 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.cs @@ -177,6 +177,58 @@ protected override bool TryDecryptCbcCore( } } + protected override bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = CreateTransformCore( + CipherMode.CFB, + paddingMode, + Key, + iv: iv.ToArray(), + blockSize: BlockSize / BitsPerByte, + feedbackSizeInBits / BitsPerByte, + paddingSize: feedbackSizeInBits / BitsPerByte, + encrypting: false); + + using (transform) + { + return transform.TransformOneShot(ciphertext, destination, out bytesWritten); + } + } + + protected override bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = CreateTransformCore( + CipherMode.CFB, + paddingMode, + Key, + iv: iv.ToArray(), + blockSize: BlockSize / BitsPerByte, + feedbackSizeInBits / BitsPerByte, + paddingSize: feedbackSizeInBits / BitsPerByte, + encrypting: true); + + using (transform) + { + return transform.TransformOneShot(plaintext, destination, out bytesWritten); + } + } + private static void ValidateCFBFeedbackSize(int feedback) { // only 8bits feedback is available on all platforms diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.cs index 33f399849909..0221bb5775bb 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.cs @@ -190,6 +190,28 @@ protected override bool TryDecryptCbcCore( } } + protected override bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + throw new CryptographicException(SR.Format(SR.Cryptography_CipherModeNotSupported, CipherMode.CFB)); + } + + protected override bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + throw new CryptographicException(SR.Format(SR.Cryptography_CipherModeNotSupported, CipherMode.CFB)); + } + private static void ValidateCFBFeedbackSize(int feedback) { // CFB not supported at all diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.cs index 593287656423..e79f466f1318 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.cs @@ -182,6 +182,58 @@ protected override bool TryDecryptCbcCore( } } + protected override bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = CreateTransformCore( + CipherMode.CFB, + paddingMode, + Key, + iv: iv.ToArray(), + blockSize: BlockSize / BitsPerByte, + paddingSize: feedbackSizeInBits / BitsPerByte, + feedbackSizeInBits / BitsPerByte, + encrypting: false); + + using (transform) + { + return transform.TransformOneShot(ciphertext, destination, out bytesWritten); + } + } + + protected override bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = CreateTransformCore( + CipherMode.CFB, + paddingMode, + Key, + iv: iv.ToArray(), + blockSize: BlockSize / BitsPerByte, + paddingSize: feedbackSizeInBits / BitsPerByte, + feedbackSizeInBits / BitsPerByte, + encrypting: true); + + using (transform) + { + return transform.TransformOneShot(plaintext, destination, out bytesWritten); + } + } + private static void ValidateCFBFeedbackSize(int feedback) { // only 8bits/64bits feedback would be valid. diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/DESProvider.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/DESProvider.cs index e4cb6a6302ee..4679eb99b167 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/DESProvider.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/DESProvider.cs @@ -5,10 +5,8 @@ namespace System.Security.Cryptography.Encryption.Des.Tests { internal class DesProvider : IDESProvider { - public DES Create() - { - return DES.Create(); - } + public DES Create() => DES.Create(); + public bool OneShotSupported => true; } public partial class DESFactory diff --git a/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/CngSymmetricAlgorithmCore.cs b/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/CngSymmetricAlgorithmCore.cs index 6b323ccf4607..6da56739bb85 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/CngSymmetricAlgorithmCore.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/CngSymmetricAlgorithmCore.cs @@ -111,35 +111,35 @@ public ICryptoTransform CreateDecryptor() public ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV) { - return CreateCryptoTransform(rgbKey, rgbIV, encrypting: true, _outer.Padding, _outer.Mode); + return CreateCryptoTransform(rgbKey, rgbIV, encrypting: true, _outer.Padding, _outer.Mode, _outer.FeedbackSize); } public ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? rgbIV) { - return CreateCryptoTransform(rgbKey, rgbIV, encrypting: false, _outer.Padding, _outer.Mode); + return CreateCryptoTransform(rgbKey, rgbIV, encrypting: false, _outer.Padding, _outer.Mode, _outer.FeedbackSize); } private ICryptoTransform CreateCryptoTransform(bool encrypting) { if (KeyInPlainText) { - return CreateCryptoTransform(_outer.BaseKey, _outer.IV, encrypting, _outer.Padding, _outer.Mode); + return CreateCryptoTransform(_outer.BaseKey, _outer.IV, encrypting, _outer.Padding, _outer.Mode, _outer.FeedbackSize); } - return CreatePersistedCryptoTransformCore(ProduceCngKey, _outer.IV, encrypting, _outer.Padding, _outer.Mode); + return CreatePersistedCryptoTransformCore(ProduceCngKey, _outer.IV, encrypting, _outer.Padding, _outer.Mode, _outer.FeedbackSize); } - public UniversalCryptoTransform CreateCryptoTransform(byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode) + public UniversalCryptoTransform CreateCryptoTransform(byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode, int feedbackSizeInBits) { if (KeyInPlainText) { - return CreateCryptoTransform(_outer.BaseKey, iv, encrypting, padding, mode); + return CreateCryptoTransform(_outer.BaseKey, iv, encrypting, padding, mode, feedbackSizeInBits); } - return CreatePersistedCryptoTransformCore(ProduceCngKey, iv, encrypting, padding, mode); + return CreatePersistedCryptoTransformCore(ProduceCngKey, iv, encrypting, padding, mode, feedbackSizeInBits); } - private UniversalCryptoTransform CreateCryptoTransform(byte[] rgbKey, byte[]? rgbIV, bool encrypting, PaddingMode padding, CipherMode mode) + private UniversalCryptoTransform CreateCryptoTransform(byte[] rgbKey, byte[]? rgbIV, bool encrypting, PaddingMode padding, CipherMode mode, int feedbackSizeInBits) { if (rgbKey == null) throw new ArgumentNullException(nameof(rgbKey)); @@ -162,19 +162,19 @@ private UniversalCryptoTransform CreateCryptoTransform(byte[] rgbKey, byte[]? rg key = _outer.PreprocessKey(key); - return CreateEphemeralCryptoTransformCore(key, iv, encrypting, padding, mode); + return CreateEphemeralCryptoTransformCore(key, iv, encrypting, padding, mode, feedbackSizeInBits); } - private UniversalCryptoTransform CreateEphemeralCryptoTransformCore(byte[] key, byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode) + private UniversalCryptoTransform CreateEphemeralCryptoTransformCore(byte[] key, byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode, int feedbackSizeInBits) { int blockSizeInBytes = _outer.BlockSize.BitSizeToByteSize(); - SafeAlgorithmHandle algorithmModeHandle = _outer.GetEphemeralModeHandle(mode); + SafeAlgorithmHandle algorithmModeHandle = _outer.GetEphemeralModeHandle(mode, feedbackSizeInBits); BasicSymmetricCipher cipher = new BasicSymmetricCipherBCrypt( algorithmModeHandle, mode, blockSizeInBytes, - _outer.GetPaddingSize(mode, _outer.FeedbackSize), + _outer.GetPaddingSize(mode, feedbackSizeInBits), key, ownsParentHandle: false, iv, @@ -183,20 +183,19 @@ private UniversalCryptoTransform CreateEphemeralCryptoTransformCore(byte[] key, return UniversalCryptoTransform.Create(padding, cipher, encrypting); } - private UniversalCryptoTransform CreatePersistedCryptoTransformCore(Func cngKeyFactory, byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode) + private UniversalCryptoTransform CreatePersistedCryptoTransformCore(Func cngKeyFactory, byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode, int feedbackSizeInBits) { // note: iv is guaranteed to be cloned before this method, so no need to clone it again int blockSizeInBytes = _outer.BlockSize.BitSizeToByteSize(); - int feedbackSizeInBytes = _outer.FeedbackSize; BasicSymmetricCipher cipher = new BasicSymmetricCipherNCrypt( cngKeyFactory, mode, blockSizeInBytes, iv, encrypting, - feedbackSizeInBytes, - _outer.GetPaddingSize(mode, _outer.FeedbackSize)); + feedbackSizeInBits, + _outer.GetPaddingSize(mode, feedbackSizeInBits)); return UniversalCryptoTransform.Create(padding, cipher, encrypting); } @@ -207,7 +206,7 @@ private CngKey ProduceCngKey() return CngKey.Open(_keyName!, _provider!, _optionOptions); } - private bool KeyInPlainText + public bool KeyInPlainText { get { return _keyName == null; } } diff --git a/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/ICngSymmetricAlgorithm.cs b/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/ICngSymmetricAlgorithm.cs index 1e1edf7c792b..eae2d44dec5c 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/ICngSymmetricAlgorithm.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/ICngSymmetricAlgorithm.cs @@ -28,7 +28,7 @@ internal interface ICngSymmetricAlgorithm // Other members. bool IsWeakKey(byte[] key); - SafeAlgorithmHandle GetEphemeralModeHandle(CipherMode mode); + SafeAlgorithmHandle GetEphemeralModeHandle(CipherMode mode, int feedbackSizeInBits); string GetNCryptAlgorithmIdentifier(); byte[] PreprocessKey(byte[] key); int GetPaddingSize(CipherMode mode, int feedbackSizeBits); diff --git a/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx index bc28c3819fc2..b919b72f3626 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx @@ -126,6 +126,9 @@ Keys used with the RSACng algorithm must have an algorithm group of RSA. + + The specified feedback size '{0}' for CipherMode '{1}' is not supported. + This key is for algorithm '{0}'. Expected '{1}'. diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/AesCng.cs b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/AesCng.cs index 311bf2702ab0..dbcbd0bd8cea 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/AesCng.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/AesCng.cs @@ -102,7 +102,8 @@ protected override bool TryDecryptEcbCore( iv: null, encrypting: false, paddingMode, - CipherMode.ECB); + CipherMode.ECB, + feedbackSizeInBits: 0); using (transform) { @@ -120,7 +121,8 @@ protected override bool TryEncryptEcbCore( iv: null, encrypting: true, paddingMode, - CipherMode.ECB); + CipherMode.ECB, + feedbackSizeInBits: 0); using (transform) { @@ -139,7 +141,8 @@ protected override bool TryEncryptCbcCore( iv: iv.ToArray(), encrypting: true, paddingMode, - CipherMode.CBC); + CipherMode.CBC, + feedbackSizeInBits: 0); using (transform) { @@ -158,7 +161,8 @@ protected override bool TryDecryptCbcCore( iv: iv.ToArray(), encrypting: false, paddingMode, - CipherMode.CBC); + CipherMode.CBC, + feedbackSizeInBits: 0); using (transform) { @@ -166,6 +170,72 @@ protected override bool TryDecryptCbcCore( } } + protected override bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = _core.CreateCryptoTransform( + iv: iv.ToArray(), + encrypting: false, + paddingMode, + CipherMode.CFB, + feedbackSizeInBits); + + using (transform) + { + return transform.TransformOneShot(ciphertext, destination, out bytesWritten); + } + } + + protected override bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = _core.CreateCryptoTransform( + iv: iv.ToArray(), + encrypting: true, + paddingMode, + CipherMode.CFB, + feedbackSizeInBits); + + using (transform) + { + return transform.TransformOneShot(plaintext, destination, out bytesWritten); + } + } + + private void ValidateCFBFeedbackSize(int feedback) + { + if (_core.KeyInPlainText) + { + // CFB8 and CFB128 are valid for bcrypt keys. + if (feedback != 8 && feedback != 128) + { + throw new CryptographicException(string.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedback, CipherMode.CFB)); + } + } + else + { + // only CFB8 is supported for ncrypt keys. + if (feedback != 8) + { + throw new CryptographicException(string.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedback, CipherMode.CFB)); + } + } + } + protected override void Dispose(bool disposing) { base.Dispose(disposing); @@ -184,11 +254,11 @@ int ICngSymmetricAlgorithm.GetPaddingSize(CipherMode mode, int feedbackSizeBits) return this.GetPaddingSize(mode, feedbackSizeBits); } - SafeAlgorithmHandle ICngSymmetricAlgorithm.GetEphemeralModeHandle(CipherMode mode) + SafeAlgorithmHandle ICngSymmetricAlgorithm.GetEphemeralModeHandle(CipherMode mode, int feedbackSizeInBits) { try { - return AesBCryptModes.GetSharedHandle(mode, FeedbackSize / 8); + return AesBCryptModes.GetSharedHandle(mode, feedbackSizeInBits / 8); } catch (NotSupportedException) { diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/TripleDESCng.cs b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/TripleDESCng.cs index b03c756c1ad9..09d2b0224353 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/TripleDESCng.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/TripleDESCng.cs @@ -103,7 +103,8 @@ protected override bool TryDecryptEcbCore( iv: null, encrypting: false, paddingMode, - CipherMode.ECB); + CipherMode.ECB, + feedbackSizeInBits: 0); using (transform) { @@ -121,7 +122,8 @@ protected override bool TryEncryptEcbCore( iv: null, encrypting: true, paddingMode, - CipherMode.ECB); + CipherMode.ECB, + feedbackSizeInBits: 0); using (transform) { @@ -140,7 +142,8 @@ protected override bool TryEncryptCbcCore( iv: iv.ToArray(), encrypting: true, paddingMode, - CipherMode.CBC); + CipherMode.CBC, + feedbackSizeInBits: 0); using (transform) { @@ -159,7 +162,8 @@ protected override bool TryDecryptCbcCore( iv: iv.ToArray(), encrypting: false, paddingMode, - CipherMode.CBC); + CipherMode.CBC, + feedbackSizeInBits: 0); using (transform) { @@ -167,11 +171,77 @@ protected override bool TryDecryptCbcCore( } } + protected override bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = _core.CreateCryptoTransform( + iv: iv.ToArray(), + encrypting: false, + paddingMode, + CipherMode.CFB, + feedbackSizeInBits); + + using (transform) + { + return transform.TransformOneShot(ciphertext, destination, out bytesWritten); + } + } + + protected override bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = _core.CreateCryptoTransform( + iv: iv.ToArray(), + encrypting: true, + paddingMode, + CipherMode.CFB, + feedbackSizeInBits); + + using (transform) + { + return transform.TransformOneShot(plaintext, destination, out bytesWritten); + } + } + protected override void Dispose(bool disposing) { base.Dispose(disposing); } + private void ValidateCFBFeedbackSize(int feedback) + { + if (_core.KeyInPlainText) + { + // CFB8 and CFB164 are valid for bcrypt keys. + if (feedback != 8 && feedback != 64) + { + throw new CryptographicException(string.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedback, CipherMode.CFB)); + } + } + else + { + // only CFB8 is supported for ncrypt keys. + if (feedback != 8) + { + throw new CryptographicException(string.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedback, CipherMode.CFB)); + } + } + } + byte[] ICngSymmetricAlgorithm.BaseKey { get { return base.Key; } set { base.Key = value; } } int ICngSymmetricAlgorithm.BaseKeySize { get { return base.KeySize; } set { base.KeySize = value; } } @@ -185,9 +255,9 @@ int ICngSymmetricAlgorithm.GetPaddingSize(CipherMode mode, int feedbackSizeBits) return this.GetPaddingSize(mode, feedbackSizeBits); } - SafeAlgorithmHandle ICngSymmetricAlgorithm.GetEphemeralModeHandle(CipherMode mode) + SafeAlgorithmHandle ICngSymmetricAlgorithm.GetEphemeralModeHandle(CipherMode mode, int feedbackSizeInBits) { - return TripleDesBCryptModes.GetSharedHandle(mode, FeedbackSize / 8); + return TripleDesBCryptModes.GetSharedHandle(mode, feedbackSizeInBits / 8); } string ICngSymmetricAlgorithm.GetNCryptAlgorithmIdentifier() diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/AesCngTests.cs b/src/libraries/System.Security.Cryptography.Cng/tests/AesCngTests.cs index dc6d484263c0..47d4f4b5dd6c 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/AesCngTests.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/AesCngTests.cs @@ -25,12 +25,21 @@ public static class AesCngTests [InlineData(256, BlockSizeBytes + BlockSizeBytes / 2, CipherMode.CBC, PaddingMode.Zeros)] // AES192-CBC-PKCS7 at 1.5 blocks [InlineData(192, BlockSizeBytes + BlockSizeBytes / 2, CipherMode.CBC, PaddingMode.PKCS7)] + // AES128-CFB8-NoPadding at 2 blocks + [InlineData(128, 2 * BlockSizeBytes, CipherMode.CFB, PaddingMode.None, 8)] public static void VerifyPersistedKey( int keySize, int plainBytesCount, CipherMode cipherMode, - PaddingMode paddingMode) + PaddingMode paddingMode, + int feedbackSizeInBits = 0) { + // Windows 7 does not support CFB except in CFB8 mode. + if (cipherMode == CipherMode.CFB && feedbackSizeInBits != 8 && PlatformDetection.IsWindows7) + { + return; + } + SymmetricCngTestHelpers.VerifyPersistedKey( s_cngAlgorithm, keySize, @@ -38,7 +47,8 @@ public static void VerifyPersistedKey( keyName => new AesCng(keyName), () => new AesCng(), cipherMode, - paddingMode); + paddingMode, + feedbackSizeInBits); } @@ -88,6 +98,16 @@ public static void VerifyMachineKey() () => new AesCng()); } + [OuterLoop("Creates/Deletes a persisted key, limit exposure to key leaking")] + [ConditionalFact(nameof(SupportsPersistedSymmetricKeys))] + public static void VerifyUnsupportedFeedbackSizeForPersistedCfb() + { + SymmetricCngTestHelpers.VerifyOneShotCfbPersistedUnsupportedFeedbackSize( + s_cngAlgorithm, + keyName => new AesCng(keyName), + notSupportedFeedbackSizeInBits: 128); + } + public static bool SupportsPersistedSymmetricKeys { get { return SymmetricCngTestHelpers.SupportsPersistedSymmetricKeys; } diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/SymmetricCngTestHelpers.cs b/src/libraries/System.Security.Cryptography.Cng/tests/SymmetricCngTestHelpers.cs index 9810072403fa..9a3c728efb16 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/SymmetricCngTestHelpers.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/SymmetricCngTestHelpers.cs @@ -18,7 +18,8 @@ internal static void VerifyPersistedKey( Func persistedFunc, Func ephemeralFunc, CipherMode cipherMode, - PaddingMode paddingMode) + PaddingMode paddingMode, + int feedbackSizeInBits) { string keyName = Guid.NewGuid().ToString(); CngKeyCreationParameters creationParameters = new CngKeyCreationParameters @@ -41,7 +42,8 @@ internal static void VerifyPersistedKey( persistedFunc, ephemeralFunc, cipherMode, - paddingMode); + paddingMode, + feedbackSizeInBits); } finally { @@ -56,7 +58,8 @@ internal static void VerifyPersistedKey( Func persistedFunc, Func ephemeralFunc, CipherMode cipherMode, - PaddingMode paddingMode) + PaddingMode paddingMode, + int feedbackSizeInBits) { byte[] plainBytes = GenerateRandom(plainBytesCount); @@ -66,6 +69,11 @@ internal static void VerifyPersistedKey( persisted.Mode = ephemeral.Mode = cipherMode; persisted.Padding = ephemeral.Padding = paddingMode; + if (cipherMode == CipherMode.CFB) + { + persisted.FeedbackSize = ephemeral.FeedbackSize = feedbackSizeInBits; + } + ephemeral.Key = persisted.Key; ephemeral.GenerateIV(); persisted.IV = ephemeral.IV; @@ -117,6 +125,12 @@ internal static void VerifyPersistedKey( oneShotEphemeralEncrypted = ephemeral.EncryptCbc(plainBytes, ephemeral.IV, paddingMode); oneShotPersistedDecrypted = persisted.DecryptCbc(oneShotEphemeralEncrypted, persisted.IV, paddingMode); } + else if (cipherMode == CipherMode.CFB) + { + oneShotPersistedEncrypted = persisted.EncryptCfb(plainBytes, persisted.IV, paddingMode, feedbackSizeInBits); + oneShotEphemeralEncrypted = ephemeral.EncryptCfb(plainBytes, ephemeral.IV, paddingMode, feedbackSizeInBits); + oneShotPersistedDecrypted = persisted.DecryptCfb(oneShotEphemeralEncrypted, persisted.IV, paddingMode, feedbackSizeInBits); + } if (oneShotPersistedEncrypted is not null) { @@ -280,7 +294,8 @@ public static void VerifyMachineKey( persistedFunc, ephemeralFunc, CipherMode.CBC, - PaddingMode.PKCS7); + PaddingMode.PKCS7, + feedbackSizeInBits: 0); } finally { @@ -289,6 +304,32 @@ public static void VerifyMachineKey( } } + public static void VerifyOneShotCfbPersistedUnsupportedFeedbackSize( + CngAlgorithm algorithm, + Func persistedFunc, + int notSupportedFeedbackSizeInBits) + { + string keyName = Guid.NewGuid().ToString(); + + // We try to delete the key later which will also dispose of it, so no need + // to put this in a using. + CngKey cngKey = CngKey.Create(algorithm, keyName); + + try + { + using (SymmetricAlgorithm alg = persistedFunc(keyName)) + { + byte[] destination = new byte[alg.BlockSize / 8]; + Assert.ThrowsAny(() => + alg.EncryptCfb(Array.Empty(), destination, PaddingMode.None, notSupportedFeedbackSizeInBits)); + } + } + finally + { + cngKey.Delete(); + } + } + private static bool? s_supportsPersistedSymmetricKeys; internal static bool SupportsPersistedSymmetricKeys { diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/TripleDESCngTests.cs b/src/libraries/System.Security.Cryptography.Cng/tests/TripleDESCngTests.cs index d68aa596c27d..4a913be0a2bb 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/TripleDESCngTests.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/TripleDESCngTests.cs @@ -39,10 +39,13 @@ public static void VerifyDefaults() [InlineData(BlockSizeBytes + BlockSizeBytes / 2, CipherMode.CBC, PaddingMode.Zeros)] // 3DES192-CBC-PKCS7 at 1.5 blocks [InlineData(BlockSizeBytes + BlockSizeBytes / 2, CipherMode.CBC, PaddingMode.PKCS7)] + // 3DES192-CFB8-NoPadding at 2 blocks + [InlineData(2 * BlockSizeBytes, CipherMode.CFB, PaddingMode.None, 8)] public static void VerifyPersistedKey( int plainBytesCount, CipherMode cipherMode, - PaddingMode paddingMode) + PaddingMode paddingMode, + int feedbackSizeInBits = 0) { SymmetricCngTestHelpers.VerifyPersistedKey( s_cngAlgorithm, @@ -51,7 +54,8 @@ public static void VerifyPersistedKey( keyName => new TripleDESCng(keyName), () => new TripleDESCng(), cipherMode, - paddingMode); + paddingMode, + feedbackSizeInBits); } [OuterLoop(/* Creates/Deletes a persisted key, limit exposure to key leaking */)] @@ -100,6 +104,16 @@ public static void VerifyMachineKey() () => new TripleDESCng()); } + [OuterLoop("Creates/Deletes a persisted key, limit exposure to key leaking")] + [ConditionalFact(nameof(SupportsPersistedSymmetricKeys))] + public static void VerifyUnsupportedFeedbackSizeForPersistedCfb() + { + SymmetricCngTestHelpers.VerifyOneShotCfbPersistedUnsupportedFeedbackSize( + s_cngAlgorithm, + keyName => new TripleDESCng(keyName), + notSupportedFeedbackSizeInBits: 64); + } + public static bool SupportsPersistedSymmetricKeys { get { return SymmetricCngTestHelpers.SupportsPersistedSymmetricKeys; } diff --git a/src/libraries/System.Security.Cryptography.Csp/tests/DESCryptoServiceProviderProvider.cs b/src/libraries/System.Security.Cryptography.Csp/tests/DESCryptoServiceProviderProvider.cs index 7181d95edc59..7d049de7e668 100644 --- a/src/libraries/System.Security.Cryptography.Csp/tests/DESCryptoServiceProviderProvider.cs +++ b/src/libraries/System.Security.Cryptography.Csp/tests/DESCryptoServiceProviderProvider.cs @@ -5,10 +5,8 @@ namespace System.Security.Cryptography.Encryption.Des.Tests { public class DESCryptoServiceProviderProvider : IDESProvider { - public DES Create() - { - return new DESCryptoServiceProvider(); - } + public DES Create() => new DESCryptoServiceProvider(); + public bool OneShotSupported => false; } public partial class DESFactory diff --git a/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs b/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs index b14a21f2daaf..bf8d40dcf4ee 100644 --- a/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs +++ b/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs @@ -82,6 +82,8 @@ public static void VerifyAllBaseMembersOverloaded(Type shimType) "TryDecryptEcbCore", "TryEncryptCbcCore", "TryDecryptCbcCore", + "TryEncryptCfbCore", + "TryDecryptCfbCore", }; IEnumerable baseMethods = shimType. diff --git a/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs b/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs index 5eee91bb97aa..d04196601cfd 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs +++ b/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs @@ -256,6 +256,9 @@ public void Clear() { } public byte[] DecryptCbc(byte[] ciphertext, byte[] iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } public byte[] DecryptCbc(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } public int DecryptCbc(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } + public byte[] DecryptCfb(byte[] ciphertext, byte[] iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } + public byte[] DecryptCfb(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } + public int DecryptCfb(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } public byte[] DecryptEcb(byte[] ciphertext, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } public byte[] DecryptEcb(System.ReadOnlySpan ciphertext, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } public int DecryptEcb(System.ReadOnlySpan ciphertext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } @@ -264,6 +267,9 @@ protected virtual void Dispose(bool disposing) { } public byte[] EncryptCbc(byte[] plaintext, byte[] iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } public byte[] EncryptCbc(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } public int EncryptCbc(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } + public byte[] EncryptCfb(byte[] plaintext, byte[] iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } + public byte[] EncryptCfb(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } + public int EncryptCfb(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } public byte[] EncryptEcb(byte[] plaintext, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } public byte[] EncryptEcb(System.ReadOnlySpan plaintext, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } public int EncryptEcb(System.ReadOnlySpan plaintext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } @@ -274,10 +280,14 @@ protected virtual void Dispose(bool disposing) { } public int GetCiphertextLengthEcb(int plaintextLength, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } public bool TryDecryptCbc(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Span destination, out int bytesWritten, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } protected virtual bool TryDecryptCbcCore(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } + public bool TryDecryptCfb(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Span destination, out int bytesWritten, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } + protected virtual bool TryDecryptCfbCore(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, int feedbackSizeInBits, out int bytesWritten) { throw null; } public bool TryDecryptEcb(System.ReadOnlySpan ciphertext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } protected virtual bool TryDecryptEcbCore(System.ReadOnlySpan ciphertext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } public bool TryEncryptCbc(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Span destination, out int bytesWritten, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } protected virtual bool TryEncryptCbcCore(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } + public bool TryEncryptCfb(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Span destination, out int bytesWritten, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } + protected virtual bool TryEncryptCfbCore(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, int feedbackSizeInBits, out int bytesWritten) { throw null; } public bool TryEncryptEcb(System.ReadOnlySpan plaintext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } protected virtual bool TryEncryptEcbCore(System.ReadOnlySpan plaintext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } public bool ValidKeySize(int bitLength) { throw null; } diff --git a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs index f5485577c070..5e5cfa33a07b 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs +++ b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs @@ -980,6 +980,481 @@ public bool TryEncryptCbc( return TryEncryptCbcCore(plaintext, iv, destination, paddingMode, out bytesWritten); } + /// + /// Decrypts data using CFB mode with the specified padding mode and + /// feedback size. + /// + /// The data to decrypt. + /// The initialization vector. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// The decrypted plaintext data. + /// + /// or is . + /// + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// + /// The ciphertext could not be decrypted successfully. + /// + /// + /// -or- + /// + /// + /// The feedback size is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public byte[] DecryptCfb(byte[] ciphertext, byte[] iv, PaddingMode paddingMode = PaddingMode.None, int feedbackSizeInBits = 8) + { + if (ciphertext is null) + throw new ArgumentNullException(nameof(ciphertext)); + if (iv is null) + throw new ArgumentNullException(nameof(iv)); + + return DecryptCfb( + new ReadOnlySpan(ciphertext), + new ReadOnlySpan(iv), + paddingMode, + feedbackSizeInBits); + } + + /// + /// Decrypts data using CFB mode with the specified padding mode and + /// feedback size. + /// + /// The data to decrypt. + /// The initialization vector. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// The decrypted plaintext data. + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// + /// The ciphertext could not be decrypted successfully. + /// + /// + /// -or- + /// + /// + /// The feedback size is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public byte[] DecryptCfb( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + PaddingMode paddingMode = PaddingMode.None, + int feedbackSizeInBits = 8) + { + CheckPaddingMode(paddingMode); + CheckInitializationVectorSize(iv); + CheckFeedbackSize(feedbackSizeInBits); + + // The default is CFB8 with no padding, so allocate a buffer + // that is not from the pool since we can return this directly if + // padding does not need to be removed. + byte[] decryptBuffer = GC.AllocateUninitializedArray(ciphertext.Length); + + if (!TryDecryptCfbCore(ciphertext, iv, decryptBuffer, paddingMode, feedbackSizeInBits, out int written) + || (uint)written > decryptBuffer.Length) + { + // This means decrypting the ciphertext grew in to a larger plaintext or overflowed. + // A user-derived class could do this, but it is not expected in any of the + // implementations that we ship. + throw new CryptographicException(SR.Argument_DestinationTooShort); + } + + // Array.Resize will no-op if the array does not need to be resized. + Array.Resize(ref decryptBuffer, written); + return decryptBuffer; + } + + /// + /// Decrypts data into the specified buffer, using CFB mode with the specified padding mode and + /// feedback size. + /// + /// The data to decrypt. + /// The initialization vector. + /// The buffer to receive the plaintext data. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// The total number of bytes written to . + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// -or- + /// + /// + /// The buffer in is too small to hold the plaintext data. + /// + /// + /// + /// + /// The ciphertext could not be decrypted successfully. + /// + /// + /// -or- + /// + /// + /// is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public int DecryptCfb( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode = PaddingMode.None, + int feedbackSizeInBits = 8) + { + CheckPaddingMode(paddingMode); + CheckInitializationVectorSize(iv); + CheckFeedbackSize(feedbackSizeInBits); + + if (!TryDecryptCfbCore(ciphertext, iv, destination, paddingMode, feedbackSizeInBits, out int written)) + { + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + } + + return written; + } + + /// + /// Attempts to decrypt data into the specified buffer, using CFB mode + /// with the specified padding mode and feedback size. + /// + /// The data to decrypt. + /// The initialization vector. + /// The buffer to receive the plaintext data. + /// When this method returns, the total number of bytes written to . + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// if was large enough to receive the decrypted data; otherwise, . + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// + /// The ciphertext could not be decrypted successfully. + /// + /// + /// -or- + /// + /// + /// is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public bool TryDecryptCfb( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + out int bytesWritten, + PaddingMode paddingMode = PaddingMode.None, + int feedbackSizeInBits = 8) + { + CheckPaddingMode(paddingMode); + CheckInitializationVectorSize(iv); + CheckFeedbackSize(feedbackSizeInBits); + + return TryDecryptCfbCore(ciphertext, iv, destination, paddingMode, feedbackSizeInBits, out bytesWritten); + } + + /// + /// Encrypts data using CFB mode with the specified padding mode and feedback size. + /// + /// The data to encrypt. + /// The initialization vector. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// The encrypted ciphertext data. + /// + /// or is . + /// + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// + /// The plaintext could not be encrypted successfully. + /// + /// + /// -or- + /// + /// + /// The feedback size is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public byte[] EncryptCfb( + byte[] plaintext, + byte[] iv, + PaddingMode paddingMode = PaddingMode.None, + int feedbackSizeInBits = 8) + { + return EncryptCfb( + new ReadOnlySpan(plaintext), + new ReadOnlySpan(iv), + paddingMode, + feedbackSizeInBits); + } + + /// + /// Encrypts data using CFB mode with the specified padding mode and feedback size. + /// + /// The data to encrypt. + /// The initialization vector. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// The encrypted ciphertext data. + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// + /// The plaintext could not be encrypted successfully. + /// + /// + /// -or- + /// + /// + /// The feedback size is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public byte[] EncryptCfb( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + PaddingMode paddingMode = PaddingMode.None, + int feedbackSizeInBits = 8) + { + CheckPaddingMode(paddingMode); + CheckInitializationVectorSize(iv); + CheckFeedbackSize(feedbackSizeInBits); + + int ciphertextLength = GetCiphertextLengthCfb(plaintext.Length, paddingMode, feedbackSizeInBits); + + // We expect most if not all uses to encrypt to exactly the ciphertextLength + byte[] buffer = GC.AllocateUninitializedArray(ciphertextLength); + + if (!TryEncryptCfbCore(plaintext, iv, buffer, paddingMode, feedbackSizeInBits, out int written) || + written != ciphertextLength) + { + // This means a user-derived implementation added more padding than we expected or + // did something non-standard (encrypt to a partial block). This can't happen for + // multiple padding blocks since the buffer would have been too small in the first + // place. It doesn't make sense to try and support partial block encryption, likely + // something went very wrong. So throw. + throw new CryptographicException(SR.Format(SR.Cryptography_EncryptedIncorrectLength, nameof(TryEncryptCfbCore))); + } + + return buffer; + } + + /// + /// Encrypts data into the specified buffer, using CFB mode with the specified padding mode + /// and feedback size. + /// + /// The data to encrypt. + /// The initialization vector. + /// The buffer to receive the ciphertext data. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// The total number of bytes written to . + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// + /// The plaintext could not be encrypted successfully. + /// + /// + /// -or- + /// + /// + /// The feedback size is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public int EncryptCfb( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode = PaddingMode.None, + int feedbackSizeInBits = 8) + { + CheckPaddingMode(paddingMode); + CheckInitializationVectorSize(iv); + CheckFeedbackSize(feedbackSizeInBits); + + if (!TryEncryptCfbCore(plaintext, iv, destination, paddingMode, feedbackSizeInBits, out int written)) + { + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + } + + return written; + } + + /// + /// Attempts to encrypt data into the specified buffer, using CFB mode with the specified padding mode + /// and feedback size. + /// + /// The data to encrypt. + /// The initialization vector. + /// The buffer to receive the ciphertext data. + /// When this method returns, the total number of bytes written to . + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// if was large enough to receive the encrypted data; otherwise, . + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// + /// The plaintext could not be encrypted successfully. + /// + /// + /// -or- + /// + /// + /// The feedback size is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public bool TryEncryptCfb( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + out int bytesWritten, + PaddingMode paddingMode = PaddingMode.None, + int feedbackSizeInBits = 8) + { + CheckPaddingMode(paddingMode); + CheckInitializationVectorSize(iv); + CheckFeedbackSize(feedbackSizeInBits); + + return TryEncryptCfbCore(plaintext, iv, destination, paddingMode, feedbackSizeInBits, out bytesWritten); + } + /// /// When overridden in a derived class, attempts to encrypt data into the specified /// buffer, using ECB mode with the specified padding mode. @@ -1090,6 +1565,68 @@ protected virtual bool TryDecryptCbcCore( throw new NotSupportedException(SR.NotSupported_SubclassOverride); } + /// + /// When overridden in a derived class, attempts to decrypt data + /// into the specified buffer, using CFB mode with the specified padding mode + /// and feedback size. + /// + /// The data to decrypt. + /// The initialization vector. + /// The buffer to receive the plaintext data. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// When this method returns, the total number of bytes written to . + /// if was large enough to receive the decrypted data; otherwise, . + /// + /// A derived class has not provided an implementation. + /// + /// + /// Derived classes must override this and provide an implementation. + /// + protected virtual bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + throw new NotSupportedException(SR.NotSupported_SubclassOverride); + } + + /// + /// When overridden in a derived class, attempts to encrypt data into the specified + /// buffer, using CFB mode with the specified padding mode and feedback size. + /// + /// The data to encrypt. + /// The initialization vector. + /// The buffer to receive the ciphertext data. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// When this method returns, the total number of bytes written to . + /// if was large enough to receive the encrypted data; otherwise, . + /// + /// A derived class has not provided an implementation. + /// + /// + /// Derived classes must override this and provide an implementation. + /// + /// Implementations of this method must write precisely + /// GetCiphertextLengthCfb(plaintext.Length, paddingMode, feedbackSizeInBits) + /// bytes to and report that via . + /// + /// + protected virtual bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + throw new NotSupportedException(SR.NotSupported_SubclassOverride); + } + private static void CheckPaddingMode(PaddingMode paddingMode) { if (paddingMode < PaddingMode.None || paddingMode > PaddingMode.ISO10126) @@ -1102,6 +1639,14 @@ private void CheckInitializationVectorSize(ReadOnlySpan iv) throw new ArgumentException(SR.Cryptography_InvalidIVSize, nameof(iv)); } + private void CheckFeedbackSize(int feedbackSizeInBits) + { + if (feedbackSizeInBits < 8 || (feedbackSizeInBits & 0b111) != 0 || feedbackSizeInBits > BlockSize) + { + throw new ArgumentException(SR.Cryptography_InvalidFeedbackSize, nameof(feedbackSizeInBits)); + } + } + protected CipherMode ModeValue; protected PaddingMode PaddingValue; protected byte[]? KeyValue; diff --git a/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs b/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs index 380cd19b099b..c03909efd391 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs +++ b/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs @@ -495,6 +495,322 @@ static bool EncryptImpl( alg.TryEncryptCbc(Array.Empty(), badIv, destination, out _, PaddingMode.None)); } + [Fact] + public static void EncryptCfb_NotSupportedInDerived() + { + AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 128 }; + + Assert.Throws(() => + alg.EncryptCfb(Array.Empty(), new byte[alg.BlockSize / 8])); + } + + [Fact] + public static void DecryptCfb_NotSupportedInDerived() + { + AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 128 }; + + Assert.Throws(() => + alg.DecryptCfb(Array.Empty(), new byte[alg.BlockSize / 8])); + } + + [Fact] + public static void EncryptCfb_EncryptProducesIncorrectlyPaddedValue() + { + static bool EncryptImpl( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + bytesWritten = destination.Length + 1; + return true; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryEncryptCfbCoreImpl = EncryptImpl, + }; + + Assert.Throws(() => + alg.EncryptCfb(Array.Empty(), new byte[alg.BlockSize / 8], PaddingMode.None)); + } + + [Fact] + public static void DecryptCfb_DecryptBytesWrittenLies() + { + static bool DecryptImpl( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + bytesWritten = destination.Length + 1; + return true; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryDecryptCfbCoreImpl = DecryptImpl, + }; + + Assert.Throws(() => + alg.DecryptCfb(new byte[128 / 8], new byte[128 / 8], feedbackSizeInBits: 128)); + } + + [Fact] + public static void EncryptCfb_EncryptCoreFails() + { + static bool EncryptImpl( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + bytesWritten = 0; + return false; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryEncryptCfbCoreImpl = EncryptImpl, + }; + + Assert.Throws(() => + alg.EncryptCfb(Array.Empty(), new byte[128 / 8], feedbackSizeInBits: 128)); + } + + [Fact] + public static void EncryptCfb_EncryptCoreOverflowWritten() + { + static bool EncryptImpl( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + bytesWritten = -1; + return true; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryEncryptCfbCoreImpl = EncryptImpl, + }; + + Assert.Throws(() => + alg.EncryptCfb(Array.Empty(), new byte[128 / 8], feedbackSizeInBits: 128)); + } + + [Fact] + public static void DecryptCfb_DecryptCoreFails() + { + static bool DecryptImpl( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + bytesWritten = 0; + return false; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryDecryptCfbCoreImpl = DecryptImpl, + }; + + Assert.Throws(() => + alg.DecryptCfb(Array.Empty(), new byte[128 / 8], feedbackSizeInBits: 128)); + } + + [Fact] + public static void DecryptCfb_DecryptCoreOverflowWritten() + { + static bool DecryptImpl( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + bytesWritten = -1; + return true; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryDecryptCfbCoreImpl = DecryptImpl, + }; + + Assert.Throws(() => + alg.DecryptCfb(Array.Empty(), new byte[128 / 8], feedbackSizeInBits: 8)); + } + + [Fact] + public static void DecryptCfb_BadInitializationVectorLength() + { + static bool DecryptImpl( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + Assert.True(false, "Initialization vector was not validated, core should not have been called."); + bytesWritten = 0; + return false; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryDecryptCfbCoreImpl = DecryptImpl, + }; + + byte[] badIv = new byte[alg.BlockSize / 8 + 1]; + byte[] destination = new byte[128 / 8]; + + AssertExtensions.Throws("iv", () => + alg.DecryptCfb(Array.Empty(), badIv, feedbackSizeInBits: 128)); + + AssertExtensions.Throws("iv", () => + alg.DecryptCfb(Array.Empty(), badIv, destination, feedbackSizeInBits: 128)); + + AssertExtensions.Throws("iv", () => + alg.TryDecryptCfb(Array.Empty(), badIv, destination, out _, feedbackSizeInBits: 128)); + } + + [Fact] + public static void EncryptCfb_BadInitializationVectorLength() + { + static bool EncryptImpl( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + Assert.True(false, "Initialization vector was not validated, core should not have been called."); + bytesWritten = 0; + return false; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryEncryptCfbCoreImpl = EncryptImpl, + }; + + byte[] badIv = new byte[alg.BlockSize / 8 + 1]; + byte[] destination = new byte[128 / 8]; + + AssertExtensions.Throws("iv", () => + alg.EncryptCfb(Array.Empty(), badIv, feedbackSizeInBits: 128)); + + AssertExtensions.Throws("iv", () => + alg.EncryptCfb(Array.Empty(), badIv, destination, feedbackSizeInBits: 128)); + + AssertExtensions.Throws("iv", () => + alg.TryEncryptCfb(Array.Empty(), badIv, destination, out _, feedbackSizeInBits: 128)); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(19)] + [InlineData(256)] + public static void DecryptCfb_BadFeedbackSizes(int feedbackSize) + { + static bool DecryptImpl( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + Assert.True(false, "Feedback size was not validated, core should not have been called."); + bytesWritten = 0; + return false; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryDecryptCfbCoreImpl = DecryptImpl, + }; + + byte[] iv = new byte[alg.BlockSize / 8]; + byte[] destination = Array.Empty(); + + AssertExtensions.Throws("feedbackSizeInBits", () => + alg.DecryptCfb(Array.Empty(), iv, feedbackSizeInBits: feedbackSize)); + + AssertExtensions.Throws("feedbackSizeInBits", () => + alg.DecryptCfb(Array.Empty(), iv, destination, feedbackSizeInBits: feedbackSize)); + + AssertExtensions.Throws("feedbackSizeInBits", () => + alg.TryDecryptCfb(Array.Empty(), iv, destination, out _, feedbackSizeInBits: feedbackSize)); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(19)] + [InlineData(256)] + public static void EncryptCfb_BadFeedbackSizes(int feedbackSize) + { + static bool EncryptImpl( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + Assert.True(false, "Feedback size was not validated, core should not have been called."); + bytesWritten = 0; + return false; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryEncryptCfbCoreImpl = EncryptImpl, + }; + + byte[] iv = new byte[alg.BlockSize / 8]; + byte[] destination = Array.Empty(); + + AssertExtensions.Throws("feedbackSizeInBits", () => + alg.DecryptCfb(Array.Empty(), iv, feedbackSizeInBits: feedbackSize)); + + AssertExtensions.Throws("feedbackSizeInBits", () => + alg.DecryptCfb(Array.Empty(), iv, destination, feedbackSizeInBits: feedbackSize)); + + AssertExtensions.Throws("feedbackSizeInBits", () => + alg.TryDecryptCfb(Array.Empty(), iv, destination, out _, feedbackSizeInBits: feedbackSize)); + } + public static IEnumerable CiphertextLengthTheories { get @@ -622,10 +938,28 @@ public delegate bool TryDecryptCbcCoreFunc( PaddingMode paddingMode, out int bytesWritten); + public delegate bool TryEncryptCfbCoreFunc( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten); + + public delegate bool TryDecryptCfbCoreFunc( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten); + public TryEncryptEcbCoreFunc TryEncryptEcbCoreImpl { get; set; } public TryDecryptEcbCoreFunc TryDecryptEcbCoreImpl { get; set; } public TryEncryptCbcCoreFunc TryEncryptCbcCoreImpl { get; set; } public TryDecryptCbcCoreFunc TryDecryptCbcCoreImpl { get; set; } + public TryEncryptCfbCoreFunc TryEncryptCfbCoreImpl { get; set; } + public TryDecryptCfbCoreFunc TryDecryptCfbCoreImpl { get; set; } protected override bool TryEncryptEcbCore( ReadOnlySpan plaintext, @@ -652,6 +986,22 @@ protected override bool TryDecryptCbcCore( Span destination, PaddingMode paddingMode, out int bytesWritten) => TryDecryptCbcCoreImpl(ciphertext, iv, destination, paddingMode, out bytesWritten); + + protected override bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) => TryEncryptCfbCoreImpl(plaintext, iv, destination, paddingMode, feedbackSizeInBits, out bytesWritten); + + protected override bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) => TryDecryptCfbCoreImpl(ciphertext, iv, destination, paddingMode, feedbackSizeInBits, out bytesWritten); } } } From 39000f186175b2037cdadea3848c6c944524f57a Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 13 Jul 2021 20:13:22 +0200 Subject: [PATCH 084/133] [main] Update dependencies from 7 repositories (#55565) * Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-optimization build 20210712.2 optimization.linux-x64.MIBC.Runtime , optimization.windows_nt-x64.MIBC.Runtime , optimization.windows_nt-x86.MIBC.Runtime , optimization.PGO.CoreCLR From Version 1.0.0-prerelease.21361.3 -> To Version 1.0.0-prerelease.21362.2 * Update dependencies from https://github.com/dotnet/arcade build 20210709.3 Microsoft.DotNet.XUnitExtensions , Microsoft.DotNet.VersionTools.Tasks , Microsoft.DotNet.Build.Tasks.TargetFramework.Sdk , Microsoft.DotNet.Build.Tasks.Packaging , Microsoft.DotNet.Build.Tasks.Installers , Microsoft.DotNet.Build.Tasks.Feed , Microsoft.DotNet.Build.Tasks.Archives , Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.ApiCompat , Microsoft.DotNet.CodeAnalysis , Microsoft.DotNet.XUnitConsoleRunner , Microsoft.DotNet.GenFacades , Microsoft.DotNet.GenAPI , Microsoft.DotNet.RemoteExecutor , Microsoft.DotNet.PackageTesting , Microsoft.DotNet.Helix.Sdk , Microsoft.DotNet.SharedFramework.Sdk From Version 6.0.0-beta.21357.3 -> To Version 6.0.0-beta.21359.3 * Update dependencies from https://github.com/dotnet/icu build 20210712.1 Microsoft.NETCore.Runtime.ICU.Transport From Version 6.0.0-preview.7.21328.1 -> To Version 6.0.0-preview.7.21362.1 * Update dependencies from https://github.com/dotnet/llvm-project build 20210712.1 runtime.win-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Tools , runtime.win-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk , runtime.osx.10.12-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Tools , runtime.osx.10.12-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk , runtime.linux-arm64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk , runtime.linux-arm64.Microsoft.NETCore.Runtime.Mono.LLVM.Tools , runtime.linux-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk , runtime.linux-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Tools From Version 11.1.0-alpha.1.21357.1 -> To Version 11.1.0-alpha.1.21362.1 * Update dependencies from https://github.com/mono/linker build 20210712.3 Microsoft.NET.ILLink.Tasks From Version 6.0.100-preview.6.21358.3 -> To Version 6.0.100-preview.6.21362.3 * Update dependencies from https://github.com/dotnet/hotreload-utils build 20210712.1 Microsoft.DotNet.HotReload.Utils.Generator.BuildTool From Version 1.0.1-alpha.0.21361.1 -> To Version 1.0.1-alpha.0.21362.1 * Update dependencies from https://github.com/dotnet/emsdk build 20210713.1 Microsoft.NET.Workload.Emscripten.Manifest-6.0.100 From Version 6.0.0-preview.7.21362.2 -> To Version 6.0.0-preview.7.21363.1 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 132 ++++++++++++++++++++-------------------- eng/Versions.props | 58 +++++++++--------- eng/common/tools.ps1 | 4 +- eng/common/tools.sh | 4 +- global.json | 8 +-- 5 files changed, 103 insertions(+), 103 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 73444fdb70f5..58a2c18606c8 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,82 +1,82 @@ - + https://github.com/dotnet/icu - e7626ad8c04b150de635f920b5e8dede0aafaf73 + 1782cd21854f8cb2b60355f4773714a8e0130696 https://github.com/dotnet/msquic d7db669b70f4dd67ec001c192f9809c218cab88b - + https://github.com/dotnet/emsdk - 7e218d66bf9ec3ca4fc70c0b63e9a162f2e33451 + 8a6313ffbdbef76fd01e32e37c802b57945531d3 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 https://github.com/microsoft/vstest @@ -118,37 +118,37 @@ https://github.com/dotnet/runtime-assets 3db3f0a34f73db72e5b918ad22e1fbe9f1c5c4da - + https://github.com/dotnet/llvm-project - a05e5e9fb80f9bb6fd9100775dfe55be6f84729d + 97d42b6f43e31449758c353cfbaf83322941611b - + https://github.com/dotnet/llvm-project - a05e5e9fb80f9bb6fd9100775dfe55be6f84729d + 97d42b6f43e31449758c353cfbaf83322941611b - + https://github.com/dotnet/llvm-project - a05e5e9fb80f9bb6fd9100775dfe55be6f84729d + 97d42b6f43e31449758c353cfbaf83322941611b - + https://github.com/dotnet/llvm-project - a05e5e9fb80f9bb6fd9100775dfe55be6f84729d + 97d42b6f43e31449758c353cfbaf83322941611b - + https://github.com/dotnet/llvm-project - a05e5e9fb80f9bb6fd9100775dfe55be6f84729d + 97d42b6f43e31449758c353cfbaf83322941611b - + https://github.com/dotnet/llvm-project - a05e5e9fb80f9bb6fd9100775dfe55be6f84729d + 97d42b6f43e31449758c353cfbaf83322941611b - + https://github.com/dotnet/llvm-project - a05e5e9fb80f9bb6fd9100775dfe55be6f84729d + 97d42b6f43e31449758c353cfbaf83322941611b - + https://github.com/dotnet/llvm-project - a05e5e9fb80f9bb6fd9100775dfe55be6f84729d + 97d42b6f43e31449758c353cfbaf83322941611b https://github.com/dotnet/runtime @@ -182,9 +182,9 @@ https://github.com/dotnet/runtime 98b7ed1a3b0543a31b5a0f9069cf44cb70c9230c - + https://github.com/mono/linker - b9501922637806f4135df09a9922d5540e203858 + 664e78edc72dd0a48e6f55e352051b6ba61bba9a https://github.com/dotnet/xharness @@ -194,29 +194,29 @@ https://github.com/dotnet/xharness c6d444eaf7e95339589ceef371cbef0a90a4add5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - ae45cbdfa6d15fce7e3cf089462f0d2b55727273 + a83e4392b6d3f377239726eedc19b6dc85b85496 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - ae45cbdfa6d15fce7e3cf089462f0d2b55727273 + a83e4392b6d3f377239726eedc19b6dc85b85496 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - ae45cbdfa6d15fce7e3cf089462f0d2b55727273 + a83e4392b6d3f377239726eedc19b6dc85b85496 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - ae45cbdfa6d15fce7e3cf089462f0d2b55727273 + a83e4392b6d3f377239726eedc19b6dc85b85496 - + https://github.com/dotnet/hotreload-utils - 640e908a67b5bc63fa615d31c7877e62c2b15062 + 33219d2b3fbc957e05f8e52a33363cf9b858bb08 https://github.com/dotnet/runtime-assets diff --git a/eng/Versions.props b/eng/Versions.props index 4f3f2a4c21aa..1e7be3b4cb98 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -52,19 +52,19 @@ 3.10.0 6.0.0-rc1.21356.1 - 6.0.0-beta.21357.3 - 6.0.0-beta.21357.3 - 6.0.0-beta.21357.3 - 6.0.0-beta.21357.3 - 6.0.0-beta.21357.3 - 6.0.0-beta.21357.3 - 2.5.1-beta.21357.3 - 6.0.0-beta.21357.3 - 6.0.0-beta.21357.3 - 6.0.0-beta.21357.3 - 6.0.0-beta.21357.3 - 6.0.0-beta.21357.3 - 6.0.0-beta.21357.3 + 6.0.0-beta.21359.3 + 6.0.0-beta.21359.3 + 6.0.0-beta.21359.3 + 6.0.0-beta.21359.3 + 6.0.0-beta.21359.3 + 6.0.0-beta.21359.3 + 2.5.1-beta.21359.3 + 6.0.0-beta.21359.3 + 6.0.0-beta.21359.3 + 6.0.0-beta.21359.3 + 6.0.0-beta.21359.3 + 6.0.0-beta.21359.3 + 6.0.0-beta.21359.3 6.0.0-preview.1.102 @@ -124,10 +124,10 @@ 6.0.0-beta.21358.1 6.0.0-beta.21358.1 - 1.0.0-prerelease.21361.3 - 1.0.0-prerelease.21361.3 - 1.0.0-prerelease.21361.3 - 1.0.0-prerelease.21361.3 + 1.0.0-prerelease.21362.2 + 1.0.0-prerelease.21362.2 + 1.0.0-prerelease.21362.2 + 1.0.0-prerelease.21362.2 16.9.0-beta1.21055.5 2.0.0-beta1.20253.1 @@ -153,7 +153,7 @@ 16.9.0-preview-20201201-01 1.0.0-prerelease.21357.4 1.0.0-prerelease.21357.4 - 1.0.1-alpha.0.21361.1 + 1.0.1-alpha.0.21362.1 2.4.1 2.4.2 1.3.0 @@ -164,23 +164,23 @@ 5.0.0-preview-20201009.2 - 6.0.100-preview.6.21358.3 + 6.0.100-preview.6.21362.3 $(MicrosoftNETILLinkTasksVersion) - 6.0.0-preview.7.21328.1 + 6.0.0-preview.7.21362.1 6.0.0-preview.7.21357.1 - 11.1.0-alpha.1.21357.1 - 11.1.0-alpha.1.21357.1 - 11.1.0-alpha.1.21357.1 - 11.1.0-alpha.1.21357.1 - 11.1.0-alpha.1.21357.1 - 11.1.0-alpha.1.21357.1 - 11.1.0-alpha.1.21357.1 - 11.1.0-alpha.1.21357.1 + 11.1.0-alpha.1.21362.1 + 11.1.0-alpha.1.21362.1 + 11.1.0-alpha.1.21362.1 + 11.1.0-alpha.1.21362.1 + 11.1.0-alpha.1.21362.1 + 11.1.0-alpha.1.21362.1 + 11.1.0-alpha.1.21362.1 + 11.1.0-alpha.1.21362.1 - 6.0.0-preview.7.21358.1 + 6.0.0-preview.7.21363.1 $(MicrosoftNETWorkloadEmscriptenManifest60100Version) diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index 4b2552032493..2df0909937d1 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -42,7 +42,7 @@ [bool]$useInstalledDotNetCli = if (Test-Path variable:useInstalledDotNetCli) { $useInstalledDotNetCli } else { $true } # Enable repos to use a particular version of the on-line dotnet-install scripts. -# default URL: https://dot.net/v1/dotnet-install.ps1 +# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1 [string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { 'v1' } # True to use global NuGet cache instead of restoring packages to repository-local directory. @@ -223,7 +223,7 @@ function GetDotNetInstallScript([string] $dotnetRoot) { if (!(Test-Path $installScript)) { Create-Directory $dotnetRoot $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit - $uri = "https://dot.net/$dotnetInstallScriptVersion/dotnet-install.ps1" + $uri = "https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1" Retry({ Write-Host "GET $uri" diff --git a/eng/common/tools.sh b/eng/common/tools.sh index 05ca99c6b281..828119be411b 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -54,7 +54,7 @@ warn_as_error=${warn_as_error:-true} use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} # Enable repos to use a particular version of the on-line dotnet-install scripts. -# default URL: https://dot.net/v1/dotnet-install.sh +# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh dotnetInstallScriptVersion=${dotnetInstallScriptVersion:-'v1'} # True to use global NuGet cache instead of restoring packages to repository-local directory. @@ -262,7 +262,7 @@ function with_retries { function GetDotNetInstallScript { local root=$1 local install_script="$root/dotnet-install.sh" - local install_script_url="https://dot.net/$dotnetInstallScriptVersion/dotnet-install.sh" + local install_script_url="https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh" if [[ ! -a "$install_script" ]]; then mkdir -p "$root" diff --git a/global.json b/global.json index e9516bcfa8f5..e25c47c09f71 100644 --- a/global.json +++ b/global.json @@ -12,11 +12,11 @@ "python3": "3.7.1" }, "msbuild-sdks": { - "Microsoft.DotNet.Build.Tasks.TargetFramework.Sdk": "6.0.0-beta.21357.3", + "Microsoft.DotNet.Build.Tasks.TargetFramework.Sdk": "6.0.0-beta.21359.3", "Microsoft.DotNet.PackageValidation": "1.0.0-preview.7.21352.4", - "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21357.3", - "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.21357.3", - "Microsoft.DotNet.SharedFramework.Sdk": "6.0.0-beta.21357.3", + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21359.3", + "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.21359.3", + "Microsoft.DotNet.SharedFramework.Sdk": "6.0.0-beta.21359.3", "Microsoft.Build.NoTargets": "3.0.4", "Microsoft.Build.Traversal": "3.0.23", "Microsoft.NET.Sdk.IL": "6.0.0-preview.7.21361.10" From dc2d0cc61f58fde5c04b5a29ba476ccd3779ab74 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Tue, 13 Jul 2021 20:20:10 +0200 Subject: [PATCH 085/133] [wasm][http] Improve compatibility of abort and cancelation of BrowserHttpHandler (#55084) * fixed handling of cancelation and abots exceptions to match unit test expectations added [ActiveIssue("https://github.com/dotnet/runtime/issues/55083", TestPlatforms.Browser)] for redirect outerloop tests * more * code review feedback --- .../HttpClientHandlerTest.RemoteServer.cs | 10 +++ .../BrowserHttpHandler/BrowserHttpHandler.cs | 64 +++++++++++++------ .../tests/FunctionalTests/HttpClientTest.cs | 7 -- 3 files changed, 56 insertions(+), 25 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.RemoteServer.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.RemoteServer.cs index 36d732d79ff9..316a58cb33f3 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.RemoteServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.RemoteServer.cs @@ -928,6 +928,7 @@ public static IEnumerable RemoteServersAndRedirectStatusCodes() [OuterLoop("Uses external servers")] [Theory, MemberData(nameof(RemoteServersAndRedirectStatusCodes))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/55083", TestPlatforms.Browser)] public async Task GetAsync_AllowAutoRedirectFalse_RedirectFromHttpToHttp_StatusCodeRedirect(Configuration.Http.RemoteServer remoteServer, int statusCode) { if (statusCode == 308 && (IsWinHttpHandler && PlatformDetection.WindowsVersion < 10)) @@ -955,6 +956,7 @@ public async Task GetAsync_AllowAutoRedirectFalse_RedirectFromHttpToHttp_StatusC [OuterLoop("Uses external servers")] [Theory, MemberData(nameof(RemoteServersAndRedirectStatusCodes))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/55083", TestPlatforms.Browser)] public async Task GetAsync_AllowAutoRedirectTrue_RedirectFromHttpToHttp_StatusCodeOK(Configuration.Http.RemoteServer remoteServer, int statusCode) { if (statusCode == 308 && (IsWinHttpHandler && PlatformDetection.WindowsVersion < 10)) @@ -982,6 +984,7 @@ public async Task GetAsync_AllowAutoRedirectTrue_RedirectFromHttpToHttp_StatusCo [OuterLoop("Uses external servers")] [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/55083", TestPlatforms.Browser)] public async Task GetAsync_AllowAutoRedirectTrue_RedirectFromHttpToHttps_StatusCodeOK() { HttpClientHandler handler = CreateHttpClientHandler(); @@ -1003,6 +1006,7 @@ public async Task GetAsync_AllowAutoRedirectTrue_RedirectFromHttpToHttps_StatusC [OuterLoop("Uses external servers")] [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/55083", TestPlatforms.Browser)] public async Task GetAsync_AllowAutoRedirectTrue_RedirectFromHttpsToHttp_StatusCodeRedirect() { HttpClientHandler handler = CreateHttpClientHandler(); @@ -1025,6 +1029,7 @@ public async Task GetAsync_AllowAutoRedirectTrue_RedirectFromHttpsToHttp_StatusC [OuterLoop("Uses external servers")] [Theory, MemberData(nameof(RemoteServersMemberData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/55083", TestPlatforms.Browser)] public async Task GetAsync_AllowAutoRedirectTrue_RedirectToUriWithParams_RequestMsgUriSet(Configuration.Http.RemoteServer remoteServer) { HttpClientHandler handler = CreateHttpClientHandler(); @@ -1050,6 +1055,7 @@ public async Task GetAsync_AllowAutoRedirectTrue_RedirectToUriWithParams_Request [InlineData(3, 2)] [InlineData(3, 3)] [InlineData(3, 4)] + [SkipOnPlatform(TestPlatforms.Browser, "MaxConnectionsPerServer not supported on Browser")] public async Task GetAsync_MaxAutomaticRedirectionsNServerHops_ThrowsIfTooMany(int maxHops, int hops) { if (IsWinHttpHandler && !PlatformDetection.IsWindows10Version1703OrGreater) @@ -1095,6 +1101,7 @@ public async Task GetAsync_MaxAutomaticRedirectionsNServerHops_ThrowsIfTooMany(i [OuterLoop("Uses external servers")] [Theory, MemberData(nameof(RemoteServersMemberData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/55083", TestPlatforms.Browser)] public async Task GetAsync_AllowAutoRedirectTrue_RedirectWithRelativeLocation(Configuration.Http.RemoteServer remoteServer) { HttpClientHandler handler = CreateHttpClientHandler(); @@ -1118,6 +1125,7 @@ public async Task GetAsync_AllowAutoRedirectTrue_RedirectWithRelativeLocation(Co [Theory, MemberData(nameof(RemoteServersMemberData))] [OuterLoop("Uses external servers")] + [SkipOnPlatform(TestPlatforms.Browser, "Credentials is not supported on Browser")] public async Task GetAsync_CredentialIsNetworkCredentialUriRedirect_StatusCodeUnauthorized(Configuration.Http.RemoteServer remoteServer) { HttpClientHandler handler = CreateHttpClientHandler(); @@ -1137,6 +1145,7 @@ public async Task GetAsync_CredentialIsNetworkCredentialUriRedirect_StatusCodeUn [Theory, MemberData(nameof(RemoteServersMemberData))] [OuterLoop("Uses external servers")] + [SkipOnPlatform(TestPlatforms.Browser, "Credentials is not supported on Browser")] public async Task HttpClientHandler_CredentialIsNotCredentialCacheAfterRedirect_StatusCodeOK(Configuration.Http.RemoteServer remoteServer) { HttpClientHandler handler = CreateHttpClientHandler(); @@ -1163,6 +1172,7 @@ public async Task HttpClientHandler_CredentialIsNotCredentialCacheAfterRedirect_ [OuterLoop("Uses external servers")] [Theory, MemberData(nameof(RemoteServersAndRedirectStatusCodes))] + [SkipOnPlatform(TestPlatforms.Browser, "Credentials is not supported on Browser")] public async Task GetAsync_CredentialIsCredentialCacheUriRedirect_StatusCodeOK(Configuration.Http.RemoteServer remoteServer, int statusCode) { if (statusCode == 308 && (IsWinHttpHandler && PlatformDetection.WindowsVersion < 10)) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs index b4450bb42564..8d022091ea39 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs @@ -139,7 +139,7 @@ public SslClientAuthenticationOptions SslOptions protected internal override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken) { - throw new PlatformNotSupportedException (); + throw new PlatformNotSupportedException(); } protected internal override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) @@ -323,10 +323,27 @@ protected internal override async Task SendAsync(HttpReques return httpResponse; } - catch (JSException jsExc) + catch (OperationCanceledException oce) when (cancellationToken.IsCancellationRequested) { - throw new System.Net.Http.HttpRequestException(jsExc.Message); + throw CancellationHelper.CreateOperationCanceledException(oce, cancellationToken); } + catch (JSException jse) + { + throw TranslateJSException(jse, cancellationToken); + } + } + + private static Exception TranslateJSException(JSException jse, CancellationToken cancellationToken) + { + if (jse.Message.StartsWith("AbortError", StringComparison.Ordinal)) + { + return CancellationHelper.CreateOperationCanceledException(jse, CancellationToken.None); + } + if (cancellationToken.IsCancellationRequested) + { + return CancellationHelper.CreateOperationCanceledException(jse, cancellationToken); + } + return new HttpRequestException(jse.Message, jse); } private sealed class WasmFetchResponse : IDisposable @@ -366,7 +383,6 @@ public void Dispose() _isDisposed = true; - _abortCts.Cancel(); _abortCts.Dispose(); _abortRegistration.Dispose(); @@ -385,28 +401,34 @@ public BrowserHttpContent(WasmFetchResponse status) _status = status ?? throw new ArgumentNullException(nameof(status)); } - private async Task GetResponseData() + private async Task GetResponseData(CancellationToken cancellationToken) { if (_data != null) { return _data; } - - using (System.Runtime.InteropServices.JavaScript.ArrayBuffer dataBuffer = (System.Runtime.InteropServices.JavaScript.ArrayBuffer)await _status.ArrayBuffer().ConfigureAwait(continueOnCapturedContext: true)) + try { - using (Uint8Array dataBinView = new Uint8Array(dataBuffer)) + using (System.Runtime.InteropServices.JavaScript.ArrayBuffer dataBuffer = (System.Runtime.InteropServices.JavaScript.ArrayBuffer)await _status.ArrayBuffer().ConfigureAwait(continueOnCapturedContext: true)) { - _data = dataBinView.ToArray(); - _status.Dispose(); + using (Uint8Array dataBinView = new Uint8Array(dataBuffer)) + { + _data = dataBinView.ToArray(); + _status.Dispose(); + } } } + catch (JSException jse) + { + throw TranslateJSException(jse, cancellationToken); + } return _data; } protected override async Task CreateContentReadStreamAsync() { - byte[] data = await GetResponseData().ConfigureAwait(continueOnCapturedContext: true); + byte[] data = await GetResponseData(CancellationToken.None).ConfigureAwait(continueOnCapturedContext: true); return new MemoryStream(data, writable: false); } @@ -414,7 +436,7 @@ protected override Task SerializeToStreamAsync(Stream stream, TransportContext? SerializeToStreamAsync(stream, context, CancellationToken.None); protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken) { - byte[] data = await GetResponseData().ConfigureAwait(continueOnCapturedContext: true); + byte[] data = await GetResponseData(cancellationToken).ConfigureAwait(continueOnCapturedContext: true); await stream.WriteAsync(data, cancellationToken).ConfigureAwait(continueOnCapturedContext: true); } protected internal override bool TryComputeLength(out long length) @@ -482,10 +504,13 @@ public override async ValueTask ReadAsync(Memory buffer, Cancellation _reader = (JSObject)body.Invoke("getReader"); } } - catch (JSException) + catch (OperationCanceledException oce) when (cancellationToken.IsCancellationRequested) { - cancellationToken.ThrowIfCancellationRequested(); - throw; + throw CancellationHelper.CreateOperationCanceledException(oce, cancellationToken); + } + catch (JSException jse) + { + throw TranslateJSException(jse, cancellationToken); } } @@ -515,10 +540,13 @@ public override async ValueTask ReadAsync(Memory buffer, Cancellation _bufferedBytes = binValue.ToArray(); } } - catch (JSException) + catch (OperationCanceledException oce) when (cancellationToken.IsCancellationRequested) + { + throw CancellationHelper.CreateOperationCanceledException(oce, cancellationToken); + } + catch (JSException jse) { - cancellationToken.ThrowIfCancellationRequested(); - throw; + throw TranslateJSException(jse, cancellationToken); } return ReadBuffered(); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs index 61727faa4330..26a01e5a1338 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs @@ -385,7 +385,6 @@ await LoopbackServer.CreateClientAndServerAsync( } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/54270", TestPlatforms.Browser)] public async Task GetStringAsync_CanBeCanceled_AlreadyCanceledCts() { var onClientFinished = new SemaphoreSlim(0, 1); @@ -410,7 +409,6 @@ await LoopbackServer.CreateClientAndServerAsync( } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/54270", TestPlatforms.Browser)] public async Task GetStringAsync_CanBeCanceled() { var cts = new CancellationTokenSource(); @@ -445,7 +443,6 @@ await server.AcceptConnectionAsync(async connection => [InlineData(1, 0)] [InlineData(1, 1)] [InlineData(1, 2)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/54270", TestPlatforms.Browser)] public async Task GetAsync_ContentCanBeCanceled(int getMode, int cancelMode) { // cancelMode: @@ -555,7 +552,6 @@ await LoopbackServer.CreateClientAndServerAsync( } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/54270", TestPlatforms.Browser)] public async Task GetByteArrayAsync_CanBeCanceled_AlreadyCanceledCts() { var onClientFinished = new SemaphoreSlim(0, 1); @@ -580,7 +576,6 @@ await LoopbackServer.CreateClientAndServerAsync( } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/54270", TestPlatforms.Browser)] public async Task GetByteArrayAsync_CanBeCanceled() { var cts = new CancellationTokenSource(); @@ -631,7 +626,6 @@ await LoopbackServer.CreateClientAndServerAsync( } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/54270", TestPlatforms.Browser)] public async Task GetStreamAsync_CanBeCanceled_AlreadyCanceledCts() { var onClientFinished = new SemaphoreSlim(0, 1); @@ -656,7 +650,6 @@ await LoopbackServer.CreateClientAndServerAsync( } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/54270", TestPlatforms.Browser)] public async Task GetStreamAsync_CanBeCanceled() { var cts = new CancellationTokenSource(); From ffed50cdf9e417f8d4e7148580c7812601bafb40 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Tue, 13 Jul 2021 14:37:00 -0400 Subject: [PATCH 086/133] Add leeway to revocation timeout tests. --- .../tests/RevocationTests/TimeoutTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/TimeoutTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/TimeoutTests.cs index dc860faf1c39..e782a0c3e504 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/TimeoutTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/TimeoutTests.cs @@ -59,7 +59,11 @@ public static void RevocationCheckingDelayed(PkiOptions pkiOptions) // OCSP/CRL to root to get intermediate statuses. It should take at least 2x the delay // plus other non-network time, so we can at least ensure it took as long as // the delay for each fetch. - Assert.True(watch.Elapsed >= delay * 2, $"watch.Elapsed: {watch.Elapsed}"); + // We expect the chain to build in at least 16 seconds (2 * delay) since each fetch + // should take `delay` number of seconds, and there are two fetchs that need to be + // performed. We allow a small amount of leeway to account for differences between + // how long the the delay is performed and the stopwatch. + Assert.True(watch.Elapsed >= delay * 2 - TimeSpan.FromSeconds(1), $"watch.Elapsed: {watch.Elapsed}"); } }); } From 4d52a53df1ef9f07f5f0122f477af13888bdd60c Mon Sep 17 00:00:00 2001 From: Alexander Nikolaev <55398552+alnikola@users.noreply.github.com> Date: Tue, 13 Jul 2021 21:10:16 +0200 Subject: [PATCH 087/133] Disable Http2_MultipleConnectionsEnabled_InfiniteRequestsCompletelyBlockOneConnection_RemaningRequestsAreHandledByNewConnection (#55593) --- .../tests/FunctionalTests/SocketsHttpHandlerTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index d8b7ec4e712c..b326c8bbb021 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -2119,6 +2119,7 @@ public async Task Http2_MultipleConnectionsEnabled_ConnectionLimitNotReached_Con } [ConditionalFact(nameof(SupportsAlpn))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/45204")] public async Task Http2_MultipleConnectionsEnabled_InfiniteRequestsCompletelyBlockOneConnection_RemaningRequestsAreHandledByNewConnection() { const int MaxConcurrentStreams = 2; From ca85119b00e8f9f88934b0130d0cee0ef87c8870 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 13 Jul 2021 15:39:29 -0400 Subject: [PATCH 088/133] Disable some PosixSignalRegistration tests on mobile targets (#55569) xunit doesn't like the fact that some of these MemberDatas were yielding 0 entries on these platforms. --- .../PosixSignalRegistrationTests.cs | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/PosixSignalRegistrationTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/PosixSignalRegistrationTests.cs index 599d1adc6c6d..4f800d51dca4 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/PosixSignalRegistrationTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/PosixSignalRegistrationTests.cs @@ -25,21 +25,21 @@ public void Create_InvalidSignal_Throws(PosixSignal signal) Assert.Throws(() => PosixSignalRegistration.Create(signal, ctx => { })); } - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile))] [MemberData(nameof(UninstallableSignals))] public void Create_UninstallableSignal_Throws(PosixSignal signal) { Assert.Throws(() => PosixSignalRegistration.Create(signal, ctx => { })); } - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile))] [MemberData(nameof(SupportedSignals))] public void Create_ValidSignal_Success(PosixSignal signal) { PosixSignalRegistration.Create(signal, ctx => { }).Dispose(); } - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile))] [MemberData(nameof(SupportedSignals))] public void Dispose_Idempotent(PosixSignal signal) { @@ -48,26 +48,21 @@ public void Dispose_Idempotent(PosixSignal signal) registration.Dispose(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile))] public void Create_RegisterForMultipleSignalsMultipletimes_Success() { var registrations = new List(); - for (int i = 0; i < 3; i++) + for (int iter = 0; iter < 3; iter++) { - foreach (object[] signal in SupportedSignals()) + for (int i = 0; i < 2; i++) { - registrations.Add(PosixSignalRegistration.Create((PosixSignal)signal[0], _ => { })); + foreach (object[] signal in SupportedSignals()) + { + registrations.Add(PosixSignalRegistration.Create((PosixSignal)signal[0], _ => { })); + } } - foreach (object[] signal in SupportedSignals()) - { - registrations.Add(PosixSignalRegistration.Create((PosixSignal)signal[0], _ => { })); - } - - foreach (PosixSignalRegistration registration in registrations) - { - registration.Dispose(); - } + registrations.ForEach(r => r.Dispose()); } } } From 4f9deeb818a89b2835533da7ee4c253d4475d316 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 13 Jul 2021 15:39:47 -0400 Subject: [PATCH 089/133] Simplify PosixSignalRegistration implementation on Unix (#55552) * Simplify PosixSignalRegistration implementation on Unix Bring it onto the same scheme as the Windows implementation. * Address PR feedback * Delete more dead code * Update PosixSignalRegistration.Unix.cs --- .../Unix/System.Native/Interop.PosixSignal.cs | 2 +- .../Native/Unix/System.Native/pal_signal.c | 10 +- .../Native/Unix/System.Native/pal_signal.h | 2 +- ...SignalRegistration.PlatformNotSupported.cs | 11 +- .../PosixSignalRegistration.Unix.cs | 273 +++++------------- .../PosixSignalRegistration.Windows.cs | 72 ++--- .../PosixSignalRegistration.cs | 50 +++- 7 files changed, 152 insertions(+), 268 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PosixSignal.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PosixSignal.cs index b42b5138ec62..65e3ca35f484 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PosixSignal.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PosixSignal.cs @@ -18,7 +18,7 @@ internal static partial class Sys internal static extern void DisablePosixSignalHandling(int signal); [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_HandleNonCanceledPosixSignal")] - internal static extern bool HandleNonCanceledPosixSignal(int signal, int handlersDisposed); + internal static extern void HandleNonCanceledPosixSignal(int signal); [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetPlatformSignalNumber")] [SuppressGCTransition] diff --git a/src/libraries/Native/Unix/System.Native/pal_signal.c b/src/libraries/Native/Unix/System.Native/pal_signal.c index 4c4017f65474..ee6ccd833c0d 100644 --- a/src/libraries/Native/Unix/System.Native/pal_signal.c +++ b/src/libraries/Native/Unix/System.Native/pal_signal.c @@ -233,7 +233,7 @@ static void SignalHandler(int sig, siginfo_t* siginfo, void* context) } } -int32_t SystemNative_HandleNonCanceledPosixSignal(int32_t signalCode, int32_t handlersDisposed) +void SystemNative_HandleNonCanceledPosixSignal(int32_t signalCode) { switch (signalCode) { @@ -276,11 +276,6 @@ int32_t SystemNative_HandleNonCanceledPosixSignal(int32_t signalCode, int32_t ha // Original handler doesn't do anything. break; } - if (handlersDisposed && g_hasPosixSignalRegistrations[signalCode - 1]) - { - // New handlers got registered. - return 0; - } // Restore and invoke the original handler. pthread_mutex_lock(&lock); { @@ -293,7 +288,6 @@ int32_t SystemNative_HandleNonCanceledPosixSignal(int32_t signalCode, int32_t ha kill(getpid(), signalCode); break; } - return 1; } // Entrypoint for the thread that handles signals where our handling @@ -385,7 +379,7 @@ static void* SignalHandlerLoop(void* arg) if (!usePosixSignalHandler) { - SystemNative_HandleNonCanceledPosixSignal(signalCode, 0); + SystemNative_HandleNonCanceledPosixSignal(signalCode); } } } diff --git a/src/libraries/Native/Unix/System.Native/pal_signal.h b/src/libraries/Native/Unix/System.Native/pal_signal.h index 138f37835a8c..146fdabbfceb 100644 --- a/src/libraries/Native/Unix/System.Native/pal_signal.h +++ b/src/libraries/Native/Unix/System.Native/pal_signal.h @@ -75,7 +75,7 @@ PALEXPORT void SystemNative_DisablePosixSignalHandling(int signalCode); /** * Performs the default runtime action for a non-canceled PosixSignal. */ -PALEXPORT int32_t SystemNative_HandleNonCanceledPosixSignal(int32_t signalCode, int32_t handlersDisposed); +PALEXPORT void SystemNative_HandleNonCanceledPosixSignal(int32_t signalCode); typedef void (*ConsoleSigTtouHandler)(void); diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.PlatformNotSupported.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.PlatformNotSupported.cs index 3ccc169b1a84..e726194c0c0e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.PlatformNotSupported.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.PlatformNotSupported.cs @@ -10,16 +10,9 @@ public sealed partial class PosixSignalRegistration private PosixSignalRegistration() { } [DynamicDependency("#ctor")] // Prevent the private ctor and the IDisposable implementation from getting linked away - public static partial PosixSignalRegistration Create(PosixSignal signal, Action handler) - { - if (handler is null) - { - throw new ArgumentNullException(nameof(handler)); - } - + private static PosixSignalRegistration Register(PosixSignal signal, Action handler) => throw new PlatformNotSupportedException(); - } - public partial void Dispose() { } + private void Unregister() { } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs index f01b2de01303..d91dd1c3a8dc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs @@ -2,265 +2,138 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; using System.Threading; namespace System.Runtime.InteropServices { public sealed partial class PosixSignalRegistration { - private static volatile bool s_initialized; - private static readonly Dictionary?>> s_registrations = new(); + private static readonly Dictionary> s_registrations = Initialize(); - private readonly Action _handler; - private readonly PosixSignal _signal; - private readonly int _signo; - private bool _registered; - private readonly object _gate = new object(); - - private PosixSignalRegistration(PosixSignal signal, int signo, Action handler) - { - _signal = signal; - _signo = signo; - _handler = handler; - } - - public static partial PosixSignalRegistration Create(PosixSignal signal, Action handler) + private static unsafe Dictionary> Initialize() { - if (handler == null) + if (!Interop.Sys.InitializeTerminalAndSignalHandling()) { - throw new ArgumentNullException(nameof(handler)); + Interop.CheckIo(-1); } - int signo = Interop.Sys.GetPlatformSignalNumber(signal); - if (signo == 0) - { - throw new PlatformNotSupportedException(); - } + Interop.Sys.SetPosixSignalHandler(&OnPosixSignal); - PosixSignalRegistration registration = new PosixSignalRegistration(signal, signo, handler); - registration.Register(); - return registration; + return new Dictionary>(); } - private unsafe void Register() + private static PosixSignalRegistration Register(PosixSignal signal, Action handler) { - if (!s_initialized) + int signo = Interop.Sys.GetPlatformSignalNumber(signal); + if (signo == 0) { - if (!Interop.Sys.InitializeTerminalAndSignalHandling()) - { - // We can't use Win32Exception because that causes a cycle with - // Microsoft.Win32.Primitives. - Interop.CheckIo(-1); - } - - Interop.Sys.SetPosixSignalHandler(&OnPosixSignal); - s_initialized = true; + throw new PlatformNotSupportedException(); } + var token = new Token(signal, signo, handler); + var registration = new PosixSignalRegistration(token); + lock (s_registrations) { - if (!s_registrations.TryGetValue(_signo, out List?>? signalRegistrations)) + if (!s_registrations.TryGetValue(signo, out HashSet? tokens)) { - signalRegistrations = new List?>(); - s_registrations.Add(_signo, signalRegistrations); + s_registrations[signo] = tokens = new HashSet(); } - if (signalRegistrations.Count == 0) + if (tokens.Count == 0 && + !Interop.Sys.EnablePosixSignalHandling(signo)) { - if (!Interop.Sys.EnablePosixSignalHandling(_signo)) - { - // We can't use Win32Exception because that causes a cycle with - // Microsoft.Win32.Primitives. - Interop.CheckIo(-1); - } + Interop.CheckIo(-1); } - signalRegistrations.Add(new WeakReference(this)); + tokens.Add(token); } - _registered = true; + return registration; } - private bool CallHandler(PosixSignalContext context) + private void Unregister() { - lock (_gate) + lock (s_registrations) { - if (_registered) + if (_token is Token token) { - _handler(context); - return true; - } + _token = null; - return false; + if (s_registrations.TryGetValue(token.SigNo, out HashSet? tokens)) + { + tokens.Remove(token); + if (tokens.Count == 0) + { + s_registrations.Remove(token.SigNo); + Interop.Sys.DisablePosixSignalHandling(token.SigNo); + } + } + } } } [UnmanagedCallersOnly] private static int OnPosixSignal(int signo, PosixSignal signal) { - PosixSignalRegistration?[]? registrations = GetRegistrations(signo); - if (registrations != null) - { - // This is called on the native signal handling thread. We need to move to another thread so - // signal handling is not blocked. Otherwise we may get deadlocked when the handler depends - // on work triggered from the signal handling thread. - - // For terminate/interrupt signals we use a dedicated Thread - // in case the ThreadPool is saturated. - bool useDedicatedThread = signal == PosixSignal.SIGINT || - signal == PosixSignal.SIGQUIT || - signal == PosixSignal.SIGTERM; + Token[]? tokens = null; - if (useDedicatedThread) - { - Thread handlerThread = new Thread(HandleSignal) - { - IsBackground = true, - Name = ".NET Signal Handler" - }; - handlerThread.UnsafeStart((signo, registrations)); - } - else + lock (s_registrations) + { + if (s_registrations.TryGetValue(signo, out HashSet? registrations)) { - ThreadPool.UnsafeQueueUserWorkItem(HandleSignal, (signo, registrations)); + tokens = new Token[registrations.Count]; + registrations.CopyTo(tokens); } - - return 1; } - return 0; - } - - private static PosixSignalRegistration?[]? GetRegistrations(int signo) - { - lock (s_registrations) + if (tokens is null) { - if (s_registrations.TryGetValue(signo, out List?>? signalRegistrations)) - { - if (signalRegistrations.Count != 0) - { - var registrations = new PosixSignalRegistration?[signalRegistrations.Count]; - bool hasRegistrations = false; - bool pruneWeakReferences = false; - - for (int i = 0; i < signalRegistrations.Count; i++) - { - if (signalRegistrations[i]!.TryGetTarget(out PosixSignalRegistration? registration)) - { - registrations[i] = registration; - hasRegistrations = true; - } - else - { - // WeakReference no longer holds an object. PosixSignalRegistration got finalized. - signalRegistrations[i] = null; - pruneWeakReferences = true; - } - } - - if (pruneWeakReferences) - { - signalRegistrations.RemoveAll(item => item is null); - } - - if (hasRegistrations) - { - return registrations; - } - else - { - Interop.Sys.DisablePosixSignalHandling(signo); - } - } - } - return null; + return 0; } - } - private static void HandleSignal(object? state) - { - HandleSignal(((int, PosixSignalRegistration?[]))state!); - } + Debug.Assert(tokens.Length != 0); - private static void HandleSignal((int signo, PosixSignalRegistration?[]? registrations) state) - { - do + // This is called on the native signal handling thread. We need to move to another thread so + // signal handling is not blocked. Otherwise we may get deadlocked when the handler depends + // on work triggered from the signal handling thread. + switch (signal) { - bool handlersCalled = false; - if (state.registrations != null) - { - PosixSignalContext ctx = new(0); - foreach (PosixSignalRegistration? registration in state.registrations) - { - if (registration != null) - { - // Different values for PosixSignal map to the same signo. - // Match the PosixSignal value used when registering. - ctx.Signal = registration._signal; - if (registration.CallHandler(ctx)) - { - handlersCalled = true; - } - } - } - - if (ctx.Cancel) + case PosixSignal.SIGINT: + case PosixSignal.SIGQUIT: + case PosixSignal.SIGTERM: + // For terminate/interrupt signals we use a dedicated Thread in case the ThreadPool is saturated. + new Thread(HandleSignal) { - return; - } - } + IsBackground = true, + Name = ".NET Signal Handler" + }.UnsafeStart((signo, tokens)); + break; - if (Interop.Sys.HandleNonCanceledPosixSignal(state.signo, handlersCalled ? 0 : 1)) - { - return; - } + default: + ThreadPool.UnsafeQueueUserWorkItem(HandleSignal, (signo, tokens)); + break; + } - // HandleNonCanceledPosixSignal returns false when handlers got registered. - state.registrations = GetRegistrations(state.signo); - } while (true); - } + return 1; - public partial void Dispose() - { - if (_registered) + static void HandleSignal(object? state) { - lock (s_registrations) - { - List?> signalRegistrations = s_registrations[_signo]; - bool pruneWeakReferences = false; - for (int i = 0; i < signalRegistrations.Count; i++) - { - if (signalRegistrations[i]!.TryGetTarget(out PosixSignalRegistration? registration)) - { - if (ReferenceEquals(this, registration)) - { - signalRegistrations.RemoveAt(i); - break; - } - } - else - { - // WeakReference no longer holds an object. PosixSignalRegistration got finalized. - signalRegistrations[i] = null; - pruneWeakReferences = true; - } - } + (int signo, Token[] tokens) = ((int, Token[]))state!; - if (pruneWeakReferences) - { - signalRegistrations.RemoveAll(item => item is null); - } - - if (signalRegistrations.Count == 0) - { - Interop.Sys.DisablePosixSignalHandling(_signo); - } + PosixSignalContext ctx = new(0); + foreach (Token token in tokens) + { + // Different values for PosixSignal map to the same signo. + // Match the PosixSignal value used when registering. + ctx.Signal = token.Signal; + token.Handler(ctx); } - // Synchronize with _handler invocations. - lock (_gate) + if (!ctx.Cancel) { - _registered = false; + Interop.Sys.HandleNonCanceledPosixSignal(signo); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Windows.cs index d3e5405053e5..a50fca2bbee9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Windows.cs @@ -6,59 +6,51 @@ namespace System.Runtime.InteropServices { - public sealed unsafe partial class PosixSignalRegistration + public sealed partial class PosixSignalRegistration { - private static readonly HashSet s_handlers = new(); + private static readonly HashSet s_registrations = new(); - private Token? _token; - - private PosixSignalRegistration(Token token) => _token = token; - - private static object SyncObj => s_handlers; - - public static partial PosixSignalRegistration Create(PosixSignal signal, Action handler) + private static unsafe PosixSignalRegistration Register(PosixSignal signal, Action handler) { - if (handler is null) + switch (signal) { - throw new ArgumentNullException(nameof(handler)); + case PosixSignal.SIGINT: + case PosixSignal.SIGQUIT: + case PosixSignal.SIGTERM: + case PosixSignal.SIGHUP: + break; + + default: + throw new PlatformNotSupportedException(); } - lock (SyncObj) - { - switch (signal) - { - case PosixSignal.SIGINT: - case PosixSignal.SIGQUIT: - case PosixSignal.SIGTERM: - case PosixSignal.SIGHUP: - break; - - default: - throw new PlatformNotSupportedException(); - } + var token = new Token(signal, handler); + var registration = new PosixSignalRegistration(token); - if (s_handlers.Count == 0 && + lock (s_registrations) + { + if (s_registrations.Count == 0 && !Interop.Kernel32.SetConsoleCtrlHandler(&HandlerRoutine, Add: true)) { throw Win32Marshal.GetExceptionForLastWin32Error(); } - var token = new Token(signal, handler); - s_handlers.Add(token); - return new PosixSignalRegistration(token); + s_registrations.Add(token); } + + return registration; } - public partial void Dispose() + private unsafe void Unregister() { - lock (SyncObj) + lock (s_registrations) { if (_token is Token token) { _token = null; - s_handlers.Remove(token); - if (s_handlers.Count == 0 && + s_registrations.Remove(token); + if (s_registrations.Count == 0 && !Interop.Kernel32.SetConsoleCtrlHandler(&HandlerRoutine, Add: false)) { throw Win32Marshal.GetExceptionForLastWin32Error(); @@ -94,9 +86,9 @@ private static Interop.BOOL HandlerRoutine(int dwCtrlType) } List? tokens = null; - lock (SyncObj) + lock (s_registrations) { - foreach (Token token in s_handlers) + foreach (Token token in s_registrations) { if (token.Signal == signal) { @@ -118,17 +110,5 @@ private static Interop.BOOL HandlerRoutine(int dwCtrlType) return context.Cancel ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; } - - private sealed class Token - { - public Token(PosixSignal signal, Action handler) - { - Signal = signal; - Handler = handler; - } - - public PosixSignal Signal { get; } - public Action Handler { get; } - } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.cs index fb2c675acb8b..811d75ddd518 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.cs @@ -9,6 +9,13 @@ namespace System.Runtime.InteropServices /// Handles a . public sealed partial class PosixSignalRegistration : IDisposable { + /// The state associated with this registration. + /// + /// This is separate from the registration instance so that this token may be stored + /// in a statically rooted table, with a finalizer on the registration able to remove it. + /// + private Token? _token; + /// Registers a that is invoked when the occurs. /// The signal to register for. /// The handler that gets invoked. @@ -28,11 +35,48 @@ public sealed partial class PosixSignalRegistration : IDisposable [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("maccatalyst")] [UnsupportedOSPlatform("tvos")] - public static partial PosixSignalRegistration Create(PosixSignal signal, Action handler); + public static PosixSignalRegistration Create(PosixSignal signal, Action handler) + { + if (handler is null) + { + throw new ArgumentNullException(nameof(handler)); + } + + return Register(signal, handler); + } + + /// Initializes the registration to wrap the specified token. + private PosixSignalRegistration(Token token) => _token = token; /// Unregister the handler. - public partial void Dispose(); + public void Dispose() + { + Unregister(); + GC.SuppressFinalize(this); + } + + /// Unregister the handler. + ~PosixSignalRegistration() => Unregister(); + + /// The state associated with a registration. + private sealed class Token + { + public Token(PosixSignal signal, Action handler) + { + Signal = signal; + Handler = handler; + } + + public Token(PosixSignal signal, int sigNo, Action handler) + { + Signal = signal; + Handler = handler; + SigNo = sigNo; + } - ~PosixSignalRegistration() => Dispose(); + public PosixSignal Signal { get; } + public Action Handler { get; } + public int SigNo { get; } + } } } From f82b730ddf4d6170c3e63fb12a52efe72629822f Mon Sep 17 00:00:00 2001 From: hrrrrustic <35951936+hrrrrustic@users.noreply.github.com> Date: Tue, 13 Jul 2021 22:53:12 +0300 Subject: [PATCH 090/133] Remove EventSourceActivity (#55575) * remove file * remove a few references to deleted file * fix corelib --- ...gnostics.Tracing.EventSource.Redist.csproj | 1 - .../System.Private.CoreLib.Shared.projitems | 1 - .../TraceLogging/EventSourceActivity.cs | 313 ------------------ 3 files changed, 315 deletions(-) delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs diff --git a/src/libraries/Microsoft.Diagnostics.Tracing.EventSource.Redist/src/Microsoft.Diagnostics.Tracing.EventSource.Redist.csproj b/src/libraries/Microsoft.Diagnostics.Tracing.EventSource.Redist/src/Microsoft.Diagnostics.Tracing.EventSource.Redist.csproj index fc47c82f88ab..36aaba499cb1 100644 --- a/src/libraries/Microsoft.Diagnostics.Tracing.EventSource.Redist/src/Microsoft.Diagnostics.Tracing.EventSource.Redist.csproj +++ b/src/libraries/Microsoft.Diagnostics.Tracing.EventSource.Redist/src/Microsoft.Diagnostics.Tracing.EventSource.Redist.csproj @@ -52,7 +52,6 @@ - diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 4be53fea1973..6c1182acbd19 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1287,7 +1287,6 @@ - diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs deleted file mode 100644 index bfd8d0ff989d..000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs +++ /dev/null @@ -1,313 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#if ES_BUILD_STANDALONE -using System; -using System.Diagnostics; -#endif - -#if ES_BUILD_STANDALONE -namespace Microsoft.Diagnostics.Tracing -#else -namespace System.Diagnostics.Tracing -#endif -{ - /// - /// Provides support for EventSource activities by marking the start and - /// end of a particular operation. - /// - internal sealed class EventSourceActivity - : IDisposable - { - /// - /// Initializes a new instance of the EventSourceActivity class that - /// is attached to the specified event source. The new activity will - /// not be attached to any related (parent) activity. - /// The activity is created in the Initialized state. - /// - /// - /// The event source to which the activity information is written. - /// - public EventSourceActivity(EventSource eventSource) - { - if (eventSource == null) - throw new ArgumentNullException(nameof(eventSource)); - - this.eventSource = eventSource; - } - - /// - /// You can make an activity out of just an EventSource. - /// - public static implicit operator EventSourceActivity(EventSource eventSource) => - new EventSourceActivity(eventSource); - - /* Properties */ - /// - /// Gets the event source to which this activity writes events. - /// - public EventSource EventSource => this.eventSource; - - /// - /// Gets this activity's unique identifier, or the default Guid if the - /// event source was disabled when the activity was initialized. - /// - public Guid Id => this.activityId; - -#if false // don't expose RelatedActivityId unless there is a need. - /// - /// Gets the unique identifier of this activity's related (parent) - /// activity. - /// - public Guid RelatedId - { - get { return this.relatedActivityId; } - } -#endif - - /// - /// Writes a Start event with the specified name and data. If the start event is not active (because the provider - /// is not on or keyword-level indicates the event is off, then the returned activity is simply the 'this' pointer - /// and it is effectively like start did not get called. - /// - /// A new activityID GUID is generated and the returned - /// EventSourceActivity remembers this activity and will mark every event (including the start stop and any writes) - /// with this activityID. In addition the Start activity will log a 'relatedActivityID' that was the activity - /// ID before the start event. This way event processors can form a linked list of all the activities that - /// caused this one (directly or indirectly). - /// - /// - /// The name to use for the event. It is strongly suggested that this name end in 'Start' (e.g. DownloadStart). - /// If you do this, then the Stop() method will automatically replace the 'Start' suffix with a 'Stop' suffix. - /// - /// Allow options (keywords, level) to be set for the write associated with this start - /// These will also be used for the stop event. - /// The data to include in the event. - public EventSourceActivity Start(string? eventName, EventSourceOptions options, T data) - { - return this.Start(eventName, ref options, ref data); - } - /// - /// Shortcut version see Start(string eventName, EventSourceOptions options, T data) Options is empty (no keywords - /// and level==Info) Data payload is empty. - /// - public EventSourceActivity Start(string? eventName) - { - EventSourceOptions options = default; - EmptyStruct data = default; - return this.Start(eventName, ref options, ref data); - } - /// - /// Shortcut version see Start(string eventName, EventSourceOptions options, T data). Data payload is empty. - /// - public EventSourceActivity Start(string? eventName, EventSourceOptions options) - { - EmptyStruct data = default; - return this.Start(eventName, ref options, ref data); - } - /// - /// Shortcut version see Start(string eventName, EventSourceOptions options, T data) Options is empty (no keywords - /// and level==Info) - /// - public EventSourceActivity Start(string? eventName, T data) - { - EventSourceOptions options = default; - return this.Start(eventName, ref options, ref data); - } - - /// - /// Writes a Stop event with the specified data, and sets the activity - /// to the Stopped state. The name is determined by the eventName used in Start. - /// If that Start event name is suffixed with 'Start' that is removed, and regardless - /// 'Stop' is appended to the result to form the Stop event name. - /// May only be called when the activity is in the Started state. - /// - /// The data to include in the event. - public void Stop(T data) - { - this.Stop(null, ref data); - } - /// - /// Used if you wish to use the non-default stop name (which is the start name with Start replace with 'Stop') - /// This can be useful to indicate unusual ways of stopping (but it is still STRONGLY recommended that - /// you start with the same prefix used for the start event and you end with the 'Stop' suffix. - /// - public void Stop(string? eventName) - { - EmptyStruct data = default; - this.Stop(eventName, ref data); - } - /// - /// Used if you wish to use the non-default stop name (which is the start name with Start replace with 'Stop') - /// This can be useful to indicate unusual ways of stopping (but it is still STRONGLY recommended that - /// you start with the same prefix used for the start event and you end with the 'Stop' suffix. - /// - public void Stop(string? eventName, T data) - { - this.Stop(eventName, ref data); - } - - /// - /// Writes an event associated with this activity to the eventSource associated with this activity. - /// May only be called when the activity is in the Started state. - /// - /// - /// The name to use for the event. If null, the name is determined from - /// data's type. - /// - /// - /// The options to use for the event. - /// - /// The data to include in the event. - public void Write(string? eventName, EventSourceOptions options, T data) - { - this.Write(this.eventSource, eventName, ref options, ref data); - } - /// - /// Writes an event associated with this activity. - /// May only be called when the activity is in the Started state. - /// - /// - /// The name to use for the event. If null, the name is determined from - /// data's type. - /// - /// The data to include in the event. - public void Write(string? eventName, T data) - { - EventSourceOptions options = default; - this.Write(this.eventSource, eventName, ref options, ref data); - } - /// - /// Writes a trivial event associated with this activity. - /// May only be called when the activity is in the Started state. - /// - /// - /// The name to use for the event. Must not be null. - /// - /// - /// The options to use for the event. - /// - public void Write(string? eventName, EventSourceOptions options) - { - EmptyStruct data = default; - this.Write(this.eventSource, eventName, ref options, ref data); - } - /// - /// Writes a trivial event associated with this activity. - /// May only be called when the activity is in the Started state. - /// - /// - /// The name to use for the event. Must not be null. - /// - public void Write(string? eventName) - { - EventSourceOptions options = default; - EmptyStruct data = default; - this.Write(this.eventSource, eventName, ref options, ref data); - } - /// - /// Writes an event to a arbitrary eventSource stamped with the activity ID of this activity. - /// - public void Write(EventSource source, string? eventName, EventSourceOptions options, T data) - { - this.Write(source, eventName, ref options, ref data); - } - - /// - /// Releases any unmanaged resources associated with this object. - /// If the activity is in the Started state, calls Stop(). - /// - public void Dispose() - { - if (this.state == State.Started) - { - EmptyStruct data = default; - this.Stop(null, ref data); - } - } - -#region private - private EventSourceActivity Start(string? eventName, ref EventSourceOptions options, ref T data) - { - if (this.state != State.Started) - throw new InvalidOperationException(); - - // If the source is not on at all, then we don't need to do anything and we can simply return ourselves. - if (!this.eventSource.IsEnabled()) - return this; - - var newActivity = new EventSourceActivity(eventSource); - if (!this.eventSource.IsEnabled(options.Level, options.Keywords)) - { - // newActivity.relatedActivityId = this.Id; - Guid relatedActivityId = this.Id; - newActivity.activityId = Guid.NewGuid(); - newActivity.startStopOptions = options; - newActivity.eventName = eventName; - newActivity.startStopOptions.Opcode = EventOpcode.Start; - this.eventSource.Write(eventName, ref newActivity.startStopOptions, ref newActivity.activityId, ref relatedActivityId, ref data); - } - else - { - // If we are not active, we don't set the eventName, which basically also turns off the Stop event as well. - newActivity.activityId = this.Id; - } - - return newActivity; - } - - private void Write(EventSource eventSource, string? eventName, ref EventSourceOptions options, ref T data) - { - if (this.state != State.Started) - throw new InvalidOperationException(); // Write after stop. - if (eventName == null) - throw new ArgumentNullException(); - - eventSource.Write(eventName, ref options, ref this.activityId, ref s_empty, ref data); - } - - private void Stop(string? eventName, ref T data) - { - if (this.state != State.Started) - throw new InvalidOperationException(); - - // If start was not fired, then stop isn't as well. - if (!StartEventWasFired) - return; - - Debug.Assert(this.eventName != null); - - this.state = State.Stopped; - if (eventName == null) - { - eventName = this.eventName; - if (eventName.EndsWith("Start", StringComparison.Ordinal)) - eventName = eventName.Substring(0, eventName.Length - 5); - eventName += "Stop"; - } - this.startStopOptions.Opcode = EventOpcode.Stop; - this.eventSource.Write(eventName, ref this.startStopOptions, ref this.activityId, ref s_empty, ref data); - } - - private enum State - { - Started, - Stopped - } - - /// - /// If eventName is non-null then we logged a start event - /// - private bool StartEventWasFired => eventName != null; - - private readonly EventSource eventSource; - private EventSourceOptions startStopOptions; - internal Guid activityId; - // internal Guid relatedActivityId; - private State state; - private string? eventName; - - internal static Guid s_empty; -#endregion - } -} From ae64899ca74968c1c0dd2d81564c1b9fab12e2b4 Mon Sep 17 00:00:00 2001 From: Mateo Torres-Ruiz Date: Tue, 13 Jul 2021 13:16:29 -0700 Subject: [PATCH 091/133] Add runtime property HOSTFXR_PATH (#55369) * Add property HOSTFXR_PATH * Comment * PR feedback * Use get_own_module_path since we are on hostfxr * Dispose FileStream --- .../TestProjects/RuntimeProperties/Program.cs | 9 ++- .../HostActivation.Tests/DotNetBuilder.cs | 16 +++++ .../HostActivation.Tests/RuntimeProperties.cs | 58 +++++++++++++++---- src/native/corehost/fxr/corehost_init.cpp | 9 ++- src/native/corehost/fxr/corehost_init.h | 3 +- src/native/corehost/fxr/fx_muxer.cpp | 23 +++++++- src/native/corehost/fxr/fx_muxer.h | 1 + 7 files changed, 102 insertions(+), 17 deletions(-) diff --git a/src/installer/tests/Assets/TestProjects/RuntimeProperties/Program.cs b/src/installer/tests/Assets/TestProjects/RuntimeProperties/Program.cs index d22677aa617d..96fa3b2adbad 100644 --- a/src/installer/tests/Assets/TestProjects/RuntimeProperties/Program.cs +++ b/src/installer/tests/Assets/TestProjects/RuntimeProperties/Program.cs @@ -14,7 +14,14 @@ public static void Main(string[] args) foreach (string propertyName in args) { - Console.WriteLine($"AppContext.GetData({propertyName}) = {System.AppContext.GetData(propertyName)}"); + var propertyValue = (string)System.AppContext.GetData(propertyName); + if (string.IsNullOrEmpty(propertyValue)) + { + Console.WriteLine($"Property '{propertyName}' was not found."); + continue; + } + + Console.WriteLine($"AppContext.GetData({propertyName}) = {propertyValue}"); } } } diff --git a/src/installer/tests/HostActivation.Tests/DotNetBuilder.cs b/src/installer/tests/HostActivation.Tests/DotNetBuilder.cs index 2a99ab5681f7..0a8d54559e22 100644 --- a/src/installer/tests/HostActivation.Tests/DotNetBuilder.cs +++ b/src/installer/tests/HostActivation.Tests/DotNetBuilder.cs @@ -174,6 +174,22 @@ public DotNetBuilder AddFramework( return this; } + public DotNetBuilder AddMockSDK( + string version, + string MNAVersion) + { + string path = Path.Combine(_path, "sdk", version); + Directory.CreateDirectory(path); + + using var _ = File.Create(Path.Combine(path, "dotnet.dll")); + + RuntimeConfig dotnetRuntimeConfig = new RuntimeConfig(Path.Combine(path, "dotnet.runtimeconfig.json")); + dotnetRuntimeConfig.WithFramework(new RuntimeConfig.Framework("Microsoft.NETCore.App", MNAVersion)); + dotnetRuntimeConfig.Save(); + + return this; + } + public DotNetCli Build() { return new DotNetCli(_path); diff --git a/src/installer/tests/HostActivation.Tests/RuntimeProperties.cs b/src/installer/tests/HostActivation.Tests/RuntimeProperties.cs index 6d8949c4132a..01b5b415ecd2 100644 --- a/src/installer/tests/HostActivation.Tests/RuntimeProperties.cs +++ b/src/installer/tests/HostActivation.Tests/RuntimeProperties.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using Microsoft.DotNet.Cli.Build; using Xunit; namespace Microsoft.DotNet.CoreSetup.Test.HostActivation @@ -25,9 +26,7 @@ public void AppConfigProperty_AppCanGetData() var dotnet = fixture.BuiltDotnet; var appDll = fixture.TestProject.AppDll; dotnet.Exec(appDll, sharedState.AppTestPropertyName) - .EnvironmentVariable("COREHOST_TRACE", "1") - .CaptureStdErr() - .CaptureStdOut() + .EnableTracingAndCaptureOutputs() .Execute() .Should().Pass() .And.HaveStdErrContaining($"Property {sharedState.AppTestPropertyName} = {sharedState.AppTestPropertyValue}") @@ -43,9 +42,7 @@ public void FrameworkConfigProperty_AppCanGetData() var dotnet = fixture.BuiltDotnet; var appDll = fixture.TestProject.AppDll; dotnet.Exec(appDll, sharedState.FrameworkTestPropertyName) - .EnvironmentVariable("COREHOST_TRACE", "1") - .CaptureStdErr() - .CaptureStdOut() + .EnableTracingAndCaptureOutputs() .Execute() .Should().Pass() .And.HaveStdErrContaining($"Property {sharedState.FrameworkTestPropertyName} = {sharedState.FrameworkTestPropertyValue}") @@ -65,15 +62,39 @@ public void DuplicateConfigProperty_AppConfigValueUsed() var dotnet = fixture.BuiltDotnet; var appDll = fixture.TestProject.AppDll; dotnet.Exec(appDll, sharedState.FrameworkTestPropertyName) - .EnvironmentVariable("COREHOST_TRACE", "1") - .CaptureStdErr() - .CaptureStdOut() + .EnableTracingAndCaptureOutputs() .Execute() .Should().Pass() .And.HaveStdErrContaining($"Property {sharedState.FrameworkTestPropertyName} = {sharedState.AppTestPropertyValue}") .And.HaveStdOutContaining($"AppContext.GetData({sharedState.FrameworkTestPropertyName}) = {sharedState.AppTestPropertyValue}"); } + [Fact] + public void HostFxrPathProperty_SetWhenRunningSDKCommand() + { + var dotnet = sharedState.MockSDK; + dotnet.Exec("--info") + .EnableTracingAndCaptureOutputs() + .Execute() + .Should().Pass() + .And.HaveStdErrContaining($"Property {sharedState.HostFxrPathPropertyName} = {dotnet.GreatestVersionHostFxrFilePath}"); + } + + [Fact] + public void HostFxrPathProperty_NotVisibleFromApp() + { + var fixture = sharedState.RuntimePropertiesFixture + .Copy(); + + var dotnet = fixture.BuiltDotnet; + var appDll = fixture.TestProject.AppDll; + dotnet.Exec(appDll, sharedState.HostFxrPathPropertyName) + .EnableTracingAndCaptureOutputs() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining($"Property '{sharedState.HostFxrPathPropertyName}' was not found."); + } + [Fact] public void DuplicateCommonProperty_Fails() { @@ -88,9 +109,7 @@ public void DuplicateCommonProperty_Fails() var dotnet = fixture.BuiltDotnet; var appDll = fixture.TestProject.AppDll; dotnet.Exec(appDll) - .EnvironmentVariable("COREHOST_TRACE", "1") - .CaptureStdErr() - .CaptureStdOut() + .EnableTracingAndCaptureOutputs() .Execute() .Should().Fail() .And.HaveStdErrContaining($"Duplicate runtime property found: {name}"); @@ -100,11 +119,13 @@ public class SharedTestState : IDisposable { public TestProjectFixture RuntimePropertiesFixture { get; } public RepoDirectoriesProvider RepoDirectories { get; } + public DotNetCli MockSDK { get; } public string AppTestPropertyName => "APP_TEST_PROPERTY"; public string AppTestPropertyValue => "VALUE_FROM_APP"; public string FrameworkTestPropertyName => "FRAMEWORK_TEST_PROPERTY"; public string FrameworkTestPropertyValue => "VALUE_FROM_FRAMEWORK"; + public string HostFxrPathPropertyName => "HOSTFXR_PATH"; private readonly string copiedDotnet; @@ -113,6 +134,19 @@ public SharedTestState() copiedDotnet = Path.Combine(TestArtifact.TestArtifactsPath, "runtimeProperties"); SharedFramework.CopyDirectory(Path.Combine(TestArtifact.TestArtifactsPath, "sharedFrameworkPublish"), copiedDotnet); + MockSDK = new DotNetBuilder(copiedDotnet, Path.Combine(TestArtifact.TestArtifactsPath, "sharedFrameworkPublish"), "exe") + .AddMicrosoftNETCoreAppFrameworkMockCoreClr("9999.0.0") + .AddMockSDK("9999.0.0-dev", "9999.0.0") + .Build(); + + File.WriteAllText(Path.Combine(MockSDK.BinPath, "global.json"), + @" +{ + ""sdk"": { + ""version"": ""9999.0.0-dev"" + } +}"); + RepoDirectories = new RepoDirectoriesProvider(builtDotnet: copiedDotnet); RuntimePropertiesFixture = new TestProjectFixture("RuntimeProperties", RepoDirectories) diff --git a/src/native/corehost/fxr/corehost_init.cpp b/src/native/corehost/fxr/corehost_init.cpp index eea9b768f5f9..2d169348fa63 100644 --- a/src/native/corehost/fxr/corehost_init.cpp +++ b/src/native/corehost/fxr/corehost_init.cpp @@ -20,7 +20,8 @@ corehost_init_t::corehost_init_t( const pal::string_t& additional_deps_serialized, const std::vector& probe_paths, const host_mode_t mode, - const fx_definition_vector_t& fx_definitions) + const fx_definition_vector_t& fx_definitions, + const std::vector>& additional_properties) : m_tfm(get_app(fx_definitions).get_runtime_config().get_tfm()) , m_deps_file(deps_file) , m_additional_deps_serialized(additional_deps_serialized) @@ -35,6 +36,12 @@ corehost_init_t::corehost_init_t( { make_cstr_arr(m_probe_paths, &m_probe_paths_cstr); + for (const auto& additional_property : additional_properties) + { + m_clr_keys.push_back(additional_property.first); + m_clr_values.push_back(additional_property.second); + } + size_t fx_count = fx_definitions.size(); m_fx_names.reserve(fx_count); m_fx_dirs.reserve(fx_count); diff --git a/src/native/corehost/fxr/corehost_init.h b/src/native/corehost/fxr/corehost_init.h index 70ea7be46fbf..6c3148b62749 100644 --- a/src/native/corehost/fxr/corehost_init.h +++ b/src/native/corehost/fxr/corehost_init.h @@ -45,7 +45,8 @@ class corehost_init_t const pal::string_t& additional_deps_serialized, const std::vector& probe_paths, const host_mode_t mode, - const fx_definition_vector_t& fx_definitions); + const fx_definition_vector_t& fx_definitions, + const std::vector>& additional_properties); const host_interface_t& get_host_init_data(); diff --git a/src/native/corehost/fxr/fx_muxer.cpp b/src/native/corehost/fxr/fx_muxer.cpp index 6a20206e2cad..8647130c18dc 100644 --- a/src/native/corehost/fxr/fx_muxer.cpp +++ b/src/native/corehost/fxr/fx_muxer.cpp @@ -370,6 +370,7 @@ namespace const pal::string_t &app_candidate, const opt_map_t &opts, host_mode_t mode, + const bool is_sdk_command, /*out*/ pal::string_t &hostpolicy_dir, /*out*/ std::unique_ptr &init) { @@ -473,6 +474,16 @@ namespace } } + std::vector> additional_properties; + if (is_sdk_command) + { + pal::string_t fxr_path; + pal::get_own_module_path(&fxr_path); + + // We pass the loaded hostfxr path to the SDK can load it without relying on dlopen/LoadLibrary to find it. + additional_properties.push_back(std::make_pair(_X("HOSTFXR_PATH"), fxr_path)); + } + const known_options opts_probe_path = known_options::additional_probing_path; std::vector spec_probe_paths = opts.count(opts_probe_path) ? opts.find(opts_probe_path)->second : std::vector(); std::vector probe_realpaths = get_probe_realpaths(fx_definitions, spec_probe_paths); @@ -485,7 +496,7 @@ namespace return StatusCode::CoreHostLibMissingFailure; } - init.reset(new corehost_init_t(host_command, host_info, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions)); + init.reset(new corehost_init_t(host_command, host_info, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions, additional_properties)); return StatusCode::Success; } @@ -498,6 +509,7 @@ namespace int new_argc, const pal::char_t** new_argv, host_mode_t mode, + const bool is_sdk_command, pal::char_t out_buffer[], int32_t buffer_size, int32_t* required_buffer_size) @@ -510,6 +522,7 @@ namespace app_candidate, opts, mode, + is_sdk_command, hostpolicy_dir, init); if (rc != StatusCode::Success) @@ -572,6 +585,7 @@ int fx_muxer_t::execute( argv, new_argoff, mode, + false /*is_sdk_command*/, result_buffer, buffer_size, required_buffer_size); @@ -621,7 +635,8 @@ namespace } const pal::string_t additional_deps_serialized; - init.reset(new corehost_init_t(pal::string_t{}, host_info, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions)); + const std::vector> additional_properties; + init.reset(new corehost_init_t(pal::string_t{}, host_info, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions, additional_properties)); return StatusCode::Success; } @@ -725,6 +740,7 @@ int fx_muxer_t::initialize_for_app( host_info.app_path, opts, mode, + false /*is_sdk_command*/, hostpolicy_dir, init); if (rc != StatusCode::Success) @@ -978,6 +994,7 @@ int fx_muxer_t::handle_exec_host_command( const pal::char_t* argv[], int argoff, host_mode_t mode, + const bool is_sdk_command, pal::char_t result_buffer[], int32_t buffer_size, int32_t* required_buffer_size) @@ -1006,6 +1023,7 @@ int fx_muxer_t::handle_exec_host_command( new_argc, new_argv, mode, + is_sdk_command, result_buffer, buffer_size, required_buffer_size); @@ -1096,6 +1114,7 @@ int fx_muxer_t::handle_cli( new_argv.data(), new_argoff, host_mode_t::muxer, + true /*is_sdk_command*/, nullptr /*result_buffer*/, 0 /*buffer_size*/, nullptr/*required_buffer_size*/); diff --git a/src/native/corehost/fxr/fx_muxer.h b/src/native/corehost/fxr/fx_muxer.h index 6794e2a1c825..b3a0e0004700 100644 --- a/src/native/corehost/fxr/fx_muxer.h +++ b/src/native/corehost/fxr/fx_muxer.h @@ -47,6 +47,7 @@ class fx_muxer_t const pal::char_t* argv[], int argoff, host_mode_t mode, + const bool is_sdk_command, pal::char_t result_buffer[], int32_t buffer_size, int32_t* required_buffer_size); From a13f713a25e5d2890f28e2394b508b4cc5fd909b Mon Sep 17 00:00:00 2001 From: Natalia Kondratyeva Date: Tue, 13 Jul 2021 22:38:24 +0200 Subject: [PATCH 092/133] [QUIC] Move ByteMixingOrNativeAVE_MinimalFailingTest to OuterLoop (#55595) Fixes #55588 --- .../System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs index 6ce1540fba59..884c18947568 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs @@ -453,6 +453,7 @@ public BufferSegment Append(ReadOnlyMemory memory) } [Fact] + [OuterLoop("May take several seconds")] public async Task ByteMixingOrNativeAVE_MinimalFailingTest() { const int writeSize = 64 * 1024; From 39152805901172400f1aad2ce511719663aa4e52 Mon Sep 17 00:00:00 2001 From: Ulrich Weigand Date: Tue, 13 Jul 2021 22:42:47 +0200 Subject: [PATCH 093/133] [mono] Fix loader incompatibility w.r.t. Resolving event (#54815) * When loading a non-satellite assembly into a non-default ALC, invoke the Resolving event in the default ALC first. * Fixes https://github.com/dotnet/runtime/issues/54814 --- src/mono/mono/metadata/assembly.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/mono/mono/metadata/assembly.c b/src/mono/mono/metadata/assembly.c index e98264a2f939..8a52e63bc856 100644 --- a/src/mono/mono/metadata/assembly.c +++ b/src/mono/mono/metadata/assembly.c @@ -1061,7 +1061,8 @@ netcore_load_reference (MonoAssemblyName *aname, MonoAssemblyLoadContext *alc, M * * 7. If this is a satellite request, call the ALC ResolveSatelliteAssembly method. * - * 8. Call the ALC Resolving event. + * 8. Call the ALC Resolving event. If the ALC is not the default and this is not + * a satellite request, call the Resolving event in the default ALC first. * * 9. Call the ALC AssemblyResolve event (except for corlib satellite assemblies). * @@ -1138,6 +1139,15 @@ netcore_load_reference (MonoAssemblyName *aname, MonoAssemblyLoadContext *alc, M } } + // For compatibility with CoreCLR, invoke the Resolving event in the default ALC first whenever loading + // a non-satellite assembly into a non-default ALC. See: https://github.com/dotnet/runtime/issues/54814 + if (!is_default && !is_satellite) { + reference = mono_alc_invoke_resolve_using_resolving_event_nofail (mono_alc_get_default (), aname); + if (reference) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Assembly found with the Resolving event (default ALC): '%s'.", aname->name); + goto leave; + } + } reference = mono_alc_invoke_resolve_using_resolving_event_nofail (alc, aname); if (reference) { mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Assembly found with the Resolving event: '%s'.", aname->name); From 271eea2a1720bfbb4098bd950198c2ebb6a96637 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Wed, 14 Jul 2021 02:44:03 +0600 Subject: [PATCH 094/133] Remove IL205 warning for System.Data.Odbc (#54809) Function which produce warning not used anywhere --- .../System.Data.Odbc/src/ILLink/ILLink.Suppressions.xml | 6 ------ .../src/System/Data/Odbc/OdbcConnectionHandle.cs | 7 ------- 2 files changed, 13 deletions(-) diff --git a/src/libraries/System.Data.Odbc/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.Data.Odbc/src/ILLink/ILLink.Suppressions.xml index 6ea564dbbf68..c6e3fb619c20 100644 --- a/src/libraries/System.Data.Odbc/src/ILLink/ILLink.Suppressions.xml +++ b/src/libraries/System.Data.Odbc/src/ILLink/ILLink.Suppressions.xml @@ -1,12 +1,6 @@  - - ILLink - IL2050 - member - M:System.Data.Odbc.OdbcConnectionHandle.SetConnectionAttribute4(System.Data.Odbc.ODBC32.SQL_ATTR,System.Transactions.IDtcTransaction,System.Int32) - ILLink IL2026 diff --git a/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionHandle.cs b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionHandle.cs index b2c7ee7cb336..8339b330196f 100644 --- a/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionHandle.cs +++ b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionHandle.cs @@ -263,12 +263,5 @@ internal ODBC32.RetCode SetConnectionAttribute3(ODBC32.SQL_ATTR attribute, strin ODBC32.RetCode retcode = Interop.Odbc.SQLSetConnectAttrW(this, attribute, buffer, length); return retcode; } - - internal ODBC32.RetCode SetConnectionAttribute4(ODBC32.SQL_ATTR attribute, System.Transactions.IDtcTransaction transaction, int length) - { - ODBC32.RetCode retcode = Interop.Odbc.SQLSetConnectAttrW(this, attribute, transaction, length); - ODBC.TraceODBC(3, "SQLSetConnectAttrW", retcode); - return retcode; - } } } From a38fc2fd272313165e30f8301d4d767a5c30f659 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Wed, 14 Jul 2021 02:47:02 +0600 Subject: [PATCH 095/133] Remove IL2050 in System.Management (#54810) * Remove IL2050 in System.Management Remove unused functions which trigger IL2050 As per discussed in #54317 * Fix location of variables --- .../Ole32/Interop.CoGetObjectContext.cs | 4 +-- .../Ole32/Interop.CreateStreamOnHGlobal.cs | 4 +-- .../src/ILLink/ILLink.Suppressions.xml | 26 +------------------ .../src/System.Management.csproj | 8 ------ 4 files changed, 5 insertions(+), 37 deletions(-) diff --git a/src/libraries/Common/src/Interop/Windows/Ole32/Interop.CoGetObjectContext.cs b/src/libraries/Common/src/Interop/Windows/Ole32/Interop.CoGetObjectContext.cs index 432632703438..c73b065ea595 100644 --- a/src/libraries/Common/src/Interop/Windows/Ole32/Interop.CoGetObjectContext.cs +++ b/src/libraries/Common/src/Interop/Windows/Ole32/Interop.CoGetObjectContext.cs @@ -9,7 +9,7 @@ internal static partial class Interop { internal static partial class Ole32 { - [DllImport(Libraries.Ole32, PreserveSig = false)] - internal static extern IStream CreateStreamOnHGlobal(IntPtr hGlobal, bool fDeleteOnRelease); + [DllImport(Libraries.Ole32)] + internal static extern int CoGetObjectContext([MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv); } } diff --git a/src/libraries/Common/src/Interop/Windows/Ole32/Interop.CreateStreamOnHGlobal.cs b/src/libraries/Common/src/Interop/Windows/Ole32/Interop.CreateStreamOnHGlobal.cs index 1b153d786dfb..d65814a2f51c 100644 --- a/src/libraries/Common/src/Interop/Windows/Ole32/Interop.CreateStreamOnHGlobal.cs +++ b/src/libraries/Common/src/Interop/Windows/Ole32/Interop.CreateStreamOnHGlobal.cs @@ -8,7 +8,7 @@ internal static partial class Interop { internal static partial class Ole32 { - [DllImport(Libraries.Ole32)] - internal static extern int CoGetObjectContext([MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv); + [DllImport(Libraries.Ole32, PreserveSig = false)] + internal static extern IStream CreateStreamOnHGlobal(IntPtr hGlobal, bool fDeleteOnRelease); } } diff --git a/src/libraries/System.Management/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.Management/src/ILLink/ILLink.Suppressions.xml index 54f262023c9d..fbf2a09e7eb9 100644 --- a/src/libraries/System.Management/src/ILLink/ILLink.Suppressions.xml +++ b/src/libraries/System.Management/src/ILLink/ILLink.Suppressions.xml @@ -31,29 +31,5 @@ member M:System.Management.MTAHelper.WorkerThread - - ILLink - IL2050 - member - M:Interop.Ole32.CoMarshalInterface(System.Runtime.InteropServices.ComTypes.IStream,System.Guid,System.IntPtr,System.UInt32,System.IntPtr,System.UInt32) - - - ILLink - IL2050 - member - M:Interop.Ole32.CoUnmarshalInterface(System.Runtime.InteropServices.ComTypes.IStream,System.Guid) - - - ILLink - IL2050 - member - M:Interop.Ole32.CreateStreamOnHGlobal(System.IntPtr,System.Boolean) - - - ILLink - IL2050 - member - M:Interop.Ole32.GetHGlobalFromStream(System.Runtime.InteropServices.ComTypes.IStream) - - \ No newline at end of file + diff --git a/src/libraries/System.Management/src/System.Management.csproj b/src/libraries/System.Management/src/System.Management.csproj index 9fac8892aed3..2d353115180a 100644 --- a/src/libraries/System.Management/src/System.Management.csproj +++ b/src/libraries/System.Management/src/System.Management.csproj @@ -22,14 +22,6 @@ Link="Common\Interop\Windows\Kernel32\Interop.LoadLibrary.cs" /> - - - - From ce0982256cd83960a97d8745a84e97acc8ebf98d Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 13 Jul 2021 17:10:18 -0400 Subject: [PATCH 096/133] Fix length check for Regex BOL FindFirstChar optimization (#55574) For a beginning-of-line anchor, in FindFirstChar we use IndexOf to quickly skip ahead to the next \n. But we neglected to check to see whether that brought us past an explicitly specified end position. This just adds the missing check. --- .../System/Text/RegularExpressions/RegexCompiler.cs | 12 +++++++----- .../Text/RegularExpressions/RegexInterpreter.cs | 4 ++-- .../tests/Regex.Match.Tests.cs | 13 ++++++++----- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs index c06b5af67c63..b9b6e791ed57 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs @@ -1240,19 +1240,21 @@ protected void GenerateFindFirstChar() { Stloc(newlinePos); - // if (newlinePos == -1) + // if (newlinePos == -1 || newlinePos + 1 > runtextend) // { // runtextpos = runtextend; // return false; // } - Label foundNextLine = DefineLabel(); Ldloc(newlinePos); Ldc(-1); - Bne(foundNextLine); - BrFar(returnFalse); + Beq(returnFalse); + Ldloc(newlinePos); + Ldc(1); + Add(); + Ldloc(_runtextendLocal); + Bgt(returnFalse); // runtextpos = newlinePos + 1; - MarkLabel(foundNextLine); Ldloc(newlinePos); Ldc(1); Add(); diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexInterpreter.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexInterpreter.cs index 4cc3dc528605..d557ec6c3aa7 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexInterpreter.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexInterpreter.cs @@ -406,7 +406,7 @@ protected override bool FindFirstChar() if (runtextpos > runtextbeg && runtext![runtextpos - 1] != '\n') { int newline = runtext.IndexOf('\n', runtextpos); - if (newline == -1) + if (newline == -1 || newline + 1 > runtextend) { runtextpos = runtextend; return false; @@ -457,7 +457,7 @@ protected override bool FindFirstChar() if (!_code.LeadingCharClasses[0].CaseInsensitive) { // singleton, left-to-right, case-sensitive - int i = runtext.AsSpan(runtextpos, runtextend - runtextpos).IndexOf(ch); + int i = span.IndexOf(ch); if (i >= 0) { runtextpos += i; diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.Match.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/Regex.Match.Tests.cs index d5d690b0c29c..719f280c9172 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/Regex.Match.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/Regex.Match.Tests.cs @@ -152,6 +152,9 @@ public static IEnumerable Match_Basic_TestData() // Using beginning/end of string chars \A, \Z: Actual - "\\Aaaa\\w+zzz\\Z" yield return new object[] { @"\Aaaa\w+zzz\Z", "aaaasdfajsdlfjzzza", RegexOptions.None, 0, 18, false, string.Empty }; + // Anchors and multiline + yield return new object[] { @"^A$", "ABC\n", RegexOptions.Multiline, 0, 2, false, string.Empty }; + // Using beginning/end of string chars \A, \Z: Actual - "\\Aaaa\\w+zzz\\Z" yield return new object[] { @"\A(line2\n)line3\Z", "line2\nline3\n", RegexOptions.Multiline, 0, 12, true, "line2\nline3" }; @@ -813,7 +816,7 @@ public static IEnumerable Match_Advanced_TestData() } }; - // Mutliline + // Multiline yield return new object[] { "(line2$\n)line3", "line1\nline2\nline3\n\nline4", RegexOptions.Multiline, 0, 24, @@ -824,7 +827,7 @@ public static IEnumerable Match_Advanced_TestData() } }; - // Mutliline + // Multiline yield return new object[] { "(line2\n^)line3", "line1\nline2\nline3\n\nline4", RegexOptions.Multiline, 0, 24, @@ -835,7 +838,7 @@ public static IEnumerable Match_Advanced_TestData() } }; - // Mutliline + // Multiline yield return new object[] { "(line3\n$\n)line4", "line1\nline2\nline3\n\nline4", RegexOptions.Multiline, 0, 24, @@ -846,7 +849,7 @@ public static IEnumerable Match_Advanced_TestData() } }; - // Mutliline + // Multiline yield return new object[] { "(line3\n^\n)line4", "line1\nline2\nline3\n\nline4", RegexOptions.Multiline, 0, 24, @@ -857,7 +860,7 @@ public static IEnumerable Match_Advanced_TestData() } }; - // Mutliline + // Multiline yield return new object[] { "(line2$\n^)line3", "line1\nline2\nline3\n\nline4", RegexOptions.Multiline, 0, 24, From 14854e209868749262bd58a64407b3802177542f Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Tue, 13 Jul 2021 14:33:41 -0700 Subject: [PATCH 097/133] Recognize MacCatalyst as a superset of iOS (#55550) Recognize MacCatalyst as a superset of iOS and apply platform guard attributes that will inform the analyzer of the relationship. --- .../src/System/OperatingSystem.cs | 15 ++++++-- .../tests/System/OperatingSystemTests.cs | 37 ++++++++++++++----- .../System.Runtime/ref/System.Runtime.cs | 2 + 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs b/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs index a0e17bfb3496..ce70c49dfc14 100644 --- a/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs +++ b/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs @@ -3,12 +3,13 @@ using System.Diagnostics; using System.Runtime.Serialization; +using System.Runtime.Versioning; namespace System { public sealed class OperatingSystem : ISerializable, ICloneable { -#if TARGET_UNIX && !TARGET_OSX +#if TARGET_UNIX && !TARGET_OSX && !TARGET_MACCATALYST && !TARGET_IOS private static readonly string s_osPlatformName = Interop.Sys.GetUnixName(); #endif @@ -102,6 +103,10 @@ public static bool IsOSPlatform(string platform) return platform.Equals("WINDOWS", StringComparison.OrdinalIgnoreCase); #elif TARGET_OSX return platform.Equals("OSX", StringComparison.OrdinalIgnoreCase) || platform.Equals("MACOS", StringComparison.OrdinalIgnoreCase); +#elif TARGET_MACCATALYST + return platform.Equals("MACCATALYST", StringComparison.OrdinalIgnoreCase) || platform.Equals("IOS", StringComparison.OrdinalIgnoreCase); +#elif TARGET_IOS + return platform.Equals("IOS", StringComparison.OrdinalIgnoreCase); #elif TARGET_UNIX return platform.Equals(s_osPlatformName, StringComparison.OrdinalIgnoreCase); #else @@ -173,18 +178,20 @@ public static bool IsAndroidVersionAtLeast(int major, int minor = 0, int build = => IsAndroid() && IsOSVersionAtLeast(major, minor, build, revision); /// - /// Indicates whether the current application is running on iOS. + /// Indicates whether the current application is running on iOS or MacCatalyst. /// + [SupportedOSPlatformGuard("maccatalyst")] public static bool IsIOS() => -#if TARGET_IOS +#if TARGET_IOS || TARGET_MACCATALYST true; #else false; #endif /// - /// Check for the iOS version (returned by 'libobjc.get_operatingSystemVersion') with a >= version comparison. Used to guard APIs that were added in the given iOS release. + /// Check for the iOS/MacCatalyst version (returned by 'libobjc.get_operatingSystemVersion') with a >= version comparison. Used to guard APIs that were added in the given iOS release. /// + [SupportedOSPlatformGuard("maccatalyst")] public static bool IsIOSVersionAtLeast(int major, int minor = 0, int build = 0) => IsIOS() && IsOSVersionAtLeast(major, minor, build, 0); diff --git a/src/libraries/System.Runtime.Extensions/tests/System/OperatingSystemTests.cs b/src/libraries/System.Runtime.Extensions/tests/System/OperatingSystemTests.cs index 6a6b1cdd6b6a..405cf4373f9b 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/OperatingSystemTests.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/OperatingSystemTests.cs @@ -132,6 +132,23 @@ public static void OSX_Is_Treated_as_macOS() [Fact, PlatformSpecific(TestPlatforms.MacCatalyst)] public static void TestIsOSVersionAtLeast_MacCatalyst() => TestIsOSVersionAtLeast("MacCatalyst"); + [Fact, PlatformSpecific(TestPlatforms.MacCatalyst)] + public static void MacCatalyst_Is_Also_iOS() + { + Assert.True(OperatingSystem.IsOSPlatform("IOS")); + Assert.True(OperatingSystem.IsIOS()); + + AssertVersionChecks(true, (major, minor, build, revision) => OperatingSystem.IsOSPlatformVersionAtLeast("IOS", major, minor, build, revision)); + AssertVersionChecks(true, (major, minor, build) => OperatingSystem.IsOSPlatformVersionAtLeast("IOS", major, minor, build)); + } + + [Fact, PlatformSpecific(TestPlatforms.iOS)] + public static void IOS_Is_Not_Also_MacCatalyst() + { + Assert.False(OperatingSystem.IsOSPlatform("MacCatalyst")); + Assert.False(OperatingSystem.IsMacCatalyst()); + } + [Fact, PlatformSpecific(TestPlatforms.tvOS)] public static void TestIsOSPlatform_TvOS() => TestIsOSPlatform("tvOS", OperatingSystem.IsTvOS); @@ -146,13 +163,13 @@ public static void OSX_Is_Treated_as_macOS() private static void TestIsOSPlatform(string currentOSName, Func currentOSCheck) { - foreach (string platfromName in AllKnownPlatformNames) + foreach (string platformName in AllKnownPlatformNames) { - bool expected = currentOSName.Equals(platfromName, StringComparison.OrdinalIgnoreCase); + bool expected = currentOSName.Equals(platformName, StringComparison.OrdinalIgnoreCase); - Assert.Equal(expected, OperatingSystem.IsOSPlatform(platfromName)); - Assert.Equal(expected, OperatingSystem.IsOSPlatform(platfromName.ToUpper())); - Assert.Equal(expected, OperatingSystem.IsOSPlatform(platfromName.ToLower())); + Assert.Equal(expected, OperatingSystem.IsOSPlatform(platformName)); + Assert.Equal(expected, OperatingSystem.IsOSPlatform(platformName.ToUpper())); + Assert.Equal(expected, OperatingSystem.IsOSPlatform(platformName.ToLower())); } Assert.True(currentOSCheck()); @@ -176,13 +193,13 @@ private static void TestIsOSPlatform(string currentOSName, Func currentOSC private static void TestIsOSVersionAtLeast(string currentOSName) { - foreach (string platfromName in AllKnownPlatformNames) + foreach (string platformName in AllKnownPlatformNames) { - bool isCurrentOS = currentOSName.Equals(platfromName, StringComparison.OrdinalIgnoreCase); + bool isCurrentOS = currentOSName.Equals(platformName, StringComparison.OrdinalIgnoreCase); - AssertVersionChecks(isCurrentOS, (major, minor, build, revision) => OperatingSystem.IsOSPlatformVersionAtLeast(platfromName, major, minor, build, revision)); - AssertVersionChecks(isCurrentOS, (major, minor, build, revision) => OperatingSystem.IsOSPlatformVersionAtLeast(platfromName.ToLower(), major, minor, build, revision)); - AssertVersionChecks(isCurrentOS, (major, minor, build, revision) => OperatingSystem.IsOSPlatformVersionAtLeast(platfromName.ToUpper(), major, minor, build, revision)); + AssertVersionChecks(isCurrentOS, (major, minor, build, revision) => OperatingSystem.IsOSPlatformVersionAtLeast(platformName, major, minor, build, revision)); + AssertVersionChecks(isCurrentOS, (major, minor, build, revision) => OperatingSystem.IsOSPlatformVersionAtLeast(platformName.ToLower(), major, minor, build, revision)); + AssertVersionChecks(isCurrentOS, (major, minor, build, revision) => OperatingSystem.IsOSPlatformVersionAtLeast(platformName.ToUpper(), major, minor, build, revision)); } AssertVersionChecks(currentOSName.Equals("Android", StringComparison.OrdinalIgnoreCase), OperatingSystem.IsAndroidVersionAtLeast); diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 2656da8c9996..35fe09cc6629 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -4965,7 +4965,9 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S public static bool IsFreeBSDVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0) { throw null; } public static bool IsAndroid() { throw null; } public static bool IsAndroidVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0) { throw null; } + [System.Runtime.Versioning.SupportedOSPlatformGuardAttribute("maccatalyst")] public static bool IsIOS() { throw null; } + [System.Runtime.Versioning.SupportedOSPlatformGuardAttribute("maccatalyst")] public static bool IsIOSVersionAtLeast(int major, int minor = 0, int build = 0) { throw null; } public static bool IsMacOS() { throw null; } public static bool IsMacOSVersionAtLeast(int major, int minor = 0, int build = 0) { throw null; } From f5316845d5a0069f1b265b5f6d3b81b827450737 Mon Sep 17 00:00:00 2001 From: Andrew Au Date: Tue, 13 Jul 2021 15:10:34 -0700 Subject: [PATCH 098/133] Avoid declaring an object cannot fit in a segment if we already have committed space for it (#55585) --- src/coreclr/gc/gc.cpp | 16 +++++++++++----- src/coreclr/gc/gcpriv.h | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 268fffcd4d7f..6cf5283476ab 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -14646,6 +14646,7 @@ BOOL gc_heap::short_on_end_of_seg (heap_segment* seg) BOOL sufficient_p = sufficient_space_regions (end_gen0_region_space, end_space_after_gc()); #else BOOL sufficient_p = sufficient_space_end_seg (allocated, + heap_segment_committed (seg), heap_segment_reserved (seg), end_space_after_gc()); #endif //USE_REGIONS @@ -37875,13 +37876,18 @@ bool gc_heap::sufficient_space_regions (size_t end_space, size_t end_space_requi return false; } #else //USE_REGIONS -BOOL gc_heap::sufficient_space_end_seg (uint8_t* start, uint8_t* seg_end, size_t end_space_required) +BOOL gc_heap::sufficient_space_end_seg (uint8_t* start, uint8_t* committed, uint8_t* reserved, size_t end_space_required) { BOOL can_fit = FALSE; - size_t end_seg_space = (size_t)(seg_end - start); - if (end_seg_space > end_space_required) + size_t committed_space = (size_t)(committed - start); + size_t end_seg_space = (size_t)(reserved - start); + if (committed_space > end_space_required) { - return check_against_hard_limit (end_space_required); + return true; + } + else if (end_seg_space > end_space_required) + { + return check_against_hard_limit (end_space_required - committed_space); } else return false; @@ -38045,7 +38051,7 @@ BOOL gc_heap::ephemeral_gen_fit_p (gc_tuning_point tp) size_t gen0_end_space = get_gen0_end_space(); BOOL can_fit = sufficient_space_regions (gen0_end_space, end_space); #else //USE_REGIONS - BOOL can_fit = sufficient_space_end_seg (start, heap_segment_reserved (ephemeral_heap_segment), end_space); + BOOL can_fit = sufficient_space_end_seg (start, heap_segment_committed (ephemeral_heap_segment), heap_segment_reserved (ephemeral_heap_segment), end_space); #endif //USE_REGIONS return can_fit; } diff --git a/src/coreclr/gc/gcpriv.h b/src/coreclr/gc/gcpriv.h index 09bd97d18918..d3f3526925d9 100644 --- a/src/coreclr/gc/gcpriv.h +++ b/src/coreclr/gc/gcpriv.h @@ -3182,7 +3182,7 @@ class gc_heap BOOL& should_expand); #ifndef USE_REGIONS PER_HEAP - BOOL sufficient_space_end_seg (uint8_t* start, uint8_t* seg_end, + BOOL sufficient_space_end_seg (uint8_t* start, uint8_t* committed, uint8_t* reserved, size_t end_space_required); #endif //!USE_REGIONS From 002370bd1b2d848dcf65ef11cf6b2f4592a6fb89 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 13 Jul 2021 15:25:10 -0700 Subject: [PATCH 099/133] Open types can exist as entries in interface map (#55372) * Open types can exist as entries in interface map - While invalid via the ECMA spec, the runtime currently represents a type explicitly instantiated over its own generic type parameters via the open type MethodTable - This is not strictly correct, as per the spec, these should be represented via an instantiated type, but changing that detail at this time is considered highly risky - This conflicts with the perf optimization around partialy interface loading which uses the open type of an interface to represent a type instantiated in the curiously recurring fashion. - The fix is to detect types instantiated over generic variables, and make them ineligible for the optimization, and to detect those cases where the optimization is ineligible, and revert back to the non-optimized behavior Fixes #55323 --- src/coreclr/vm/methodtable.cpp | 5 ++-- src/coreclr/vm/methodtable.h | 3 ++- src/coreclr/vm/methodtable.inl | 2 +- src/coreclr/vm/methodtablebuilder.cpp | 11 ++++++--- .../CuriouslyRecurringThroughInterface.cs | 23 +++++++++++++++++++ .../CuriouslyRecurringThroughInterface.csproj | 11 +++++++++ 6 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 src/tests/Loader/classloader/generics/Instantiation/Positive/CuriouslyRecurringThroughInterface.cs create mode 100644 src/tests/Loader/classloader/generics/Instantiation/Positive/CuriouslyRecurringThroughInterface.csproj diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index 834d3cfc8c40..8bdc5f0c2a89 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -1577,6 +1577,7 @@ BOOL MethodTable::CanCastByVarianceToInterfaceOrDelegate(MethodTable *pTargetMT, // Shortcut for generic approx type scenario if (pMTInterfaceMapOwner != NULL && + !pMTInterfaceMapOwner->ContainsGenericVariables() && IsSpecialMarkerTypeForGenericCasting() && GetTypeDefRid() == pTargetMT->GetTypeDefRid() && GetModule() == pTargetMT->GetModule() && @@ -1603,7 +1604,7 @@ BOOL MethodTable::CanCastByVarianceToInterfaceOrDelegate(MethodTable *pTargetMT, for (DWORD i = 0; i < inst.GetNumArgs(); i++) { TypeHandle thArg = inst[i]; - if (IsSpecialMarkerTypeForGenericCasting() && pMTInterfaceMapOwner) + if (IsSpecialMarkerTypeForGenericCasting() && pMTInterfaceMapOwner && !pMTInterfaceMapOwner->ContainsGenericVariables()) { thArg = pMTInterfaceMapOwner; } @@ -9820,7 +9821,7 @@ PTR_MethodTable MethodTable::InterfaceMapIterator::GetInterface(MethodTable* pMT CONTRACT_END; MethodTable *pResult = m_pMap->GetMethodTable(); - if (pResult->IsSpecialMarkerTypeForGenericCasting()) + if (pResult->IsSpecialMarkerTypeForGenericCasting() && !pMTOwner->ContainsGenericVariables()) { TypeHandle ownerAsInst[MaxGenericParametersForSpecialMarkerType]; for (DWORD i = 0; i < MaxGenericParametersForSpecialMarkerType; i++) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 4063e50b7b61..df712a378d4e 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -2245,7 +2245,8 @@ class MethodTable { if (pCurrentMethodTable->HasSameTypeDefAs(pMT) && pMT->HasInstantiation() && - pCurrentMethodTable->IsSpecialMarkerTypeForGenericCasting() && + pCurrentMethodTable->IsSpecialMarkerTypeForGenericCasting() && + !pMTOwner->ContainsGenericVariables() && pMT->GetInstantiation().ContainsAllOneType(pMTOwner)) { exactMatch = true; diff --git a/src/coreclr/vm/methodtable.inl b/src/coreclr/vm/methodtable.inl index a3adc702cbe3..b1af313a2955 100644 --- a/src/coreclr/vm/methodtable.inl +++ b/src/coreclr/vm/methodtable.inl @@ -1571,7 +1571,7 @@ FORCEINLINE BOOL MethodTable::ImplementsInterfaceInline(MethodTable *pInterface) while (--numInterfaces); // Second scan, looking for the curiously recurring generic scenario - if (pInterface->HasInstantiation() && pInterface->GetInstantiation().ContainsAllOneType(this)) + if (pInterface->HasInstantiation() && !ContainsGenericVariables() && pInterface->GetInstantiation().ContainsAllOneType(this)) { numInterfaces = GetNumInterfaces(); pInfo = GetInterfaceMap(); diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 89926ffdaae0..154c642cf40d 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -9101,8 +9101,13 @@ MethodTableBuilder::LoadExactInterfaceMap(MethodTable *pMT) MethodTable **pExactMTs = (MethodTable**) _alloca(sizeof(MethodTable *) * nInterfacesCount); BOOL duplicates; bool retry = false; - bool retryWithExactInterfaces = !pMT->IsValueType() || pMT->IsSharedByGenericInstantiations(); // Always use exact loading behavior with classes or shared generics, as they have to deal with inheritance, and the - // inexact matching logic for classes would be more complex to write. + + // Always use exact loading behavior with classes or shared generics, as they have to deal with inheritance, and the + // inexact matching logic for classes would be more complex to write. + // Also always use the exact loading behavior with any generic that contains generic variables, as the open type is used + // to represent a type instantiated over its own generic variables, and the special marker type is currently the open type + // and we make this case distinguishable by simply disallowing the optimization in those cases. + bool retryWithExactInterfaces = !pMT->IsValueType() || pMT->IsSharedByGenericInstantiations() || pMT->ContainsGenericVariables(); DWORD nAssigned = 0; do @@ -9132,7 +9137,7 @@ MethodTableBuilder::LoadExactInterfaceMap(MethodTable *pMT) (const Substitution*)0, retryWithExactInterfaces ? NULL : pMT).GetMethodTable(); - bool uninstGenericCase = pNewIntfMT->IsSpecialMarkerTypeForGenericCasting(); + bool uninstGenericCase = !retryWithExactInterfaces && pNewIntfMT->IsSpecialMarkerTypeForGenericCasting(); duplicates |= InsertMethodTable(pNewIntfMT, pExactMTs, nInterfacesCount, &nAssigned); diff --git a/src/tests/Loader/classloader/generics/Instantiation/Positive/CuriouslyRecurringThroughInterface.cs b/src/tests/Loader/classloader/generics/Instantiation/Positive/CuriouslyRecurringThroughInterface.cs new file mode 100644 index 000000000000..e8a1139f53b9 --- /dev/null +++ b/src/tests/Loader/classloader/generics/Instantiation/Positive/CuriouslyRecurringThroughInterface.cs @@ -0,0 +1,23 @@ +namespace CuriouslyRecurringPatternThroughInterface +{ + interface IGeneric + { + } + interface ICuriouslyRecurring : IGeneric> + { + } + class CuriouslyRecurringThroughInterface : ICuriouslyRecurring + { + } + + class Program + { + static object _o; + static int Main(string[] args) + { + // Test that the a generic using a variant of the curiously recurring pattern involving an interface can be loaded. + _o = typeof(CuriouslyRecurringThroughInterface); + return 100; + } + } +} \ No newline at end of file diff --git a/src/tests/Loader/classloader/generics/Instantiation/Positive/CuriouslyRecurringThroughInterface.csproj b/src/tests/Loader/classloader/generics/Instantiation/Positive/CuriouslyRecurringThroughInterface.csproj new file mode 100644 index 000000000000..b566f0236979 --- /dev/null +++ b/src/tests/Loader/classloader/generics/Instantiation/Positive/CuriouslyRecurringThroughInterface.csproj @@ -0,0 +1,11 @@ + + + true + Exe + BuildAndRun + 1 + + + + + \ No newline at end of file From b1a8e3ed946f07297716dbde2456571407875dfd Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 13 Jul 2021 15:26:47 -0700 Subject: [PATCH 100/133] Do PGO restore as part of PGO path lookup. (#55584) --- src/coreclr/build-runtime.cmd | 27 +++++++-------------------- src/coreclr/build-runtime.sh | 21 +++++++-------------- 2 files changed, 14 insertions(+), 34 deletions(-) diff --git a/src/coreclr/build-runtime.cmd b/src/coreclr/build-runtime.cmd index 731c255468e8..ab805f370aa2 100644 --- a/src/coreclr/build-runtime.cmd +++ b/src/coreclr/build-runtime.cmd @@ -246,10 +246,6 @@ if NOT "%__BuildType%"=="Release" ( set __PgoOptimize=0 ) -if %__PgoOptimize%==0 ( - set __RestoreOptData=0 -) - set "__BinDir=%__RootBinDir%\bin\coreclr\%__TargetOS%.%__BuildArch%.%__BuildType%" set "__IntermediatesDir=%__RootBinDir%\obj\coreclr\%__TargetOS%.%__BuildArch%.%__BuildType%" set "__LogsDir=%__RootBinDir%\log\!__BuildType!" @@ -335,29 +331,20 @@ REM === Restore optimization profile data REM === REM ========================================================================================= -set OptDataProjectFilePath=%__ProjectDir%\.nuget\optdata\optdata.csproj -if %__RestoreOptData% EQU 1 ( - echo %__MsgPrefix%Restoring the OptimizationData Package - set "__BinLog=\"%__LogsDir%\OptRestore_%__TargetOS%__%__BuildArch%__%__BuildType%.binlog\"" - powershell -NoProfile -ExecutionPolicy ByPass -NoLogo -File "%__RepoRootDir%\eng\common\msbuild.ps1" /clp:nosummary %__ArcadeScriptArgs%^ - "%OptDataProjectFilePath%" /t:Restore^ - %__CommonMSBuildArgs% %__UnprocessedBuildArgs%^ - /nodereuse:false /bl:!__BinLog! - if not !errorlevel! == 0 ( - set __exitCode=!errorlevel! - echo %__ErrMsgPrefix%%__MsgPrefix%Error: Failed to restore the optimization data package. - goto ExitWithCode - ) -) set __PgoOptDataPath= if %__PgoOptimize% EQU 1 ( + set OptDataProjectFilePath=%__ProjectDir%\.nuget\optdata\optdata.csproj + set __OptDataRestoreArg= + if %__RestoreOptData% EQU 1 ( + set __OptDataRestoreArg=/restore + ) set PgoDataPackagePathOutputFile=%__IntermediatesDir%\optdatapath.txt set "__BinLog=\"%__LogsDir%\PgoVersionRead_%__TargetOS%__%__BuildArch%__%__BuildType%.binlog\"" REM Parse the optdata package versions out of msbuild so that we can pass them on to CMake powershell -NoProfile -ExecutionPolicy ByPass -NoLogo -File "%__RepoRootDir%\eng\common\msbuild.ps1" /clp:nosummary %__ArcadeScriptArgs%^ - "%OptDataProjectFilePath%" /t:DumpPgoDataPackagePath^ - /p:PgoDataPackagePathOutputFile="!PgoDataPackagePathOutputFile!"^ + "!OptDataProjectFilePath!" /t:DumpPgoDataPackagePath^ + /p:PgoDataPackagePathOutputFile="!PgoDataPackagePathOutputFile!" !__OptDataRestoreArg!^ %__CommonMSBuildArgs% %__UnprocessedBuildArgs% /bl:!__BinLog! if not !errorlevel! == 0 ( diff --git a/src/coreclr/build-runtime.sh b/src/coreclr/build-runtime.sh index 0b39613aac36..1d9881f281b1 100755 --- a/src/coreclr/build-runtime.sh +++ b/src/coreclr/build-runtime.sh @@ -39,27 +39,20 @@ setup_dirs_local() restore_optdata() { local OptDataProjectFilePath="$__ProjectRoot/.nuget/optdata/optdata.csproj" - if [[ "$__SkipRestoreOptData" == 0 && "$__IsMSBuildOnNETCoreSupported" == 1 ]]; then - echo "Restoring the OptimizationData package" - "$__RepoRootDir/eng/common/msbuild.sh" /clp:nosummary $__ArcadeScriptArgs \ - $OptDataProjectFilePath /t:Restore /m \ - -bl:"$__LogsDir/OptRestore_$__ConfigTriplet.binlog" \ - $__CommonMSBuildArgs $__UnprocessedBuildArgs \ - /nodereuse:false - local exit_code="$?" - if [[ "$exit_code" != 0 ]]; then - echo "${__ErrMsgPrefix}Failed to restore the optimization data package." - exit "$exit_code" - fi - fi if [[ "$__PgoOptimize" == 1 && "$__IsMSBuildOnNETCoreSupported" == 1 ]]; then # Parse the optdata package versions out of msbuild so that we can pass them on to CMake local PgoDataPackagePathOutputFile="${__IntermediatesDir}/optdatapath.txt" + local RestoreArg="" + + if [[ "$__SkipRestoreOptData" == "0" ]]; then + RestoreArg="/restore" + fi + # Writes into ${PgoDataPackagePathOutputFile} - "$__RepoRootDir/eng/common/msbuild.sh" /clp:nosummary $__ArcadeScriptArgs $OptDataProjectFilePath /t:DumpPgoDataPackagePath \ + "$__RepoRootDir/eng/common/msbuild.sh" /clp:nosummary $__ArcadeScriptArgs $OptDataProjectFilePath $RestoreArg /t:DumpPgoDataPackagePath \ ${__CommonMSBuildArgs} /p:PgoDataPackagePathOutputFile=${PgoDataPackagePathOutputFile} \ -bl:"$__LogsDir/PgoVersionRead_$__ConfigTriplet.binlog" > /dev/null 2>&1 local exit_code="$?" From f9076c7e3671c8b720d031ce541452a37bf56b95 Mon Sep 17 00:00:00 2001 From: Dan Moseley Date: Tue, 13 Jul 2021 16:33:32 -0600 Subject: [PATCH 101/133] Reduce subtest count in Reflection (#55537) * Reduce reflection subtest count * typo --- .../tests/System/Reflection/ModuleTests.cs | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System/Reflection/ModuleTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/ModuleTests.cs index 29fe532e6c0e..8c42d79063f3 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/ModuleTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/ModuleTests.cs @@ -242,21 +242,20 @@ public void GetMethods() AssertExtensions.SequenceEqual(new[]{ "TestMethodFoo", "TestMethodFoo", "TestMethodBar" }, methodNames ); } - public static IEnumerable Types => - Module.GetTypes().Select(t => new object[] { t }); + public static IEnumerable Types => Module.GetTypes(); - [Theory] - [MemberData(nameof(Types))] - public void ResolveType(Type t) + [Fact] + public void ResolveTypes() { - Assert.Equal(t, Module.ResolveType(t.MetadataToken)); + foreach(Type t in Types) + Assert.Equal(t, Module.ResolveType(t.MetadataToken)); } public static IEnumerable BadResolveTypes => new[] { new object[] { 1234 }, - new object[] { typeof(ModuleTests).GetMethod("ResolveType").MetadataToken }, + new object[] { typeof(ModuleTests).GetMethod("ResolveTypes").MetadataToken }, } .Union(NullTokens); @@ -270,14 +269,14 @@ public void ResolveTypeFail(int token) }); } - public static IEnumerable Methods => - typeof(ModuleTests).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly).Select(m => new object[] { m }); + public static IEnumerable Methods => + typeof(ModuleTests).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly); - [Theory] - [MemberData(nameof(Methods))] - public void ResolveMethod(MethodInfo t) + [Fact] + public void ResolveMethodsByMethodInfo() { - Assert.Equal(t, Module.ResolveMethod(t.MetadataToken)); + foreach(MethodInfo mi in Methods) + Assert.Equal(mi, Module.ResolveMethod(mi.MetadataToken)); } public static IEnumerable BadResolveMethods => @@ -299,15 +298,15 @@ public void ResolveMethodFail(int token) }); } - public static IEnumerable Fields => - typeof(ModuleTests).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly).Select(f => new object[] { f }); + public static IEnumerable Fields => + typeof(ModuleTests).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly); - [Theory] - [MemberData(nameof(Fields))] + [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/52072", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - public void ResolveField(FieldInfo t) + public void ResolveFieldsByFieldInfo() { - Assert.Equal(t, Module.ResolveField(t.MetadataToken)); + foreach(FieldInfo fi in Fields) + Assert.Equal(fi, Module.ResolveField(fi.MetadataToken)); } public static IEnumerable BadResolveFields => @@ -348,13 +347,25 @@ public void ResolveStringFail(int token) }); } - [Theory] - [MemberData(nameof(Types))] - [MemberData(nameof(Methods))] - [MemberData(nameof(Fields))] - public void ResolveMember(MemberInfo member) + [Fact] + public void ResolveTypesByMemberInfo() + { + foreach(MemberInfo mi in Types) + Assert.Equal(mi, Module.ResolveMember(mi.MetadataToken)); + } + + [Fact] + public void ResolveMethodsByMemberInfo() + { + foreach (MemberInfo mi in Methods) + Assert.Equal(mi, Module.ResolveMember(mi.MetadataToken)); + } + + [Fact] + public void ResolveFieldsByMemberInfo() { - Assert.Equal(member, Module.ResolveMember(member.MetadataToken)); + foreach (MemberInfo mi in Fields) + Assert.Equal(mi, Module.ResolveMember(mi.MetadataToken)); } [Fact] From 97202193214ef74ff7d9518047be61c29e012ce7 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 13 Jul 2021 17:40:28 -0500 Subject: [PATCH 102/133] Add property ordering feature (#55586) --- .../System.Text.Json/ref/System.Text.Json.cs | 6 ++ .../src/System.Text.Json.csproj | 3 +- .../Attributes/JsonPropertyOrderAttribute.cs | 28 ++++++++ .../Metadata/JsonPropertyInfo.cs | 11 +++ .../Serialization/Metadata/JsonTypeInfo.cs | 11 +++ .../Serialization/PropertyOrderTests.cs | 68 +++++++++++++++++++ .../System.Text.Json.Tests.csproj | 1 + 7 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonPropertyOrderAttribute.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyOrderTests.cs diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index 26b72dd6493a..97e39311a09e 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -832,6 +832,12 @@ public sealed partial class JsonPropertyNameAttribute : System.Text.Json.Seriali public JsonPropertyNameAttribute(string name) { } public string Name { get { throw null; } } } + [System.AttributeUsageAttribute(System.AttributeTargets.Field | System.AttributeTargets.Property, AllowMultiple = false)] + public sealed partial class JsonPropertyOrderAttribute : System.Text.Json.Serialization.JsonAttribute + { + public JsonPropertyOrderAttribute(int order) { } + public int Order { get { throw null; } } + } [System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true)] public sealed partial class JsonSerializableAttribute : System.Text.Json.Serialization.JsonAttribute { diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 3206cbd6de1f..08855731abe6 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -22,7 +22,7 @@ - + @@ -89,6 +89,7 @@ + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonPropertyOrderAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonPropertyOrderAttribute.cs new file mode 100644 index 000000000000..8126e75690f4 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonPropertyOrderAttribute.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Text.Json.Serialization +{ + /// + /// Specifies the property order that is present in the JSON when serializing. Lower values are serialized first. + /// If the attribute is not specified, the default value is 0. + /// + /// If multiple properties have the same value, the ordering is undefined between them. + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] + public sealed class JsonPropertyOrderAttribute : JsonAttribute + { + /// + /// Initializes a new instance of with the specified order. + /// + /// The order of the property. + public JsonPropertyOrderAttribute(int order) + { + Order = order; + } + + /// + /// The serialization order of the property. + /// + public int Order { get; } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs index 95081b8519de..9e34cc60a356 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs @@ -74,6 +74,12 @@ internal virtual void GetPolicies(JsonIgnoreCondition? ignoreCondition, JsonNumb DeterminePropertyName(); DetermineIgnoreCondition(ignoreCondition); + JsonPropertyOrderAttribute? orderAttr = GetAttribute(MemberInfo); + if (orderAttr != null) + { + Order = orderAttr.Order; + } + JsonNumberHandlingAttribute? attribute = GetAttribute(MemberInfo); DetermineNumberHandlingForProperty(attribute?.Handling, declaringTypeNumberHandling); } @@ -366,6 +372,11 @@ internal abstract void InitializeForTypeInfo( internal JsonSerializerOptions Options { get; set; } = null!; // initialized in Init method + /// + /// The property order. + /// + internal int Order { get; set; } + internal bool ReadJsonAndAddExtensionProperty( object obj, ref ReadStack state, diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs index ba90400e2465..077de6d05372 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs @@ -197,6 +197,8 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json PropertyInfo[] properties = type.GetProperties(bindingFlags); + bool propertyOrderSpecified = false; + // PropertyCache is not accessed by other threads until the current JsonTypeInfo instance // is finished initializing and added to the cache on JsonSerializerOptions. // Default 'capacity' to the common non-polymorphic + property case. @@ -229,6 +231,7 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json propertyInfo, isVirtual, typeNumberHandling, + ref propertyOrderSpecified, ref ignoredMembers); } else @@ -263,6 +266,7 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json fieldInfo, isVirtual: false, typeNumberHandling, + ref propertyOrderSpecified, ref ignoredMembers); } } @@ -286,6 +290,11 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json properties = currentType.GetProperties(bindingFlags); }; + if (propertyOrderSpecified) + { + PropertyCache.List.Sort((p1, p2) => p1.Value!.Order.CompareTo(p2.Value!.Order)); + } + if (converter.ConstructorIsParameterized) { InitializeConstructorParameters(converter.ConstructorInfo!); @@ -327,6 +336,7 @@ private void CacheMember( MemberInfo memberInfo, bool isVirtual, JsonNumberHandling? typeNumberHandling, + ref bool propertyOrderSpecified, ref Dictionary? ignoredMembers) { bool hasExtensionAttribute = memberInfo.GetCustomAttribute(typeof(JsonExtensionDataAttribute)) != null; @@ -347,6 +357,7 @@ private void CacheMember( else { CacheMember(jsonPropertyInfo, PropertyCache, ref ignoredMembers); + propertyOrderSpecified |= jsonPropertyInfo.Order != 0; } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyOrderTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyOrderTests.cs new file mode 100644 index 000000000000..07b16caa00dc --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyOrderTests.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public static class PropertyOrderTests + { + private class MyPoco_BeforeAndAfter + { + public int B { get; set; } + + [JsonPropertyOrder(1)] + public int A { get; set; } + + [JsonPropertyOrder(-1)] + public int C { get; set; } + } + + [Fact] + public static void BeforeAndAfterDefaultOrder() + { + string json = JsonSerializer.Serialize(new MyPoco_BeforeAndAfter()); + Assert.Equal("{\"C\":0,\"B\":0,\"A\":0}", json); + } + + private class MyPoco_After + { + [JsonPropertyOrder(2)] + public int C { get; set; } + + public int B { get; set; } + public int D { get; set; } + + [JsonPropertyOrder(1)] + public int A { get; set; } + } + + [Fact] + public static void AfterDefaultOrder() + { + string json = JsonSerializer.Serialize(new MyPoco_After()); + Assert.EndsWith("\"A\":0,\"C\":0}", json); + // Order of B and D are not defined except they come before A and C + } + + private class MyPoco_Before + { + [JsonPropertyOrder(-1)] + public int C { get; set; } + + public int B { get; set; } + public int D { get; set; } + + [JsonPropertyOrder(-2)] + public int A { get; set; } + } + + [Fact] + public static void BeforeDefaultOrder() + { + string json = JsonSerializer.Serialize(new MyPoco_Before()); + Assert.StartsWith("{\"A\":0,\"C\":0", json); + // Order of B and D are not defined except they come after A and C + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj index 20d919ca164e..9e117638ddb8 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj @@ -166,6 +166,7 @@ + From ccfe21882e4a2206ce49cd5b32d3eb3cab3e530f Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Tue, 13 Jul 2021 16:44:57 -0700 Subject: [PATCH 103/133] Consume DistributedContextPropagator in DiagnosticsHandler (#55392) --- .../tests/PropagatorTests.cs | 8 + .../System.Net.Http/ref/System.Net.Http.cs | 2 + .../ref/System.Net.Http.csproj | 1 + .../BrowserHttpHandler/SocketsHttpHandler.cs | 10 +- .../src/System/Net/Http/DiagnosticsHandler.cs | 127 ++++++----- .../Http/DiagnosticsHandlerLoggingStrings.cs | 6 - .../src/System/Net/Http/HttpClientHandler.cs | 35 +-- .../src/System/Net/Http/HttpRequestMessage.cs | 12 +- .../HttpConnectionSettings.cs | 4 + .../SocketsHttpHandler/RedirectHandler.cs | 2 + .../SocketsHttpHandler/SocketsHttpHandler.cs | 23 ++ .../tests/FunctionalTests/DiagnosticsTests.cs | 207 ++++++++++++++---- 12 files changed, 307 insertions(+), 130 deletions(-) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/PropagatorTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/PropagatorTests.cs index 2a914f62ad53..5935ad4770e2 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/PropagatorTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/PropagatorTests.cs @@ -527,6 +527,14 @@ private Activity CreateW3CActivity(string name, string state, IEnumerable>? ConnectCallback { get { throw null; } set { } } public Func>? PlaintextStreamFilter { get { throw null; } set { } } + [System.CLSCompliantAttribute(false)] + public System.Diagnostics.DistributedContextPropagator? ActivityHeadersPropagator { get { throw null; } set { } } } public sealed class SocketsHttpConnectionContext { diff --git a/src/libraries/System.Net.Http/ref/System.Net.Http.csproj b/src/libraries/System.Net.Http/ref/System.Net.Http.csproj index ae6a8158fa04..1b20e03c7905 100644 --- a/src/libraries/System.Net.Http/ref/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/ref/System.Net.Http.csproj @@ -14,5 +14,6 @@ + diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs index 4805010002b2..1cd0dbdb7a64 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs @@ -3,13 +3,12 @@ using System.Collections.Generic; using System.IO; -using System.Net.Quic; -using System.Net.Quic.Implementations; using System.Net.Security; using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics; namespace System.Net.Http { @@ -173,6 +172,13 @@ public HeaderEncodingSelector? ResponseHeaderEncodingSelecto set => throw new PlatformNotSupportedException(); } + [CLSCompliant(false)] + public DistributedContextPropagator? ActivityHeadersPropagator + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + protected internal override Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) => throw new PlatformNotSupportedException(); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs index 21b161f9fbb4..75954afccab5 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs @@ -13,36 +13,59 @@ namespace System.Net.Http /// /// DiagnosticHandler notifies DiagnosticSource subscribers about outgoing Http requests /// - internal sealed class DiagnosticsHandler : DelegatingHandler + internal sealed class DiagnosticsHandler : HttpMessageHandlerStage { private static readonly DiagnosticListener s_diagnosticListener = new DiagnosticListener(DiagnosticsHandlerLoggingStrings.DiagnosticListenerName); - /// - /// DiagnosticHandler constructor - /// - /// Inner handler: Windows or Unix implementation of HttpMessageHandler. - /// Note that DiagnosticHandler is the latest in the pipeline - public DiagnosticsHandler(HttpMessageHandler innerHandler) : base(innerHandler) + private readonly HttpMessageHandler _innerHandler; + private readonly DistributedContextPropagator _propagator; + private readonly HeaderDescriptor[]? _propagatorFields; + + public DiagnosticsHandler(HttpMessageHandler innerHandler, DistributedContextPropagator propagator, bool autoRedirect = false) { + Debug.Assert(IsGloballyEnabled()); + Debug.Assert(innerHandler is not null && propagator is not null); + + _innerHandler = innerHandler; + _propagator = propagator; + + // Prepare HeaderDescriptors for fields we need to clear when following redirects + if (autoRedirect && _propagator.Fields is IReadOnlyCollection fields && fields.Count > 0) + { + var fieldDescriptors = new List(fields.Count); + foreach (string field in fields) + { + if (field is not null && HeaderDescriptor.TryGet(field, out HeaderDescriptor descriptor)) + { + fieldDescriptors.Add(descriptor); + } + } + _propagatorFields = fieldDescriptors.ToArray(); + } } - internal static bool IsEnabled() + private static bool IsEnabled() { - // check if there is a parent Activity (and propagation is not suppressed) - // or if someone listens to HttpHandlerDiagnosticListener - return IsGloballyEnabled() && (Activity.Current != null || s_diagnosticListener.IsEnabled()); + // check if there is a parent Activity or if someone listens to HttpHandlerDiagnosticListener + return Activity.Current != null || s_diagnosticListener.IsEnabled(); } internal static bool IsGloballyEnabled() => GlobalHttpSettings.DiagnosticsHandler.EnableActivityPropagation; - // SendAsyncCore returns already completed ValueTask for when async: false is passed. - // Internally, it calls the synchronous Send method of the base class. - protected internal override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken) => - SendAsyncCore(request, async: false, cancellationToken).AsTask().GetAwaiter().GetResult(); - - protected internal override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => - SendAsyncCore(request, async: true, cancellationToken).AsTask(); + internal override ValueTask SendAsync(HttpRequestMessage request, bool async, CancellationToken cancellationToken) + { + if (IsEnabled()) + { + return SendAsyncCore(request, async, cancellationToken); + } + else + { + return async ? + new ValueTask(_innerHandler.SendAsync(request, cancellationToken)) : + new ValueTask(_innerHandler.Send(request, cancellationToken)); + } + } private async ValueTask SendAsyncCore(HttpRequestMessage request, bool async, CancellationToken cancellationToken) @@ -58,6 +81,16 @@ private async ValueTask SendAsyncCore(HttpRequestMessage re throw new ArgumentNullException(nameof(request), SR.net_http_handler_norequest); } + // Since we are reusing the request message instance on redirects, clear any existing headers + // Do so before writing DiagnosticListener events as instrumentations use those to inject headers + if (request.WasRedirected() && _propagatorFields is HeaderDescriptor[] fields) + { + foreach (HeaderDescriptor field in fields) + { + request.Headers.Remove(field); + } + } + Activity? activity = null; DiagnosticListener diagnosticListener = s_diagnosticListener; @@ -72,8 +105,8 @@ private async ValueTask SendAsyncCore(HttpRequestMessage re try { return async ? - await base.SendAsync(request, cancellationToken).ConfigureAwait(false) : - base.Send(request, cancellationToken); + await _innerHandler.SendAsync(request, cancellationToken).ConfigureAwait(false) : + _innerHandler.Send(request, cancellationToken); } finally { @@ -119,8 +152,8 @@ await base.SendAsync(request, cancellationToken).ConfigureAwait(false) : try { response = async ? - await base.SendAsync(request, cancellationToken).ConfigureAwait(false) : - base.Send(request, cancellationToken); + await _innerHandler.SendAsync(request, cancellationToken).ConfigureAwait(false) : + _innerHandler.Send(request, cancellationToken); return response; } catch (OperationCanceledException) @@ -170,6 +203,16 @@ await base.SendAsync(request, cancellationToken).ConfigureAwait(false) : } } + protected override void Dispose(bool disposing) + { + if (disposing) + { + _innerHandler.Dispose(); + } + + base.Dispose(disposing); + } + #region private private sealed class ActivityStartData @@ -269,42 +312,18 @@ internal ResponseData(HttpResponseMessage? response, Guid loggingRequestId, long public override string ToString() => $"{{ {nameof(Response)} = {Response}, {nameof(LoggingRequestId)} = {LoggingRequestId}, {nameof(Timestamp)} = {Timestamp}, {nameof(RequestTaskStatus)} = {RequestTaskStatus} }}"; } - private static void InjectHeaders(Activity currentActivity, HttpRequestMessage request) + private void InjectHeaders(Activity currentActivity, HttpRequestMessage request) { - if (currentActivity.IdFormat == ActivityIdFormat.W3C) + _propagator.Inject(currentActivity, request, static (carrier, key, value) => { - if (!request.Headers.Contains(DiagnosticsHandlerLoggingStrings.TraceParentHeaderName)) + if (carrier is HttpRequestMessage request && + key is not null && + HeaderDescriptor.TryGet(key, out HeaderDescriptor descriptor) && + !request.Headers.TryGetHeaderValue(descriptor, out _)) { - request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.TraceParentHeaderName, currentActivity.Id); - if (currentActivity.TraceStateString != null) - { - request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.TraceStateHeaderName, currentActivity.TraceStateString); - } + request.Headers.TryAddWithoutValidation(descriptor, value); } - } - else - { - if (!request.Headers.Contains(DiagnosticsHandlerLoggingStrings.RequestIdHeaderName)) - { - request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.RequestIdHeaderName, currentActivity.Id); - } - } - - // we expect baggage to be empty or contain a few items - using (IEnumerator> e = currentActivity.Baggage.GetEnumerator()) - { - if (e.MoveNext()) - { - var baggage = new List(); - do - { - KeyValuePair item = e.Current; - baggage.Add(new NameValueHeaderValue(WebUtility.UrlEncode(item.Key), WebUtility.UrlEncode(item.Value)).ToString()); - } - while (e.MoveNext()); - request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.CorrelationContextHeaderName, baggage); - } - } + }); } [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandlerLoggingStrings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandlerLoggingStrings.cs index 0fa57394c1cc..cd91daaed3cb 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandlerLoggingStrings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandlerLoggingStrings.cs @@ -15,11 +15,5 @@ internal static class DiagnosticsHandlerLoggingStrings public const string ExceptionEventName = "System.Net.Http.Exception"; public const string ActivityName = "System.Net.Http.HttpRequestOut"; public const string ActivityStartName = "System.Net.Http.HttpRequestOut.Start"; - - public const string RequestIdHeaderName = "Request-Id"; - public const string CorrelationContextHeaderName = "Correlation-Context"; - - public const string TraceParentHeaderName = "traceparent"; - public const string TraceStateHeaderName = "tracestate"; } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs index 2e3289643cfb..fce5166f279e 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs @@ -9,6 +9,7 @@ using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; +using System.Diagnostics; #if TARGET_BROWSER using HttpHandlerType = System.Net.Http.BrowserHttpHandler; #else @@ -20,7 +21,14 @@ namespace System.Net.Http public partial class HttpClientHandler : HttpMessageHandler { private readonly HttpHandlerType _underlyingHandler; - private readonly DiagnosticsHandler? _diagnosticsHandler; + + private HttpMessageHandler Handler +#if TARGET_BROWSER + { get; } +#else + => _underlyingHandler; +#endif + private ClientCertificateOption _clientCertificateOptions; private volatile bool _disposed; @@ -28,10 +36,15 @@ public partial class HttpClientHandler : HttpMessageHandler public HttpClientHandler() { _underlyingHandler = new HttpHandlerType(); + +#if TARGET_BROWSER + Handler = _underlyingHandler; if (DiagnosticsHandler.IsGloballyEnabled()) { - _diagnosticsHandler = new DiagnosticsHandler(_underlyingHandler); + Handler = new DiagnosticsHandler(Handler, DistributedContextPropagator.Current); } +#endif + ClientCertificateOptions = ClientCertificateOption.Manual; } @@ -288,21 +301,11 @@ public SslProtocols SslProtocols public IDictionary Properties => _underlyingHandler.Properties; [UnsupportedOSPlatform("browser")] - protected internal override HttpResponseMessage Send(HttpRequestMessage request, - CancellationToken cancellationToken) - { - return DiagnosticsHandler.IsEnabled() && _diagnosticsHandler != null ? - _diagnosticsHandler.Send(request, cancellationToken) : - _underlyingHandler.Send(request, cancellationToken); - } + protected internal override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken) => + Handler.Send(request, cancellationToken); - protected internal override Task SendAsync(HttpRequestMessage request, - CancellationToken cancellationToken) - { - return DiagnosticsHandler.IsEnabled() && _diagnosticsHandler != null ? - _diagnosticsHandler.SendAsync(request, cancellationToken) : - _underlyingHandler.SendAsync(request, cancellationToken); - } + protected internal override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => + Handler.SendAsync(request, cancellationToken); // lazy-load the validator func so it can be trimmed by the ILLinker if it isn't used. private static Func? s_dangerousAcceptAnyServerCertificateValidator; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs index d0c88d60f771..daeda85fff79 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs @@ -15,6 +15,7 @@ public class HttpRequestMessage : IDisposable private const int MessageNotYetSent = 0; private const int MessageAlreadySent = 1; + private const int MessageIsRedirect = 2; // Track whether the message has been sent. // The message shouldn't be sent again if this field is equal to MessageAlreadySent. @@ -159,12 +160,13 @@ public override string ToString() return sb.ToString(); } - internal bool MarkAsSent() - { - return Interlocked.Exchange(ref _sendStatus, MessageAlreadySent) == MessageNotYetSent; - } + internal bool MarkAsSent() => Interlocked.CompareExchange(ref _sendStatus, MessageAlreadySent, MessageNotYetSent) == MessageNotYetSent; + + internal bool WasSentByHttpClient() => (_sendStatus & MessageAlreadySent) != 0; + + internal void MarkAsRedirected() => _sendStatus |= MessageIsRedirect; - internal bool WasSentByHttpClient() => _sendStatus == MessageAlreadySent; + internal bool WasRedirected() => (_sendStatus & MessageIsRedirect) != 0; #region IDisposable Members diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index 03cb3e9a4062..a24a6403b299 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -8,6 +8,7 @@ using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; +using System.Diagnostics; namespace System.Net.Http { @@ -47,6 +48,8 @@ internal sealed class HttpConnectionSettings internal HeaderEncodingSelector? _requestHeaderEncodingSelector; internal HeaderEncodingSelector? _responseHeaderEncodingSelector; + internal DistributedContextPropagator? _activityHeadersPropagator = DistributedContextPropagator.Current; + internal Version _maxHttpVersion; internal SslClientAuthenticationOptions? _sslOptions; @@ -119,6 +122,7 @@ public HttpConnectionSettings CloneAndNormalize() _connectCallback = _connectCallback, _plaintextStreamFilter = _plaintextStreamFilter, _initialHttp2StreamWindowSize = _initialHttp2StreamWindowSize, + _activityHeadersPropagator = _activityHeadersPropagator, }; // TODO: Remove if/when QuicImplementationProvider is removed from System.Net.Quic. diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs index 856b4e61da39..5d4b4846a37e 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs @@ -75,6 +75,8 @@ internal override async ValueTask SendAsync(HttpRequestMess } } + request.MarkAsRedirected(); + // Issue the redirected request. response = await _redirectInnerHandler.SendAsync(request, async, cancellationToken).ConfigureAwait(false); } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index 42361fba0808..68fbd071e7d6 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using System.Diagnostics.CodeAnalysis; using System.Text; +using System.Diagnostics; namespace System.Net.Http { @@ -448,6 +449,22 @@ public HeaderEncodingSelector? ResponseHeaderEncodingSelecto } } + /// + /// Gets or sets the to use when propagating the distributed trace and context. + /// Use to disable propagation. + /// Defaults to . + /// + [CLSCompliant(false)] + public DistributedContextPropagator? ActivityHeadersPropagator + { + get => _settings._activityHeadersPropagator; + set + { + CheckDisposedOrStarted(); + _settings._activityHeadersPropagator = value; + } + } + protected override void Dispose(bool disposing) { if (disposing && !_disposed) @@ -478,6 +495,12 @@ private HttpMessageHandlerStage SetupHandlerChain() handler = new HttpAuthenticatedConnectionHandler(poolManager); } + // DiagnosticsHandler is inserted before RedirectHandler so that trace propagation is done on redirects as well + if (DiagnosticsHandler.IsGloballyEnabled() && settings._activityHeadersPropagator is DistributedContextPropagator propagator) + { + handler = new DiagnosticsHandler(handler, propagator, settings._allowAutoRedirect); + } + if (settings._allowAutoRedirect) { // Just as with WinHttpHandler, for security reasons, we do not support authentication on redirects diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs index 1fb6fd925fd3..49e4b0a38480 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs @@ -256,7 +256,7 @@ public void SendAsync_ExpectedDiagnosticCancelledLogging() GetProperty(kvp.Value, "Request"); TaskStatus status = GetProperty(kvp.Value, "RequestTaskStatus"); Assert.Equal(TaskStatus.Canceled, status); - activityStopTcs.SetResult();; + activityStopTcs.SetResult(); } }); @@ -308,6 +308,7 @@ public void SendAsync_ExpectedDiagnosticSourceActivityLogging(ActivityIdFormat i parentActivity.AddBaggage("correlationId", Guid.NewGuid().ToString("N").ToString()); parentActivity.AddBaggage("moreBaggage", Guid.NewGuid().ToString("N").ToString()); parentActivity.AddTag("tag", "tag"); // add tag to ensure it is not injected into request + parentActivity.TraceStateString = "Foo"; parentActivity.Start(); @@ -344,7 +345,7 @@ public void SendAsync_ExpectedDiagnosticSourceActivityLogging(ActivityIdFormat i activityStopResponseLogged = GetProperty(kvp.Value, "Response"); TaskStatus requestStatus = GetProperty(kvp.Value, "RequestTaskStatus"); Assert.Equal(TaskStatus.RanToCompletion, requestStatus); - activityStopTcs.SetResult();; + activityStopTcs.SetResult(); } }); @@ -403,13 +404,10 @@ public void SendAsync_ExpectedDiagnosticSourceActivityLogging_InvalidBaggage() HttpRequestMessage request = GetProperty(kvp.Value, "Request"); Assert.True(request.Headers.TryGetValues("Request-Id", out var requestId)); Assert.True(request.Headers.TryGetValues("Correlation-Context", out var correlationContext)); - Assert.Equal(3, correlationContext.Count()); - Assert.Contains("key=value", correlationContext); - Assert.Contains("bad%2Fkey=value", correlationContext); - Assert.Contains("goodkey=bad%2Fvalue", correlationContext); + Assert.Equal("key=value, goodkey=bad%2Fvalue, bad%2Fkey=value", Assert.Single(correlationContext)); TaskStatus requestStatus = GetProperty(kvp.Value, "RequestTaskStatus"); Assert.Equal(TaskStatus.RanToCompletion, requestStatus); - activityStopTcs.SetResult();; + activityStopTcs.SetResult(); } else if (kvp.Key.Equals("System.Net.Http.Exception")) { @@ -467,7 +465,7 @@ public void SendAsync_ExpectedDiagnosticSourceActivityLoggingDoesNotOverwriteHea Assert.False(request.Headers.TryGetValues("traceparent", out var _)); Assert.False(request.Headers.TryGetValues("tracestate", out var _)); - activityStopTcs.SetResult();; + activityStopTcs.SetResult(); } }); @@ -519,7 +517,7 @@ public void SendAsync_ExpectedDiagnosticSourceActivityLoggingDoesNotOverwriteW3C } else if (kvp.Key.Equals("System.Net.Http.HttpRequestOut.Stop")) { - activityStopTcs.SetResult();; + activityStopTcs.SetResult(); } }); @@ -608,7 +606,7 @@ public void SendAsync_ExpectedDiagnosticExceptionActivityLogging() GetProperty(kvp.Value, "Request"); TaskStatus requestStatus = GetProperty(kvp.Value, "RequestTaskStatus"); Assert.Equal(TaskStatus.Faulted, requestStatus); - activityStopTcs.SetResult();; + activityStopTcs.SetResult(); } else if (kvp.Key.Equals("System.Net.Http.Exception")) { @@ -647,7 +645,7 @@ public void SendAsync_ExpectedDiagnosticSynchronousExceptionActivityLogging() GetProperty(kvp.Value, "Request"); TaskStatus requestStatus = GetProperty(kvp.Value, "RequestTaskStatus"); Assert.Equal(TaskStatus.Faulted, requestStatus); - activityStopTcs.SetResult();; + activityStopTcs.SetResult(); } else if (kvp.Key.Equals("System.Net.Http.Exception")) { @@ -796,42 +794,49 @@ public void SendAsync_ExpectedDiagnosticExceptionOnlyActivityLogging() }, UseVersion.ToString(), TestAsync.ToString()).Dispose(); } - [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - public void SendAsync_ExpectedActivityPropagationWithoutListener() + public static IEnumerable UseSocketsHttpHandler_WithIdFormat_MemberData() { - RemoteExecutor.Invoke(async (useVersion, testAsync) => - { - Activity parent = new Activity("parent").Start(); + yield return new object[] { true, ActivityIdFormat.Hierarchical }; + yield return new object[] { true, ActivityIdFormat.W3C }; + yield return new object[] { false, ActivityIdFormat.Hierarchical }; + yield return new object[] { false, ActivityIdFormat.W3C }; + } - await GetFactoryForVersion(useVersion).CreateClientAndServerAsync( - async uri => - { - await GetAsync(useVersion, testAsync, uri); - }, - async server => - { - HttpRequestData requestData = await server.AcceptConnectionSendResponseAndCloseAsync(); - AssertHeadersAreInjected(requestData, parent); - }); - }, UseVersion.ToString(), TestAsync.ToString()).Dispose(); + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [MemberData(nameof(UseSocketsHttpHandler_WithIdFormat_MemberData))] + public async Task SendAsync_ExpectedActivityPropagationWithoutListener(bool useSocketsHttpHandler, ActivityIdFormat idFormat) + { + Activity parent = new Activity("parent"); + parent.SetIdFormat(idFormat); + parent.Start(); + + await GetFactoryForVersion(UseVersion).CreateClientAndServerAsync( + async uri => + { + await GetAsync(UseVersion.ToString(), TestAsync.ToString(), uri, useSocketsHttpHandler: useSocketsHttpHandler); + }, + async server => + { + HttpRequestData requestData = await server.HandleRequestAsync(); + AssertHeadersAreInjected(requestData, parent); + }); } - [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - public void SendAsync_ExpectedActivityPropagationWithoutListenerOrParentActivity() + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [InlineData(true)] + [InlineData(false)] + public async Task SendAsync_ExpectedActivityPropagationWithoutListenerOrParentActivity(bool useSocketsHttpHandler) { - RemoteExecutor.Invoke(async (useVersion, testAsync) => - { - await GetFactoryForVersion(useVersion).CreateClientAndServerAsync( - async uri => - { - await GetAsync(useVersion, testAsync, uri); - }, - async server => - { - HttpRequestData requestData = await server.AcceptConnectionSendResponseAndCloseAsync(); - AssertNoHeadersAreInjected(requestData); - }); - }, UseVersion.ToString(), TestAsync.ToString()).Dispose(); + await GetFactoryForVersion(UseVersion).CreateClientAndServerAsync( + async uri => + { + await GetAsync(UseVersion.ToString(), TestAsync.ToString(), uri, useSocketsHttpHandler: useSocketsHttpHandler); + }, + async server => + { + HttpRequestData requestData = await server.HandleRequestAsync(); + AssertNoHeadersAreInjected(requestData); + }); } [ConditionalTheory(nameof(EnableActivityPropagationEnvironmentVariableIsNotSetAndRemoteExecutorSupported))] @@ -877,6 +882,56 @@ await GetFactoryForVersion(useVersion).CreateClientAndServerAsync( }, UseVersion.ToString(), TestAsync.ToString(), envVarValue).Dispose(); } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [MemberData(nameof(UseSocketsHttpHandler_WithIdFormat_MemberData))] + public async Task SendAsync_HeadersAreInjectedOnRedirects(bool useSocketsHttpHandler, ActivityIdFormat idFormat) + { + Activity parent = new Activity("parent"); + parent.SetIdFormat(idFormat); + parent.TraceStateString = "Foo"; + parent.Start(); + + await GetFactoryForVersion(UseVersion).CreateServerAsync(async (originalServer, originalUri) => + { + await GetFactoryForVersion(UseVersion).CreateServerAsync(async (redirectServer, redirectUri) => + { + Task clientTask = GetAsync(UseVersion.ToString(), TestAsync.ToString(), originalUri, useSocketsHttpHandler: useSocketsHttpHandler); + + Task serverTask = originalServer.HandleRequestAsync(HttpStatusCode.Redirect, new[] { new HttpHeaderData("Location", redirectUri.AbsoluteUri) }); + + await Task.WhenAny(clientTask, serverTask); + Assert.False(clientTask.IsCompleted, $"{clientTask.Status}: {clientTask.Exception}"); + HttpRequestData firstRequestData = await serverTask; + AssertHeadersAreInjected(firstRequestData, parent); + + serverTask = redirectServer.HandleRequestAsync(); + await TestHelper.WhenAllCompletedOrAnyFailed(clientTask, serverTask); + HttpRequestData secondRequestData = await serverTask; + AssertHeadersAreInjected(secondRequestData, parent); + + if (idFormat == ActivityIdFormat.W3C) + { + string firstParent = GetHeaderValue(firstRequestData, "traceparent"); + string firstState = GetHeaderValue(firstRequestData, "tracestate"); + Assert.True(ActivityContext.TryParse(firstParent, firstState, out ActivityContext firstContext)); + + string secondParent = GetHeaderValue(secondRequestData, "traceparent"); + string secondState = GetHeaderValue(secondRequestData, "tracestate"); + Assert.True(ActivityContext.TryParse(secondParent, secondState, out ActivityContext secondContext)); + + Assert.Equal(firstContext.TraceId, secondContext.TraceId); + Assert.Equal(firstContext.TraceFlags, secondContext.TraceFlags); + Assert.Equal(firstContext.TraceState, secondContext.TraceState); + Assert.NotEqual(firstContext.SpanId, secondContext.SpanId); + } + else + { + Assert.NotEqual(GetHeaderValue(firstRequestData, "Request-Id"), GetHeaderValue(secondRequestData, "Request-Id")); + } + }); + }); + } + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] [InlineData(true)] [InlineData(false)] @@ -893,12 +948,56 @@ await GetFactoryForVersion(useVersion).CreateClientAndServerAsync( (HttpRequestMessage request, _) = await GetAsync(useVersion, testAsync, uri); string headerName = parent.IdFormat == ActivityIdFormat.Hierarchical ? "Request-Id" : "traceparent"; + Assert.Equal(bool.Parse(switchValue), request.Headers.Contains(headerName)); }, async server => await server.HandleRequestAsync()); }, UseVersion.ToString(), TestAsync.ToString(), switchValue.ToString()).Dispose(); } + public static IEnumerable SocketsHttpHandlerPropagators_WithIdFormat_MemberData() + { + foreach (var propagator in new[] { null, DistributedContextPropagator.CreateDefaultPropagator(), DistributedContextPropagator.CreateNoOutputPropagator(), DistributedContextPropagator.CreatePassThroughPropagator() }) + { + foreach (ActivityIdFormat format in new[] { ActivityIdFormat.Hierarchical, ActivityIdFormat.W3C }) + { + yield return new object[] { propagator, format }; + } + } + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [MemberData(nameof(SocketsHttpHandlerPropagators_WithIdFormat_MemberData))] + public async Task SendAsync_CustomSocketsHttpHandlerPropagator_PropagatorIsUsed(DistributedContextPropagator propagator, ActivityIdFormat idFormat) + { + Activity parent = new Activity("parent"); + parent.SetIdFormat(idFormat); + parent.Start(); + + await GetFactoryForVersion(UseVersion).CreateClientAndServerAsync( + async uri => + { + using var handler = new SocketsHttpHandler { ActivityHeadersPropagator = propagator }; + handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; }; + using var client = new HttpClient(handler); + var request = CreateRequest(HttpMethod.Get, uri, UseVersion, exactVersion: true); + await client.SendAsync(TestAsync, request); + }, + async server => + { + HttpRequestData requestData = await server.HandleRequestAsync(); + + if (propagator is null || ReferenceEquals(propagator, DistributedContextPropagator.CreateNoOutputPropagator())) + { + AssertNoHeadersAreInjected(requestData); + } + else + { + AssertHeadersAreInjected(requestData, parent, ReferenceEquals(propagator, DistributedContextPropagator.CreatePassThroughPropagator())); + } + }); + } + private static T GetProperty(object obj, string propertyName) { Type t = obj.GetType(); @@ -925,7 +1024,7 @@ private static void AssertNoHeadersAreInjected(HttpRequestData request) Assert.Null(GetHeaderValue(request, "Correlation-Context")); } - private static void AssertHeadersAreInjected(HttpRequestData request, Activity parent) + private static void AssertHeadersAreInjected(HttpRequestData request, Activity parent, bool passthrough = false) { string requestId = GetHeaderValue(request, "Request-Id"); string traceparent = GetHeaderValue(request, "traceparent"); @@ -935,7 +1034,7 @@ private static void AssertHeadersAreInjected(HttpRequestData request, Activity p { Assert.True(requestId != null, "Request-Id was not injected when instrumentation was enabled"); Assert.StartsWith(parent.Id, requestId); - Assert.NotEqual(parent.Id, requestId); + Assert.Equal(passthrough, parent.Id == requestId); Assert.Null(traceparent); Assert.Null(tracestate); } @@ -944,6 +1043,7 @@ private static void AssertHeadersAreInjected(HttpRequestData request, Activity p Assert.Null(requestId); Assert.True(traceparent != null, "traceparent was not injected when W3C instrumentation was enabled"); Assert.StartsWith($"00-{parent.TraceId.ToHexString()}-", traceparent); + Assert.Equal(passthrough, parent.Id == traceparent); Assert.Equal(parent.TraceStateString, tracestate); } @@ -960,10 +1060,23 @@ private static void AssertHeadersAreInjected(HttpRequestData request, Activity p } } - private static async Task<(HttpRequestMessage, HttpResponseMessage)> GetAsync(string useVersion, string testAsync, Uri uri, CancellationToken cancellationToken = default) + private static async Task<(HttpRequestMessage, HttpResponseMessage)> GetAsync(string useVersion, string testAsync, Uri uri, CancellationToken cancellationToken = default, bool useSocketsHttpHandler = false) { - HttpClientHandler handler = CreateHttpClientHandler(useVersion); - handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates; + HttpMessageHandler handler; + if (useSocketsHttpHandler) + { + var socketsHttpHandler = new SocketsHttpHandler(); + socketsHttpHandler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; }; + handler = socketsHttpHandler; + } + else + { + handler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates + }; + } + using var client = new HttpClient(handler); var request = CreateRequest(HttpMethod.Get, uri, Version.Parse(useVersion), exactVersion: true); return (request, await client.SendAsync(bool.Parse(testAsync), request, cancellationToken)); From bf0fdf0a78a1411f5abed5d892dcac2ba21899e1 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 13 Jul 2021 20:10:24 -0400 Subject: [PATCH 104/133] Fix Task.WhenAny failure mode when passed ICollection of zero tasks (#55580) --- .../src/System/Threading/Tasks/Task.cs | 8 +++++++- .../System.Threading.Tasks/tests/MethodCoverage.cs | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs index a64dc0084299..89fab991d734 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -6305,8 +6305,14 @@ public static Task WhenAny(IEnumerable tasks) return WhenAny(taskArray); } + int count = taskCollection.Count; + if (count <= 0) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_EmptyTaskList, ExceptionArgument.tasks); + } + int index = 0; - taskArray = new Task[taskCollection.Count]; + taskArray = new Task[count]; foreach (Task task in tasks) { if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); diff --git a/src/libraries/System.Threading.Tasks/tests/MethodCoverage.cs b/src/libraries/System.Threading.Tasks/tests/MethodCoverage.cs index e696a38cf924..923875f6e43c 100644 --- a/src/libraries/System.Threading.Tasks/tests/MethodCoverage.cs +++ b/src/libraries/System.Threading.Tasks/tests/MethodCoverage.cs @@ -188,6 +188,20 @@ public static void Task_WhenAny_TwoTasks_InvalidArgs_Throws() AssertExtensions.Throws("task2", () => Task.WhenAny(Task.FromResult(2), null)); } + [Fact] + public static void Task_WhenAny_NoTasks_Throws() + { + AssertExtensions.Throws("tasks", () => { Task.WhenAny(new Task[0]); }); + AssertExtensions.Throws("tasks", () => { Task.WhenAny(new List()); }); + AssertExtensions.Throws("tasks", () => { Task.WhenAny(EmptyIterator()); }); + + AssertExtensions.Throws("tasks", () => { Task.WhenAny(new Task[0]); }); + AssertExtensions.Throws("tasks", () => { Task.WhenAny(new List>()); }); + AssertExtensions.Throws("tasks", () => { Task.WhenAny(EmptyIterator>()); }); + + static IEnumerable EmptyIterator() { yield break; } + } + [Fact] public static async Task Task_WhenAny_TwoTasks_OnePreCompleted() { From 3e8ef855c496b1aa1b884625ca90e5db75457d22 Mon Sep 17 00:00:00 2001 From: Alexander Nikolaev <55398552+alnikola@users.noreply.github.com> Date: Wed, 14 Jul 2021 02:10:52 +0200 Subject: [PATCH 105/133] Enable Http2_MultipleConnectionsEnabled_ConnectionLimitNotReached_ConcurrentRequestsSuccessfullyHandled (#55572) * Enable Http2_MultipleConnectionsEnabled_ConnectionLimitNotReached_ConcurrentRequestsSuccessfullyHandled * Update SocketsHttpHandlerTest.cs --- .../tests/FunctionalTests/SocketsHttpHandlerTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index b326c8bbb021..1e364ea96651 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -2078,7 +2078,6 @@ public sealed class SocketsHttpHandlerTest_Http2 : HttpClientHandlerTest_Http2 public SocketsHttpHandlerTest_Http2(ITestOutputHelper output) : base(output) { } [ConditionalFact(nameof(SupportsAlpn))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/41078")] public async Task Http2_MultipleConnectionsEnabled_ConnectionLimitNotReached_ConcurrentRequestsSuccessfullyHandled() { const int MaxConcurrentStreams = 2; From 69a4f832e5cc7ed449a1be5bbb6d3a720e2f30fa Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 14 Jul 2021 08:11:58 +0800 Subject: [PATCH 106/133] Address System.Net.Http.WinHttpHandler's nullable warnings targeting .NETCoreApp (#54995) * Update nullability for WinHttpHandler * Update project file * Use ! for PtrToStringUni * Move assertion of _sessionHandle * Remove false assertion about RequestHandle. --- .../Windows/WinHttp/Interop.winhttp.cs | 10 +-- .../src/System.Net.Http.WinHttpHandler.csproj | 2 - .../src/System/Net/Http/WinHttpAuthHelper.cs | 49 +++++++---- .../Net/Http/WinHttpCertificateHelper.cs | 4 +- .../System/Net/Http/WinHttpChannelBinding.cs | 4 +- .../Net/Http/WinHttpCookieContainerAdapter.cs | 25 ++++-- .../src/System/Net/Http/WinHttpHandler.cs | 82 +++++++++++++------ .../System/Net/Http/WinHttpRequestCallback.cs | 15 ++-- .../System/Net/Http/WinHttpRequestState.cs | 30 +++---- .../System/Net/Http/WinHttpRequestStream.cs | 1 + .../Net/Http/WinHttpResponseHeaderReader.cs | 3 +- .../System/Net/Http/WinHttpResponseParser.cs | 18 ++-- .../System/Net/Http/WinHttpResponseStream.cs | 6 +- .../src/System/Net/Http/WinHttpTraceHelper.cs | 4 +- .../Net/Http/WinHttpTransportContext.cs | 4 +- 15 files changed, 163 insertions(+), 94 deletions(-) diff --git a/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp.cs b/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp.cs index 7ac85b3155bb..82a8218338ca 100644 --- a/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp.cs +++ b/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp.cs @@ -13,8 +13,8 @@ internal static partial class WinHttp public static extern SafeWinHttpHandle WinHttpOpen( IntPtr userAgent, uint accessType, - string proxyName, - string proxyBypass, int flags); + string? proxyName, + string? proxyBypass, int flags); [DllImport(Interop.Libraries.WinHttp, CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] @@ -33,7 +33,7 @@ public static extern SafeWinHttpHandle WinHttpOpenRequest( SafeWinHttpHandle connectHandle, string verb, string objectName, - string version, + string? version, string referrer, string acceptTypes, uint flags); @@ -161,8 +161,8 @@ public static extern bool WinHttpSetCredentials( SafeWinHttpHandle requestHandle, uint authTargets, uint authScheme, - string userName, - string password, + string? userName, + string? password, IntPtr reserved); [DllImport(Interop.Libraries.WinHttp, CharSet = CharSet.Unicode, SetLastError = true)] diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj b/src/libraries/System.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj index 525d50608ff7..99e54a8c729c 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj @@ -10,8 +10,6 @@ SR.PlatformNotSupported_WinHttpHandler - - annotations diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpAuthHelper.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpAuthHelper.cs index f659c9c2f07e..b0b6d86c6754 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpAuthHelper.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpAuthHelper.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; - +using System.Diagnostics.CodeAnalysis; using SafeWinHttpHandle = Interop.WinHttp.SafeWinHttpHandle; namespace System.Net.Http @@ -14,7 +14,7 @@ internal sealed class WinHttpAuthHelper // WINHTTP_AUTH_SCHEME_NTLM = 0x00000002; // WINHTTP_AUTH_SCHEME_DIGEST = 0x00000008; // WINHTTP_AUTH_SCHEME_NEGOTIATE = 0x00000010; - private static readonly string[] s_authSchemeStringMapping = + private static readonly string?[] s_authSchemeStringMapping = { null, "Basic", @@ -54,6 +54,10 @@ public void CheckResponseForAuthentication( uint supportedSchemes = 0; uint firstSchemeIgnored = 0; uint authTarget = 0; + + Debug.Assert(state.RequestMessage != null); + Debug.Assert(state.RequestMessage.RequestUri != null); + Debug.Assert(state.RequestHandle != null); Uri uri = state.RequestMessage.RequestUri; state.RetryRequest = false; @@ -121,7 +125,7 @@ public void CheckResponseForAuthentication( state.LastStatusCode = statusCode; // If we don't have any proxy credentials to try, then we end up with 407. - ICredentials proxyCreds = state.Proxy == null ? + ICredentials? proxyCreds = state.Proxy == null ? state.DefaultProxyCredentials : state.Proxy.Credentials; if (proxyCreds == null) @@ -161,6 +165,7 @@ public void CheckResponseForAuthentication( default: if (state.PreAuthenticate && serverAuthScheme != 0) { + Debug.Assert(state.ServerCredentials != null); SaveServerCredentialsToCache(uri, serverAuthScheme, state.ServerCredentials); } break; @@ -169,6 +174,10 @@ public void CheckResponseForAuthentication( public void PreAuthenticateRequest(WinHttpRequestState state, uint proxyAuthScheme) { + Debug.Assert(state.RequestHandle != null); + Debug.Assert(state.RequestMessage != null); + Debug.Assert(state.RequestMessage.RequestUri != null); + // Set proxy credentials if we have them. // If a proxy authentication challenge was responded to, reset // those credentials before each SendRequest, because the proxy @@ -177,8 +186,9 @@ public void PreAuthenticateRequest(WinHttpRequestState state, uint proxyAuthSche // 407-401-407-401- loop. if (proxyAuthScheme != 0) { - ICredentials proxyCredentials; - Uri proxyUri; + ICredentials? proxyCredentials; + Uri? proxyUri; + if (state.Proxy != null) { proxyCredentials = state.Proxy.Credentials; @@ -190,6 +200,9 @@ public void PreAuthenticateRequest(WinHttpRequestState state, uint proxyAuthSche proxyUri = state.RequestMessage.RequestUri; } + Debug.Assert(proxyCredentials != null); + Debug.Assert(proxyUri != null); + SetWinHttpCredential( state.RequestHandle, proxyCredentials, @@ -202,7 +215,7 @@ public void PreAuthenticateRequest(WinHttpRequestState state, uint proxyAuthSche if (state.PreAuthenticate) { uint authScheme; - NetworkCredential serverCredentials; + NetworkCredential? serverCredentials; if (GetServerCredentialsFromCache( state.RequestMessage.RequestUri, out authScheme, @@ -226,18 +239,18 @@ public void PreAuthenticateRequest(WinHttpRequestState state, uint proxyAuthSche public bool GetServerCredentialsFromCache( Uri uri, out uint serverAuthScheme, - out NetworkCredential serverCredentials) + [NotNullWhen(true)] out NetworkCredential? serverCredentials) { serverAuthScheme = 0; serverCredentials = null; - NetworkCredential cred = null; + NetworkCredential? cred = null; lock (_credentialCacheLock) { foreach (uint authScheme in s_authSchemePriorityOrder) { - cred = _credentialCache.GetCredential(uri, s_authSchemeStringMapping[authScheme]); + cred = _credentialCache.GetCredential(uri, s_authSchemeStringMapping[authScheme]!); if (cred != null) { serverAuthScheme = authScheme; @@ -253,10 +266,10 @@ public bool GetServerCredentialsFromCache( public void SaveServerCredentialsToCache(Uri uri, uint authScheme, ICredentials serverCredentials) { - string authType = s_authSchemeStringMapping[authScheme]; + string? authType = s_authSchemeStringMapping[authScheme]; Debug.Assert(!string.IsNullOrEmpty(authType)); - NetworkCredential cred = serverCredentials.GetCredential(uri, authType); + NetworkCredential? cred = serverCredentials.GetCredential(uri, authType); if (cred != null) { lock (_credentialCacheLock) @@ -303,15 +316,17 @@ private bool SetWinHttpCredential( uint authScheme, uint authTarget) { - string userName; - string password; + string? userName; + string? password; Debug.Assert(credentials != null); Debug.Assert(authScheme != 0); Debug.Assert(authTarget == Interop.WinHttp.WINHTTP_AUTH_TARGET_PROXY || authTarget == Interop.WinHttp.WINHTTP_AUTH_TARGET_SERVER); - NetworkCredential networkCredential = credentials.GetCredential(uri, s_authSchemeStringMapping[authScheme]); + string? authType = s_authSchemeStringMapping[authScheme]; + Debug.Assert(!string.IsNullOrEmpty(authType)); + NetworkCredential? networkCredential = credentials.GetCredential(uri, authType); if (networkCredential == null) { @@ -367,7 +382,7 @@ private bool SetWinHttpCredential( return true; } - private static uint ChooseAuthScheme(uint supportedSchemes, Uri uri, ICredentials credentials) + private static uint ChooseAuthScheme(uint supportedSchemes, Uri? uri, ICredentials? credentials) { if (credentials == null) { @@ -383,9 +398,11 @@ private static uint ChooseAuthScheme(uint supportedSchemes, Uri uri, ICredential return 0; } + Debug.Assert(uri != null); + foreach (uint authScheme in s_authSchemePriorityOrder) { - if ((supportedSchemes & authScheme) != 0 && credentials.GetCredential(uri, s_authSchemeStringMapping[authScheme]) != null) + if ((supportedSchemes & authScheme) != 0 && credentials.GetCredential(uri, s_authSchemeStringMapping[authScheme]!) != null) { return authScheme; } diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpCertificateHelper.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpCertificateHelper.cs index da4baadb3590..472116113fac 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpCertificateHelper.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpCertificateHelper.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Net.Security; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -20,7 +21,6 @@ public static void BuildChain( out X509Chain chain, out SslPolicyErrors sslPolicyErrors) { - chain = null; sslPolicyErrors = SslPolicyErrors.None; // Build the chain. @@ -69,6 +69,8 @@ public static void BuildChain( Interop.Crypt32.CertChainPolicyIgnoreFlags.CERT_CHAIN_POLICY_IGNORE_ALL & ~Interop.Crypt32.CertChainPolicyIgnoreFlags.CERT_CHAIN_POLICY_IGNORE_INVALID_NAME_FLAG; + Debug.Assert(chain.SafeHandle != null); + Interop.Crypt32.CERT_CHAIN_POLICY_STATUS status = default; status.cbSize = (uint)sizeof(Interop.Crypt32.CERT_CHAIN_POLICY_STATUS); if (Interop.Crypt32.CertVerifyCertificateChainPolicy( diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpChannelBinding.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpChannelBinding.cs index 8011f2859d0d..9d3dbc814536 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpChannelBinding.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpChannelBinding.cs @@ -14,7 +14,7 @@ namespace System.Net.Http internal sealed class WinHttpChannelBinding : ChannelBinding { private int _size; - private string _cachedToString; + private string? _cachedToString; internal WinHttpChannelBinding(SafeWinHttpHandle requestHandle) { @@ -56,7 +56,7 @@ public override int Size } } - public override string ToString() + public override string? ToString() { if (_cachedToString == null && !IsInvalid) { diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpCookieContainerAdapter.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpCookieContainerAdapter.cs index ff0ac8798aee..739995f5e732 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpCookieContainerAdapter.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpCookieContainerAdapter.cs @@ -15,17 +15,21 @@ internal static class WinHttpCookieContainerAdapter public static void AddResponseCookiesToContainer(WinHttpRequestState state) { - HttpRequestMessage request = state.RequestMessage; - SafeWinHttpHandle requestHandle = state.RequestHandle; - CookieContainer cookieContainer = state.Handler.CookieContainer; + HttpRequestMessage? request = state.RequestMessage; + SafeWinHttpHandle? requestHandle = state.RequestHandle; + Debug.Assert(state.Handler != null); + CookieContainer? cookieContainer = state.Handler.CookieContainer; Debug.Assert(state.Handler.CookieUsePolicy == CookieUsePolicy.UseSpecifiedCookieContainer); + Debug.Assert(request != null); + Debug.Assert(requestHandle != null); Debug.Assert(cookieContainer != null); + Debug.Assert(request.RequestUri != null); // Get 'Set-Cookie' headers from response. - char[] buffer = null; + char[]? buffer = null; uint index = 0; - string cookieHeader; + string? cookieHeader; while (WinHttpResponseParser.GetResponseHeader( requestHandle, Interop.WinHttp.WINHTTP_QUERY_SET_COOKIE, ref buffer, ref index, out cookieHeader)) { @@ -44,9 +48,12 @@ public static void AddResponseCookiesToContainer(WinHttpRequestState state) public static void ResetCookieRequestHeaders(WinHttpRequestState state, Uri redirectUri) { - SafeWinHttpHandle requestHandle = state.RequestHandle; + SafeWinHttpHandle? requestHandle = state.RequestHandle; + Debug.Assert(state.Handler != null); Debug.Assert(state.Handler.CookieUsePolicy == CookieUsePolicy.UseSpecifiedCookieContainer); + Debug.Assert(state.Handler.CookieContainer != null); + Debug.Assert(requestHandle != null); // Clear cookies. if (!Interop.WinHttp.WinHttpAddRequestHeaders( @@ -64,7 +71,7 @@ public static void ResetCookieRequestHeaders(WinHttpRequestState state, Uri redi // Re-add cookies. The GetCookieHeader() method will return the correct set of // cookies based on the redirectUri. - string cookieHeader = GetCookieHeader(redirectUri, state.Handler.CookieContainer); + string? cookieHeader = GetCookieHeader(redirectUri, state.Handler.CookieContainer); if (!string.IsNullOrEmpty(cookieHeader)) { if (!Interop.WinHttp.WinHttpAddRequestHeaders( @@ -78,9 +85,9 @@ public static void ResetCookieRequestHeaders(WinHttpRequestState state, Uri redi } } - public static string GetCookieHeader(Uri uri, CookieContainer cookies) + public static string? GetCookieHeader(Uri uri, CookieContainer cookies) { - string cookieHeader = null; + string? cookieHeader = null; Debug.Assert(cookies != null); diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs index 9ce26b2716d2..93781b803cc7 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs @@ -46,16 +46,16 @@ public class WinHttpHandler : HttpMessageHandler private static readonly StringWithQualityHeaderValue s_deflateHeaderValue = new StringWithQualityHeaderValue("deflate"); [ThreadStatic] - private static StringBuilder t_requestHeadersBuilder; + private static StringBuilder? t_requestHeadersBuilder; private readonly object _lockObject = new object(); private bool _doManualDecompressionCheck; - private WinInetProxyHelper _proxyHelper; + private WinInetProxyHelper? _proxyHelper; private bool _automaticRedirection = HttpHandlerDefaults.DefaultAutomaticRedirection; private int _maxAutomaticRedirections = HttpHandlerDefaults.DefaultMaxAutomaticRedirections; private DecompressionMethods _automaticDecompression = HttpHandlerDefaults.DefaultAutomaticDecompression; private CookieUsePolicy _cookieUsePolicy = CookieUsePolicy.UseInternalCookieStoreOnly; - private CookieContainer _cookieContainer; + private CookieContainer? _cookieContainer; private bool _enableMultipleHttp2Connections; private SslProtocols _sslProtocols = SslProtocols.None; // Use most secure protocols available. @@ -64,15 +64,15 @@ private Func< X509Certificate2, X509Chain, SslPolicyErrors, - bool> _serverCertificateValidationCallback; + bool>? _serverCertificateValidationCallback; private bool _checkCertificateRevocationList; private ClientCertificateOption _clientCertificateOption = ClientCertificateOption.Manual; - private X509Certificate2Collection _clientCertificates; // Only create collection when required. - private ICredentials _serverCredentials; + private X509Certificate2Collection? _clientCertificates; // Only create collection when required. + private ICredentials? _serverCredentials; private bool _preAuthenticate; private WindowsProxyUsePolicy _windowsProxyUsePolicy = WindowsProxyUsePolicy.UseWinHttpProxy; - private ICredentials _defaultProxyCredentials; - private IWebProxy _proxy; + private ICredentials? _defaultProxyCredentials; + private IWebProxy? _proxy; private int _maxConnectionsPerServer = int.MaxValue; private TimeSpan _sendTimeout = TimeSpan.FromSeconds(30); private TimeSpan _receiveHeadersTimeout = TimeSpan.FromSeconds(30); @@ -86,10 +86,10 @@ private Func< private int _maxResponseHeadersLength = HttpHandlerDefaults.DefaultMaxResponseHeadersLength; private int _maxResponseDrainSize = 64 * 1024; - private IDictionary _properties; // Only create dictionary when required. + private IDictionary? _properties; // Only create dictionary when required. private volatile bool _operationStarted; private volatile bool _disposed; - private SafeWinHttpHandle _sessionHandle; + private SafeWinHttpHandle? _sessionHandle; private readonly WinHttpAuthHelper _authHelper = new WinHttpAuthHelper(); public WinHttpHandler() @@ -628,8 +628,8 @@ protected override Task SendAsync( Task.Factory.StartNew(s => { - var whrs = (WinHttpRequestState)s; - _ = whrs.Handler.StartRequestAsync(whrs); + var whrs = (WinHttpRequestState)s!; + _ = whrs.Handler!.StartRequestAsync(whrs); }, state, CancellationToken.None, @@ -649,7 +649,7 @@ private static WinHttpChunkMode GetChunkedModeForSend(HttpRequestMessage request chunkedMode = WinHttpChunkMode.Manual; } - HttpContent requestContent = requestMessage.Content; + HttpContent? requestContent = requestMessage.Content; if (requestContent != null) { if (requestContent.Headers.ContentLength.HasValue) @@ -697,12 +697,14 @@ private static WinHttpChunkMode GetChunkedModeForSend(HttpRequestMessage request private static void AddRequestHeaders( SafeWinHttpHandle requestHandle, HttpRequestMessage requestMessage, - CookieContainer cookies, + CookieContainer? cookies, DecompressionMethods manuallyProcessedDecompressionMethods) { + Debug.Assert(requestMessage.RequestUri != null); + // Get a StringBuilder to use for creating the request headers. // We cache one in TLS to avoid creating a new one for each request. - StringBuilder requestHeadersBuffer = t_requestHeadersBuilder; + StringBuilder? requestHeadersBuffer = t_requestHeadersBuilder; if (requestHeadersBuffer != null) { requestHeadersBuffer.Clear(); @@ -735,7 +737,7 @@ private static void AddRequestHeaders( // Manually add cookies. if (cookies != null && cookies.Count > 0) { - string cookieHeader = WinHttpCookieContainerAdapter.GetCookieHeader(requestMessage.RequestUri, cookies); + string? cookieHeader = WinHttpCookieContainerAdapter.GetCookieHeader(requestMessage.RequestUri, cookies); if (!string.IsNullOrEmpty(cookieHeader)) { requestHeadersBuffer.AppendLine(cookieHeader); @@ -789,6 +791,8 @@ private void EnsureSessionHandleExists(WinHttpRequestState state) if (state.WindowsProxyUsePolicy == WindowsProxyUsePolicy.UseCustomProxy) { Debug.Assert(state.Proxy != null); + Debug.Assert(state.RequestMessage != null); + Debug.Assert(state.RequestMessage.RequestUri != null); try { state.Proxy.GetProxy(state.RequestMessage.RequestUri); @@ -878,6 +882,11 @@ private void EnsureSessionHandleExists(WinHttpRequestState state) private async Task StartRequestAsync(WinHttpRequestState state) { + Debug.Assert(state.RequestMessage != null); + Debug.Assert(state.RequestMessage.RequestUri != null); + Debug.Assert(state.Handler != null); + Debug.Assert(state.Tcs != null); + if (state.CancellationToken.IsCancellationRequested) { state.Tcs.TrySetCanceled(state.CancellationToken); @@ -885,11 +894,12 @@ private async Task StartRequestAsync(WinHttpRequestState state) return; } - Task sendRequestBodyTask = null; - SafeWinHttpHandle connectHandle = null; + Task? sendRequestBodyTask = null; + SafeWinHttpHandle? connectHandle = null; try { EnsureSessionHandleExists(state); + Debug.Assert(_sessionHandle != null); SetEnableHttp2PlusClientCertificate(state.RequestMessage.RequestUri, state.RequestMessage.Version); @@ -904,7 +914,7 @@ private async Task StartRequestAsync(WinHttpRequestState state) // Try to use the requested version if a known/supported version was explicitly requested. // Otherwise, we simply use winhttp's default. - string httpVersion = null; + string? httpVersion = null; if (state.RequestMessage.Version == HttpVersion.Version10) { httpVersion = "HTTP/1.0"; @@ -940,7 +950,7 @@ private async Task StartRequestAsync(WinHttpRequestState state) // will have the side-effect of WinHTTP cancelling any pending I/O and accelerating its callbacks // on the handle and thus releasing the awaiting tasks in the loop below. This helps to provide // a more timely, cooperative, cancellation pattern. - using (state.CancellationToken.Register(s => ((WinHttpRequestState)s).RequestHandle.Dispose(), state)) + using (state.CancellationToken.Register(s => ((WinHttpRequestState)s!).RequestHandle!.Dispose(), state)) { do { @@ -1042,8 +1052,10 @@ private async Task StartRequestAsync(WinHttpRequestState state) } } - private void OpenRequestHandle(WinHttpRequestState state, SafeWinHttpHandle connectHandle, string httpVersion, out WinHttpChunkMode chunkedModeForSend, out SafeWinHttpHandle requestHandle) + private void OpenRequestHandle(WinHttpRequestState state, SafeWinHttpHandle connectHandle, string? httpVersion, out WinHttpChunkMode chunkedModeForSend, out SafeWinHttpHandle requestHandle) { + Debug.Assert(state.RequestMessage != null); + Debug.Assert(state.RequestMessage.RequestUri != null); chunkedModeForSend = GetChunkedModeForSend(state.RequestMessage); // Create an HTTP request handle. @@ -1090,7 +1102,7 @@ static uint GetRequestFlags(WinHttpRequestState state, WinHttpChunkMode chunkedM // .NET Framework behavior. System.Uri establishes the baseline rules for percent-encoding // of reserved characters. uint flags = Interop.WinHttp.WINHTTP_FLAG_ESCAPE_DISABLE; - if (state.RequestMessage.RequestUri.Scheme == UriScheme.Https) + if (state.RequestMessage!.RequestUri!.Scheme == UriScheme.Https) { flags |= Interop.WinHttp.WINHTTP_FLAG_SECURE; } @@ -1204,6 +1216,10 @@ private void SetSessionHandleTimeoutOptions(SafeWinHttpHandle sessionHandle) private void SetRequestHandleOptions(WinHttpRequestState state) { + Debug.Assert(state.RequestHandle != null); + Debug.Assert(state.RequestMessage != null); + Debug.Assert(state.RequestMessage.RequestUri != null); + SetRequestHandleProxyOptions(state); SetRequestHandleDecompressionOptions(state.RequestHandle); SetRequestHandleRedirectionOptions(state.RequestHandle); @@ -1217,6 +1233,10 @@ private void SetRequestHandleOptions(WinHttpRequestState state) private void SetRequestHandleProxyOptions(WinHttpRequestState state) { + Debug.Assert(state.RequestMessage != null); + Debug.Assert(state.RequestMessage.RequestUri != null); + Debug.Assert(state.RequestHandle != null); + // We've already set the proxy on the session handle if we're using no proxy or default proxy settings. // We only need to change it on the request handle if we have a specific IWebProxy or need to manually // implement Wininet-style auto proxy detection. @@ -1233,14 +1253,15 @@ private void SetRequestHandleProxyOptions(WinHttpRequestState state) { Debug.Assert(state.WindowsProxyUsePolicy == WindowsProxyUsePolicy.UseCustomProxy); updateProxySettings = true; - if (state.Proxy.IsBypassed(uri)) + + Uri? proxyUri = state.Proxy.IsBypassed(uri) ? null : state.Proxy.GetProxy(uri); + if (proxyUri == null) { proxyInfo.AccessType = Interop.WinHttp.WINHTTP_ACCESS_TYPE_NO_PROXY; } else { proxyInfo.AccessType = Interop.WinHttp.WINHTTP_ACCESS_TYPE_NAMED_PROXY; - Uri proxyUri = state.Proxy.GetProxy(uri); string proxyString = proxyUri.Scheme + "://" + proxyUri.Authority; proxyInfo.Proxy = Marshal.StringToHGlobalUni(proxyString); } @@ -1373,7 +1394,7 @@ private void SetRequestHandleClientCertificateOptions(SafeWinHttpHandle requestH return; } - X509Certificate2 clientCertificate = null; + X509Certificate2? clientCertificate = null; if (_clientCertificateOption == ClientCertificateOption.Manual) { clientCertificate = CertificateHelper.GetEligibleClientCertificate(ClientCertificates); @@ -1399,6 +1420,8 @@ private void SetRequestHandleClientCertificateOptions(SafeWinHttpHandle requestH private void SetEnableHttp2PlusClientCertificate(Uri requestUri, Version requestVersion) { + Debug.Assert(_sessionHandle != null); + if (requestUri.Scheme != UriScheme.Https || requestVersion != HttpVersion20) { return; @@ -1447,6 +1470,7 @@ internal static void SetNoClientCertificate(SafeWinHttpHandle requestHandle) private void SetRequestHandleCredentialsOptions(WinHttpRequestState state) { + Debug.Assert(state.RequestHandle != null); // By default, WinHTTP sets the default credentials policy such that it automatically sends default credentials // (current user's logged on Windows credentials) to a proxy when needed (407 response). It only sends // default credentials to a server (401 response) if the server is considered to be on the Intranet. @@ -1517,6 +1541,7 @@ private static void SetWinHttpOption( private void HandleAsyncException(WinHttpRequestState state, Exception ex) { + Debug.Assert(state.Tcs != null); if (state.CancellationToken.IsCancellationRequested) { // If the exception was due to the cancellation token being canceled, throw cancellation exception. @@ -1608,6 +1633,8 @@ private RendezvousAwaitable InternalSendRequestAsync(WinHttpRequestState st { lock (state.Lock) { + Debug.Assert(state.RequestHandle != null); + state.Pin(); if (!Interop.WinHttp.WinHttpSendRequest( state.RequestHandle, @@ -1633,6 +1660,9 @@ private RendezvousAwaitable InternalSendRequestAsync(WinHttpRequestState st private async Task InternalSendRequestBodyAsync(WinHttpRequestState state, WinHttpChunkMode chunkedModeForSend) { + Debug.Assert(state.RequestMessage != null); + Debug.Assert(state.RequestMessage.Content != null); + using (var requestStream = new WinHttpRequestStream(state, chunkedModeForSend)) { await state.RequestMessage.Content.CopyToAsync(requestStream, state.TransportContext).ConfigureAwait(false); @@ -1644,6 +1674,8 @@ private RendezvousAwaitable InternalReceiveResponseHeadersAsync(WinHttpRequ { lock (state.Lock) { + Debug.Assert(state.RequestHandle != null); + if (!Interop.WinHttp.WinHttpReceiveResponse(state.RequestHandle, IntPtr.Zero)) { throw WinHttpException.CreateExceptionUsingLastError(nameof(Interop.WinHttp.WinHttpReceiveResponse)); diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestCallback.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestCallback.cs index 3b2212ec42aa..f4a8f6eef82e 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestCallback.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestCallback.cs @@ -40,7 +40,7 @@ public static void WinHttpCallback( return; } - WinHttpRequestState state = WinHttpRequestState.FromIntPtr(context); + WinHttpRequestState? state = WinHttpRequestState.FromIntPtr(context); Debug.Assert(state != null, "WinHttpCallback must have a non-null state object"); RequestCallback(handle, state, internetStatus, statusInformation, statusInformationLength); @@ -84,8 +84,7 @@ private static void RequestCallback( return; case Interop.WinHttp.WINHTTP_CALLBACK_STATUS_REDIRECT: - string redirectUriString = Marshal.PtrToStringUni(statusInformation); - var redirectUri = new Uri(redirectUriString); + var redirectUri = new Uri(Marshal.PtrToStringUni(statusInformation)!); OnRequestRedirect(state, redirectUri); return; @@ -192,6 +191,8 @@ private static void OnRequestReceiveResponseHeadersComplete(WinHttpRequestState private static void OnRequestRedirect(WinHttpRequestState state, Uri redirectUri) { Debug.Assert(state != null, "OnRequestRedirect: state is null"); + Debug.Assert(state.Handler != null, "OnRequestRedirect: state.Handler is null"); + Debug.Assert(state.RequestMessage != null, "OnRequestRedirect: state.RequestMessage is null"); Debug.Assert(redirectUri != null, "OnRequestRedirect: redirectUri is null"); // If we're manually handling cookies, we need to reset them based on the new URI. @@ -234,6 +235,8 @@ private static void OnRequestSendingRequest(WinHttpRequestState state) { Debug.Assert(state != null, "OnRequestSendingRequest: state is null"); Debug.Assert(state.RequestHandle != null, "OnRequestSendingRequest: state.RequestHandle is null"); + Debug.Assert(state.RequestMessage != null, "OnRequestSendingRequest: state.RequestMessage is null"); + Debug.Assert(state.RequestMessage.RequestUri != null, "OnRequestSendingRequest: state.RequestMessage.RequestUri is null"); if (state.RequestMessage.RequestUri.Scheme != UriScheme.Https) { @@ -280,7 +283,7 @@ private static void OnRequestSendingRequest(WinHttpRequestState state) var serverCertificate = new X509Certificate2(certHandle); Interop.Crypt32.CertFreeCertificateContext(certHandle); - X509Chain chain = null; + X509Chain? chain = null; SslPolicyErrors sslPolicyErrors; bool result = false; @@ -391,6 +394,7 @@ private static void OnRequestError(WinHttpRequestState state, Interop.WinHttp.WI break; case Interop.WinHttp.API_WRITE_DATA: + Debug.Assert(state.TcsInternalWriteDataToRequestStream != null); if (asyncResult.dwError == Interop.WinHttp.ERROR_WINHTTP_OPERATION_CANCELLED) { if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(state, "API_WRITE_DATA - ERROR_WINHTTP_OPERATION_CANCELLED"); @@ -414,7 +418,8 @@ private static void OnRequestError(WinHttpRequestState state, Interop.WinHttp.WI private static void ResetAuthRequestHeaders(WinHttpRequestState state) { const string AuthHeaderNameWithColon = "Authorization:"; - SafeWinHttpHandle requestHandle = state.RequestHandle; + SafeWinHttpHandle? requestHandle = state.RequestHandle; + Debug.Assert(requestHandle != null); // Clear auth headers. if (!Interop.WinHttp.WinHttpAddRequestHeaders( diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestState.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestState.cs index 40275b5c8d71..366acfa61ad7 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestState.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestState.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Net.Security; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; @@ -28,7 +29,7 @@ internal sealed class WinHttpRequestState : IDisposable // A GCHandle for this operation object. // This is owned by the callback and will be deallocated when the sessionHandle has been closed. private GCHandle _operationHandle; - private WinHttpTransportContext _transportContext; + private WinHttpTransportContext? _transportContext; private volatile bool _disposed; // To detect redundant calls. public WinHttpRequestState() @@ -49,10 +50,10 @@ public void Pin() } } - public static WinHttpRequestState FromIntPtr(IntPtr gcHandle) + public static WinHttpRequestState? FromIntPtr(IntPtr gcHandle) { GCHandle stateHandle = GCHandle.FromIntPtr(gcHandle); - return (WinHttpRequestState)stateHandle.Target; + return (WinHttpRequestState?)stateHandle.Target; } public IntPtr ToIntPtr() @@ -92,16 +93,16 @@ public void ClearSendRequestState() } } - public TaskCompletionSource Tcs { get; set; } + public TaskCompletionSource? Tcs { get; set; } public CancellationToken CancellationToken { get; set; } - public HttpRequestMessage RequestMessage { get; set; } + public HttpRequestMessage? RequestMessage { get; set; } - public WinHttpHandler Handler { get; set; } + public WinHttpHandler? Handler { get; set; } - private SafeWinHttpHandle _requestHandle; - public SafeWinHttpHandle RequestHandle + private SafeWinHttpHandle? _requestHandle; + public SafeWinHttpHandle? RequestHandle { get { @@ -120,12 +121,13 @@ public SafeWinHttpHandle RequestHandle } } - public Exception SavedException { get; set; } + public Exception? SavedException { get; set; } public bool CheckCertificateRevocationList { get; set; } - public Func ServerCertificateValidationCallback { get; set; } + public Func? ServerCertificateValidationCallback { get; set; } + [AllowNull] public WinHttpTransportContext TransportContext { get { return _transportContext ?? (_transportContext = new WinHttpTransportContext()); } @@ -134,11 +136,11 @@ public WinHttpTransportContext TransportContext public WindowsProxyUsePolicy WindowsProxyUsePolicy { get; set; } - public IWebProxy Proxy { get; set; } + public IWebProxy? Proxy { get; set; } - public ICredentials ServerCredentials { get; set; } + public ICredentials? ServerCredentials { get; set; } - public ICredentials DefaultProxyCredentials { get; set; } + public ICredentials? DefaultProxyCredentials { get; set; } public bool PreAuthenticate { get; set; } @@ -147,7 +149,7 @@ public WinHttpTransportContext TransportContext public bool RetryRequest { get; set; } public RendezvousAwaitable LifecycleAwaitable { get; set; } = new RendezvousAwaitable(); - public TaskCompletionSource TcsInternalWriteDataToRequestStream { get; set; } + public TaskCompletionSource? TcsInternalWriteDataToRequestStream { get; set; } public bool AsyncReadInProgress { get; set; } // WinHttpResponseStream state. diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestStream.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestStream.cs index 301e5a6a21fc..5e79072f44ba 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestStream.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestStream.cs @@ -33,6 +33,7 @@ internal WinHttpRequestStream(WinHttpRequestState state, WinHttpChunkMode chunke // Take copy of handle from state. // The state's request handle will be set to null once the response stream starts. + Debug.Assert(_state.RequestHandle != null); _requestHandle = _state.RequestHandle; } diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseHeaderReader.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseHeaderReader.cs index 02ce3184f8fe..f2e3292321ce 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseHeaderReader.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseHeaderReader.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace System.Net.Http { @@ -28,7 +29,7 @@ public WinHttpResponseHeaderReader(char[] buffer, int startIndex, int length) /// Empty header lines are skipped, as are malformed header lines that are missing a colon character. /// /// true if the next header was read successfully, or false if all characters have been read. - public bool ReadHeader(out string name, out string value) + public bool ReadHeader([NotNullWhen(true)] out string? name, [NotNullWhen(true)] out string? value) { int startIndex; int length; diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseParser.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseParser.cs index 9f324ada27a0..053091aadbbf 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseParser.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseParser.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; using System.Net.Http.Headers; @@ -21,8 +22,11 @@ public static HttpResponseMessage CreateResponseMessage( WinHttpRequestState state, DecompressionMethods manuallyProcessedDecompressionMethods) { - HttpRequestMessage request = state.RequestMessage; - SafeWinHttpHandle requestHandle = state.RequestHandle; + HttpRequestMessage? request = state.RequestMessage; + SafeWinHttpHandle? requestHandle = state.RequestHandle; + Debug.Assert(request != null); + Debug.Assert(requestHandle != null); + var response = new HttpResponseMessage(); bool stripEncodingHeaders = false; @@ -133,9 +137,9 @@ public static uint GetResponseHeaderNumberInfo(SafeWinHttpHandle requestHandle, public static unsafe bool GetResponseHeader( SafeWinHttpHandle requestHandle, uint infoLevel, - ref char[] buffer, + ref char[]? buffer, ref uint index, - out string headerValue) + [NotNullWhen(true)] out string? headerValue) { const int StackLimit = 128; @@ -286,7 +290,7 @@ private static string GetReasonPhrase(HttpStatusCode statusCode, char[] buffer, // If it's a known reason phrase, use the known reason phrase instead of allocating a new string. - string knownReasonPhrase = HttpStatusDescription.Get(statusCode); + string? knownReasonPhrase = HttpStatusDescription.Get(statusCode); return (knownReasonPhrase != null && knownReasonPhrase.AsSpan().SequenceEqual(buffer.AsSpan(0, bufferLength))) ? knownReasonPhrase : @@ -313,7 +317,7 @@ private static void ParseResponseHeaders( reader.ReadLine(); // Parse the array of headers and split them between Content headers and Response headers. - while (reader.ReadHeader(out string headerName, out string headerValue)) + while (reader.ReadHeader(out string? headerName, out string? headerValue)) { if (!responseHeaders.TryAddWithoutValidation(headerName, headerValue)) { @@ -350,7 +354,7 @@ public static void ParseResponseTrailers( var reader = new WinHttpResponseHeaderReader(buffer, 0, bufferLength); // Parse the array of headers and split them between Content headers and Response headers. - while (reader.ReadHeader(out string headerName, out string headerValue)) + while (reader.ReadHeader(out string? headerName, out string? headerValue)) { responseTrailers.TryAddWithoutValidation(headerName, headerValue); } diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseStream.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseStream.cs index d7432541b94b..74f0abbbb805 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseStream.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseStream.cs @@ -113,7 +113,7 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio private async Task CopyToAsyncCore(Stream destination, byte[] buffer, CancellationToken cancellationToken) { _state.PinReceiveBuffer(buffer); - CancellationTokenRegistration ctr = cancellationToken.Register(s => ((WinHttpResponseStream)s).CancelPendingResponseStreamReadOperation(), this); + CancellationTokenRegistration ctr = cancellationToken.Register(s => ((WinHttpResponseStream)s!).CancelPendingResponseStreamReadOperation(), this); _state.AsyncReadInProgress = true; try { @@ -223,7 +223,7 @@ private async Task ReadAsyncCore(byte[] buffer, int offset, int count, Canc } _state.PinReceiveBuffer(buffer); - var ctr = token.Register(s => ((WinHttpResponseStream)s).CancelPendingResponseStreamReadOperation(), this); + var ctr = token.Register(s => ((WinHttpResponseStream)s!).CancelPendingResponseStreamReadOperation(), this); _state.AsyncReadInProgress = true; try { @@ -330,7 +330,7 @@ protected override void Dispose(bool disposing) if (_requestHandle != null) { _requestHandle.Dispose(); - _requestHandle = null; + _requestHandle = null!; } } } diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpTraceHelper.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpTraceHelper.cs index e039d50dad19..096dc180c72f 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpTraceHelper.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpTraceHelper.cs @@ -9,7 +9,7 @@ namespace System.Net.Http { internal static class WinHttpTraceHelper { - public static void TraceCallbackStatus(object thisOrContextObject, IntPtr handle, IntPtr context, uint status, [CallerMemberName] string memberName = null) + public static void TraceCallbackStatus(object? thisOrContextObject, IntPtr handle, IntPtr context, uint status, [CallerMemberName] string? memberName = null) { Debug.Assert(NetEventSource.Log.IsEnabled()); @@ -19,7 +19,7 @@ public static void TraceCallbackStatus(object thisOrContextObject, IntPtr handle memberName); } - public static void TraceAsyncError(object thisOrContextObject, Interop.WinHttp.WINHTTP_ASYNC_RESULT asyncResult, [CallerMemberName] string memberName = null) + public static void TraceAsyncError(object thisOrContextObject, Interop.WinHttp.WINHTTP_ASYNC_RESULT asyncResult, [CallerMemberName] string? memberName = null) { Debug.Assert(NetEventSource.Log.IsEnabled()); diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpTransportContext.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpTransportContext.cs index 1c3d5eea7179..ab8ee83c1a3e 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpTransportContext.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpTransportContext.cs @@ -10,13 +10,13 @@ namespace System.Net.Http { internal sealed class WinHttpTransportContext : TransportContext { - private WinHttpChannelBinding _channelBinding; + private WinHttpChannelBinding? _channelBinding; internal WinHttpTransportContext() { } - public override ChannelBinding GetChannelBinding(ChannelBindingKind kind) + public override ChannelBinding? GetChannelBinding(ChannelBindingKind kind) { // WinHTTP only supports retrieval of ChannelBindingKind.Endpoint for CBT. if (kind == ChannelBindingKind.Endpoint) From 87e98f36c72cfd7c129c85dee8ef727f32f8fd4b Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Tue, 13 Jul 2021 20:16:54 -0400 Subject: [PATCH 107/133] Obsolete CryptoConfig.EncodeOID (#55592) --- docs/project/list-of-diagnostics.md | 1 + src/libraries/Common/src/System/Obsoletions.cs | 3 +++ .../ref/System.Security.Cryptography.Algorithms.cs | 1 + .../Security/Cryptography/CryptoConfig.Browser.cs | 1 + .../src/System/Security/Cryptography/CryptoConfig.cs | 1 + .../tests/CryptoConfigTests.cs | 10 ++++++++++ 6 files changed, 17 insertions(+) diff --git a/docs/project/list-of-diagnostics.md b/docs/project/list-of-diagnostics.md index 2d1685323175..cfb593d70b54 100644 --- a/docs/project/list-of-diagnostics.md +++ b/docs/project/list-of-diagnostics.md @@ -85,6 +85,7 @@ The PR that reveals the implementation of the ` throw new PlatformNotSupportedException(SR.SystemSecurityCryptographyAlgorithms_PlatformNotSupported); [UnsupportedOSPlatform("browser")] + [Obsolete(Obsoletions.CryptoConfigEncodeOIDMessage, DiagnosticId = Obsoletions.CryptoConfigEncodeOIDDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public static byte[] EncodeOID(string str) => throw new PlatformNotSupportedException(SR.SystemSecurityCryptographyAlgorithms_PlatformNotSupported); [RequiresUnreferencedCode("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.cs index ff73ce605054..e7d3919b2f3c 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.cs @@ -508,6 +508,7 @@ public static void AddOID(string oid, params string[] names) } [UnsupportedOSPlatform("browser")] + [Obsolete(Obsoletions.CryptoConfigEncodeOIDMessage, DiagnosticId = Obsoletions.CryptoConfigEncodeOIDDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public static byte[] EncodeOID(string str) { if (str == null) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/CryptoConfigTests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/CryptoConfigTests.cs index 067a50a10015..b72437094402 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/CryptoConfigTests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/CryptoConfigTests.cs @@ -348,25 +348,30 @@ public static void CreateFromName_CtorArguments() [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public static void EncodeOID_Validation() { +#pragma warning disable SYSLIB0031 // EncodeOID is obsolete Assert.Throws(() => CryptoConfig.EncodeOID(null)); Assert.Throws(() => CryptoConfig.EncodeOID(string.Empty)); Assert.Throws(() => CryptoConfig.EncodeOID("BAD.OID")); Assert.Throws(() => CryptoConfig.EncodeOID("1.2.BAD.OID")); Assert.Throws(() => CryptoConfig.EncodeOID("1." + uint.MaxValue)); +#pragma warning restore SYSLIB0031 } [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public static void EncodeOID_Compat() { +#pragma warning disable SYSLIB0031 // EncodeOID is obsolete string actual = CryptoConfig.EncodeOID("-1.2.-3").ByteArrayToHex(); Assert.Equal("0602DAFD", actual); // Negative values not checked +#pragma warning restore SYSLIB0031 } [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public static void EncodeOID_Length_Boundary() { +#pragma warning disable SYSLIB0031 // EncodeOID is obsolete string valueToRepeat = "1.1"; // Build a string like 1.11.11.11. ... .11.1, which has 0x80 separators. @@ -379,6 +384,7 @@ public static void EncodeOID_Length_Boundary() // and would just clutter up this test, so only verify it doesn't throw. s = new StringBuilder(valueToRepeat.Length * 0x7f).Insert(0, valueToRepeat, 0x7f).ToString(); CryptoConfig.EncodeOID(s); +#pragma warning restore SYSLIB0031 } [Theory] @@ -392,9 +398,11 @@ public static void EncodeOID_Value_Boundary_And_Compat(uint elementValue, string { // Boundary cases in EncodeOID; output may produce the wrong value mathematically due to encoding // algorithm semantics but included here for compat reasons. +#pragma warning disable SYSLIB0031 // EncodeOID is obsolete byte[] actual = CryptoConfig.EncodeOID("1." + elementValue.ToString()); byte[] expected = expectedEncoding.HexToByteArray(); Assert.Equal(expected, actual); +#pragma warning restore SYSLIB0031 } [Theory] @@ -404,12 +412,14 @@ public static void EncodeOID_Value_Boundary_And_Compat(uint elementValue, string [InlineData("MD5", "1.2.840.113549.2.5", "06082A864886F70D0205")] public static void MapAndEncodeOID(string alg, string expectedOid, string expectedEncoding) { +#pragma warning disable SYSLIB0031 // EncodeOID is obsolete string oid = CryptoConfig.MapNameToOID(alg); Assert.Equal(expectedOid, oid); byte[] actual = CryptoConfig.EncodeOID(oid); byte[] expected = expectedEncoding.HexToByteArray(); Assert.Equal(expected, actual); +#pragma warning restore SYSLIB0031 } private static void VerifyCreateFromName(string name) From f6eb259db626d563c15ba340feb6f440d1e1c8ee Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Tue, 13 Jul 2021 17:34:26 -0700 Subject: [PATCH 108/133] Disable generic math (#55540) --- src/coreclr/clr.featuredefines.props | 2 +- src/libraries/System.Runtime/ref/System.Runtime.csproj | 2 +- src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj | 2 +- src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/clr.featuredefines.props b/src/coreclr/clr.featuredefines.props index 08fca8de6dd6..184e885470f6 100644 --- a/src/coreclr/clr.featuredefines.props +++ b/src/coreclr/clr.featuredefines.props @@ -9,7 +9,7 @@ true true true - true + false true diff --git a/src/libraries/System.Runtime/ref/System.Runtime.csproj b/src/libraries/System.Runtime/ref/System.Runtime.csproj index c978be13789e..9cbad9dddb80 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.csproj +++ b/src/libraries/System.Runtime/ref/System.Runtime.csproj @@ -1,7 +1,7 @@ true - true + false v4.0.30319 + + + + + + + + + + diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Unix.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Unix.cs new file mode 100644 index 000000000000..d2e4f44a6751 --- /dev/null +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Unix.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO.Compression +{ + public static partial class ZipFileExtensions + { + static partial void SetExternalAttributes(FileStream fs, ZipArchiveEntry entry) + { + Interop.Sys.FileStatus status; + Interop.CheckIo(Interop.Sys.FStat(fs.SafeFileHandle, out status), fs.Name); + + entry.ExternalAttributes |= status.Mode << 16; + } + } +} diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs index 00d82c242b0a..c9199a97f2a8 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs @@ -94,7 +94,7 @@ internal static ZipArchiveEntry DoCreateEntryFromFile(this ZipArchive destinatio // Argument checking gets passed down to FileStream's ctor and CreateEntry - using (Stream fs = new FileStream(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 0x1000, useAsync: false)) + using (FileStream fs = new FileStream(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 0x1000, useAsync: false)) { ZipArchiveEntry entry = compressionLevel.HasValue ? destination.CreateEntry(entryName, compressionLevel.Value) @@ -109,11 +109,15 @@ internal static ZipArchiveEntry DoCreateEntryFromFile(this ZipArchive destinatio entry.LastWriteTime = lastWrite; + SetExternalAttributes(fs, entry); + using (Stream es = entry.Open()) fs.CopyTo(es); return entry; } } + + static partial void SetExternalAttributes(FileStream fs, ZipArchiveEntry entry); } } diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs new file mode 100644 index 000000000000..4da8b87b43ea --- /dev/null +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs @@ -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. + +namespace System.IO.Compression +{ + public static partial class ZipFileExtensions + { + static partial void ExtractExternalAttributes(FileStream fs, ZipArchiveEntry entry) + { + // Only extract USR, GRP, and OTH file permissions, and ignore + // S_ISUID, S_ISGID, and S_ISVTX bits. This matches unzip's default behavior. + // It is off by default because of this comment: + + // "It's possible that a file in an archive could have one of these bits set + // and, unknown to the person unzipping, could allow others to execute the + // file as the user or group. The new option -K bypasses this check." + const int ExtractPermissionMask = 0x1FF; + int permissions = (entry.ExternalAttributes >> 16) & ExtractPermissionMask; + + // If the permissions weren't set at all, don't write the file's permissions, + // since the .zip could have been made using a previous version of .NET, which didn't + // include the permissions, or was made on Windows. + if (permissions != 0) + { + Interop.CheckIo(Interop.Sys.FChMod(fs.SafeFileHandle, permissions), fs.Name); + } + } + } +} diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs index cad1a12c5a48..a74aca915faa 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs @@ -1,8 +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.ComponentModel; - namespace System.IO.Compression { public static partial class ZipFileExtensions @@ -75,15 +73,19 @@ public static void ExtractToFile(this ZipArchiveEntry source, string destination // Rely on FileStream's ctor for further checking destinationFileName parameter FileMode fMode = overwrite ? FileMode.Create : FileMode.CreateNew; - using (Stream fs = new FileStream(destinationFileName, fMode, FileAccess.Write, FileShare.None, bufferSize: 0x1000, useAsync: false)) + using (FileStream fs = new FileStream(destinationFileName, fMode, FileAccess.Write, FileShare.None, bufferSize: 0x1000, useAsync: false)) { using (Stream es = source.Open()) es.CopyTo(fs); + + ExtractExternalAttributes(fs, source); } File.SetLastWriteTime(destinationFileName, source.LastWriteTime.DateTime); } + static partial void ExtractExternalAttributes(FileStream fs, ZipArchiveEntry entry); + internal static void ExtractRelativeToDirectory(this ZipArchiveEntry source, string destinationDirectoryName) => ExtractRelativeToDirectory(source, destinationDirectoryName, overwrite: false); diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj b/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj index bf9e78ab7428..9fbde72eef7b 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj +++ b/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj @@ -1,6 +1,6 @@ - + - $(NetCoreAppCurrent) + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser @@ -23,6 +23,12 @@ + + + + + + diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs index 3ba4081397b7..18768b5a592f 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs +++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs @@ -450,5 +450,28 @@ private static async Task UpdateArchive(ZipArchive archive, string installFile, } } } + + [Fact] + public void CreateSetsExternalAttributesCorrectly() + { + string folderName = zfolder("normal"); + string filepath = GetTestFilePath(); + ZipFile.CreateFromDirectory(folderName, filepath); + + using (ZipArchive archive = ZipFile.Open(filepath, ZipArchiveMode.Read)) + { + foreach (ZipArchiveEntry entry in archive.Entries) + { + if (OperatingSystem.IsWindows()) + { + Assert.Equal(0, entry.ExternalAttributes); + } + else + { + Assert.NotEqual(0, entry.ExternalAttributes); + } + } + } + } } } diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs new file mode 100644 index 000000000000..ab61ae92443d --- /dev/null +++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs @@ -0,0 +1,159 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; +using Xunit; + +namespace System.IO.Compression.Tests +{ + public class ZipFile_Unix : ZipFileTestBase + { + [Fact] + public void UnixCreateSetsPermissionsInExternalAttributes() + { + // '7600' tests that S_ISUID, S_ISGID, and S_ISVTX bits get preserved in ExternalAttributes + string[] testPermissions = new[] { "777", "755", "644", "600", "7600" }; + + using (var tempFolder = new TempDirectory(Path.Combine(GetTestFilePath(), "testFolder"))) + { + foreach (string permission in testPermissions) + { + CreateFile(tempFolder.Path, permission); + } + + string archivePath = GetTestFilePath(); + ZipFile.CreateFromDirectory(tempFolder.Path, archivePath); + + using (ZipArchive archive = ZipFile.OpenRead(archivePath)) + { + Assert.Equal(5, archive.Entries.Count); + + foreach (ZipArchiveEntry entry in archive.Entries) + { + Assert.EndsWith(".txt", entry.Name, StringComparison.Ordinal); + EnsureExternalAttributes(entry.Name.Substring(0, entry.Name.Length - 4), entry); + } + + void EnsureExternalAttributes(string permissions, ZipArchiveEntry entry) + { + Assert.Equal(Convert.ToInt32(permissions, 8), (entry.ExternalAttributes >> 16) & 0xFFF); + } + } + + // test that round tripping the archive has the same file permissions + using (var extractFolder = new TempDirectory(Path.Combine(GetTestFilePath(), "extract"))) + { + ZipFile.ExtractToDirectory(archivePath, extractFolder.Path); + + foreach (string permission in testPermissions) + { + string filename = Path.Combine(extractFolder.Path, permission + ".txt"); + Assert.True(File.Exists(filename)); + + EnsureFilePermissions(filename, permission); + } + } + } + } + + [Fact] + public void UnixExtractSetsFilePermissionsFromExternalAttributes() + { + // '7600' tests that S_ISUID, S_ISGID, and S_ISVTX bits don't get extracted to file permissions + string[] testPermissions = new[] { "777", "755", "644", "754", "7600" }; + byte[] contents = Encoding.UTF8.GetBytes("contents"); + + string archivePath = GetTestFilePath(); + using (FileStream fileStream = new FileStream(archivePath, FileMode.CreateNew)) + using (ZipArchive archive = new ZipArchive(fileStream, ZipArchiveMode.Create)) + { + foreach (string permission in testPermissions) + { + ZipArchiveEntry entry = archive.CreateEntry(permission + ".txt"); + entry.ExternalAttributes = Convert.ToInt32(permission, 8) << 16; + using Stream stream = entry.Open(); + stream.Write(contents); + stream.Flush(); + } + } + + using (var tempFolder = new TempDirectory(GetTestFilePath())) + { + ZipFile.ExtractToDirectory(archivePath, tempFolder.Path); + + foreach (string permission in testPermissions) + { + string filename = Path.Combine(tempFolder.Path, permission + ".txt"); + Assert.True(File.Exists(filename)); + + EnsureFilePermissions(filename, permission); + } + } + } + + private static void CreateFile(string folderPath, string permissions) + { + string filename = Path.Combine(folderPath, $"{permissions}.txt"); + File.WriteAllText(filename, "contents"); + + Assert.Equal(0, Interop.Sys.ChMod(filename, Convert.ToInt32(permissions, 8))); + } + + private static void EnsureFilePermissions(string filename, string permissions) + { + Interop.Sys.FileStatus status; + Assert.Equal(0, Interop.Sys.Stat(filename, out status)); + + // note that we don't extract S_ISUID, S_ISGID, and S_ISVTX bits, + // so only use the last 3 numbers of permissions to verify the file permissions + permissions = permissions.Length > 3 ? permissions.Substring(permissions.Length - 3) : permissions; + Assert.Equal(Convert.ToInt32(permissions, 8), status.Mode & 0xFFF); + } + + [Theory] + [InlineData("sharpziplib.zip", null)] // ExternalAttributes are not set in this .zip, use the system default + [InlineData("Linux_RW_RW_R__.zip", "664")] + [InlineData("Linux_RWXRW_R__.zip", "764")] + [InlineData("OSX_RWXRW_R__.zip", "764")] + public void UnixExtractFilePermissionsCompat(string zipName, string expectedPermissions) + { + expectedPermissions = GetExpectedPermissions(expectedPermissions); + + string zipFileName = compat(zipName); + using (var tempFolder = new TempDirectory(GetTestFilePath())) + { + ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path); + + using ZipArchive archive = ZipFile.Open(zipFileName, ZipArchiveMode.Read); + foreach (ZipArchiveEntry entry in archive.Entries) + { + string filename = Path.Combine(tempFolder.Path, entry.FullName); + Assert.True(File.Exists(filename), $"File '{filename}' should exist"); + + EnsureFilePermissions(filename, expectedPermissions); + } + } + } + + private static string GetExpectedPermissions(string expectedPermissions) + { + if (string.IsNullOrEmpty(expectedPermissions)) + { + // Create a new file, and get its permissions to get the current system default permissions + + using (var tempFolder = new TempDirectory()) + { + string filename = Path.Combine(tempFolder.Path, Path.GetRandomFileName()); + File.WriteAllText(filename, "contents"); + + Interop.Sys.FileStatus status; + Assert.Equal(0, Interop.Sys.Stat(filename, out status)); + + expectedPermissions = Convert.ToString(status.Mode & 0xFFF, 8); + } + } + + return expectedPermissions; + } + } +} From 1778ae292f58ff0c88c599f32c65383ff92ef746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marie=20P=C3=ADchov=C3=A1?= <11718369+ManickaP@users.noreply.github.com> Date: Wed, 14 Jul 2021 09:19:45 +0200 Subject: [PATCH 114/133] H/3 and Quic AppContext switch (#55332) * Renamed AllowDraftHttp3 to AllowHttp3AndQuic app context switch * Usage of AllowHttp3AndQuic switch in S.N.Quic and enabling it in tests * Renamed AppContext switch to Http3Support --- .../src/System/Net/Http/GlobalHttpSettings.cs | 12 +++--- .../HttpConnectionSettings.cs | 2 +- .../System.Net.Http.Functional.Tests.csproj | 4 ++ .../StressTests/HttpStress/HttpStress.csproj | 4 ++ .../MsQuic/Internal/MsQuicApi.cs | 40 ++++++++++++++++++- .../System.Net.Quic.Functional.Tests.csproj | 3 ++ 6 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/GlobalHttpSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/GlobalHttpSettings.cs index 7382f4ca0da1..664d296dcb6d 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/GlobalHttpSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/GlobalHttpSettings.cs @@ -26,12 +26,12 @@ internal static class SocketsHttpHandler "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2SUPPORT", true); - // Default to allowing draft HTTP/3, but enable that to be overridden - // by an AppContext switch, or by an environment variable being set to false/0. - public static bool AllowDraftHttp3 { get; } = RuntimeSettingParser.QueryRuntimeSettingSwitch( - "System.Net.SocketsHttpHandler.Http3DraftSupport", - "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP3DRAFTSUPPORT", - true); + // Default to disable HTTP/3 (and by an extent QUIC), but enable that to be overridden + // by an AppContext switch, or by an environment variable being set to true/1. + public static bool AllowHttp3 { get; } = RuntimeSettingParser.QueryRuntimeSettingSwitch( + "System.Net.SocketsHttpHandler.Http3Support", + "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP3SUPPORT", + false); // Switch to disable the HTTP/2 dynamic window scaling algorithm. Enabled by default. public static bool DisableDynamicHttp2WindowSizing { get; } = RuntimeSettingParser.QueryRuntimeSettingSwitch( diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index a24a6403b299..410acc16b69a 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -70,7 +70,7 @@ internal sealed class HttpConnectionSettings public HttpConnectionSettings() { bool allowHttp2 = GlobalHttpSettings.SocketsHttpHandler.AllowHttp2; - bool allowHttp3 = GlobalHttpSettings.SocketsHttpHandler.AllowDraftHttp3; + bool allowHttp3 = GlobalHttpSettings.SocketsHttpHandler.AllowHttp3; _maxHttpVersion = allowHttp3 && allowHttp2 ? HttpVersion.Version30 : allowHttp2 ? HttpVersion.Version20 : diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj index b3413cbef305..667b35e9a8af 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj @@ -9,6 +9,10 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Linux;$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent)-OSX + + + + WasmTestOnBrowser $(TestArchiveRoot)browseronly/ diff --git a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/HttpStress.csproj b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/HttpStress.csproj index db333cc634f6..80b4aa21b108 100644 --- a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/HttpStress.csproj +++ b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/HttpStress.csproj @@ -7,6 +7,10 @@ enable + + + + diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs index cba34f6dbbaf..b691d3c5c14d 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs @@ -125,10 +125,18 @@ private MsQuicApi(NativeApi* vtable) static MsQuicApi() { - if (OperatingSystem.IsWindows() && !IsWindowsVersionSupported()) + if (!IsHttp3Enabled()) { - IsQuicSupported = false; + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info(null, $"HTTP/3 and QUIC is not enabled, see 'System.Net.SocketsHttpHandler.Http3Support' AppContext switch."); + } + return; + } + + if (OperatingSystem.IsWindows() && !IsWindowsVersionSupported()) + { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(null, $"Current Windows version ({Environment.OSVersion}) is not supported by QUIC. Minimal supported version is {MinWindowsVersion}"); @@ -163,6 +171,34 @@ static MsQuicApi() } } + // Note that this is copy-pasted from S.N.Http just to hide S.N.Quic behind the same AppContext switch + // since this library is considered "private" for 6.0. + // We should get rid of this once S.N.Quic API surface is officially exposed. + private static bool IsHttp3Enabled() + { + bool value; + + // First check for the AppContext switch, giving it priority over the environment variable. + if (AppContext.TryGetSwitch("System.Net.SocketsHttpHandler.Http3Support", out value)) + { + return value; + } + + // AppContext switch wasn't used. Check the environment variable. + string? envVar = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP3SUPPORT"); + + if (bool.TryParse(envVar, out value)) + { + return value; + } + else if (uint.TryParse(envVar, out uint intVal)) + { + return intVal != 0; + } + + return false; + } + private static bool IsWindowsVersionSupported() => OperatingSystem.IsWindowsVersionAtLeast(MinWindowsVersion.Major, MinWindowsVersion.Minor, MinWindowsVersion.Build, MinWindowsVersion.Revision); diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj b/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj index 2a17894a5555..583b2b6c4861 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj @@ -4,6 +4,9 @@ true $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix + + + From 01ce681dc4f2e6720caae5043b04c3fe797cebb4 Mon Sep 17 00:00:00 2001 From: Zoltan Varga Date: Wed, 14 Jul 2021 05:07:37 -0400 Subject: [PATCH 115/133] [mono] Add wrapper info for native func wrappers. (#55602) Fixes https://github.com/dotnet/runtime/issues/55567. --- src/mono/mono/metadata/marshal.c | 8 ++++---- src/mono/mono/metadata/marshal.h | 1 + src/mono/mono/mini/aot-runtime.h | 2 +- src/mono/mono/mini/interp/transform.c | 5 ++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/mono/mono/metadata/marshal.c b/src/mono/mono/metadata/marshal.c index 0b5cdf064079..0a9d8f8ff1ab 100644 --- a/src/mono/mono/metadata/marshal.c +++ b/src/mono/mono/metadata/marshal.c @@ -3443,7 +3443,7 @@ mono_marshal_get_native_func_wrapper (MonoImage *image, MonoMethodSignature *sig MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func) { MonoMethodSignature *csig; - + WrapperInfo *info; SignaturePointerPair key, *new_key; MonoMethodBuilder *mb; MonoMethod *res; @@ -3474,14 +3474,14 @@ mono_marshal_get_native_func_wrapper (MonoImage *image, MonoMethodSignature *sig new_key->sig = csig; new_key->pointer = func; - res = mono_mb_create_and_cache_full (cache, new_key, mb, csig, csig->param_count + 16, NULL, &found); + info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_NATIVE_FUNC); + + res = mono_mb_create_and_cache_full (cache, new_key, mb, csig, csig->param_count + 16, info, &found); if (found) g_free (new_key); mono_mb_free (mb); - mono_marshal_set_wrapper_info (res, NULL); - return res; } diff --git a/src/mono/mono/metadata/marshal.h b/src/mono/mono/metadata/marshal.h index 08300a001efd..d56f30e0f9d7 100644 --- a/src/mono/mono/metadata/marshal.h +++ b/src/mono/mono/metadata/marshal.h @@ -113,6 +113,7 @@ typedef enum { WRAPPER_SUBTYPE_RUNTIME_INVOKE_VIRTUAL, /* Subtypes of MONO_WRAPPER_MANAGED_TO_NATIVE */ WRAPPER_SUBTYPE_ICALL_WRAPPER, // specifically JIT icalls + WRAPPER_SUBTYPE_NATIVE_FUNC, WRAPPER_SUBTYPE_NATIVE_FUNC_AOT, WRAPPER_SUBTYPE_NATIVE_FUNC_INDIRECT, WRAPPER_SUBTYPE_PINVOKE, diff --git a/src/mono/mono/mini/aot-runtime.h b/src/mono/mono/mini/aot-runtime.h index af40e8bbb12f..dff55a92563f 100644 --- a/src/mono/mono/mini/aot-runtime.h +++ b/src/mono/mono/mini/aot-runtime.h @@ -11,7 +11,7 @@ #include "mini.h" /* Version number of the AOT file format */ -#define MONO_AOT_FILE_VERSION 181 +#define MONO_AOT_FILE_VERSION 182 #define MONO_AOT_TRAMP_PAGE_SIZE 16384 diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index e75fefe393c7..7c45880f8d56 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -3458,9 +3458,8 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target * every time based on the signature. */ if (method->wrapper_type == MONO_WRAPPER_MANAGED_TO_NATIVE) { - WrapperInfo *info = mono_marshal_get_wrapper_info (method); - if (info) { - MonoMethod *pinvoke_method = info->d.managed_to_native.method; + MonoMethod *pinvoke_method = mono_marshal_method_from_wrapper (method); + if (pinvoke_method) { imethod = mono_interp_get_imethod (pinvoke_method, error); return_val_if_nok (error, FALSE); } From 7bd472498e690e9421df86d5a9d728faa939742c Mon Sep 17 00:00:00 2001 From: Ulrich Weigand Date: Wed, 14 Jul 2021 11:52:27 +0200 Subject: [PATCH 116/133] [mono] Fix race during mono_image_storage_open (#55201) --- src/mono/mono/metadata/image.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/mono/mono/metadata/image.c b/src/mono/mono/metadata/image.c index 68b7b377266c..3bd2a6306add 100644 --- a/src/mono/mono/metadata/image.c +++ b/src/mono/mono/metadata/image.c @@ -1263,8 +1263,11 @@ mono_image_storage_trypublish (MonoImageStorage *candidate, MonoImageStorage **o gboolean result; mono_images_storage_lock (); MonoImageStorage *val = (MonoImageStorage *)g_hash_table_lookup (images_storage_hash, candidate->key); + if (val && !mono_refcount_tryinc (val)) { + // We raced against a mono_image_storage_dtor in progress. + val = NULL; + } if (val) { - mono_refcount_inc (val); *out_storage = val; result = FALSE; } else { @@ -1295,8 +1298,11 @@ mono_image_storage_tryaddref (const char *key, MonoImageStorage **found) gboolean result = FALSE; mono_images_storage_lock (); MonoImageStorage *val = (MonoImageStorage *)g_hash_table_lookup (images_storage_hash, key); + if (val && !mono_refcount_tryinc (val)) { + // We raced against a mono_image_storage_dtor in progress. + val = NULL; + } if (val) { - mono_refcount_inc (val); *found = val; result = TRUE; } From afaf666eff08435123eb649ac138419f4c9b9344 Mon Sep 17 00:00:00 2001 From: Noah Falk Date: Wed, 14 Jul 2021 03:29:04 -0700 Subject: [PATCH 117/133] DiagnosticSourceEventSource supports base class properties (#55613) Fixes #41300 Previously DiagnosticSourceEventSource would only iterate and recognize properties that were declared on the most derived type. Now it can capture properties that were inherited from a base class too. --- .../DiagnosticSourceEventSource.cs | 4 +- .../DiagnosticSourceEventSourceBridgeTests.cs | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs index 72f7572313f9..d2ee34fe439d 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs @@ -1099,7 +1099,7 @@ private void Dispose() { TransformSpec? newSerializableArgs = null; TypeInfo curTypeInfo = type.GetTypeInfo(); - foreach (PropertyInfo property in curTypeInfo.DeclaredProperties) + foreach (PropertyInfo property in curTypeInfo.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { // prevent TransformSpec from attempting to implicitly transform index properties if (property.GetMethod == null || property.GetMethod!.GetParameters().Length > 0) @@ -1319,7 +1319,7 @@ public static PropertyFetch FetcherForProperty(Type? type, string propertyName) } else { - PropertyInfo? propertyInfo = typeInfo.GetDeclaredProperty(propertyName); + PropertyInfo? propertyInfo = typeInfo.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); if (propertyInfo == null) { Log.Message($"Property {propertyName} not found on {type}. Ensure the name is spelled correctly. If you published the application with PublishTrimmed=true, ensure the property was not trimmed away."); diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs index 2a59c8b48e4f..ecf73aa7f06c 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs @@ -490,6 +490,40 @@ public void TestSpecificEvents() }).Dispose(); } + /// + /// Tests that DiagnosticSourceEventSource can read property values from base classes + /// + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void TestBaseClassProperties() + { + RemoteExecutor.Invoke(() => + { + using (var eventSourceListener = new TestDiagnosticSourceEventListener()) + using (var diagnosticSourceListener = new DiagnosticListener("TestBaseClassProperties")) + { + Assert.Equal(0, eventSourceListener.EventCount); + eventSourceListener.Enable( + " TestBaseClassProperties/TestEvent1:Point_X=Point.X;Point_Y=Point.Y;Url_2=Url2\r\n"); + + /***************************************************************************************/ + // Emit an event that matches the first pattern. + MyClass val = new MyDerivedClass() { Url = "MyUrl", Point = new MyPoint() { X = 3, Y = 5 }, Url2 = "Second url", AnotherString = "another" }; + if (diagnosticSourceListener.IsEnabled("TestEvent1")) + diagnosticSourceListener.Write("TestEvent1", val); + + Assert.Equal(1, eventSourceListener.EventCount); // Exactly one more event has been emitted. + Assert.Equal("TestBaseClassProperties", eventSourceListener.LastEvent.SourceName); + Assert.Equal("TestEvent1", eventSourceListener.LastEvent.EventName); + Assert.Equal(7, eventSourceListener.LastEvent.Arguments.Count); + Assert.Equal("another", eventSourceListener.LastEvent.Arguments["AnotherString"]); + Assert.Equal("3", eventSourceListener.LastEvent.Arguments["Point_X"]); + Assert.Equal("5", eventSourceListener.LastEvent.Arguments["Point_Y"]); + Assert.Equal("Second url", eventSourceListener.LastEvent.Arguments["Url_2"]); + eventSourceListener.ResetEventCountAndLastEvent(); + } + }).Dispose(); + } + /// /// Test that things work properly for Linux newline conventions. /// @@ -1314,6 +1348,12 @@ internal class MyClass public MyPoint Point { get; set; } } + internal class MyDerivedClass : MyClass + { + public string Url2 { get; set; } + public string AnotherString { get; set; } + } + /// /// classes for test data. /// From 21e36e872556c157d4b65a1c997d92dfb31f18c5 Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Wed, 14 Jul 2021 14:01:14 +0300 Subject: [PATCH 118/133] Inliner: Extend IL limit for profiled call-sites, allow inlining for switches. (#55478) --- src/coreclr/jit/block.cpp | 3 +- src/coreclr/jit/compiler.cpp | 3 + src/coreclr/jit/compiler.h | 1 + src/coreclr/jit/fgbasic.cpp | 64 +++++++++++++--- src/coreclr/jit/fgprofile.cpp | 25 +++++++ src/coreclr/jit/importer.cpp | 41 ++++++----- src/coreclr/jit/inline.def | 1 + src/coreclr/jit/inline.h | 1 + src/coreclr/jit/inlinepolicy.cpp | 73 ++++++++++++++----- src/coreclr/jit/inlinepolicy.h | 4 + src/coreclr/jit/jitconfigvalues.h | 3 +- .../Linq/Expressions/DebugViewWriter.cs | 2 + 12 files changed, 171 insertions(+), 50 deletions(-) diff --git a/src/coreclr/jit/block.cpp b/src/coreclr/jit/block.cpp index 128c4e033d9f..5df8c1e4358c 100644 --- a/src/coreclr/jit/block.cpp +++ b/src/coreclr/jit/block.cpp @@ -1431,6 +1431,7 @@ BasicBlock* Compiler::bbNewBasicBlock(BBjumpKinds jumpKind) /* Give the block a number, set the ancestor count and weight */ ++fgBBcount; + ++fgBBNumMax; if (compIsForInlining()) { @@ -1438,7 +1439,7 @@ BasicBlock* Compiler::bbNewBasicBlock(BBjumpKinds jumpKind) } else { - block->bbNum = ++fgBBNumMax; + block->bbNum = fgBBNumMax; } if (compRationalIRForm) diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index bf49e63ab0cb..fe5734213afc 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -6272,6 +6272,9 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr, // a potential inline candidate. InlineResult prejitResult(this, methodHnd, "prejit"); + // Profile data allows us to avoid early "too many IL bytes" outs. + prejitResult.NoteBool(InlineObservation::CALLSITE_HAS_PROFILE, fgHaveSufficientProfileData()); + // Do the initial inline screen. impCanInlineIL(methodHnd, methodInfo, forceInline, &prejitResult); diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index b60353d8e844..9027a5d23352 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -5814,6 +5814,7 @@ class Compiler void WalkSpanningTree(SpanningTreeVisitor* visitor); void fgSetProfileWeight(BasicBlock* block, BasicBlock::weight_t weight); void fgApplyProfileScale(); + bool fgHaveSufficientProfileData(); // fgIsUsingProfileWeights - returns true if we have real profile data for this method // or if we have some fake profile data for the stress mode diff --git a/src/coreclr/jit/fgbasic.cpp b/src/coreclr/jit/fgbasic.cpp index 62a9467da63f..6c65586fbca1 100644 --- a/src/coreclr/jit/fgbasic.cpp +++ b/src/coreclr/jit/fgbasic.cpp @@ -818,8 +818,24 @@ class FgStack return false; } const unsigned argNum = value - SLOT_ARGUMENT; - assert(argNum < info->argCnt); - return info->inlArgInfo[argNum].argIsInvariant; + if (argNum < info->argCnt) + { + return info->inlArgInfo[argNum].argIsInvariant; + } + return false; + } + static bool IsExactArgument(FgSlot value, InlineInfo* info) + { + if ((info == nullptr) || !IsArgument(value)) + { + return false; + } + const unsigned argNum = value - SLOT_ARGUMENT; + if (argNum < info->argCnt) + { + return info->inlArgInfo[argNum].argIsExact; + } + return false; } static unsigned SlotTypeToArgNum(FgSlot value) { @@ -876,6 +892,10 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed if (makeInlineObservations) { + // Set default values for profile (to avoid NoteFailed in CALLEE_IL_CODE_SIZE's handler) + // these will be overridden later. + compInlineResult->NoteBool(InlineObservation::CALLSITE_HAS_PROFILE, true); + compInlineResult->NoteDouble(InlineObservation::CALLSITE_PROFILE_FREQUENCY, 1.0); // Observe force inline state and code size. compInlineResult->NoteBool(InlineObservation::CALLEE_IS_FORCE_INLINE, isForceInline); compInlineResult->NoteInt(InlineObservation::CALLEE_IL_CODE_SIZE, codeSize); @@ -1031,7 +1051,8 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed if (makeInlineObservations) { FgStack::FgSlot slot = pushedStack.Top(); - if (FgStack::IsConstantOrConstArg(slot, impInlineInfo)) + if (FgStack::IsConstantOrConstArg(slot, impInlineInfo) || + FgStack::IsExactArgument(slot, impInlineInfo)) { compInlineResult->Note(InlineObservation::CALLSITE_FOLDABLE_EXPR_UN); handled = true; // and keep argument in the pushedStack @@ -1338,44 +1359,59 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed FgStack::FgSlot arg0 = pushedStack.Top(1); FgStack::FgSlot arg1 = pushedStack.Top(0); - if ((FgStack::IsConstant(arg0) && FgStack::IsConstArgument(arg1, impInlineInfo)) || - (FgStack::IsConstant(arg1) && FgStack::IsConstArgument(arg0, impInlineInfo)) || - (FgStack::IsConstArgument(arg0, impInlineInfo) && - FgStack::IsConstArgument(arg1, impInlineInfo))) + // Const op ConstArg -> ConstArg + if (FgStack::IsConstant(arg0) && FgStack::IsConstArgument(arg1, impInlineInfo)) { // keep stack unchanged handled = true; compInlineResult->Note(InlineObservation::CALLSITE_FOLDABLE_EXPR); } - if ((FgStack::IsConstant(arg0) && FgStack::IsConstant(arg1)) || - (FgStack::IsConstant(arg1) && FgStack::IsConstant(arg0))) + // ConstArg op Const -> ConstArg + // ConstArg op ConstArg -> ConstArg + else if (FgStack::IsConstArgument(arg0, impInlineInfo) && + FgStack::IsConstantOrConstArg(arg1, impInlineInfo)) + { + if (FgStack::IsConstant(arg1)) + { + pushedStack.Push(arg0); + } + handled = true; + compInlineResult->Note(InlineObservation::CALLSITE_FOLDABLE_EXPR); + } + // Const op Const -> Const + else if (FgStack::IsConstant(arg0) && FgStack::IsConstant(arg1)) { // both are constants, but we're mostly interested in cases where a const arg leads to // a foldable expression. handled = true; } + // Arg op ConstArg + // Arg op Const else if (FgStack::IsArgument(arg0) && FgStack::IsConstantOrConstArg(arg1, impInlineInfo)) { // "Arg op CNS" --> keep arg0 in the stack for the next ops + pushedStack.Push(arg0); handled = true; compInlineResult->Note(InlineObservation::CALLEE_BINARY_EXRP_WITH_CNS); } + // ConstArg op Arg + // Const op Arg else if (FgStack::IsArgument(arg1) && FgStack::IsConstantOrConstArg(arg0, impInlineInfo)) { // "CNS op ARG" --> keep arg1 in the stack for the next ops - pushedStack.Push(arg1); handled = true; compInlineResult->Note(InlineObservation::CALLEE_BINARY_EXRP_WITH_CNS); } - + // X / ConstArg + // X % ConstArg if (FgStack::IsConstArgument(arg1, impInlineInfo)) { - // Special case: "X / ConstArg" or "X % ConstArg" if ((opcode == CEE_DIV) || (opcode == CEE_DIV_UN) || (opcode == CEE_REM) || (opcode == CEE_REM_UN)) { compInlineResult->Note(InlineObservation::CALLSITE_DIV_BY_CNS); } + pushedStack.Push(arg0); handled = true; } } @@ -1583,6 +1619,10 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed if (makeInlineObservations) { compInlineResult->Note(InlineObservation::CALLEE_HAS_SWITCH); + if (FgStack::IsConstantOrConstArg(pushedStack.Top(), impInlineInfo)) + { + compInlineResult->Note(InlineObservation::CALLSITE_FOLDABLE_SWITCH); + } // Fail fast, if we're inlining and can't handle this. if (isInlining && compInlineResult->IsFailure()) diff --git a/src/coreclr/jit/fgprofile.cpp b/src/coreclr/jit/fgprofile.cpp index 1659b2d39052..31d7c208ebaf 100644 --- a/src/coreclr/jit/fgprofile.cpp +++ b/src/coreclr/jit/fgprofile.cpp @@ -47,6 +47,31 @@ bool Compiler::fgHaveProfileData() return (fgPgoSchema != nullptr); } +//------------------------------------------------------------------------ +// fgHaveSufficientProfileData: check if profile data is available +// and is sufficient enough to be trustful. +// +// Returns: +// true if so +// +// Note: +// See notes for fgHaveProfileData. +// +bool Compiler::fgHaveSufficientProfileData() +{ + if (!fgHaveProfileData()) + { + return false; + } + + if ((fgFirstBB != nullptr) && (fgPgoSource == ICorJitInfo::PgoSource::Static)) + { + const BasicBlock::weight_t sufficientSamples = 1000; + return fgFirstBB->bbWeight > sufficientSamples; + } + return true; +} + //------------------------------------------------------------------------ // fgApplyProfileScale: scale inlinee counts by appropriate scale factor // diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index c30e9a177846..b3857cdec583 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -13267,8 +13267,6 @@ void Compiler::impImportBlockCode(BasicBlock* block) goto COND_JUMP; case CEE_SWITCH: - assert(!compIsForInlining()); - if (tiVerificationNeeded) { Verify(impStackTop().seTypeInfo.IsType(TI_INT), "Bad switch val"); @@ -19080,33 +19078,30 @@ void Compiler::impMakeDiscretionaryInlineObservations(InlineInfo* pInlineInfo, I inlineResult->NoteInt(InlineObservation::CALLSITE_FREQUENCY, static_cast(frequency)); inlineResult->NoteInt(InlineObservation::CALLSITE_WEIGHT, (int)(weight)); + bool hasProfile = false; + double profileFreq = 0.0; + // If the call site has profile data, report the relative frequency of the site. // - if ((pInlineInfo != nullptr) && rootCompiler->fgHaveProfileData() && pInlineInfo->iciBlock->hasProfileWeight()) + if ((pInlineInfo != nullptr) && rootCompiler->fgHaveSufficientProfileData()) { - BasicBlock::weight_t callSiteWeight = pInlineInfo->iciBlock->bbWeight; - BasicBlock::weight_t entryWeight = rootCompiler->fgFirstBB->bbWeight; - BasicBlock::weight_t profileFreq = entryWeight == 0.0f ? 0.0f : callSiteWeight / entryWeight; + const BasicBlock::weight_t callSiteWeight = pInlineInfo->iciBlock->bbWeight; + const BasicBlock::weight_t entryWeight = rootCompiler->fgFirstBB->bbWeight; + profileFreq = entryWeight == 0.0f ? 0.0 : callSiteWeight / entryWeight; + hasProfile = true; assert(callSiteWeight >= 0); assert(entryWeight >= 0); - - BasicBlock::weight_t sufficientSamples = 1000.0f; - - if (!rootCompiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT) || - ((callSiteWeight + entryWeight) > sufficientSamples)) - { - // Let's not report profiles for methods with insufficient samples during prejitting. - inlineResult->NoteBool(InlineObservation::CALLSITE_HAS_PROFILE, true); - inlineResult->NoteDouble(InlineObservation::CALLSITE_PROFILE_FREQUENCY, profileFreq); - } } - else if ((pInlineInfo == nullptr) && rootCompiler->fgHaveProfileData()) + else if (pInlineInfo == nullptr) { // Simulate a hot callsite for PrejitRoot mode. - inlineResult->NoteBool(InlineObservation::CALLSITE_HAS_PROFILE, true); - inlineResult->NoteDouble(InlineObservation::CALLSITE_PROFILE_FREQUENCY, 1.0); + hasProfile = true; + profileFreq = 1.0; } + + inlineResult->NoteBool(InlineObservation::CALLSITE_HAS_PROFILE, hasProfile); + inlineResult->NoteDouble(InlineObservation::CALLSITE_PROFILE_FREQUENCY, profileFreq); } /***************************************************************************** @@ -19252,6 +19247,10 @@ void Compiler::impCheckCanInline(GenTreeCall* call, goto _exit; } + // Profile data allows us to avoid early "too many IL bytes" outs. + pParam->result->NoteBool(InlineObservation::CALLSITE_HAS_PROFILE, + pParam->pThis->fgHaveSufficientProfileData()); + bool forceInline; forceInline = !!(pParam->methAttr & CORINFO_FLG_FORCEINLINE); @@ -19474,6 +19473,10 @@ void Compiler::impInlineRecordArgInfo(InlineInfo* pInlineInfo, } } + bool isExact = false; + bool isNonNull = false; + inlCurArgInfo->argIsExact = (gtGetClassHandle(curArgVal, &isExact, &isNonNull) != NO_CLASS_HANDLE) && isExact; + // If the arg is a local that is address-taken, we can't safely // directly substitute it into the inlinee. // diff --git a/src/coreclr/jit/inline.def b/src/coreclr/jit/inline.def index a16d82888b64..cbd85ff240de 100644 --- a/src/coreclr/jit/inline.def +++ b/src/coreclr/jit/inline.def @@ -182,6 +182,7 @@ INLINE_OBSERVATION(FOLDABLE_INTRINSIC, int, "foldable intrinsic", INLINE_OBSERVATION(FOLDABLE_EXPR, int, "foldable binary expression", INFORMATION, CALLSITE) INLINE_OBSERVATION(FOLDABLE_EXPR_UN, int, "foldable unary expression", INFORMATION, CALLSITE) INLINE_OBSERVATION(FOLDABLE_BRANCH, int, "foldable branch", INFORMATION, CALLSITE) +INLINE_OBSERVATION(FOLDABLE_SWITCH, int, "foldable switch", INFORMATION, CALLSITE) INLINE_OBSERVATION(DIV_BY_CNS, int, "dividy by const", INFORMATION, CALLSITE) INLINE_OBSERVATION(CONSTANT_ARG_FEEDS_TEST, bool, "constant argument feeds test", INFORMATION, CALLSITE) INLINE_OBSERVATION(DEPTH, int, "depth", INFORMATION, CALLSITE) diff --git a/src/coreclr/jit/inline.h b/src/coreclr/jit/inline.h index bc08dd8110d2..6a39b2d9cb5d 100644 --- a/src/coreclr/jit/inline.h +++ b/src/coreclr/jit/inline.h @@ -610,6 +610,7 @@ struct InlArgInfo unsigned argHasStargOp : 1; // Is there STARG(s) operation on this argument? unsigned argIsByRefToStructLocal : 1; // Is this arg an address of a struct local or a normed struct local or a // field in them? + unsigned argIsExact : 1; // Is this arg of an exact class? }; // InlLclVarInfo describes inline candidate argument and local variable properties. diff --git a/src/coreclr/jit/inlinepolicy.cpp b/src/coreclr/jit/inlinepolicy.cpp index fdf4ae19341c..e84ff2858a01 100644 --- a/src/coreclr/jit/inlinepolicy.cpp +++ b/src/coreclr/jit/inlinepolicy.cpp @@ -326,7 +326,6 @@ void DefaultPolicy::NoteBool(InlineObservation obs, bool value) m_ArgFeedsRangeCheck++; break; - case InlineObservation::CALLEE_HAS_SWITCH: case InlineObservation::CALLEE_UNSUPPORTED_OPCODE: propagate = true; break; @@ -1294,6 +1293,14 @@ void ExtendedDefaultPolicy::NoteBool(InlineObservation obs, bool value) m_FoldableBranch++; break; + case InlineObservation::CALLSITE_FOLDABLE_SWITCH: + m_FoldableSwitch++; + break; + + case InlineObservation::CALLEE_HAS_SWITCH: + m_Switch++; + break; + case InlineObservation::CALLSITE_DIV_BY_CNS: m_DivByCns++; break; @@ -1327,7 +1334,14 @@ void ExtendedDefaultPolicy::NoteInt(InlineObservation obs, int value) { assert(m_IsForceInlineKnown); assert(value != 0); - m_CodeSize = static_cast(value); + m_CodeSize = static_cast(value); + unsigned maxCodeSize = static_cast(JitConfig.JitExtDefaultPolicyMaxIL()); + + // TODO: Enable for PgoSource::Static as well if it's not the generic profile we bundle. + if (m_HasProfile && (m_RootCompiler->fgPgoSource == ICorJitInfo::PgoSource::Dynamic)) + { + maxCodeSize = static_cast(JitConfig.JitExtDefaultPolicyMaxILProf()); + } if (m_IsForceInline) { @@ -1339,7 +1353,7 @@ void ExtendedDefaultPolicy::NoteInt(InlineObservation obs, int value) // Candidate based on small size SetCandidate(InlineObservation::CALLEE_BELOW_ALWAYS_INLINE_SIZE); } - else if (m_CodeSize <= (unsigned)JitConfig.JitExtDefaultPolicyMaxIL()) + else if (m_CodeSize <= maxCodeSize) { // Candidate, pending profitability evaluation SetCandidate(InlineObservation::CALLEE_IS_DISCRETIONARY_INLINE); @@ -1357,16 +1371,16 @@ void ExtendedDefaultPolicy::NoteInt(InlineObservation obs, int value) { SetNever(InlineObservation::CALLEE_DOES_NOT_RETURN); } - else if (!m_IsForceInline) + else if (!m_IsForceInline && !m_HasProfile) { unsigned bbLimit = (unsigned)JitConfig.JitExtDefaultPolicyMaxBB(); if (m_IsPrejitRoot) { // We're not able to recognize arg-specific foldable branches // in prejit-root mode. - bbLimit += 3; + bbLimit += 5 + m_Switch * 10; } - bbLimit += m_FoldableBranch; + bbLimit += m_FoldableBranch + m_FoldableSwitch * 10; if ((unsigned)value > bbLimit) { SetNever(InlineObservation::CALLEE_TOO_MANY_BASIC_BLOCKS); @@ -1419,13 +1433,13 @@ double ExtendedDefaultPolicy::DetermineMultiplier() if (m_ReturnsStructByValue) { // For structs-passed-by-value we might avoid expensive copy operations if we inline. - multiplier += 1.5; + multiplier += 2.0; JITDUMP("\nInline candidate returns a struct by value. Multiplier increased to %g.", multiplier); } else if (m_ArgIsStructByValue > 0) { // Same here - multiplier += 1.5; + multiplier += 2.0; JITDUMP("\n%d arguments are structs passed by value. Multiplier increased to %g.", m_ArgIsStructByValue, multiplier); } @@ -1451,13 +1465,13 @@ double ExtendedDefaultPolicy::DetermineMultiplier() if (m_ArgFeedsRangeCheck > 0) { - multiplier += 0.5; + multiplier += 1.0; JITDUMP("\nInline candidate has arg that feeds range check. Multiplier increased to %g.", multiplier); } if (m_NonGenericCallsGeneric) { - multiplier += 1.5; + multiplier += 2.0; JITDUMP("\nInline candidate is generic and caller is not. Multiplier increased to %g.", multiplier); } @@ -1507,7 +1521,7 @@ double ExtendedDefaultPolicy::DetermineMultiplier() if (m_Intrinsic > 0) { // In most cases such intrinsics are lowered as single CPU instructions - multiplier += 1.5; + multiplier += 1.0 + m_Intrinsic * 0.3; JITDUMP("\nInline has %d intrinsics. Multiplier increased to %g.", m_Intrinsic, multiplier); } @@ -1636,6 +1650,28 @@ double ExtendedDefaultPolicy::DetermineMultiplier() break; } + if (m_FoldableSwitch > 0) + { + multiplier += 6.0; + JITDUMP("\nInline candidate has %d foldable switches. Multiplier increased to %g.", m_FoldableSwitch, + multiplier); + } + else if (m_Switch > 0) + { + if (m_IsPrejitRoot) + { + // Assume the switches can be foldable in PrejitRoot mode. + multiplier += 6.0; + JITDUMP("\nPrejit root candidate has %d switches. Multiplier increased to %g.", m_Switch, multiplier); + } + else + { + // TODO: Investigate cases where it makes sense to inline non-foldable switches + multiplier = 0.0; + JITDUMP("\nInline candidate has %d switches. Multiplier limited to %g.", m_Switch, multiplier); + } + } + if (m_HasProfile) { // There are cases when Profile Data can be misleading or polluted: @@ -1657,14 +1693,16 @@ double ExtendedDefaultPolicy::DetermineMultiplier() { multiplier *= min(m_ProfileFrequency, 1.0) * profileScale; } - JITDUMP("\nCallsite has profile data: %g.", m_ProfileFrequency); + JITDUMP("\nCallsite has profile data: %g. Multiplier limited to %g.", m_ProfileFrequency, multiplier); } - if (m_RootCompiler->lvaTableCnt > ((unsigned)(JitConfig.JitMaxLocalsToTrack() / 4))) + // Slow down if there are already too many locals + if (m_RootCompiler->lvaTableCnt > 64) { - // Slow down inlining if we already have to many locals in the rootCompiler. - multiplier /= ((double)m_RootCompiler->lvaTableCnt / ((double)JitConfig.JitMaxLocalsToTrack() / 4.0)); - JITDUMP("\nCaller %d locals. Multiplier decreased to %g.", m_RootCompiler->lvaTableCnt, multiplier); + // E.g. MaxLocalsToTrack = 1024 and lvaTableCnt = 512 -> multiplier *= 0.5; + const double lclFullness = min(1.0, (double)m_RootCompiler->lvaTableCnt / JitConfig.JitMaxLocalsToTrack()); + multiplier *= (1.0 - lclFullness); + JITDUMP("\nCaller has %d locals. Multiplier decreased to %g.", m_RootCompiler->lvaTableCnt, multiplier); } if (m_BackwardJump) @@ -1738,6 +1776,8 @@ void ExtendedDefaultPolicy::OnDumpXml(FILE* file, unsigned indent) const XATTR_I4(m_FoldableExpr) XATTR_I4(m_FoldableExprUn) XATTR_I4(m_FoldableBranch) + XATTR_I4(m_FoldableSwitch) + XATTR_I4(m_Switch) XATTR_I4(m_DivByCns) XATTR_B(m_ReturnsStructByValue) XATTR_B(m_IsFromValueClass) @@ -1927,7 +1967,6 @@ void DiscretionaryPolicy::NoteDouble(InlineObservation obs, double value) { assert(obs == InlineObservation::CALLSITE_PROFILE_FREQUENCY); assert(value >= 0.0); - assert(m_ProfileFrequency == 0.0); m_ProfileFrequency = value; } diff --git a/src/coreclr/jit/inlinepolicy.h b/src/coreclr/jit/inlinepolicy.h index 7d0d83bd7364..466e17fe0e11 100644 --- a/src/coreclr/jit/inlinepolicy.h +++ b/src/coreclr/jit/inlinepolicy.h @@ -204,6 +204,8 @@ class ExtendedDefaultPolicy : public DefaultPolicy , m_FoldableExpr(0) , m_FoldableExprUn(0) , m_FoldableBranch(0) + , m_FoldableSwitch(0) + , m_Switch(0) , m_DivByCns(0) , m_ReturnsStructByValue(false) , m_IsFromValueClass(false) @@ -252,6 +254,8 @@ class ExtendedDefaultPolicy : public DefaultPolicy unsigned m_FoldableExpr; unsigned m_FoldableExprUn; unsigned m_FoldableBranch; + unsigned m_FoldableSwitch; + unsigned m_Switch; unsigned m_DivByCns; bool m_ReturnsStructByValue : 1; bool m_IsFromValueClass : 1; diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 3220193c662a..cd685c2cc103 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -463,7 +463,8 @@ CONFIG_STRING(JitInlineReplayFile, W("JitInlineReplayFile")) // Extended version of DefaultPolicy that includes a more precise IL scan, // relies on PGO if it exists and generally is more aggressive. CONFIG_INTEGER(JitExtDefaultPolicy, W("JitExtDefaultPolicy"), 1) -CONFIG_INTEGER(JitExtDefaultPolicyMaxIL, W("JitExtDefaultPolicyMaxIL"), 0x64) +CONFIG_INTEGER(JitExtDefaultPolicyMaxIL, W("JitExtDefaultPolicyMaxIL"), 0x80) +CONFIG_INTEGER(JitExtDefaultPolicyMaxILProf, W("JitExtDefaultPolicyMaxILProf"), 0x400) CONFIG_INTEGER(JitExtDefaultPolicyMaxBB, W("JitExtDefaultPolicyMaxBB"), 7) // Inliner uses the following formula for PGO-driven decisions: diff --git a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/DebugViewWriter.cs b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/DebugViewWriter.cs index af967733a5a7..2f2ecf4b3843 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/DebugViewWriter.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/DebugViewWriter.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.IO; using System.Reflection; +using System.Runtime.CompilerServices; namespace System.Linq.Expressions { @@ -168,6 +169,7 @@ private void Out(string s, Flow after) Out(Flow.None, s, after); } + [MethodImpl(MethodImplOptions.NoInlining)] private void Out(Flow before, string s, Flow after) { switch (GetFlow(before)) From 559011c93114421d99ce7c1938227f73593a1f2d Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Wed, 14 Jul 2021 05:13:32 -0700 Subject: [PATCH 119/133] [debugger] Fix debugging after hot reloading (#55599) * Fix deug after hotreload. * do not change mono.proj * fix formatting. * Remove extra space. --- src/mono/mono/metadata/mono-debug.c | 8 ++++++-- src/mono/mono/mini/interp/transform.c | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/mono/mono/metadata/mono-debug.c b/src/mono/mono/metadata/mono-debug.c index 58ecee24d459..10554e663ef4 100644 --- a/src/mono/mono/metadata/mono-debug.c +++ b/src/mono/mono/metadata/mono-debug.c @@ -1120,8 +1120,12 @@ mono_debug_get_seq_points (MonoDebugMethodInfo *minfo, char **source_file, GPtrA if (img->has_updates) { int idx = mono_metadata_token_index (minfo->method->token); gpointer ptr = mono_metadata_update_get_updated_method_ppdb (img, idx); - mono_ppdb_get_seq_points_enc (ptr, seq_points, n_seq_points); - } else if (minfo->handle->ppdb) + if (ptr != NULL) { + mono_ppdb_get_seq_points_enc (ptr, seq_points, n_seq_points); + return; + } + } + if (minfo->handle->ppdb) mono_ppdb_get_seq_points (minfo, source_file, source_file_list, source_files, seq_points, n_seq_points); else mono_debug_symfile_get_seq_points (minfo, source_file, source_file_list, source_files, seq_points, n_seq_points); diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index 7c45880f8d56..c677122e6a5f 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -9757,7 +9757,10 @@ mono_interp_transform_method (InterpMethod *imethod, ThreadContext *context, Mon MonoJitMemoryManager *jit_mm = get_default_jit_mm (); jit_mm_lock (jit_mm); - g_hash_table_replace (jit_mm->seq_points, imethod->method, imethod->jinfo->seq_points); + gpointer seq_points = g_hash_table_lookup (jit_mm->seq_points, imethod->method); + if (!seq_points || seq_points != imethod->jinfo->seq_points) + g_hash_table_replace (jit_mm->seq_points, imethod->method, imethod->jinfo->seq_points); + jit_mm_unlock (jit_mm); // FIXME: Add a different callback ? From 599b7acd519ab28c48770fd11fd61d250a0eae35 Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Wed, 14 Jul 2021 05:13:46 -0700 Subject: [PATCH 120/133] [wasm] [debugger] Support method calls (#55458) * Implement method calls and method calls with simple parameters. * removing useless change. * Support const char. Support object. * Adding more tests as suggested by @pavelsavara. * Adding a test calling a method that returns void and change an attribute value. * Changing what @pavelsavara suggested. * Fix merge. --- .../BrowserDebugProxy/DevToolsHelper.cs | 6 +- .../BrowserDebugProxy/EvaluateExpression.cs | 143 ++- .../MemberReferenceResolver.cs | 93 +- .../debugger/BrowserDebugProxy/MonoProxy.cs | 178 ++-- .../BrowserDebugProxy/MonoSDBHelper.cs | 827 ++++++++++-------- .../EvaluateOnCallFrameTests.cs | 99 +++ .../debugger/DebuggerTestSuite/MonoJsTests.cs | 10 +- .../debugger-test/debugger-evaluate-test.cs | 97 ++ 8 files changed, 945 insertions(+), 508 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs index 6f1ec09317e0..fb4eda76ca24 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs @@ -301,13 +301,13 @@ public DebugStore Store } } - public PerScopeCache GetCacheForScope(int scope_id) + public PerScopeCache GetCacheForScope(int scopeId) { - if (perScopeCaches.TryGetValue(scope_id, out PerScopeCache cache)) + if (perScopeCaches.TryGetValue(scopeId, out PerScopeCache cache)) return cache; cache = new PerScopeCache(); - perScopeCaches[scope_id] = cache; + perScopeCaches[scopeId] = cache; return cache; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index 73de23d6fcc4..58374c43565c 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -27,38 +27,54 @@ private class FindVariableNMethodCall : CSharpSyntaxWalker public List methodCall = new List(); public List memberAccesses = new List(); public List argValues = new List(); + public Dictionary memberAccessValues = new Dictionary(); + private int visitCount; + public bool hasMethodCalls; + public void VisitInternal(SyntaxNode node) + { + Visit(node); + visitCount++; + } public override void Visit(SyntaxNode node) { // TODO: PointerMemberAccessExpression - if (node is MemberAccessExpressionSyntax maes - && node.Kind() == SyntaxKind.SimpleMemberAccessExpression - && !(node.Parent is MemberAccessExpressionSyntax)) + if (visitCount == 0) { - memberAccesses.Add(maes); - } + if (node is MemberAccessExpressionSyntax maes + && node.Kind() == SyntaxKind.SimpleMemberAccessExpression + && !(node.Parent is MemberAccessExpressionSyntax) + && !(node.Parent is InvocationExpressionSyntax)) + { + memberAccesses.Add(maes); + } - if (node is IdentifierNameSyntax identifier - && !(identifier.Parent is MemberAccessExpressionSyntax) - && !identifiers.Any(x => x.Identifier.Text == identifier.Identifier.Text)) - { - identifiers.Add(identifier); + if (node is IdentifierNameSyntax identifier + && !(identifier.Parent is MemberAccessExpressionSyntax) + && !identifiers.Any(x => x.Identifier.Text == identifier.Identifier.Text)) + { + identifiers.Add(identifier); + } } if (node is InvocationExpressionSyntax) { - methodCall.Add(node as InvocationExpressionSyntax); - throw new Exception("Method Call is not implemented yet"); + if (visitCount == 1) + methodCall.Add(node as InvocationExpressionSyntax); + hasMethodCalls = true; } + if (node is AssignmentExpressionSyntax) throw new Exception("Assignment is not implemented yet"); base.Visit(node); } - public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable ma_values, IEnumerable id_values) + public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable ma_values, IEnumerable id_values, IEnumerable method_values) { - CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot(); var memberAccessToParamName = new Dictionary(); + var methodCallToParamName = new Dictionary(); + + CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot(); // 1. Replace all this.a occurrences with this_a_ABDE root = root.ReplaceNodes(memberAccesses, (maes, _) => @@ -77,25 +93,61 @@ public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable ma_val return SyntaxFactory.IdentifierName(id_name); }); + // 1.1 Replace all this.a() occurrences with this_a_ABDE + root = root.ReplaceNodes(methodCall, (m, _) => + { + string iesStr = m.ToString(); + if (!methodCallToParamName.TryGetValue(iesStr, out string id_name)) + { + // Generate a random suffix + string suffix = Guid.NewGuid().ToString().Substring(0, 5); + string prefix = iesStr.Trim().Replace(".", "_").Replace("(", "_").Replace(")", "_"); + id_name = $"{prefix}_{suffix}"; + methodCallToParamName[iesStr] = id_name; + } + + return SyntaxFactory.IdentifierName(id_name); + }); + var paramsSet = new HashSet(); // 2. For every unique member ref, add a corresponding method param - foreach ((MemberAccessExpressionSyntax maes, JObject value) in memberAccesses.Zip(ma_values)) + if (ma_values != null) { - string node_str = maes.ToString(); - if (!memberAccessToParamName.TryGetValue(node_str, out string id_name)) + foreach ((MemberAccessExpressionSyntax maes, JObject value) in memberAccesses.Zip(ma_values)) { - throw new Exception($"BUG: Expected to find an id name for the member access string: {node_str}"); + string node_str = maes.ToString(); + if (!memberAccessToParamName.TryGetValue(node_str, out string id_name)) + { + throw new Exception($"BUG: Expected to find an id name for the member access string: {node_str}"); + } + memberAccessValues[id_name] = value; + root = UpdateWithNewMethodParam(root, id_name, value); } + } - root = UpdateWithNewMethodParam(root, id_name, value); + if (id_values != null) + { + foreach ((IdentifierNameSyntax idns, JObject value) in identifiers.Zip(id_values)) + { + root = UpdateWithNewMethodParam(root, idns.Identifier.Text, value); + } } - foreach ((IdentifierNameSyntax idns, JObject value) in identifiers.Zip(id_values)) + if (method_values != null) { - root = UpdateWithNewMethodParam(root, idns.Identifier.Text, value); + foreach ((InvocationExpressionSyntax ies, JObject value) in methodCall.Zip(method_values)) + { + string node_str = ies.ToString(); + if (!methodCallToParamName.TryGetValue(node_str, out string id_name)) + { + throw new Exception($"BUG: Expected to find an id name for the member access string: {node_str}"); + } + root = UpdateWithNewMethodParam(root, id_name, value); + } } + return syntaxTree.WithRootAndOptions(root, syntaxTree.Options); CompilationUnitSyntax UpdateWithNewMethodParam(CompilationUnitSyntax root, string id_name, JObject value) @@ -139,9 +191,9 @@ private object ConvertJSToCSharpType(JToken variable) case "boolean": return value?.Value(); case "object": - if (subType == "null") - return null; - break; + return null; + case "void": + return null; } throw new Exception($"Evaluate of this datatype {type} not implemented yet");//, "Unsupported"); } @@ -158,8 +210,11 @@ private string GetTypeFullName(JToken variable) { if (subType == "null") return variable["className"].Value(); - break; + else + return "object"; } + case "void": + return "object"; default: return value.GetType().FullName; } @@ -211,6 +266,20 @@ private static async Task> ResolveIdentifiers(IEnumerable> ResolveMethodCalls(IEnumerable methodCalls, Dictionary memberAccessValues, MemberReferenceResolver resolver, CancellationToken token) + { + var values = new List(); + foreach (InvocationExpressionSyntax methodCall in methodCalls) + { + JObject value = await resolver.Resolve(methodCall, memberAccessValues, token); + if (value == null) + throw new ReturnAsErrorException($"Failed to resolve member access for {methodCall}", "ReferenceError"); + + values.Add(value); + } + return values; + } + [UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "Suppressing the warning until gets fixed, see https://github.com/dotnet/runtime/issues/51202")] internal static async Task CompileAndRunTheExpression(string expression, MemberReferenceResolver resolver, CancellationToken token) @@ -231,17 +300,17 @@ public static object Evaluate() throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree"); FindVariableNMethodCall findVarNMethodCall = new FindVariableNMethodCall(); - findVarNMethodCall.Visit(expressionTree); + findVarNMethodCall.VisitInternal(expressionTree); // this fails with `"a)"` // because the code becomes: return (a)); // and the returned expression from GetExpressionFromSyntaxTree is `a`! if (expressionTree.Kind() == SyntaxKind.IdentifierName || expressionTree.Kind() == SyntaxKind.ThisExpression) { - string var_name = expressionTree.ToString(); - JObject value = await resolver.Resolve(var_name, token); + string varName = expressionTree.ToString(); + JObject value = await resolver.Resolve(varName, token); if (value == null) - throw new ReturnAsErrorException($"Cannot find member named '{var_name}'.", "ReferenceError"); + throw new ReturnAsErrorException($"Cannot find member named '{varName}'.", "ReferenceError"); return value; } @@ -256,7 +325,19 @@ public static object Evaluate() IList identifierValues = await ResolveIdentifiers(findVarNMethodCall.identifiers, resolver, token); - syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, memberAccessValues, identifierValues); + syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, memberAccessValues, identifierValues, null); + + if (findVarNMethodCall.hasMethodCalls) + { + expressionTree = GetExpressionFromSyntaxTree(syntaxTree); + + findVarNMethodCall.VisitInternal(expressionTree); + + IList methodValues = await ResolveMethodCalls(findVarNMethodCall.methodCall, findVarNMethodCall.memberAccessValues, resolver, token); + + syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, null, null, methodValues); + } + expressionTree = GetExpressionFromSyntaxTree(syntaxTree); if (expressionTree == null) throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree"); @@ -313,7 +394,7 @@ public static object Evaluate() private static object ConvertCSharpToJSType(object v, ITypeSymbol type) { if (v == null) - return new { type = "object", subtype = "null", className = type.ToString() }; + return new { type = "object", subtype = "null", className = type.ToString(), description = type.ToString() }; if (v is string s) { diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index c4e578a0a2c9..cd21ac641909 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -9,6 +9,8 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.IO; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; namespace Microsoft.WebAssembly.Diagnostics { @@ -22,14 +24,14 @@ internal class MemberReferenceResolver private ILogger logger; private bool locals_fetched; - public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId session_id, int scope_id, ILogger logger) + public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId sessionId, int scopeId, ILogger logger) { - sessionId = session_id; - scopeId = scope_id; + this.sessionId = sessionId; + this.scopeId = scopeId; this.proxy = proxy; this.ctx = ctx; this.logger = logger; - scopeCache = ctx.GetCacheForScope(scope_id); + scopeCache = ctx.GetCacheForScope(scopeId); } public async Task GetValueFromObject(JToken objRet, CancellationToken token) { @@ -37,7 +39,7 @@ public async Task GetValueFromObject(JToken objRet, CancellationToken t { if (DotnetObjectId.TryParse(objRet?["value"]?["objectId"]?.Value(), out DotnetObjectId objectId)) { - var exceptionObject = await proxy.sdbHelper.GetObjectValues(sessionId, int.Parse(objectId.Value), true, false, false, true, token); + var exceptionObject = await proxy.SdbHelper.GetObjectValues(sessionId, int.Parse(objectId.Value), true, false, false, true, token); var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value().Equals("_message")); exceptionObjectMessage["value"]["value"] = objRet["value"]?["className"]?.Value() + ": " + exceptionObjectMessage["value"]?["value"]?.Value(); return exceptionObjectMessage["value"]?.Value(); @@ -51,10 +53,10 @@ public async Task GetValueFromObject(JToken objRet, CancellationToken t { if (DotnetObjectId.TryParse(objRet?["get"]?["objectIdValue"]?.Value(), out DotnetObjectId objectId)) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.WriteObj(objectId, proxy.sdbHelper); - var ret = await proxy.sdbHelper.InvokeMethod(sessionId, command_params.ToArray(), objRet["get"]["methodId"].Value(), objRet["name"].Value(), token); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.WriteObj(objectId, proxy.SdbHelper); + var ret = await proxy.SdbHelper.InvokeMethod(sessionId, commandParams.ToArray(), objRet["get"]["methodId"].Value(), objRet["name"].Value(), token); return await GetValueFromObject(ret, token); } @@ -62,12 +64,16 @@ public async Task GetValueFromObject(JToken objRet, CancellationToken t return null; } // Checks Locals, followed by `this` - public async Task Resolve(string var_name, CancellationToken token) + public async Task Resolve(string varName, CancellationToken token) { - string[] parts = var_name.Split("."); + //has method calls + if (varName.Contains('(')) + return null; + + string[] parts = varName.Split("."); JObject rootObject = null; - if (scopeCache.MemberReferences.TryGetValue(var_name, out JObject ret)) { + if (scopeCache.MemberReferences.TryGetValue(varName, out JObject ret)) { return ret; } foreach (string part in parts) @@ -81,8 +87,8 @@ public async Task Resolve(string var_name, CancellationToken token) return null; if (DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId objectId)) { - var root_res_obj = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token); - var objRet = root_res_obj.FirstOrDefault(objPropAttr => objPropAttr["name"].Value() == partTrimmed); + var rootResObj = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token); + var objRet = rootResObj.FirstOrDefault(objPropAttr => objPropAttr["name"].Value() == partTrimmed); if (objRet == null) return null; @@ -109,8 +115,8 @@ public async Task Resolve(string var_name, CancellationToken token) } else if (DotnetObjectId.TryParse(objThis?["value"]?["objectId"]?.Value(), out DotnetObjectId objectId)) { - var root_res_obj = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token); - var objRet = root_res_obj.FirstOrDefault(objPropAttr => objPropAttr["name"].Value() == partTrimmed); + var rootResObj = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token); + var objRet = rootResObj.FirstOrDefault(objPropAttr => objPropAttr["name"].Value() == partTrimmed); if (objRet != null) { rootObject = await GetValueFromObject(objRet, token); @@ -122,9 +128,62 @@ public async Task Resolve(string var_name, CancellationToken token) } } } - scopeCache.MemberReferences[var_name] = rootObject; + scopeCache.MemberReferences[varName] = rootObject; return rootObject; } + public async Task Resolve(InvocationExpressionSyntax method, Dictionary memberAccessValues, CancellationToken token) + { + var methodName = ""; + try + { + JObject rootObject = null; + var expr = method.Expression; + if (expr is MemberAccessExpressionSyntax) + { + var memberAccessExpressionSyntax = expr as MemberAccessExpressionSyntax; + rootObject = await Resolve(memberAccessExpressionSyntax.Expression.ToString(), token); + methodName = memberAccessExpressionSyntax.Name.ToString(); + } + if (rootObject != null) + { + DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId objectId); + var typeId = await proxy.SdbHelper.GetTypeIdFromObject(sessionId, int.Parse(objectId.Value), true, token); + int methodId = await proxy.SdbHelper.GetMethodIdByName(sessionId, typeId[0], methodName, token); + if (methodId == 0) { + var typeName = await proxy.SdbHelper.GetTypeName(sessionId, typeId[0], token); + throw new Exception($"Method '{methodName}' not found in type '{typeName}'"); + } + var command_params_obj = new MemoryStream(); + var commandParamsObjWriter = new MonoBinaryWriter(command_params_obj); + commandParamsObjWriter.WriteObj(objectId, proxy.SdbHelper); + if (method.ArgumentList != null) + { + commandParamsObjWriter.Write((int)method.ArgumentList.Arguments.Count); + foreach (var arg in method.ArgumentList.Arguments) + { + if (arg.Expression is LiteralExpressionSyntax) + { + if (!await commandParamsObjWriter.WriteConst(sessionId, arg.Expression as LiteralExpressionSyntax, proxy.SdbHelper, token)) + return null; + } + if (arg.Expression is IdentifierNameSyntax) + { + var argParm = arg.Expression as IdentifierNameSyntax; + if (!await commandParamsObjWriter.WriteJsonValue(sessionId, memberAccessValues[argParm.Identifier.Text], proxy.SdbHelper, token)) + return null; + } + } + var retMethod = await proxy.SdbHelper.InvokeMethod(sessionId, command_params_obj.ToArray(), methodId, "methodRet", token); + return await GetValueFromObject(retMethod, token); + } + } + return null; + } + catch (Exception) + { + throw new Exception($"Unable to evaluate method '{methodName}'"); + } + } } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index f14bc1e16a99..1872ec1d2a93 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -18,7 +18,7 @@ namespace Microsoft.WebAssembly.Diagnostics { internal class MonoProxy : DevToolsProxy { - internal MonoSDBHelper sdbHelper; + internal MonoSDBHelper SdbHelper { get; } private IList urlSymbolServerList; private static HttpClient client = new HttpClient(); private HashSet sessions = new HashSet(); @@ -29,7 +29,7 @@ internal class MonoProxy : DevToolsProxy public MonoProxy(ILoggerFactory loggerFactory, IList urlSymbolServerList) : base(loggerFactory) { this.urlSymbolServerList = urlSymbolServerList ?? new List(); - sdbHelper = new MonoSDBHelper(this); + SdbHelper = new MonoSDBHelper(this); } internal ExecutionContext GetContext(SessionId sessionId) @@ -449,7 +449,7 @@ protected override async Task AcceptCommand(MessageId id, string method, J } } else - await sdbHelper.EnableExceptions(id, state, token); + await SdbHelper.EnableExceptions(id, state, token); // Pass this on to JS too return false; } @@ -542,16 +542,16 @@ private async Task CallOnFunction(MessageId id, JObject args, Cancellation switch (objectId.Scheme) { case "object": - args["details"] = await sdbHelper.GetObjectProxy(id, int.Parse(objectId.Value), token); + args["details"] = await SdbHelper.GetObjectProxy(id, int.Parse(objectId.Value), token); break; case "valuetype": - args["details"] = await sdbHelper.GetValueTypeProxy(id, int.Parse(objectId.Value), token); + args["details"] = await SdbHelper.GetValueTypeProxy(id, int.Parse(objectId.Value), token); break; case "pointer": - args["details"] = await sdbHelper.GetPointerContent(id, int.Parse(objectId.Value), token); + args["details"] = await SdbHelper.GetPointerContent(id, int.Parse(objectId.Value), token); break; case "array": - args["details"] = await sdbHelper.GetArrayValues(id, int.Parse(objectId.Value), token); + args["details"] = await SdbHelper.GetArrayValues(id, int.Parse(objectId.Value), token); break; case "cfo_res": { @@ -580,10 +580,10 @@ private async Task CallOnFunction(MessageId id, JObject args, Cancellation if (res.Value?["result"]?["value"]?["type"] == null) //it means that is not a buffer returned from the debugger-agent { byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value()); - var ret_debugger_cmd = new MemoryStream(newBytes); - var ret_debugger_cmd_reader = new MonoBinaryReader(ret_debugger_cmd); - ret_debugger_cmd_reader.ReadByte(); //number of objects returned. - var obj = await sdbHelper.CreateJObjectForVariableValue(id, ret_debugger_cmd_reader, "ret", false, -1, token); + var retDebuggerCmd = new MemoryStream(newBytes); + var retDebuggerCmdReader = new MonoBinaryReader(retDebuggerCmd); + retDebuggerCmdReader.ReadByte(); //number of objects returned. + var obj = await SdbHelper.CreateJObjectForVariableValue(id, retDebuggerCmdReader, "ret", false, -1, token); /*JTokenType? res_value_type = res.Value?["result"]?["value"]?.Type;*/ res = Result.OkFromObject(new { result = obj["value"]}); SendResponse(id, res, token); @@ -606,7 +606,7 @@ private async Task OnSetVariableValue(MessageId id, int scopeId, string va var varToSetValue = varIds.FirstOrDefault(v => v.Name == varName); if (varToSetValue == null) return false; - var res = await sdbHelper.SetVariableValue(id, ctx.ThreadId, scopeId, varToSetValue.Index, varValue["value"].Value(), token); + var res = await SdbHelper.SetVariableValue(id, ctx.ThreadId, scopeId, varToSetValue.Index, varValue["value"].Value(), token); if (res) SendResponse(id, Result.Ok(new JObject()), token); else @@ -635,13 +635,13 @@ internal async Task RuntimeGetPropertiesInternal(SessionId id, DotnetObj return res.Value?["result"]; } case "valuetype": - return await sdbHelper.GetValueTypeValues(id, int.Parse(objectId.Value), accessorPropertiesOnly, token); + return await SdbHelper.GetValueTypeValues(id, int.Parse(objectId.Value), accessorPropertiesOnly, token); case "array": - return await sdbHelper.GetArrayValues(id, int.Parse(objectId.Value), token); + return await SdbHelper.GetArrayValues(id, int.Parse(objectId.Value), token); case "object": - return await sdbHelper.GetObjectValues(id, int.Parse(objectId.Value), true, false, accessorPropertiesOnly, ownProperties, token); + return await SdbHelper.GetObjectValues(id, int.Parse(objectId.Value), true, false, accessorPropertiesOnly, ownProperties, token); case "pointer": - return new JArray{await sdbHelper.GetPointerContent(id, int.Parse(objectId.Value), token)}; + return new JArray{await SdbHelper.GetPointerContent(id, int.Parse(objectId.Value), token)}; case "cfo_res": { Result res = await SendMonoCommand(id, MonoCommands.GetDetails(int.Parse(objectId.Value), args), token); @@ -690,34 +690,34 @@ private async Task EvaluateCondition(SessionId sessionId, ExecutionContext return false; } - private async Task ProcessEnC(SessionId sessionId, ExecutionContext context, MonoBinaryReader ret_debugger_cmd_reader, CancellationToken token) + private async Task ProcessEnC(SessionId sessionId, ExecutionContext context, MonoBinaryReader retDebuggerCmdReader, CancellationToken token) { - int moduleId = ret_debugger_cmd_reader.ReadInt32(); - int meta_size = ret_debugger_cmd_reader.ReadInt32(); - byte[] meta_buf = ret_debugger_cmd_reader.ReadBytes(meta_size); - int pdb_size = ret_debugger_cmd_reader.ReadInt32(); - byte[] pdb_buf = ret_debugger_cmd_reader.ReadBytes(pdb_size); + int moduleId = retDebuggerCmdReader.ReadInt32(); + int meta_size = retDebuggerCmdReader.ReadInt32(); + byte[] meta_buf = retDebuggerCmdReader.ReadBytes(meta_size); + int pdb_size = retDebuggerCmdReader.ReadInt32(); + byte[] pdb_buf = retDebuggerCmdReader.ReadBytes(pdb_size); - var assembly_name = await sdbHelper.GetAssemblyNameFromModule(sessionId, moduleId, token); + var assemblyName = await SdbHelper.GetAssemblyNameFromModule(sessionId, moduleId, token); DebugStore store = await LoadStore(sessionId, token); - AssemblyInfo asm = store.GetAssemblyByName(assembly_name); + AssemblyInfo asm = store.GetAssemblyByName(assemblyName); foreach (var method in store.EnC(sessionId, asm, meta_buf, pdb_buf)) await ResetBreakpoint(sessionId, method, token); return true; } - private async Task SendBreakpointsOfMethodUpdated(SessionId sessionId, ExecutionContext context, MonoBinaryReader ret_debugger_cmd_reader, CancellationToken token) + private async Task SendBreakpointsOfMethodUpdated(SessionId sessionId, ExecutionContext context, MonoBinaryReader retDebuggerCmdReader, CancellationToken token) { - var method_id = ret_debugger_cmd_reader.ReadInt32(); - var method_token = await sdbHelper.GetMethodToken(sessionId, method_id, token); - var assembly_id = await sdbHelper.GetAssemblyIdFromMethod(sessionId, method_id, token); - var assembly_name = await sdbHelper.GetAssemblyName(sessionId, assembly_id, token); - var method_name = await sdbHelper.GetMethodName(sessionId, method_id, token); + var method_id = retDebuggerCmdReader.ReadInt32(); + var method_token = await SdbHelper.GetMethodToken(sessionId, method_id, token); + var assembly_id = await SdbHelper.GetAssemblyIdFromMethod(sessionId, method_id, token); + var assembly_name = await SdbHelper.GetAssemblyName(sessionId, assembly_id, token); + var method_name = await SdbHelper.GetMethodName(sessionId, method_id, token); DebugStore store = await LoadStore(sessionId, token); AssemblyInfo asm = store.GetAssemblyByName(assembly_name); if (asm == null) { - assembly_name = await sdbHelper.GetAssemblyNameFull(sessionId, assembly_id, token); + assembly_name = await SdbHelper.GetAssemblyNameFull(sessionId, assembly_id, token); asm = store.GetAssemblyByName(assembly_name); if (asm == null) { @@ -743,28 +743,28 @@ private async Task SendCallStack(SessionId sessionId, ExecutionContext con { var callFrames = new List(); var frames = new List(); - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(thread_id); - command_params_writer.Write(0); - command_params_writer.Write(-1); - var ret_debugger_cmd_reader = await sdbHelper.SendDebuggerAgentCommand(sessionId, CmdThread.GetFrameInfo, command_params, token); - var frame_count = ret_debugger_cmd_reader.ReadInt32(); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(thread_id); + commandParamsWriter.Write(0); + commandParamsWriter.Write(-1); + var retDebuggerCmdReader = await SdbHelper.SendDebuggerAgentCommand(sessionId, CmdThread.GetFrameInfo, commandParams, token); + var frame_count = retDebuggerCmdReader.ReadInt32(); //Console.WriteLine("frame_count - " + frame_count); for (int j = 0; j < frame_count; j++) { - var frame_id = ret_debugger_cmd_reader.ReadInt32(); - var method_id = ret_debugger_cmd_reader.ReadInt32(); - var il_pos = ret_debugger_cmd_reader.ReadInt32(); - var flags = ret_debugger_cmd_reader.ReadByte(); - var method_token = await sdbHelper.GetMethodToken(sessionId, method_id, token); - var assembly_id = await sdbHelper.GetAssemblyIdFromMethod(sessionId, method_id, token); - var assembly_name = await sdbHelper.GetAssemblyName(sessionId, assembly_id, token); - var method_name = await sdbHelper.GetMethodName(sessionId, method_id, token); + var frame_id = retDebuggerCmdReader.ReadInt32(); + var methodId = retDebuggerCmdReader.ReadInt32(); + var il_pos = retDebuggerCmdReader.ReadInt32(); + var flags = retDebuggerCmdReader.ReadByte(); + var method_token = await SdbHelper.GetMethodToken(sessionId, methodId, token); + var assembly_id = await SdbHelper.GetAssemblyIdFromMethod(sessionId, methodId, token); + var assembly_name = await SdbHelper.GetAssemblyName(sessionId, assembly_id, token); + var method_name = await SdbHelper.GetMethodName(sessionId, methodId, token); DebugStore store = await LoadStore(sessionId, token); AssemblyInfo asm = store.GetAssemblyByName(assembly_name); if (asm == null) { - assembly_name = await sdbHelper.GetAssemblyNameFull(sessionId, assembly_id, token); //maybe is a lazy loaded assembly + assembly_name = await SdbHelper.GetAssemblyNameFull(sessionId, assembly_id, token); //maybe is a lazy loaded assembly asm = store.GetAssemblyByName(assembly_name); if (asm == null) { @@ -794,7 +794,7 @@ private async Task SendCallStack(SessionId sessionId, ExecutionContext con continue; } - method.DebuggerId = method_id; + method.DebuggerId = methodId; SourceLocation location = method?.GetLocationByIl(il_pos); @@ -881,43 +881,43 @@ private async Task OnReceiveDebuggerAgentEvent(SessionId sessionId, JObjec ExecutionContext context = GetContext(sessionId); byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value()); - var ret_debugger_cmd = new MemoryStream(newBytes); - var ret_debugger_cmd_reader = new MonoBinaryReader(ret_debugger_cmd); - ret_debugger_cmd_reader.ReadBytes(11); //skip HEADER_LEN - ret_debugger_cmd_reader.ReadByte(); //suspend_policy - var number_of_events = ret_debugger_cmd_reader.ReadInt32(); //number of events -> should be always one + var retDebuggerCmd = new MemoryStream(newBytes); + var retDebuggerCmdReader = new MonoBinaryReader(retDebuggerCmd); + retDebuggerCmdReader.ReadBytes(11); //skip HEADER_LEN + retDebuggerCmdReader.ReadByte(); //suspend_policy + var number_of_events = retDebuggerCmdReader.ReadInt32(); //number of events -> should be always one for (int i = 0 ; i < number_of_events; i++) { - var event_kind = (EventKind)ret_debugger_cmd_reader.ReadByte(); //event kind - var request_id = ret_debugger_cmd_reader.ReadInt32(); //request id + var event_kind = (EventKind)retDebuggerCmdReader.ReadByte(); //event kind + var request_id = retDebuggerCmdReader.ReadInt32(); //request id if (event_kind == EventKind.Step) - await sdbHelper.ClearSingleStep(sessionId, request_id, token); - int thread_id = ret_debugger_cmd_reader.ReadInt32(); + await SdbHelper.ClearSingleStep(sessionId, request_id, token); + int thread_id = retDebuggerCmdReader.ReadInt32(); switch (event_kind) { case EventKind.MethodUpdate: { - var ret = await SendBreakpointsOfMethodUpdated(sessionId, context, ret_debugger_cmd_reader, token); + var ret = await SendBreakpointsOfMethodUpdated(sessionId, context, retDebuggerCmdReader, token); await SendCommand(sessionId, "Debugger.resume", new JObject(), token); return ret; } case EventKind.EnC: { - var ret = await ProcessEnC(sessionId, context, ret_debugger_cmd_reader, token); + var ret = await ProcessEnC(sessionId, context, retDebuggerCmdReader, token); await SendCommand(sessionId, "Debugger.resume", new JObject(), token); return ret; } case EventKind.Exception: { string reason = "exception"; - int object_id = ret_debugger_cmd_reader.ReadInt32(); - var caught = ret_debugger_cmd_reader.ReadByte(); - var exceptionObject = await sdbHelper.GetObjectValues(sessionId, object_id, true, false, false, true, token); + int object_id = retDebuggerCmdReader.ReadInt32(); + var caught = retDebuggerCmdReader.ReadByte(); + var exceptionObject = await SdbHelper.GetObjectValues(sessionId, object_id, true, false, false, true, token); var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value().Equals("message")); var data = JObject.FromObject(new { type = "object", subtype = "error", - className = await sdbHelper.GetClassNameFromObject(sessionId, object_id, token), + className = await SdbHelper.GetClassNameFromObject(sessionId, object_id, token), uncaught = caught == 0, description = exceptionObjectMessage["value"]["value"].Value(), objectId = $"dotnet:object:{object_id}" @@ -932,9 +932,9 @@ private async Task OnReceiveDebuggerAgentEvent(SessionId sessionId, JObjec { Breakpoint bp = context.BreakpointRequests.Values.SelectMany(v => v.Locations).FirstOrDefault(b => b.RemoteId == request_id); string reason = "other";//other means breakpoint - int method_id = 0; + int methodId = 0; if (event_kind != EventKind.UserBreak) - method_id = ret_debugger_cmd_reader.ReadInt32(); + methodId = retDebuggerCmdReader.ReadInt32(); var ret = await SendCallStack(sessionId, context, reason, thread_id, bp, null, args?["callFrames"]?.Values(), token); return ret; } @@ -1018,7 +1018,7 @@ private async Task OnResume(MessageId msg_id, CancellationToken token) } //discard managed frames - sdbHelper.ClearCache(); + SdbHelper.ClearCache(); GetContext(msg_id).ClearState(); } @@ -1031,9 +1031,9 @@ private async Task Step(MessageId msg_id, StepKind kind, CancellationToken if (context.CallStack.Count <= 1 && kind == StepKind.Out) return false; - var step = await sdbHelper.Step(msg_id, context.ThreadId, kind, token); + var step = await SdbHelper.Step(msg_id, context.ThreadId, kind, token); if (step == false) { - sdbHelper.ClearCache(); + SdbHelper.ClearCache(); context.ClearState(); await SendCommand(msg_id, "Debugger.stepOut", new JObject(), token); return false; @@ -1110,7 +1110,7 @@ private async Task OnAssemblyLoadedJSEvent(SessionId sessionId, JObject ev } } - private async Task OnEvaluateOnCallFrame(MessageId msg_id, int scope_id, string expression, CancellationToken token) + private async Task OnEvaluateOnCallFrame(MessageId msg_id, int scopeId, string expression, CancellationToken token) { try { @@ -1118,7 +1118,7 @@ private async Task OnEvaluateOnCallFrame(MessageId msg_id, int scope_id, s if (context.CallStack == null) return false; - var resolver = new MemberReferenceResolver(this, context, msg_id, scope_id, logger); + var resolver = new MemberReferenceResolver(this, context, msg_id, scopeId, logger); JObject retValue = await resolver.Resolve(expression, token); if (retValue == null) @@ -1151,24 +1151,24 @@ private async Task OnEvaluateOnCallFrame(MessageId msg_id, int scope_id, s return true; } - internal async Task GetScopeProperties(SessionId msg_id, int scope_id, CancellationToken token) + internal async Task GetScopeProperties(SessionId msg_id, int scopeId, CancellationToken token) { try { ExecutionContext ctx = GetContext(msg_id); - Frame scope = ctx.CallStack.FirstOrDefault(s => s.Id == scope_id); + Frame scope = ctx.CallStack.FirstOrDefault(s => s.Id == scopeId); if (scope == null) - return Result.Err(JObject.FromObject(new { message = $"Could not find scope with id #{scope_id}" })); + return Result.Err(JObject.FromObject(new { message = $"Could not find scope with id #{scopeId}" })); - VarInfo[] var_ids = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset); + VarInfo[] varIds = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset); - var values = await sdbHelper.StackFrameGetValues(msg_id, scope.Method, ctx.ThreadId, scope_id, var_ids, token); + var values = await SdbHelper.StackFrameGetValues(msg_id, scope.Method, ctx.ThreadId, scopeId, varIds, token); if (values != null) { if (values == null || values.Count == 0) return Result.OkFromObject(new { result = Array.Empty() }); - PerScopeCache frameCache = ctx.GetCacheForScope(scope_id); + PerScopeCache frameCache = ctx.GetCacheForScope(scopeId); foreach (JObject value in values) { frameCache.Locals[value["name"]?.Value()] = value; @@ -1191,9 +1191,9 @@ private async Task SetMonoBreakpoint(SessionId sessionId, string req int method_token = bp.Location.CliLocation.Method.Token; int il_offset = bp.Location.CliLocation.Offset; - var assembly_id = await sdbHelper.GetAssemblyId(sessionId, asm_name, token); - var method_id = await sdbHelper.GetMethodIdByToken(sessionId, assembly_id, method_token, token); - var breakpoint_id = await sdbHelper.SetBreakpoint(sessionId, method_id, il_offset, token); + var assembly_id = await SdbHelper.GetAssemblyId(sessionId, asm_name, token); + var methodId = await SdbHelper.GetMethodIdByToken(sessionId, assembly_id, method_token, token); + var breakpoint_id = await SdbHelper.SetBreakpoint(sessionId, methodId, il_offset, token); if (breakpoint_id > 0) { @@ -1258,22 +1258,22 @@ private async Task RuntimeReady(SessionId sessionId, CancellationTok if (Interlocked.CompareExchange(ref context.ready, new TaskCompletionSource(), null) != null) return await context.ready.Task; - var command_params = new MemoryStream(); - var ret_debugger_cmd_reader = await sdbHelper.SendDebuggerAgentCommand(sessionId, CmdEventRequest.ClearAllBreakpoints, command_params, token); - if (ret_debugger_cmd_reader == null) + var commandParams = new MemoryStream(); + var retDebuggerCmdReader = await SdbHelper.SendDebuggerAgentCommand(sessionId, CmdEventRequest.ClearAllBreakpoints, commandParams, token); + if (retDebuggerCmdReader == null) { Log("verbose", $"Failed to clear breakpoints"); } if (context.PauseOnCaught && context.PauseOnUncaught) - await sdbHelper.EnableExceptions(sessionId, "all", token); + await SdbHelper.EnableExceptions(sessionId, "all", token); else if (context.PauseOnUncaught) - await sdbHelper.EnableExceptions(sessionId, "uncaught", token); + await SdbHelper.EnableExceptions(sessionId, "uncaught", token); - await sdbHelper.SetProtocolVersion(sessionId, token); - await sdbHelper.EnableReceiveRequests(sessionId, EventKind.UserBreak, token); - await sdbHelper.EnableReceiveRequests(sessionId, EventKind.EnC, token); - await sdbHelper.EnableReceiveRequests(sessionId, EventKind.MethodUpdate, token); + await SdbHelper.SetProtocolVersion(sessionId, token); + await SdbHelper.EnableReceiveRequests(sessionId, EventKind.UserBreak, token); + await SdbHelper.EnableReceiveRequests(sessionId, EventKind.EnC, token); + await SdbHelper.EnableReceiveRequests(sessionId, EventKind.MethodUpdate, token); DebugStore store = await LoadStore(sessionId, token); @@ -1306,7 +1306,7 @@ private async Task RemoveBreakpoint(SessionId msg_id, JObject args, bool isEnCRe foreach (Breakpoint bp in breakpointRequest.Locations) { - var breakpoint_removed = await sdbHelper.RemoveBreakpoint(msg_id, bp.RemoteId, token); + var breakpoint_removed = await SdbHelper.RemoveBreakpoint(msg_id, bp.RemoteId, token); if (breakpoint_removed) { bp.RemoteId = -1; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 42ad54e6e2c6..d12533c1e9ff 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -14,6 +14,8 @@ using Newtonsoft.Json.Linq; using System.Net.Http; using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; namespace Microsoft.WebAssembly.Diagnostics { @@ -462,7 +464,7 @@ public override void Write(int val) Array.Reverse(bytes, 0, bytes.Length); Write(bytes); } - public void WriteObj(DotnetObjectId objectId, MonoSDBHelper sdbHelper) + public void WriteObj(DotnetObjectId objectId, MonoSDBHelper SdbHelper) { if (objectId.Scheme == "object") { @@ -471,9 +473,90 @@ public void WriteObj(DotnetObjectId objectId, MonoSDBHelper sdbHelper) } if (objectId.Scheme == "valuetype") { - Write(sdbHelper.valueTypes[int.Parse(objectId.Value)].valueTypeBuffer); + Write(SdbHelper.valueTypes[int.Parse(objectId.Value)].valueTypeBuffer); } } + public async Task WriteConst(SessionId sessionId, LiteralExpressionSyntax constValue, MonoSDBHelper SdbHelper, CancellationToken token) + { + switch (constValue.Kind()) + { + case SyntaxKind.NumericLiteralExpression: + { + Write((byte)ElementType.I4); + Write((int)constValue.Token.Value); + return true; + } + case SyntaxKind.StringLiteralExpression: + { + int stringId = await SdbHelper.CreateString(sessionId, (string)constValue.Token.Value, token); + Write((byte)ElementType.String); + Write((int)stringId); + return true; + } + case SyntaxKind.TrueLiteralExpression: + { + Write((byte)ElementType.Boolean); + Write((int)1); + return true; + } + case SyntaxKind.FalseLiteralExpression: + { + Write((byte)ElementType.Boolean); + Write((int)0); + return true; + } + case SyntaxKind.NullLiteralExpression: + { + Write((byte)ValueTypeId.Null); + Write((byte)0); //not used + Write((int)0); //not used + return true; + } + case SyntaxKind.CharacterLiteralExpression: + { + Write((byte)ElementType.Char); + Write((int)(char)constValue.Token.Value); + return true; + } + } + return false; + } + + public async Task WriteJsonValue(SessionId sessionId, JObject objValue, MonoSDBHelper SdbHelper, CancellationToken token) + { + switch (objValue["type"].Value()) + { + case "number": + { + Write((byte)ElementType.I4); + Write(objValue["value"].Value()); + return true; + } + case "string": + { + int stringId = await SdbHelper.CreateString(sessionId, objValue["value"].Value(), token); + Write((byte)ElementType.String); + Write((int)stringId); + return true; + } + case "boolean": + { + Write((byte)ElementType.Boolean); + if (objValue["value"].Value()) + Write((int)1); + else + Write((int)0); + return true; + } + case "object": + { + DotnetObjectId.TryParse(objValue["objectId"]?.Value(), out DotnetObjectId objectId); + WriteObj(objectId, SdbHelper); + return true; + } + } + return false; + } } internal class FieldTypeClass { @@ -545,24 +628,24 @@ public void ClearCache() public async Task SetProtocolVersion(SessionId sessionId, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(MAJOR_VERSION); - command_params_writer.Write(MINOR_VERSION); - command_params_writer.Write((byte)0); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(MAJOR_VERSION); + commandParamsWriter.Write(MINOR_VERSION); + commandParamsWriter.Write((byte)0); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdVM.SetProtocolVersion, command_params, token); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdVM.SetProtocolVersion, commandParams, token); return true; } - public async Task EnableReceiveRequests(SessionId sessionId, EventKind event_kind, CancellationToken token) + public async Task EnableReceiveRequests(SessionId sessionId, EventKind eventKind, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write((byte)event_kind); - command_params_writer.Write((byte)SuspendPolicy.None); - command_params_writer.Write((byte)0); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Set, command_params, token); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write((byte)eventKind); + commandParamsWriter.Write((byte)SuspendPolicy.None); + commandParamsWriter.Write((byte)0); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Set, commandParams, token); return true; } @@ -573,9 +656,9 @@ internal async Task SendDebuggerAgentCommandInternal(SessionId throw new Exception($"SendDebuggerAgentCommand Error - {(CommandSet)command_set} - {command}"); } byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value()); - var ret_debugger_cmd = new MemoryStream(newBytes); - var ret_debugger_cmd_reader = new MonoBinaryReader(ret_debugger_cmd); - return ret_debugger_cmd_reader; + var retDebuggerCmd = new MemoryStream(newBytes); + var retDebuggerCmdReader = new MonoBinaryReader(retDebuggerCmd); + return retDebuggerCmdReader; } internal CommandSet GetCommandSetForCommand(T command) => @@ -611,49 +694,61 @@ internal async Task SendDebuggerAgentCommandWithParmsInternal( throw new Exception("SendDebuggerAgentCommandWithParms Error"); } byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value()); - var ret_debugger_cmd = new MemoryStream(newBytes); - var ret_debugger_cmd_reader = new MonoBinaryReader(ret_debugger_cmd); - return ret_debugger_cmd_reader; + var retDebuggerCmd = new MemoryStream(newBytes); + var retDebuggerCmdReader = new MonoBinaryReader(retDebuggerCmd); + return retDebuggerCmdReader; } - public async Task GetMethodToken(SessionId sessionId, int method_id, CancellationToken token) + public async Task CreateString(SessionId sessionId, string value, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(method_id); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdAppDomain.GetRootDomain, commandParams, token); + var root_domain = retDebuggerCmdReader.ReadInt32(); + commandParamsWriter.Write(root_domain); + commandParamsWriter.WriteString(value); + retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdAppDomain.CreateString, commandParams, token); + return retDebuggerCmdReader.ReadInt32(); + } - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.Token, command_params, token); - return ret_debugger_cmd_reader.ReadInt32() & 0xffffff; //token + public async Task GetMethodToken(SessionId sessionId, int methodId, CancellationToken token) + { + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(methodId); + + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdMethod.Token, commandParams, token); + return retDebuggerCmdReader.ReadInt32() & 0xffffff; //token } public async Task GetMethodIdByToken(SessionId sessionId, int assembly_id, int method_token, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(assembly_id); - command_params_writer.Write(method_token | (int)TokenType.MdtMethodDef); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdAssembly.GetMethodFromToken, command_params, token); - return ret_debugger_cmd_reader.ReadInt32(); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(assembly_id); + commandParamsWriter.Write(method_token | (int)TokenType.MdtMethodDef); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdAssembly.GetMethodFromToken, commandParams, token); + return retDebuggerCmdReader.ReadInt32(); } - public async Task GetAssemblyIdFromMethod(SessionId sessionId, int method_id, CancellationToken token) + public async Task GetAssemblyIdFromMethod(SessionId sessionId, int methodId, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(method_id); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(methodId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.Assembly, command_params, token); - return ret_debugger_cmd_reader.ReadInt32(); //assembly_id + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdMethod.Assembly, commandParams, token); + return retDebuggerCmdReader.ReadInt32(); //assembly_id } public async Task GetAssemblyId(SessionId sessionId, string asm_name, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.WriteString(asm_name); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.WriteString(asm_name); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdVM.GetAssemblyByName, command_params, token); - return ret_debugger_cmd_reader.ReadInt32(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdVM.GetAssemblyByName, commandParams, token); + return retDebuggerCmdReader.ReadInt32(); } public async Task GetAssemblyNameFromModule(SessionId sessionId, int moduleId, CancellationToken token) @@ -669,90 +764,90 @@ public async Task GetAssemblyNameFromModule(SessionId sessionId, int mod public async Task GetAssemblyName(SessionId sessionId, int assembly_id, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(assembly_id); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(assembly_id); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdAssembly.GetLocation, command_params, token); - return ret_debugger_cmd_reader.ReadString(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdAssembly.GetLocation, commandParams, token); + return retDebuggerCmdReader.ReadString(); } public async Task GetAssemblyNameFull(SessionId sessionId, int assembly_id, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(assembly_id); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(assembly_id); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdAssembly.GetName, command_params, token); - var name = ret_debugger_cmd_reader.ReadString(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdAssembly.GetName, commandParams, token); + var name = retDebuggerCmdReader.ReadString(); return name.Remove(name.IndexOf(",")) + ".dll"; } - public async Task GetMethodName(SessionId sessionId, int method_id, CancellationToken token) + public async Task GetMethodName(SessionId sessionId, int methodId, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(method_id); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(methodId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetNameFull, command_params, token); - var methodName = ret_debugger_cmd_reader.ReadString(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetNameFull, commandParams, token); + var methodName = retDebuggerCmdReader.ReadString(); return methodName.Substring(methodName.IndexOf(":")+1); } - public async Task MethodIsStatic(SessionId sessionId, int method_id, CancellationToken token) + public async Task MethodIsStatic(SessionId sessionId, int methodId, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(method_id); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(methodId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetInfo, command_params, token); - var flags = ret_debugger_cmd_reader.ReadInt32(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetInfo, commandParams, token); + var flags = retDebuggerCmdReader.ReadInt32(); return (flags & 0x0010) > 0; //check method is static } - public async Task GetParamCount(SessionId sessionId, int method_id, CancellationToken token) + public async Task GetParamCount(SessionId sessionId, int methodId, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(method_id); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(methodId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetParamInfo, command_params, token); - ret_debugger_cmd_reader.ReadInt32(); - int param_count = ret_debugger_cmd_reader.ReadInt32(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetParamInfo, commandParams, token); + retDebuggerCmdReader.ReadInt32(); + int param_count = retDebuggerCmdReader.ReadInt32(); return param_count; } - public async Task GetReturnType(SessionId sessionId, int method_id, CancellationToken token) + public async Task GetReturnType(SessionId sessionId, int methodId, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(method_id); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(methodId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetParamInfo, command_params, token); - ret_debugger_cmd_reader.ReadInt32(); - ret_debugger_cmd_reader.ReadInt32(); - ret_debugger_cmd_reader.ReadInt32(); - var retType = ret_debugger_cmd_reader.ReadInt32(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetParamInfo, commandParams, token); + retDebuggerCmdReader.ReadInt32(); + retDebuggerCmdReader.ReadInt32(); + retDebuggerCmdReader.ReadInt32(); + var retType = retDebuggerCmdReader.ReadInt32(); var ret = await GetTypeName(sessionId, retType, token); return ret; } - public async Task GetParameters(SessionId sessionId, int method_id, CancellationToken token) + public async Task GetParameters(SessionId sessionId, int methodId, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(method_id); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(methodId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetParamInfo, command_params, token); - ret_debugger_cmd_reader.ReadInt32(); - var paramCount = ret_debugger_cmd_reader.ReadInt32(); - ret_debugger_cmd_reader.ReadInt32(); - var retType = ret_debugger_cmd_reader.ReadInt32(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetParamInfo, commandParams, token); + retDebuggerCmdReader.ReadInt32(); + var paramCount = retDebuggerCmdReader.ReadInt32(); + retDebuggerCmdReader.ReadInt32(); + var retType = retDebuggerCmdReader.ReadInt32(); var parameters = "("; for (int i = 0 ; i < paramCount; i++) { - var paramType = ret_debugger_cmd_reader.ReadInt32(); + var paramType = retDebuggerCmdReader.ReadInt32(); parameters += await GetTypeName(sessionId, paramType, token); parameters = parameters.Replace("System.Func", "Func"); if (i + 1 < paramCount) @@ -762,50 +857,50 @@ public async Task GetParameters(SessionId sessionId, int method_id, Canc return parameters; } - public async Task SetBreakpoint(SessionId sessionId, int method_id, long il_offset, CancellationToken token) + public async Task SetBreakpoint(SessionId sessionId, int methodId, long il_offset, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write((byte)EventKind.Breakpoint); - command_params_writer.Write((byte)SuspendPolicy.None); - command_params_writer.Write((byte)1); - command_params_writer.Write((byte)ModifierKind.LocationOnly); - command_params_writer.Write(method_id); - command_params_writer.WriteLong(il_offset); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Set, command_params, token); - return ret_debugger_cmd_reader.ReadInt32(); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write((byte)EventKind.Breakpoint); + commandParamsWriter.Write((byte)SuspendPolicy.None); + commandParamsWriter.Write((byte)1); + commandParamsWriter.Write((byte)ModifierKind.LocationOnly); + commandParamsWriter.Write(methodId); + commandParamsWriter.WriteLong(il_offset); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Set, commandParams, token); + return retDebuggerCmdReader.ReadInt32(); } public async Task RemoveBreakpoint(SessionId sessionId, int breakpoint_id, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write((byte)EventKind.Breakpoint); - command_params_writer.Write((int) breakpoint_id); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write((byte)EventKind.Breakpoint); + commandParamsWriter.Write((int) breakpoint_id); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Clear, command_params, token); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Clear, commandParams, token); - if (ret_debugger_cmd_reader != null) + if (retDebuggerCmdReader != null) return true; return false; } public async Task Step(SessionId sessionId, int thread_id, StepKind kind, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write((byte)EventKind.Step); - command_params_writer.Write((byte)SuspendPolicy.None); - command_params_writer.Write((byte)1); - command_params_writer.Write((byte)ModifierKind.Step); - command_params_writer.Write(thread_id); - command_params_writer.Write((int)0); - command_params_writer.Write((int)kind); - command_params_writer.Write((int)(StepFilter.StaticCtor | StepFilter.DebuggerHidden)); //filter - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Set, command_params, token); - if (ret_debugger_cmd_reader == null) + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write((byte)EventKind.Step); + commandParamsWriter.Write((byte)SuspendPolicy.None); + commandParamsWriter.Write((byte)1); + commandParamsWriter.Write((byte)ModifierKind.Step); + commandParamsWriter.Write(thread_id); + commandParamsWriter.Write((int)0); + commandParamsWriter.Write((int)kind); + commandParamsWriter.Write((int)(StepFilter.StaticCtor | StepFilter.DebuggerHidden)); //filter + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Set, commandParams, token); + if (retDebuggerCmdReader == null) return false; - var isBPOnManagedCode = ret_debugger_cmd_reader.ReadInt32(); + var isBPOnManagedCode = retDebuggerCmdReader.ReadInt32(); if (isBPOnManagedCode == 0) return false; return true; @@ -813,14 +908,14 @@ public async Task Step(SessionId sessionId, int thread_id, StepKind kind, public async Task ClearSingleStep(SessionId sessionId, int req_id, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write((byte)EventKind.Step); - command_params_writer.Write((int) req_id); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write((byte)EventKind.Step); + commandParamsWriter.Write((int) req_id); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Clear, command_params, token); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Clear, commandParams, token); - if (ret_debugger_cmd_reader != null) + if (retDebuggerCmdReader != null) return true; return false; } @@ -828,19 +923,19 @@ public async Task ClearSingleStep(SessionId sessionId, int req_id, Cancell public async Task> GetTypeFields(SessionId sessionId, int type_id, CancellationToken token) { var ret = new List(); - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(type_id); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(type_id); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetFields, command_params, token); - var nFields = ret_debugger_cmd_reader.ReadInt32(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetFields, commandParams, token); + var nFields = retDebuggerCmdReader.ReadInt32(); for (int i = 0 ; i < nFields; i++) { - int fieldId = ret_debugger_cmd_reader.ReadInt32(); //fieldId - string fieldNameStr = ret_debugger_cmd_reader.ReadString(); - int typeId = ret_debugger_cmd_reader.ReadInt32(); //typeId - ret_debugger_cmd_reader.ReadInt32(); //attrs + int fieldId = retDebuggerCmdReader.ReadInt32(); //fieldId + string fieldNameStr = retDebuggerCmdReader.ReadString(); + int typeId = retDebuggerCmdReader.ReadInt32(); //typeId + retDebuggerCmdReader.ReadInt32(); //attrs if (fieldNameStr.Contains("k__BackingField")) { fieldNameStr = fieldNameStr.Replace("k__BackingField", ""); @@ -864,17 +959,17 @@ public string ReplaceCommonClassNames(string className) } public async Task GetTypeName(SessionId sessionId, int type_id, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(type_id); - command_params_writer.Write((int) MonoTypeNameFormat.FormatReflection); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetInfo, command_params, token); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(type_id); + commandParamsWriter.Write((int) MonoTypeNameFormat.FormatReflection); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetInfo, commandParams, token); - ret_debugger_cmd_reader.ReadString(); + retDebuggerCmdReader.ReadString(); - ret_debugger_cmd_reader.ReadString(); + retDebuggerCmdReader.ReadString(); - string className = ret_debugger_cmd_reader.ReadString(); + string className = retDebuggerCmdReader.ReadString(); className = className.Replace("+", "."); className = Regex.Replace(className, @"`\d+", ""); @@ -889,47 +984,47 @@ public async Task GetTypeName(SessionId sessionId, int type_id, Cancella public async Task GetStringValue(SessionId sessionId, int string_id, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(string_id); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(string_id); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdString.GetValue, command_params, token); - var isUtf16 = ret_debugger_cmd_reader.ReadByte(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdString.GetValue, commandParams, token); + var isUtf16 = retDebuggerCmdReader.ReadByte(); if (isUtf16 == 0) { - return ret_debugger_cmd_reader.ReadString(); + return retDebuggerCmdReader.ReadString(); } return null; } public async Task GetArrayLength(SessionId sessionId, int object_id, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(object_id); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdArray.GetLength, command_params, token); - var length = ret_debugger_cmd_reader.ReadInt32(); - length = ret_debugger_cmd_reader.ReadInt32(); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(object_id); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdArray.GetLength, commandParams, token); + var length = retDebuggerCmdReader.ReadInt32(); + length = retDebuggerCmdReader.ReadInt32(); return length; } public async Task> GetTypeIdFromObject(SessionId sessionId, int object_id, bool withParents, CancellationToken token) { List ret = new List(); - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(object_id); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(object_id); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdObject.RefGetType, command_params, token); - var type_id = ret_debugger_cmd_reader.ReadInt32(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdObject.RefGetType, commandParams, token); + var type_id = retDebuggerCmdReader.ReadInt32(); ret.Add(type_id); if (withParents) { - command_params = new MemoryStream(); - command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(type_id); - ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetParents, command_params, token); - var parentsCount = ret_debugger_cmd_reader.ReadInt32(); + commandParams = new MemoryStream(); + commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(type_id); + retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetParents, commandParams, token); + var parentsCount = retDebuggerCmdReader.ReadInt32(); for (int i = 0 ; i < parentsCount; i++) { - ret.Add(ret_debugger_cmd_reader.ReadInt32()); + ret.Add(retDebuggerCmdReader.ReadInt32()); } } return ret; @@ -944,82 +1039,82 @@ public async Task GetClassNameFromObject(SessionId sessionId, int object public async Task GetMethodIdByName(SessionId sessionId, int type_id, string method_name, CancellationToken token) { var ret = new List(); - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write((int)type_id); - command_params_writer.WriteString(method_name); - command_params_writer.Write((int)(0x10 | 4)); //instance methods - command_params_writer.Write((int)1); //case sensitive - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetMethodsByNameFlags, command_params, token); - var nMethods = ret_debugger_cmd_reader.ReadInt32(); - return ret_debugger_cmd_reader.ReadInt32(); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write((int)type_id); + commandParamsWriter.WriteString(method_name); + commandParamsWriter.Write((int)(0x10 | 4)); //instance methods + commandParamsWriter.Write((int)1); //case sensitive + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetMethodsByNameFlags, commandParams, token); + var nMethods = retDebuggerCmdReader.ReadInt32(); + return retDebuggerCmdReader.ReadInt32(); } public async Task IsDelegate(SessionId sessionId, int objectId, CancellationToken token) { var ret = new List(); - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write((int)objectId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdObject.RefIsDelegate, command_params, token); - return ret_debugger_cmd_reader.ReadByte() == 1; + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write((int)objectId); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdObject.RefIsDelegate, commandParams, token); + return retDebuggerCmdReader.ReadByte() == 1; } public async Task GetDelegateMethod(SessionId sessionId, int objectId, CancellationToken token) { var ret = new List(); - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write((int)objectId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdObject.RefDelegateGetMethod, command_params, token); - return ret_debugger_cmd_reader.ReadInt32(); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write((int)objectId); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdObject.RefDelegateGetMethod, commandParams, token); + return retDebuggerCmdReader.ReadInt32(); } public async Task GetDelegateMethodDescription(SessionId sessionId, int objectId, CancellationToken token) { var methodId = await GetDelegateMethod(sessionId, objectId, token); - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(methodId); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(methodId); //Console.WriteLine("methodId - " + methodId); if (methodId == 0) return ""; - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetName, command_params, token); - var methodName = ret_debugger_cmd_reader.ReadString(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetName, commandParams, token); + var methodName = retDebuggerCmdReader.ReadString(); var returnType = await GetReturnType(sessionId, methodId, token); var parameters = await GetParameters(sessionId, methodId, token); return $"{returnType} {methodName} {parameters}"; } - public async Task InvokeMethod(SessionId sessionId, byte[] valueTypeBuffer, int method_id, string varName, CancellationToken token) + public async Task InvokeMethod(SessionId sessionId, byte[] valueTypeBuffer, int methodId, string varName, CancellationToken token) { MemoryStream parms = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(parms); - command_params_writer.Write(method_id); - command_params_writer.Write(valueTypeBuffer); - command_params_writer.Write(0); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdVM.InvokeMethod, parms, token); - ret_debugger_cmd_reader.ReadByte(); //number of objects returned. - return await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, varName, false, -1, token); + var commandParamsWriter = new MonoBinaryWriter(parms); + commandParamsWriter.Write(methodId); + commandParamsWriter.Write(valueTypeBuffer); + commandParamsWriter.Write(0); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdVM.InvokeMethod, parms, token); + retDebuggerCmdReader.ReadByte(); //number of objects returned. + return await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, varName, false, -1, token); } public async Task CreateJArrayForProperties(SessionId sessionId, int typeId, byte[] object_buffer, JArray attributes, bool isAutoExpandable, string objectId, bool isOwn, CancellationToken token) { JArray ret = new JArray(); - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(typeId); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(typeId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, command_params, token); - var nProperties = ret_debugger_cmd_reader.ReadInt32(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, commandParams, token); + var nProperties = retDebuggerCmdReader.ReadInt32(); for (int i = 0 ; i < nProperties; i++) { - ret_debugger_cmd_reader.ReadInt32(); //propertyId - string propertyNameStr = ret_debugger_cmd_reader.ReadString(); - var getMethodId = ret_debugger_cmd_reader.ReadInt32(); - ret_debugger_cmd_reader.ReadInt32(); //setmethod - var attrs = ret_debugger_cmd_reader.ReadInt32(); //attrs + retDebuggerCmdReader.ReadInt32(); //propertyId + string propertyNameStr = retDebuggerCmdReader.ReadString(); + var getMethodId = retDebuggerCmdReader.ReadInt32(); + retDebuggerCmdReader.ReadInt32(); //setmethod + var attrs = retDebuggerCmdReader.ReadInt32(); //attrs if (getMethodId == 0 || await GetParamCount(sessionId, getMethodId, token) != 0 || await MethodIsStatic(sessionId, getMethodId, token)) continue; JObject propRet = null; @@ -1041,7 +1136,7 @@ public async Task CreateJArrayForProperties(SessionId sessionId, int typ get = new { type = "function", - objectId = $"{objectId}:method_id:{getMethodId}", + objectId = $"{objectId}:methodId:{getMethodId}", className = "Function", description = "get " + propertyNameStr + " ()", methodId = getMethodId, @@ -1059,30 +1154,30 @@ public async Task CreateJArrayForProperties(SessionId sessionId, int typ public async Task GetPointerContent(SessionId sessionId, int pointerId, CancellationToken token) { var ret = new List(); - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.WriteLong(pointerValues[pointerId].address); - command_params_writer.Write(pointerValues[pointerId].typeId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdPointer.GetValue, command_params, token); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.WriteLong(pointerValues[pointerId].address); + commandParamsWriter.Write(pointerValues[pointerId].typeId); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdPointer.GetValue, commandParams, token); var varName = pointerValues[pointerId].varName; if (int.TryParse(varName, out _)) varName = $"[{varName}]"; - return await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, "*" + varName, false, -1, token); + return await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, "*" + varName, false, -1, token); } public async Task GetPropertiesValuesOfValueType(SessionId sessionId, int valueTypeId, CancellationToken token) { JArray ret = new JArray(); var valueType = valueTypes[valueTypeId]; - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(valueType.typeId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetParents, command_params, token); - var parentsCount = ret_debugger_cmd_reader.ReadInt32(); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(valueType.typeId); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetParents, commandParams, token); + var parentsCount = retDebuggerCmdReader.ReadInt32(); List typesToGetProperties = new List(); typesToGetProperties.Add(valueType.typeId); for (int i = 0 ; i < parentsCount; i++) { - typesToGetProperties.Add(ret_debugger_cmd_reader.ReadInt32()); + typesToGetProperties.Add(retDebuggerCmdReader.ReadInt32()); } for (int i = 0 ; i < typesToGetProperties.Count; i++) { @@ -1155,12 +1250,12 @@ public JObject CreateJObjectForChar(int value) return CreateJObject(description, "symbol", description, true); } - public async Task CreateJObjectForPtr(SessionId sessionId, ElementType etype, MonoBinaryReader ret_debugger_cmd_reader, string name, CancellationToken token) + public async Task CreateJObjectForPtr(SessionId sessionId, ElementType etype, MonoBinaryReader retDebuggerCmdReader, string name, CancellationToken token) { string type; string value; - long valueAddress = ret_debugger_cmd_reader.ReadLong(); - var typeId = ret_debugger_cmd_reader.ReadInt32(); + long valueAddress = retDebuggerCmdReader.ReadLong(); + var typeId = retDebuggerCmdReader.ReadInt32(); var className = ""; if (etype == ElementType.FnPtr) className = "(*())"; //to keep the old behavior @@ -1183,24 +1278,24 @@ public async Task CreateJObjectForPtr(SessionId sessionId, ElementType return CreateJObject(value, type, value, false, className, $"dotnet:pointer:{pointerId}", "pointer"); } - public async Task CreateJObjectForString(SessionId sessionId, MonoBinaryReader ret_debugger_cmd_reader, CancellationToken token) + public async Task CreateJObjectForString(SessionId sessionId, MonoBinaryReader retDebuggerCmdReader, CancellationToken token) { - var string_id = ret_debugger_cmd_reader.ReadInt32(); + var string_id = retDebuggerCmdReader.ReadInt32(); var value = await GetStringValue(sessionId, string_id, token); return CreateJObject(value, "string", value, false); } - public async Task CreateJObjectForArray(SessionId sessionId, MonoBinaryReader ret_debugger_cmd_reader, CancellationToken token) + public async Task CreateJObjectForArray(SessionId sessionId, MonoBinaryReader retDebuggerCmdReader, CancellationToken token) { - var objectId = ret_debugger_cmd_reader.ReadInt32(); + var objectId = retDebuggerCmdReader.ReadInt32(); var value = await GetClassNameFromObject(sessionId, objectId, token); var length = await GetArrayLength(sessionId, objectId, token); return CreateJObject(null, "object", $"{value.ToString()}({length})", false, value.ToString(), "dotnet:array:" + objectId, null, "array"); } - public async Task CreateJObjectForObject(SessionId sessionId, MonoBinaryReader ret_debugger_cmd_reader, int typeIdFromAttribute, CancellationToken token) + public async Task CreateJObjectForObject(SessionId sessionId, MonoBinaryReader retDebuggerCmdReader, int typeIdFromAttribute, CancellationToken token) { - var objectId = ret_debugger_cmd_reader.ReadInt32(); + var objectId = retDebuggerCmdReader.ReadInt32(); var className = ""; var type_id = await GetTypeIdFromObject(sessionId, objectId, false, token); className = await GetTypeName(sessionId, type_id[0], token); @@ -1221,22 +1316,22 @@ public async Task CreateJObjectForObject(SessionId sessionId, MonoBinar return CreateJObject(null, "object", description, false, className, $"dotnet:object:{objectId}"); } - public async Task CreateJObjectForValueType(SessionId sessionId, MonoBinaryReader ret_debugger_cmd_reader, string name, long initialPos, CancellationToken token) + public async Task CreateJObjectForValueType(SessionId sessionId, MonoBinaryReader retDebuggerCmdReader, string name, long initialPos, CancellationToken token) { JObject fieldValueType = null; - var isEnum = ret_debugger_cmd_reader.ReadByte(); - var isBoxed = ret_debugger_cmd_reader.ReadByte() == 1; - var typeId = ret_debugger_cmd_reader.ReadInt32(); + var isEnum = retDebuggerCmdReader.ReadByte(); + var isBoxed = retDebuggerCmdReader.ReadByte() == 1; + var typeId = retDebuggerCmdReader.ReadInt32(); var className = await GetTypeName(sessionId, typeId, token); var description = className; - var numFields = ret_debugger_cmd_reader.ReadInt32(); + var numFields = retDebuggerCmdReader.ReadInt32(); var fields = await GetTypeFields(sessionId, typeId, token); JArray valueTypeFields = new JArray(); if (className.IndexOf("System.Nullable<") == 0) //should we call something on debugger-agent to check??? { - ret_debugger_cmd_reader.ReadByte(); //ignoring the boolean type - var isNull = ret_debugger_cmd_reader.ReadInt32(); - var value = await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, name, false, -1, token); + retDebuggerCmdReader.ReadByte(); //ignoring the boolean type + var isNull = retDebuggerCmdReader.ReadInt32(); + var value = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, name, false, -1, token); if (isNull != 0) return value; else @@ -1244,21 +1339,21 @@ public async Task CreateJObjectForValueType(SessionId sessionId, MonoBi } for (int i = 0; i < numFields ; i++) { - fieldValueType = await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, fields.ElementAt(i).Name, true, fields.ElementAt(i).TypeId, token); + fieldValueType = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, fields.ElementAt(i).Name, true, fields.ElementAt(i).TypeId, token); valueTypeFields.Add(fieldValueType); } - long endPos = ret_debugger_cmd_reader.BaseStream.Position; + long endPos = retDebuggerCmdReader.BaseStream.Position; var valueTypeId = Interlocked.Increment(ref debugger_object_id); - ret_debugger_cmd_reader.BaseStream.Position = initialPos; + retDebuggerCmdReader.BaseStream.Position = initialPos; byte[] valueTypeBuffer = new byte[endPos - initialPos]; - ret_debugger_cmd_reader.Read(valueTypeBuffer, 0, (int)(endPos - initialPos)); - ret_debugger_cmd_reader.BaseStream.Position = endPos; + retDebuggerCmdReader.Read(valueTypeBuffer, 0, (int)(endPos - initialPos)); + retDebuggerCmdReader.BaseStream.Position = endPos; valueTypes[valueTypeId] = new ValueTypeClass(name, valueTypeBuffer, valueTypeFields, typeId, AutoExpandable(className), valueTypeId); if (AutoInvokeToString(className) || isEnum == 1) { - int method_id = await GetMethodIdByName(sessionId, typeId, "ToString", token); - var retMethod = await InvokeMethod(sessionId, valueTypeBuffer, method_id, "methodRet", token); + int methodId = await GetMethodIdByName(sessionId, typeId, "ToString", token); + var retMethod = await InvokeMethod(sessionId, valueTypeBuffer, methodId, "methodRet", token); description = retMethod["value"]?["value"].Value(); if (className.Equals("System.Guid")) description = description.ToUpper(); //to keep the old behavior @@ -1269,16 +1364,16 @@ public async Task CreateJObjectForValueType(SessionId sessionId, MonoBi return CreateJObject(null, "object", description, false, className, $"dotnet:valuetype:{valueTypeId}", null, null, true, true, isEnum == 1); } - public async Task CreateJObjectForNull(SessionId sessionId, MonoBinaryReader ret_debugger_cmd_reader, CancellationToken token) + public async Task CreateJObjectForNull(SessionId sessionId, MonoBinaryReader retDebuggerCmdReader, CancellationToken token) { string className = ""; - ElementType variableType = (ElementType)ret_debugger_cmd_reader.ReadByte(); + ElementType variableType = (ElementType)retDebuggerCmdReader.ReadByte(); switch (variableType) { case ElementType.String: case ElementType.Class: { - var type_id = ret_debugger_cmd_reader.ReadInt32(); + var type_id = retDebuggerCmdReader.ReadInt32(); className = await GetTypeName(sessionId, type_id, token); break; @@ -1286,18 +1381,18 @@ public async Task CreateJObjectForNull(SessionId sessionId, MonoBinaryR case ElementType.SzArray: case ElementType.Array: { - ElementType byte_type = (ElementType)ret_debugger_cmd_reader.ReadByte(); - var rank = ret_debugger_cmd_reader.ReadInt32(); + ElementType byte_type = (ElementType)retDebuggerCmdReader.ReadByte(); + var rank = retDebuggerCmdReader.ReadInt32(); if (byte_type == ElementType.Class) { - var internal_type_id = ret_debugger_cmd_reader.ReadInt32(); + var internal_type_id = retDebuggerCmdReader.ReadInt32(); } - var type_id = ret_debugger_cmd_reader.ReadInt32(); + var type_id = retDebuggerCmdReader.ReadInt32(); className = await GetTypeName(sessionId, type_id, token); break; } default: { - var type_id = ret_debugger_cmd_reader.ReadInt32(); + var type_id = retDebuggerCmdReader.ReadInt32(); className = await GetTypeName(sessionId, type_id, token); break; } @@ -1305,10 +1400,10 @@ public async Task CreateJObjectForNull(SessionId sessionId, MonoBinaryR return CreateJObject(null, "object", className, false, className, null, null, "null"); } - public async Task CreateJObjectForVariableValue(SessionId sessionId, MonoBinaryReader ret_debugger_cmd_reader, string name, bool isOwn, int typeIdFromAttribute, CancellationToken token) + public async Task CreateJObjectForVariableValue(SessionId sessionId, MonoBinaryReader retDebuggerCmdReader, string name, bool isOwn, int typeIdFromAttribute, CancellationToken token) { - long initialPos = ret_debugger_cmd_reader == null ? 0 : ret_debugger_cmd_reader.BaseStream.Position; - ElementType etype = (ElementType)ret_debugger_cmd_reader.ReadByte(); + long initialPos = retDebuggerCmdReader == null ? 0 : retDebuggerCmdReader.BaseStream.Position; + ElementType etype = (ElementType)retDebuggerCmdReader.ReadByte(); JObject ret = null; switch (etype) { case ElementType.I: @@ -1317,108 +1412,114 @@ public async Task CreateJObjectForVariableValue(SessionId sessionId, Mo case (ElementType)ValueTypeId.Type: case (ElementType)ValueTypeId.VType: case (ElementType)ValueTypeId.FixedArray: - ret = new JObject{{"Type", "void"}}; + ret = JObject.FromObject(new { + value = new + { + type = "void", + value = "void", + description = "void" + }}); break; case ElementType.Boolean: { - var value = ret_debugger_cmd_reader.ReadInt32(); + var value = retDebuggerCmdReader.ReadInt32(); ret = CreateJObjectForBoolean(value); break; } case ElementType.I1: { - var value = ret_debugger_cmd_reader.ReadSByte(); + var value = retDebuggerCmdReader.ReadSByte(); ret = CreateJObjectForNumber(value); break; } case ElementType.I2: case ElementType.I4: { - var value = ret_debugger_cmd_reader.ReadInt32(); + var value = retDebuggerCmdReader.ReadInt32(); ret = CreateJObjectForNumber(value); break; } case ElementType.U1: { - var value = ret_debugger_cmd_reader.ReadUByte(); + var value = retDebuggerCmdReader.ReadUByte(); ret = CreateJObjectForNumber(value); break; } case ElementType.U2: { - var value = ret_debugger_cmd_reader.ReadUShort(); + var value = retDebuggerCmdReader.ReadUShort(); ret = CreateJObjectForNumber(value); break; } case ElementType.U4: { - var value = ret_debugger_cmd_reader.ReadUInt32(); + var value = retDebuggerCmdReader.ReadUInt32(); ret = CreateJObjectForNumber(value); break; } case ElementType.R4: { - float value = BitConverter.Int32BitsToSingle(ret_debugger_cmd_reader.ReadInt32()); + float value = BitConverter.Int32BitsToSingle(retDebuggerCmdReader.ReadInt32()); ret = CreateJObjectForNumber(value); break; } case ElementType.Char: { - var value = ret_debugger_cmd_reader.ReadInt32(); + var value = retDebuggerCmdReader.ReadInt32(); ret = CreateJObjectForChar(value); break; } case ElementType.I8: { - long value = ret_debugger_cmd_reader.ReadLong(); + long value = retDebuggerCmdReader.ReadLong(); ret = CreateJObjectForNumber(value); break; } case ElementType.U8: { - ulong high = (ulong) ret_debugger_cmd_reader.ReadInt32(); - ulong low = (ulong) ret_debugger_cmd_reader.ReadInt32(); + ulong high = (ulong) retDebuggerCmdReader.ReadInt32(); + ulong low = (ulong) retDebuggerCmdReader.ReadInt32(); var value = ((high << 32) | low); ret = CreateJObjectForNumber(value); break; } case ElementType.R8: { - double value = ret_debugger_cmd_reader.ReadDouble(); + double value = retDebuggerCmdReader.ReadDouble(); ret = CreateJObjectForNumber(value); break; } case ElementType.FnPtr: case ElementType.Ptr: { - ret = await CreateJObjectForPtr(sessionId, etype, ret_debugger_cmd_reader, name, token); + ret = await CreateJObjectForPtr(sessionId, etype, retDebuggerCmdReader, name, token); break; } case ElementType.String: { - ret = await CreateJObjectForString(sessionId, ret_debugger_cmd_reader, token); + ret = await CreateJObjectForString(sessionId, retDebuggerCmdReader, token); break; } case ElementType.SzArray: case ElementType.Array: { - ret = await CreateJObjectForArray(sessionId, ret_debugger_cmd_reader, token); + ret = await CreateJObjectForArray(sessionId, retDebuggerCmdReader, token); break; } case ElementType.Class: case ElementType.Object: { - ret = await CreateJObjectForObject(sessionId, ret_debugger_cmd_reader, typeIdFromAttribute, token); + ret = await CreateJObjectForObject(sessionId, retDebuggerCmdReader, typeIdFromAttribute, token); break; } case ElementType.ValueType: { - ret = await CreateJObjectForValueType(sessionId, ret_debugger_cmd_reader, name, initialPos, token); + ret = await CreateJObjectForValueType(sessionId, retDebuggerCmdReader, name, initialPos, token); break; } case (ElementType)ValueTypeId.Null: { - ret = await CreateJObjectForNull(sessionId, ret_debugger_cmd_reader, token); + ret = await CreateJObjectForNull(sessionId, retDebuggerCmdReader, token); break; } } @@ -1430,32 +1531,32 @@ public async Task CreateJObjectForVariableValue(SessionId sessionId, Mo public async Task IsAsyncMethod(SessionId sessionId, int methodId, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(methodId); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(methodId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.AsyncDebugInfo, command_params, token); - return ret_debugger_cmd_reader.ReadByte() == 1 ; //token + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdMethod.AsyncDebugInfo, commandParams, token); + return retDebuggerCmdReader.ReadByte() == 1 ; //token } - public async Task StackFrameGetValues(SessionId sessionId, MethodInfo method, int thread_id, int frame_id, VarInfo[] var_ids, CancellationToken token) + public async Task StackFrameGetValues(SessionId sessionId, MethodInfo method, int thread_id, int frame_id, VarInfo[] varIds, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - MonoBinaryReader ret_debugger_cmd_reader = null; - command_params_writer.Write(thread_id); - command_params_writer.Write(frame_id); - command_params_writer.Write(var_ids.Length); - foreach (var var in var_ids) + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + MonoBinaryReader retDebuggerCmdReader = null; + commandParamsWriter.Write(thread_id); + commandParamsWriter.Write(frame_id); + commandParamsWriter.Write(varIds.Length); + foreach (var var in varIds) { - command_params_writer.Write(var.Index); + commandParamsWriter.Write(var.Index); } if (await IsAsyncMethod(sessionId, method.DebuggerId, token)) { - ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetThis, command_params, token); - ret_debugger_cmd_reader.ReadByte(); //ignore type - var objectId = ret_debugger_cmd_reader.ReadInt32(); + retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetThis, commandParams, token); + retDebuggerCmdReader.ReadByte(); //ignore type + var objectId = retDebuggerCmdReader.ReadInt32(); var asyncLocals = await GetObjectValues(sessionId, objectId, true, false, false, false, token); asyncLocals = new JArray(asyncLocals.Where( asyncLocal => !asyncLocal["name"].Value().Contains("<>") || asyncLocal["name"].Value().EndsWith("__this"))); foreach (var asyncLocal in asyncLocals) @@ -1469,16 +1570,16 @@ public async Task StackFrameGetValues(SessionId sessionId, MethodInfo me } JArray locals = new JArray(); - ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetValues, command_params, token); - foreach (var var in var_ids) + retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetValues, commandParams, token); + foreach (var var in varIds) { - var var_json = await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, var.Name, false, -1, token); + var var_json = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, var.Name, false, -1, token); locals.Add(var_json); } if (!method.IsStatic()) { - ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetThis, command_params, token); - var var_json = await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, "this", false, -1, token); + retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetThis, commandParams, token); + var var_json = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, "this", false, -1, token); var_json.Add("fieldOffset", -1); locals.Add(var_json); } @@ -1504,21 +1605,21 @@ public async Task GetValueTypeProxy(SessionId sessionId, int valueTypeId return valueTypes[valueTypeId].valueTypeProxy; valueTypes[valueTypeId].valueTypeProxy = new JArray(valueTypes[valueTypeId].valueTypeJson); - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(valueTypes[valueTypeId].typeId); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(valueTypes[valueTypeId].typeId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, command_params, token); - var nProperties = ret_debugger_cmd_reader.ReadInt32(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, commandParams, token); + var nProperties = retDebuggerCmdReader.ReadInt32(); for (int i = 0 ; i < nProperties; i++) { - ret_debugger_cmd_reader.ReadInt32(); //propertyId - string propertyNameStr = ret_debugger_cmd_reader.ReadString(); + retDebuggerCmdReader.ReadInt32(); //propertyId + string propertyNameStr = retDebuggerCmdReader.ReadString(); - var getMethodId = ret_debugger_cmd_reader.ReadInt32(); - ret_debugger_cmd_reader.ReadInt32(); //setmethod - ret_debugger_cmd_reader.ReadInt32(); //attrs + var getMethodId = retDebuggerCmdReader.ReadInt32(); + retDebuggerCmdReader.ReadInt32(); //setmethod + retDebuggerCmdReader.ReadInt32(); //attrs if (await MethodIsStatic(sessionId, getMethodId, token)) continue; var command_params_to_proxy = new MemoryStream(); @@ -1542,16 +1643,16 @@ public async Task GetValueTypeProxy(SessionId sessionId, int valueTypeId public async Task GetArrayValues(SessionId sessionId, int arrayId, CancellationToken token) { var length = await GetArrayLength(sessionId, arrayId, token); - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(arrayId); - command_params_writer.Write(0); - command_params_writer.Write(length); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdArray.GetValues, command_params, token); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(arrayId); + commandParamsWriter.Write(0); + commandParamsWriter.Write(length); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdArray.GetValues, commandParams, token); JArray array = new JArray(); for (int i = 0 ; i < length ; i++) { - var var_json = await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, i.ToString(), false, -1, token); + var var_json = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, i.ToString(), false, -1, token); array.Add(var_json); } return array; @@ -1559,25 +1660,25 @@ public async Task GetArrayValues(SessionId sessionId, int arrayId, Cance public async Task EnableExceptions(SessionId sessionId, string state, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write((byte)EventKind.Exception); - command_params_writer.Write((byte)SuspendPolicy.None); - command_params_writer.Write((byte)1); - command_params_writer.Write((byte)ModifierKind.ExceptionOnly); - command_params_writer.Write(0); //exc_class + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write((byte)EventKind.Exception); + commandParamsWriter.Write((byte)SuspendPolicy.None); + commandParamsWriter.Write((byte)1); + commandParamsWriter.Write((byte)ModifierKind.ExceptionOnly); + commandParamsWriter.Write(0); //exc_class if (state == "all") - command_params_writer.Write((byte)1); //caught + commandParamsWriter.Write((byte)1); //caught else - command_params_writer.Write((byte)0); //caught + commandParamsWriter.Write((byte)0); //caught if (state == "uncaught" || state == "all") - command_params_writer.Write((byte)1); //uncaught + commandParamsWriter.Write((byte)1); //uncaught else - command_params_writer.Write((byte)0); //uncaught - command_params_writer.Write((byte)1);//subclasses - command_params_writer.Write((byte)0);//not_filtered_feature - command_params_writer.Write((byte)0);//everything_else - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Set, command_params, token); + commandParamsWriter.Write((byte)0); //uncaught + commandParamsWriter.Write((byte)1);//subclasses + commandParamsWriter.Write((byte)0);//not_filtered_feature + commandParamsWriter.Write((byte)0);//everything_else + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Set, commandParams, token); return true; } public async Task GetObjectValues(SessionId sessionId, int objectId, bool withProperties, bool withSetter, bool accessorPropertiesOnly, bool ownProperties, CancellationToken token) @@ -1608,23 +1709,23 @@ public async Task GetObjectValues(SessionId sessionId, int objectId, boo var fields = await GetTypeFields(sessionId, typeId[i], token); JArray objectFields = new JArray(); - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(objectId); - command_params_writer.Write(fields.Count); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(objectId); + commandParamsWriter.Write(fields.Count); foreach (var field in fields) { - command_params_writer.Write(field.Id); + commandParamsWriter.Write(field.Id); } - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdObject.RefGetValues, command_params, token); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdObject.RefGetValues, commandParams, token); foreach (var field in fields) { - long initialPos = ret_debugger_cmd_reader.BaseStream.Position; - int valtype = ret_debugger_cmd_reader.ReadByte(); - ret_debugger_cmd_reader.BaseStream.Position = initialPos; - var fieldValue = await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, field.Name, i == 0, field.TypeId, token); + long initialPos = retDebuggerCmdReader.BaseStream.Position; + int valtype = retDebuggerCmdReader.ReadByte(); + retDebuggerCmdReader.BaseStream.Position = initialPos; + var fieldValue = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, field.Name, i == 0, field.TypeId, token); if (ret.Where(attribute => attribute["name"].Value().Equals(fieldValue["name"].Value())).Any()) { continue; @@ -1652,8 +1753,8 @@ public async Task GetObjectValues(SessionId sessionId, int objectId, boo if (!withProperties) return ret; var command_params_obj = new MemoryStream(); - var command_params_obj_writer = new MonoBinaryWriter(command_params_obj); - command_params_obj_writer.WriteObj(new DotnetObjectId("object", $"{objectId}"), this); + var commandParamsObjWriter = new MonoBinaryWriter(command_params_obj); + commandParamsObjWriter.WriteObj(new DotnetObjectId("object", $"{objectId}"), this); var props = await CreateJArrayForProperties(sessionId, typeId[i], command_params_obj.ToArray(), ret, false, $"dotnet:object:{objectId}", i == 0, token); ret = new JArray(ret.Union(props)); @@ -1706,19 +1807,19 @@ public async Task GetObjectProxy(SessionId sessionId, int objectId, Canc var typeIds = await GetTypeIdFromObject(sessionId, objectId, true, token); foreach (var typeId in typeIds) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - command_params_writer.Write(typeId); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(typeId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, command_params, token); - var nProperties = ret_debugger_cmd_reader.ReadInt32(); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, commandParams, token); + var nProperties = retDebuggerCmdReader.ReadInt32(); for (int i = 0 ; i < nProperties; i++) { - ret_debugger_cmd_reader.ReadInt32(); //propertyId - string propertyNameStr = ret_debugger_cmd_reader.ReadString(); - var getMethodId = ret_debugger_cmd_reader.ReadInt32(); - var setMethodId = ret_debugger_cmd_reader.ReadInt32(); //setmethod - var attrValue = ret_debugger_cmd_reader.ReadInt32(); //attrs + retDebuggerCmdReader.ReadInt32(); //propertyId + string propertyNameStr = retDebuggerCmdReader.ReadString(); + var getMethodId = retDebuggerCmdReader.ReadInt32(); + var setMethodId = retDebuggerCmdReader.ReadInt32(); //setmethod + var attrValue = retDebuggerCmdReader.ReadInt32(); //attrs //Console.WriteLine($"{propertyNameStr} - {attrValue}"); if (ret.Where(attribute => attribute["name"].Value().Equals(propertyNameStr)).Any()) { @@ -1770,19 +1871,19 @@ public async Task GetObjectProxy(SessionId sessionId, int objectId, Canc public async Task SetVariableValue(SessionId sessionId, int thread_id, int frame_id, int varId, string newValue, CancellationToken token) { - var command_params = new MemoryStream(); - var command_params_writer = new MonoBinaryWriter(command_params); - MonoBinaryReader ret_debugger_cmd_reader = null; - command_params_writer.Write(thread_id); - command_params_writer.Write(frame_id); - command_params_writer.Write(1); - command_params_writer.Write(varId); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + MonoBinaryReader retDebuggerCmdReader = null; + commandParamsWriter.Write(thread_id); + commandParamsWriter.Write(frame_id); + commandParamsWriter.Write(1); + commandParamsWriter.Write(varId); JArray locals = new JArray(); - ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetValues, command_params, token); - int etype = ret_debugger_cmd_reader.ReadByte(); + retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetValues, commandParams, token); + int etype = retDebuggerCmdReader.ReadByte(); try { - ret_debugger_cmd_reader = await SendDebuggerAgentCommandWithParms(sessionId, CmdFrame.SetValues, command_params, etype, newValue, token); + retDebuggerCmdReader = await SendDebuggerAgentCommandWithParms(sessionId, CmdFrame.SetValues, commandParams, etype, newValue, token); } catch (Exception) { diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 993e6165cc0a..d6803ee8670f 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -502,6 +502,105 @@ async Task EvaluateOnCallFrameFail(string call_frame_id, params (string expressi AssertEqual(arg.class_name, res.Error["result"]?["className"]?.Value(), $"Error className did not match for expression '{arg.expression}'"); } } + + + [Fact] + public async Task EvaluateSimpleMethodCallsError() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateMethodTestsClass/TestEvaluate", "run", 9, "run", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + var (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethodWrong()", expect_ok: false ); + AssertEqual("Method 'MyMethodWrong' not found in type 'DebuggerTests.EvaluateMethodTestsClass.ParmToTest'", res.Error["message"]?.Value(), "wrong error message"); + + (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethod(1)", expect_ok: false ); + AssertEqual("Unable to evaluate method 'MyMethod'", res.Error["message"]?.Value(), "wrong error message"); + + (_, res) = await EvaluateOnCallFrame(id, "this.CallMethodWithParm(\"1\")", expect_ok: false ); + AssertEqual("Unable to evaluate method 'CallMethodWithParm'", res.Error["message"]?.Value(), "wrong error message"); + + (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjNull.MyMethod()", expect_ok: false ); + AssertEqual("Object reference not set to an instance of an object.", res.Error["message"]?.Value(), "wrong error message"); + + (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjException.MyMethod()", expect_ok: false ); + AssertEqual("Object reference not set to an instance of an object.", res.Error["message"]?.Value(), "wrong error message"); + }); + + [Fact] + public async Task EvaluateSimpleMethodCallsWithoutParms() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateMethodTestsClass/TestEvaluate", "run", 9, "run", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + await EvaluateOnCallFrameAndCheck(id, + ("this.CallMethod()", TNumber(1)), + ("this.CallMethod()", TNumber(1)), + ("this.ParmToTestObj.MyMethod()", TString("methodOK")), + ("this.objToTest.MyMethod()", TString("methodOK"))); + }); + + + [Fact] + public async Task EvaluateSimpleMethodCallsWithConstParms() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateMethodTestsClass/TestEvaluate", "run", 9, "run", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + await EvaluateOnCallFrameAndCheck(id, + ("this.CallMethodWithParm(10)", TNumber(11)), + ("this.CallMethodWithMultipleParms(10, 10)", TNumber(21)), + ("this.CallMethodWithParmBool(true)", TString("TRUE")), + ("this.CallMethodWithParmBool(false)", TString("FALSE")), + ("this.CallMethodWithParmString(\"concat\")", TString("str_const_concat")), + ("this.CallMethodWithParm(10) + this.a", TNumber(12)), + ("this.CallMethodWithObj(null)", TNumber(-1)), + ("this.CallMethodWithChar('a')", TString("str_const_a"))); + }); + + [Fact] + public async Task EvaluateSimpleMethodCallsWithVariableParms() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateMethodTestsClass/TestEvaluate", "run", 9, "run", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + await EvaluateOnCallFrameAndCheck(id, + ("this.CallMethodWithParm(this.a)", TNumber(2)), + ("this.CallMethodWithMultipleParms(this.a, 10)", TNumber(12)), + ("this.CallMethodWithParmString(this.str)", TString("str_const_str_const_")), + ("this.CallMethodWithParmBool(this.t)", TString("TRUE")), + ("this.CallMethodWithParmBool(this.f)", TString("FALSE")), + ("this.CallMethodWithParm(this.a) + this.a", TNumber(3)), + ("this.CallMethodWithObj(this.objToTest)", TNumber(10))); + }); + + + [Fact] + public async Task EvaluateSimpleMethodCallsCheckChangedValue() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateMethodTestsClass/TestEvaluate", "run", 9, "run", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + var frame = pause_location["callFrames"][0]; + var props = await GetObjectOnFrame(frame, "this"); + CheckNumber(props, "a", 1); + + await EvaluateOnCallFrameAndCheck(id, + ("this.CallMethodChangeValue()", TObject("object", is_null : true))); + + frame = pause_location["callFrames"][0]; + props = await GetObjectOnFrame(frame, "this"); + CheckNumber(props, "a", 11); + }); } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs index 4054c6a2cea1..b45825acfcbd 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs @@ -21,18 +21,18 @@ await EvaluateAndCheck( "window.setTimeout(function() { invoke_static_method('[debugger-test] Math:IntAdd', 1, 2); })", null, -1, -1, "IntAdd"); - var var_ids = new[] + var varIds = new[] { new { index = 0, name = "one" }, }; - var scope_id = "-12"; - var expression = $"MONO.mono_wasm_get_variables({scope_id}, {JsonConvert.SerializeObject(var_ids)})"; + var scopeId = "-12"; + var expression = $"MONO.mono_wasm_get_variables({scopeId}, {JsonConvert.SerializeObject(varIds)})"; var res = await cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), token); Assert.False(res.IsOk); - scope_id = "30000"; - expression = $"MONO.mono_wasm_get_variables({scope_id}, {JsonConvert.SerializeObject(var_ids)})"; + scopeId = "30000"; + expression = $"MONO.mono_wasm_get_variables({scopeId}, {JsonConvert.SerializeObject(varIds)})"; res = await cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), token); Assert.False(res.IsOk); } diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs index a5fbd57a300f..1583ce1fd6f8 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs @@ -309,4 +309,101 @@ public async Task GenericInstanceMethodAsync(int g, int h, string valStrin return await Task.FromResult(default(T)); } } + public class EvaluateMethodTestsClass + { + public class ParmToTest + { + public int a; + public int b; + public ParmToTest() + { + a = 10; + b = 10; + } + public string MyMethod() + { + return "methodOK"; + } + } + public class TestEvaluate + { + public int a; + public int b; + public int c; + public string str = "str_const_"; + public bool t = true; + public bool f = false; + public ParmToTest objToTest; + public ParmToTest ParmToTestObj => objToTest; + public ParmToTest ParmToTestObjNull => null; + public ParmToTest ParmToTestObjException => throw new Exception("error2"); + public void run(int g, int h, string a, string valString, int this_a) + { + objToTest = new ParmToTest(); + int d = g + 1; + int e = g + 2; + int f = g + 3; + int i = d + e + f; + this.a = 1; + b = 2; + c = 3; + this.a = this.a + 1; + b = b + 1; + c = c + 1; + } + + public int CallMethod() + { + return a; + } + + public int CallMethodWithParm(int parm) + { + return a + parm; + } + + public void CallMethodChangeValue() + { + a = a + 10; + } + + public int CallMethodWithMultipleParms(int parm, int parm2) + { + return a + parm + parm2; + } + + public string CallMethodWithParmString(string parm) + { + return str + parm; + } + + public string CallMethodWithParmBool(bool parm) + { + if (parm) + return "TRUE"; + return "FALSE"; + } + + public int CallMethodWithObj(ParmToTest parm) + { + if (parm == null) + return -1; + return parm.a; + } + + + public string CallMethodWithChar(char parm) + { + return str + parm; + } + } + + public static void EvaluateMethods() + { + TestEvaluate f = new TestEvaluate(); + f.run(100, 200, "9000", "test", 45); + } + + } + } From 0a42d495324391f6bdce8b619bcc6eec9d6f9964 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 14 Jul 2021 15:32:27 +0300 Subject: [PATCH 121/133] remove superfluous comment (#55634) Fix #55389 --- .../src/System/Collections/Generic/LinkedList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/LinkedList.cs b/src/libraries/System.Collections/src/System/Collections/Generic/LinkedList.cs index cd2718696213..00431d4eeb47 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/LinkedList.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/LinkedList.cs @@ -177,7 +177,7 @@ public void Clear() while (current != null) { LinkedListNode temp = current; - current = current.Next; // use Next the instead of "next", otherwise it will loop forever + current = current.Next; temp.Invalidate(); } From 56d9474f530579c67b294cbbde0724e966dbb05b Mon Sep 17 00:00:00 2001 From: Zoltan Varga Date: Wed, 14 Jul 2021 08:53:09 -0400 Subject: [PATCH 122/133] [mono][wasm] Avoid compiling llvm bitcode files with -emit-llvm. (#55630) It causes the output to be a bitcode file as well, which is passed to the emscripten link step, which will compile it to wasm sequentially, slowing down linking. Fixes https://github.com/dotnet/runtime/issues/54935. --- src/mono/wasm/build/WasmApp.Native.targets | 1 + src/mono/wasm/wasm.proj | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 4b8335a74d6d..dec821537797 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -218,6 +218,7 @@ <_EmccCFlags Include="-DLINK_ICALLS=1" Condition="'$(WasmLinkIcalls)' == 'true'" /> <_EmccCFlags Include="-DCORE_BINDINGS" /> <_EmccCFlags Include="-DGEN_PINVOKE=1" /> + <_EmccCFlags Include="-emit-llvm" /> <_EmccCFlags Include=""-I%(_EmccIncludePaths.Identity)"" /> <_EmccCFlags Include="-g" Condition="'$(WasmNativeDebugSymbols)' == 'true'" /> diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 45640378ac3c..b8ee77e5e273 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -69,7 +69,6 @@ <_EmccCommonFlags Include="-s "EXPORTED_RUNTIME_METHODS=['ccall', 'FS_createPath', 'FS_createDataFile', 'cwrap', 'setValue', 'getValue', 'UTF8ToString', 'UTF8ArrayToString', 'addFunction']"" /> <_EmccCommonFlags Include="-s "EXPORTED_FUNCTIONS=['_putchar']"" /> <_EmccCommonFlags Include="--source-map-base http://example.com" /> - <_EmccCommonFlags Include="-emit-llvm" /> <_EmccCommonFlags Include="-s MODULARIZE=1" Condition="'$(WasmEnableES6)' != 'false'" /> <_EmccCommonFlags Include="-s EXPORT_ES6=1" Condition="'$(WasmEnableES6)' != 'false'" /> From 4e9d89ac0a333b86867cf898b9ad438d324c66d1 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 14 Jul 2021 09:12:09 -0400 Subject: [PATCH 123/133] Add ArgumentNullException.ThrowIfNull (#55594) --- .../src/System/Net/WebClient.cs | 70 +++++------- .../src/System/ArgumentNullException.cs | 17 +++ .../System.Reflection.TypeExtensions.csproj | 3 +- .../src/System/Reflection/Requires.cs | 16 --- .../src/System/Reflection/TypeExtensions.cs | 106 +++++++++--------- .../System.Runtime/ref/System.Runtime.cs | 1 + .../System/ArgumentNullExceptionTests.cs | 27 +++++ 7 files changed, 130 insertions(+), 110 deletions(-) delete mode 100644 src/libraries/System.Reflection.TypeExtensions/src/System/Reflection/Requires.cs diff --git a/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs b/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs index 27289121acd0..553bf48943d5 100644 --- a/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs +++ b/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs @@ -143,7 +143,7 @@ public Encoding Encoding get { return _encoding; } set { - ThrowIfNull(value, nameof(value)); + ArgumentNullException.ThrowIfNull(value, nameof(value)); _encoding = value; } } @@ -280,7 +280,7 @@ public byte[] DownloadData(string address) => public byte[] DownloadData(Uri address) { - ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); StartOperation(); try @@ -320,8 +320,8 @@ public void DownloadFile(string address, string fileName) => public void DownloadFile(Uri address, string fileName) { - ThrowIfNull(address, nameof(address)); - ThrowIfNull(fileName, nameof(fileName)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(fileName, nameof(fileName)); WebRequest? request = null; FileStream? fs = null; @@ -359,7 +359,7 @@ public Stream OpenRead(string address) => public Stream OpenRead(Uri address) { - ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); WebRequest? request = null; StartOperation(); @@ -392,7 +392,7 @@ public Stream OpenWrite(string address, string? method) => public Stream OpenWrite(Uri address, string? method) { - ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); if (method == null) { method = MapToDefaultMethod(address); @@ -432,8 +432,8 @@ public byte[] UploadData(string address, string? method, byte[] data) => public byte[] UploadData(Uri address, string? method, byte[] data) { - ThrowIfNull(address, nameof(address)); - ThrowIfNull(data, nameof(data)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(data, nameof(data)); if (method == null) { method = MapToDefaultMethod(address); @@ -553,8 +553,8 @@ public byte[] UploadFile(string address, string? method, string fileName) => public byte[] UploadFile(Uri address, string? method, string fileName) { - ThrowIfNull(address, nameof(address)); - ThrowIfNull(fileName, nameof(fileName)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(fileName, nameof(fileName)); if (method == null) { method = MapToDefaultMethod(address); @@ -626,8 +626,8 @@ public byte[] UploadValues(string address, string? method, NameValueCollection d public byte[] UploadValues(Uri address, string? method, NameValueCollection data) { - ThrowIfNull(address, nameof(address)); - ThrowIfNull(data, nameof(data)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(data, nameof(data)); if (method == null) { method = MapToDefaultMethod(address); @@ -665,8 +665,8 @@ public string UploadString(string address, string? method, string data) => public string UploadString(Uri address, string? method, string data) { - ThrowIfNull(address, nameof(address)); - ThrowIfNull(data, nameof(data)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(data, nameof(data)); if (method == null) { method = MapToDefaultMethod(address); @@ -691,7 +691,7 @@ public string DownloadString(string address) => public string DownloadString(Uri address) { - ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); StartOperation(); try @@ -781,7 +781,7 @@ private void CopyHeadersTo(WebRequest request) private Uri GetUri(string address) { - ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); Uri? uri; if (_baseAddress != null) @@ -801,7 +801,7 @@ private Uri GetUri(string address) private Uri GetUri(Uri address) { - ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); Uri? uri = address; @@ -1297,7 +1297,7 @@ public void OpenReadAsync(Uri address) => public void OpenReadAsync(Uri address, object? userToken) { - ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); AsyncOperation asyncOp = StartAsyncOperation(userToken); try @@ -1335,7 +1335,7 @@ public void OpenWriteAsync(Uri address, string? method) => public void OpenWriteAsync(Uri address, string? method, object? userToken) { - ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); if (method == null) { method = MapToDefaultMethod(address); @@ -1396,7 +1396,7 @@ public void DownloadStringAsync(Uri address) => public void DownloadStringAsync(Uri address, object? userToken) { - ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); AsyncOperation asyncOp = StartAsyncOperation(userToken); try @@ -1422,7 +1422,7 @@ public void DownloadDataAsync(Uri address) => public void DownloadDataAsync(Uri address, object? userToken) { - ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); AsyncOperation asyncOp = StartAsyncOperation(userToken); try @@ -1448,8 +1448,8 @@ public void DownloadFileAsync(Uri address, string fileName) => public void DownloadFileAsync(Uri address, string fileName, object? userToken) { - ThrowIfNull(address, nameof(address)); - ThrowIfNull(fileName, nameof(fileName)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(fileName, nameof(fileName)); FileStream? fs = null; AsyncOperation asyncOp = StartAsyncOperation(userToken); @@ -1474,8 +1474,8 @@ public void UploadStringAsync(Uri address, string? method, string data) => public void UploadStringAsync(Uri address, string? method, string data, object? userToken) { - ThrowIfNull(address, nameof(address)); - ThrowIfNull(data, nameof(data)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(data, nameof(data)); if (method == null) { method = MapToDefaultMethod(address); @@ -1525,8 +1525,8 @@ public void UploadDataAsync(Uri address, string? method, byte[] data) => public void UploadDataAsync(Uri address, string? method, byte[] data, object? userToken) { - ThrowIfNull(address, nameof(address)); - ThrowIfNull(data, nameof(data)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(data, nameof(data)); if (method == null) { method = MapToDefaultMethod(address); @@ -1566,8 +1566,8 @@ public void UploadFileAsync(Uri address, string? method, string fileName) => public void UploadFileAsync(Uri address, string? method, string fileName, object? userToken) { - ThrowIfNull(address, nameof(address)); - ThrowIfNull(fileName, nameof(fileName)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(fileName, nameof(fileName)); if (method == null) { method = MapToDefaultMethod(address); @@ -1605,8 +1605,8 @@ public void UploadValuesAsync(Uri address, string? method, NameValueCollection d public void UploadValuesAsync(Uri address, string? method, NameValueCollection data, object? userToken) { - ThrowIfNull(address, nameof(address)); - ThrowIfNull(data, nameof(data)); + ArgumentNullException.ThrowIfNull(address, nameof(address)); + ArgumentNullException.ThrowIfNull(data, nameof(data)); if (method == null) { method = MapToDefaultMethod(address); @@ -1940,14 +1940,6 @@ private void PostProgressChanged(AsyncOperation asyncOp, ProgressData progress) } } - private static void ThrowIfNull(object argument, string parameterName) - { - if (argument == null) - { - throw new ArgumentNullException(parameterName); - } - } - #region Supporting Types private sealed class ProgressData { diff --git a/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs b/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs index 0f8a1923ad50..5f8894e29147 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs @@ -10,6 +10,8 @@ ** =============================================================================*/ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Runtime.Serialization; namespace System @@ -50,5 +52,20 @@ public ArgumentNullException(string? paramName, string? message) protected ArgumentNullException(SerializationInfo info, StreamingContext context) : base(info, context) { } + + /// Throws an if is null. + /// The reference type argument to validate as non-null. + /// The name of the parameter with which corresponds. + public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpression("argument")] string? paramName = null) + { + if (argument is null) + { + Throw(paramName); + } + } + + [DoesNotReturn] + private static void Throw(string? paramName) => + throw new ArgumentNullException(paramName); } } diff --git a/src/libraries/System.Reflection.TypeExtensions/src/System.Reflection.TypeExtensions.csproj b/src/libraries/System.Reflection.TypeExtensions/src/System.Reflection.TypeExtensions.csproj index 3e598d9015c0..e078dd282ebe 100644 --- a/src/libraries/System.Reflection.TypeExtensions/src/System.Reflection.TypeExtensions.csproj +++ b/src/libraries/System.Reflection.TypeExtensions/src/System.Reflection.TypeExtensions.csproj @@ -5,10 +5,9 @@ $(NetCoreAppCurrent) - - \ No newline at end of file + diff --git a/src/libraries/System.Reflection.TypeExtensions/src/System/Reflection/Requires.cs b/src/libraries/System.Reflection.TypeExtensions/src/System/Reflection/Requires.cs deleted file mode 100644 index 8fa51255d4fe..000000000000 --- a/src/libraries/System.Reflection.TypeExtensions/src/System/Reflection/Requires.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Reflection -{ - internal static class Requires - { - internal static void NotNull(object obj, string name) - { - if (obj == null) - { - throw new ArgumentNullException(name); - } - } - } -} diff --git a/src/libraries/System.Reflection.TypeExtensions/src/System/Reflection/TypeExtensions.cs b/src/libraries/System.Reflection.TypeExtensions/src/System/Reflection/TypeExtensions.cs index 3e3b6ca2961c..6c020bbfffd4 100644 --- a/src/libraries/System.Reflection.TypeExtensions/src/System/Reflection/TypeExtensions.cs +++ b/src/libraries/System.Reflection.TypeExtensions/src/System/Reflection/TypeExtensions.cs @@ -11,14 +11,14 @@ public static class TypeExtensions [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] this Type type, Type[] types) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetConstructor(types); } public static ConstructorInfo[] GetConstructors( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] this Type type) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetConstructors(); } @@ -26,7 +26,7 @@ public static ConstructorInfo[] GetConstructors( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] this Type type, BindingFlags bindingAttr) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetConstructors(bindingAttr); } @@ -39,7 +39,7 @@ public static MemberInfo[] GetDefaultMembers( | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicNestedTypes)] this Type type) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetDefaultMembers(); } @@ -47,7 +47,7 @@ public static MemberInfo[] GetDefaultMembers( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents)] this Type type, string name) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetEvent(name); } @@ -56,14 +56,14 @@ public static MemberInfo[] GetDefaultMembers( string name, BindingFlags bindingAttr) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetEvent(name, bindingAttr); } public static EventInfo[] GetEvents( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents)] this Type type) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetEvents(); } @@ -71,7 +71,7 @@ public static EventInfo[] GetEvents( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] this Type type, BindingFlags bindingAttr) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetEvents(bindingAttr); } @@ -79,7 +79,7 @@ public static EventInfo[] GetEvents( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] this Type type, string name) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetField(name); } @@ -88,14 +88,14 @@ public static EventInfo[] GetEvents( string name, BindingFlags bindingAttr) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetField(name, bindingAttr); } public static FieldInfo[] GetFields( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] this Type type) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetFields(); } @@ -103,20 +103,20 @@ public static FieldInfo[] GetFields( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] this Type type, BindingFlags bindingAttr) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetFields(bindingAttr); } public static Type[] GetGenericArguments(this Type type) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetGenericArguments(); } public static Type[] GetInterfaces( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type type) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetInterfaces(); } @@ -130,7 +130,7 @@ public static MemberInfo[] GetMember( | DynamicallyAccessedMemberTypes.PublicNestedTypes)] this Type type, string name) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetMember(name); } @@ -139,7 +139,7 @@ public static MemberInfo[] GetMember( string name, BindingFlags bindingAttr) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetMember(name, bindingAttr); } @@ -152,7 +152,7 @@ public static MemberInfo[] GetMembers( | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicNestedTypes)] this Type type) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetMembers(); } @@ -160,7 +160,7 @@ public static MemberInfo[] GetMembers( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] this Type type, BindingFlags bindingAttr) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetMembers(bindingAttr); } @@ -168,7 +168,7 @@ public static MemberInfo[] GetMembers( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] this Type type, string name) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetMethod(name); } @@ -177,7 +177,7 @@ public static MemberInfo[] GetMembers( string name, BindingFlags bindingAttr) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetMethod(name, bindingAttr); } @@ -186,14 +186,14 @@ public static MemberInfo[] GetMembers( string name, Type[] types) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetMethod(name, types); } public static MethodInfo[] GetMethods( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] this Type type) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetMethods(); } @@ -201,7 +201,7 @@ public static MethodInfo[] GetMethods( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] this Type type, BindingFlags bindingAttr) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetMethods(bindingAttr); } @@ -210,7 +210,7 @@ public static MethodInfo[] GetMethods( string name, BindingFlags bindingAttr) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetNestedType(name, bindingAttr); } @@ -218,14 +218,14 @@ public static Type[] GetNestedTypes( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes)] this Type type, BindingFlags bindingAttr) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetNestedTypes(bindingAttr); } public static PropertyInfo[] GetProperties( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] this Type type) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetProperties(); } @@ -233,7 +233,7 @@ public static PropertyInfo[] GetProperties( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] this Type type, BindingFlags bindingAttr) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetProperties(bindingAttr); } @@ -241,7 +241,7 @@ public static PropertyInfo[] GetProperties( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] this Type type, string name) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetProperty(name); } @@ -250,7 +250,7 @@ public static PropertyInfo[] GetProperties( string name, BindingFlags bindingAttr) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetProperty(name, bindingAttr); } @@ -259,7 +259,7 @@ public static PropertyInfo[] GetProperties( string name, Type? returnType) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetProperty(name, returnType); } @@ -269,19 +269,19 @@ public static PropertyInfo[] GetProperties( Type? returnType, Type[] types) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.GetProperty(name, returnType, types); } public static bool IsAssignableFrom(this Type type, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] Type? c) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.IsAssignableFrom(c); } public static bool IsInstanceOfType(this Type type, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? o) { - Requires.NotNull(type, nameof(type)); + ArgumentNullException.ThrowIfNull(type, nameof(type)); return type.IsInstanceOfType(o); } } @@ -291,20 +291,20 @@ public static class AssemblyExtensions [RequiresUnreferencedCode("Types might be removed")] public static Type[] GetExportedTypes(this Assembly assembly) { - Requires.NotNull(assembly, nameof(assembly)); + ArgumentNullException.ThrowIfNull(assembly, nameof(assembly)); return assembly.GetExportedTypes(); } public static Module[] GetModules(this Assembly assembly) { - Requires.NotNull(assembly, nameof(assembly)); + ArgumentNullException.ThrowIfNull(assembly, nameof(assembly)); return assembly.GetModules(); } [RequiresUnreferencedCode("Types might be removed")] public static Type[] GetTypes(this Assembly assembly) { - Requires.NotNull(assembly, nameof(assembly)); + ArgumentNullException.ThrowIfNull(assembly, nameof(assembly)); return assembly.GetTypes(); } } @@ -313,37 +313,37 @@ public static class EventInfoExtensions { public static MethodInfo? GetAddMethod(this EventInfo eventInfo) { - Requires.NotNull(eventInfo, nameof(eventInfo)); + ArgumentNullException.ThrowIfNull(eventInfo, nameof(eventInfo)); return eventInfo.GetAddMethod(); } public static MethodInfo? GetAddMethod(this EventInfo eventInfo, bool nonPublic) { - Requires.NotNull(eventInfo, nameof(eventInfo)); + ArgumentNullException.ThrowIfNull(eventInfo, nameof(eventInfo)); return eventInfo.GetAddMethod(nonPublic); } public static MethodInfo? GetRaiseMethod(this EventInfo eventInfo) { - Requires.NotNull(eventInfo, nameof(eventInfo)); + ArgumentNullException.ThrowIfNull(eventInfo, nameof(eventInfo)); return eventInfo.GetRaiseMethod(); } public static MethodInfo? GetRaiseMethod(this EventInfo eventInfo, bool nonPublic) { - Requires.NotNull(eventInfo, nameof(eventInfo)); + ArgumentNullException.ThrowIfNull(eventInfo, nameof(eventInfo)); return eventInfo.GetRaiseMethod(nonPublic); } public static MethodInfo? GetRemoveMethod(this EventInfo eventInfo) { - Requires.NotNull(eventInfo, nameof(eventInfo)); + ArgumentNullException.ThrowIfNull(eventInfo, nameof(eventInfo)); return eventInfo.GetRemoveMethod(); } public static MethodInfo? GetRemoveMethod(this EventInfo eventInfo, bool nonPublic) { - Requires.NotNull(eventInfo, nameof(eventInfo)); + ArgumentNullException.ThrowIfNull(eventInfo, nameof(eventInfo)); return eventInfo.GetRemoveMethod(nonPublic); } } @@ -358,7 +358,7 @@ public static class MemberInfoExtensions /// This maybe public static bool HasMetadataToken(this MemberInfo member) { - Requires.NotNull(member, nameof(member)); + ArgumentNullException.ThrowIfNull(member, nameof(member)); try { @@ -380,7 +380,7 @@ public static bool HasMetadataToken(this MemberInfo member) /// public static int GetMetadataToken(this MemberInfo member) { - Requires.NotNull(member, nameof(member)); + ArgumentNullException.ThrowIfNull(member, nameof(member)); int token = GetMetadataTokenOrZeroOrThrow(member); @@ -413,7 +413,7 @@ public static class MethodInfoExtensions { public static MethodInfo GetBaseDefinition(this MethodInfo method) { - Requires.NotNull(method, nameof(method)); + ArgumentNullException.ThrowIfNull(method, nameof(method)); return method.GetBaseDefinition(); } } @@ -422,13 +422,13 @@ public static class ModuleExtensions { public static bool HasModuleVersionId(this Module module) { - Requires.NotNull(module, nameof(module)); + ArgumentNullException.ThrowIfNull(module, nameof(module)); return true; // not expected to fail on platforms with Module.ModuleVersionId built-in. } public static Guid GetModuleVersionId(this Module module) { - Requires.NotNull(module, nameof(module)); + ArgumentNullException.ThrowIfNull(module, nameof(module)); return module.ModuleVersionId; } } @@ -437,37 +437,37 @@ public static class PropertyInfoExtensions { public static MethodInfo[] GetAccessors(this PropertyInfo property) { - Requires.NotNull(property, nameof(property)); + ArgumentNullException.ThrowIfNull(property, nameof(property)); return property.GetAccessors(); } public static MethodInfo[] GetAccessors(this PropertyInfo property, bool nonPublic) { - Requires.NotNull(property, nameof(property)); + ArgumentNullException.ThrowIfNull(property, nameof(property)); return property.GetAccessors(nonPublic); } public static MethodInfo? GetGetMethod(this PropertyInfo property) { - Requires.NotNull(property, nameof(property)); + ArgumentNullException.ThrowIfNull(property, nameof(property)); return property.GetGetMethod(); } public static MethodInfo? GetGetMethod(this PropertyInfo property, bool nonPublic) { - Requires.NotNull(property, nameof(property)); + ArgumentNullException.ThrowIfNull(property, nameof(property)); return property.GetGetMethod(nonPublic); } public static MethodInfo? GetSetMethod(this PropertyInfo property) { - Requires.NotNull(property, nameof(property)); + ArgumentNullException.ThrowIfNull(property, nameof(property)); return property.GetSetMethod(); } public static MethodInfo? GetSetMethod(this PropertyInfo property, bool nonPublic) { - Requires.NotNull(property, nameof(property)); + ArgumentNullException.ThrowIfNull(property, nameof(property)); return property.GetSetMethod(nonPublic); } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 35fe09cc6629..76bbc5dd6495 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -284,6 +284,7 @@ protected ArgumentNullException(System.Runtime.Serialization.SerializationInfo i public ArgumentNullException(string? paramName) { } public ArgumentNullException(string? message, System.Exception? innerException) { } public ArgumentNullException(string? paramName, string? message) { } + public static void ThrowIfNull([System.Diagnostics.CodeAnalysis.NotNullAttribute] object? argument, [System.Runtime.CompilerServices.CallerArgumentExpressionAttribute("argument")] string? paramName = null) { throw null; } } public partial class ArgumentOutOfRangeException : System.ArgumentException { diff --git a/src/libraries/System.Runtime/tests/System/ArgumentNullExceptionTests.cs b/src/libraries/System.Runtime/tests/System/ArgumentNullExceptionTests.cs index 577e7c4ba26e..78384466f7fb 100644 --- a/src/libraries/System.Runtime/tests/System/ArgumentNullExceptionTests.cs +++ b/src/libraries/System.Runtime/tests/System/ArgumentNullExceptionTests.cs @@ -47,5 +47,32 @@ public static void Ctor_String_String() Assert.Contains(message, exception.Message); Assert.Contains(argumentName, exception.Message); } + + [Fact] + public static void ThrowIfNull_NonNull_DoesntThrow() + { + foreach (object o in new[] { new object(), "", "argument" }) + { + ArgumentNullException.ThrowIfNull(o); + ArgumentNullException.ThrowIfNull(o, "paramName"); + } + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("name")] + public static void ThrowIfNull_Null_ThrowsArgumentNullException(string paramName) + { + AssertExtensions.Throws(paramName, () => ArgumentNullException.ThrowIfNull(null, paramName)); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/csharplang/issues/287")] + public static void ThrowIfNull_UsesArgumentExpression() + { + object something = null; + AssertExtensions.Throws(nameof(something), () => ArgumentNullException.ThrowIfNull(something)); + } } } From a487e4ce5ae118d9b292b8b8b5130f77c577c821 Mon Sep 17 00:00:00 2001 From: Zoltan Varga Date: Wed, 14 Jul 2021 09:45:04 -0400 Subject: [PATCH 124/133] [mono][wasm] Mark memcpy LLVM instructions as volatile when they are used to copy (#55598) valuetypes containing references. This is needed to make sure llvm keeps the valuetypes on the C stack for GC tracking. Fixes random crashes when running the System.Collections.NonGeneric testsuite. --- src/mono/mono/mini/mini-llvm.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/mono/mono/mini/mini-llvm.c b/src/mono/mono/mini/mini-llvm.c index 088d843cd062..c8a3a90e1571 100644 --- a/src/mono/mono/mini/mini-llvm.c +++ b/src/mono/mono/mini/mini-llvm.c @@ -7230,6 +7230,7 @@ process_bb (EmitContext *ctx, MonoBasicBlock *bb) MonoClass *klass = ins->klass; LLVMValueRef src = NULL, dst, args [5]; gboolean done = FALSE; + gboolean is_volatile = FALSE; if (!klass) { // FIXME: @@ -7285,15 +7286,20 @@ process_bb (EmitContext *ctx, MonoBasicBlock *bb) if (done) break; +#ifdef TARGET_WASM + is_volatile = m_class_has_references (klass); +#endif + int aindex = 0; args [aindex ++] = dst; args [aindex ++] = src; args [aindex ++] = LLVMConstInt (LLVMInt32Type (), mono_class_value_size (klass, NULL), FALSE); + #if LLVM_API_VERSION < 900 // FIXME: Alignment args [aindex ++] = LLVMConstInt (LLVMInt32Type (), 0, FALSE); #endif - args [aindex ++] = LLVMConstInt (LLVMInt1Type (), 0, FALSE); + args [aindex ++] = LLVMConstInt (LLVMInt1Type (), is_volatile ? 1 : 0, FALSE); call_intrins (ctx, INTRINS_MEMCPY, args, ""); break; } From 7cfa94509078e599df748fdea11a1cf7d371ebf1 Mon Sep 17 00:00:00 2001 From: Natalia Kondratyeva Date: Wed, 14 Jul 2021 16:00:44 +0200 Subject: [PATCH 125/133] [QUIC] Cosmetic changes to Read pipeline (#55591) Follow-up for NITs and cosmetic changes from #55505 --- .../Implementations/MsQuic/MsQuicStream.cs | 74 ++++++++++++------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs index 585aca851b3f..0c29a9285d67 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs @@ -32,6 +32,8 @@ private sealed class State { public SafeMsQuicStreamHandle Handle = null!; // set in ctor. public GCHandle StateGCHandle; + + public MsQuicStream? Stream; // roots the stream in the pinned state to prevent GC during an async read I/O. public MsQuicConnection.State ConnectionState = null!; // set in ctor. public string TraceId = null!; // set in ctor. @@ -48,7 +50,7 @@ private sealed class State // set when ReadState.PendingRead: public Memory ReceiveUserBuffer; public CancellationTokenRegistration ReceiveCancellationRegistration; - public MsQuicStream? RootedReceiveStream; // roots the stream in the pinned state to prevent GC during an async read I/O. + // Resettable completions to be used for multiple calls to receive. public readonly ResettableCompletionSource ReceiveResettableCompletionSource = new ResettableCompletionSource(); public SendState SendState; @@ -363,27 +365,38 @@ internal override ValueTask ReadAsync(Memory destination, Cancellatio NetEventSource.Info(_state, $"{TraceId()} Stream reading into Memory of '{destination.Length}' bytes."); } - ReadState readState; - long abortError = -1; - bool canceledSynchronously = false; + ReadState initialReadState; // value before transitions + long abortError; + bool preCanceled = false; lock (_state) { - readState = _state.ReadState; + initialReadState = _state.ReadState; abortError = _state.ReadErrorCode; - if (readState != ReadState.PendingRead && cancellationToken.IsCancellationRequested) + // Failure scenario: pre-canceled token. Transition: any -> Aborted + // PendingRead state indicates there is another concurrent read operation in flight + // which is forbidden, so it is handled separately + if (initialReadState != ReadState.PendingRead && cancellationToken.IsCancellationRequested) { - readState = ReadState.Aborted; + initialReadState = ReadState.Aborted; _state.ReadState = ReadState.Aborted; - canceledSynchronously = true; + preCanceled = true; + } + + // Success scenario: EOS already reached, completing synchronously. No transition (final state) + if (initialReadState == ReadState.ReadsCompleted) + { + return new ValueTask(0); } - else if (readState == ReadState.None) + + // Success scenario: no data available yet, will return a task to wait on. Transition None->PendingRead + if (initialReadState == ReadState.None) { - Debug.Assert(_state.RootedReceiveStream is null); + Debug.Assert(_state.Stream is null); _state.ReceiveUserBuffer = destination; - _state.RootedReceiveStream = this; + _state.Stream = this; _state.ReadState = ReadState.PendingRead; if (cancellationToken.CanBeCanceled) @@ -396,7 +409,7 @@ internal override ValueTask ReadAsync(Memory destination, Cancellatio lock (state) { completePendingRead = state.ReadState == ReadState.PendingRead; - state.RootedReceiveStream = null; + state.Stream = null; state.ReceiveUserBuffer = null; state.ReadState = ReadState.Aborted; } @@ -414,7 +427,9 @@ internal override ValueTask ReadAsync(Memory destination, Cancellatio return _state.ReceiveResettableCompletionSource.GetValueTask(); } - else if (readState == ReadState.IndividualReadComplete) + + // Success scenario: data already available, completing synchronously. Transition IndividualReadComplete->None + if (initialReadState == ReadState.IndividualReadComplete) { _state.ReadState = ReadState.None; @@ -431,25 +446,22 @@ internal override ValueTask ReadAsync(Memory destination, Cancellatio } } + // All success scenarios returned at this point. Failure scenarios below: + Exception? ex = null; - switch (readState) + switch (initialReadState) { - case ReadState.ReadsCompleted: - return new ValueTask(0); case ReadState.PendingRead: ex = new InvalidOperationException("Only one read is supported at a time."); break; case ReadState.Aborted: - ex = - canceledSynchronously ? new OperationCanceledException(cancellationToken) : // aborted by token being canceled before the async op started. - abortError == -1 ? new QuicOperationAbortedException() : // aborted by user via some other operation. - new QuicStreamAbortedException(abortError); // aborted by peer. - + ex = preCanceled ? new OperationCanceledException(cancellationToken) : + ThrowHelper.GetStreamAbortedException(abortError); break; case ReadState.ConnectionClosed: default: - Debug.Assert(readState == ReadState.ConnectionClosed, $"{nameof(ReadState)} of '{readState}' is unaccounted for in {nameof(ReadAsync)}."); + Debug.Assert(initialReadState == ReadState.ConnectionClosed, $"{nameof(ReadState)} of '{initialReadState}' is unaccounted for in {nameof(ReadAsync)}."); ex = GetConnectionAbortedException(_state); break; } @@ -490,7 +502,7 @@ internal override void AbortRead(long errorCode) if (_state.ReadState == ReadState.PendingRead) { shouldComplete = true; - _state.RootedReceiveStream = null; + _state.Stream = null; _state.ReceiveUserBuffer = null; } if (_state.ReadState < ReadState.ReadsCompleted) @@ -833,7 +845,7 @@ private static unsafe uint HandleEventRecv(State state, ref StreamEvent evt) if (receiveEvent.BufferCount == 0) { // This is a 0-length receive that happens once reads are finished (via abort or otherwise). - // State changes for this are handled elsewhere. + // State changes for this are handled in PEER_SEND_SHUTDOWN / PEER_SEND_ABORT / SHUTDOWN_COMPLETE event handlers. return MsQuicStatusCodes.Success; } @@ -847,6 +859,12 @@ private static unsafe uint HandleEventRecv(State state, ref StreamEvent evt) case ReadState.None: // ReadAsync() hasn't been called yet. Stash the buffer so the next ReadAsync call completes synchronously. + // We are overwriting state.ReceiveQuicBuffers here even if we only partially consumed them + // and it is intended, because unconsumed data will arrive again from the point we've stopped. + // New RECEIVE event wouldn't come until we call EnableReceive(), and we call it only after we've consumed + // as much as we could and said so to msquic in ReceiveComplete(taken), so new event will have all the + // remaining data. + if ((uint)state.ReceiveQuicBuffers.Length < receiveEvent.BufferCount) { QuicBuffer[] oldReceiveBuffers = state.ReceiveQuicBuffers; @@ -872,7 +890,7 @@ private static unsafe uint HandleEventRecv(State state, ref StreamEvent evt) state.ReceiveCancellationRegistration.Unregister(); shouldComplete = true; - state.RootedReceiveStream = null; + state.Stream = null; state.ReadState = ReadState.None; readLength = CopyMsQuicBuffersToUserBuffer(new ReadOnlySpan(receiveEvent.Buffers, (int)receiveEvent.BufferCount), state.ReceiveUserBuffer.Span); @@ -975,7 +993,7 @@ private static uint HandleEventShutdownComplete(State state, ref StreamEvent evt if (state.ReadState == ReadState.PendingRead) { shouldReadComplete = true; - state.RootedReceiveStream = null; + state.Stream = null; state.ReceiveUserBuffer = null; } if (state.ReadState < ReadState.ReadsCompleted) @@ -1029,7 +1047,7 @@ private static uint HandleEventPeerSendAborted(State state, ref StreamEvent evt) if (state.ReadState == ReadState.PendingRead) { shouldComplete = true; - state.RootedReceiveStream = null; + state.Stream = null; state.ReceiveUserBuffer = null; } state.ReadState = ReadState.Aborted; @@ -1057,7 +1075,7 @@ private static uint HandleEventPeerSendShutdown(State state) if (state.ReadState == ReadState.PendingRead) { shouldComplete = true; - state.RootedReceiveStream = null; + state.Stream = null; state.ReceiveUserBuffer = null; } if (state.ReadState < ReadState.ReadsCompleted) From b45b5fc9b17a15536f416a55733258eebef5ad46 Mon Sep 17 00:00:00 2001 From: Alexander Nikolaev <55398552+alnikola@users.noreply.github.com> Date: Wed, 14 Jul 2021 16:05:27 +0200 Subject: [PATCH 126/133] Disable Http2_MultipleConnectionsEnabled_ConnectionLimitNotReached_ConcurrentRequestsSuccessfullyHandled Reverts #55572. The test is still failing. --- .../tests/FunctionalTests/SocketsHttpHandlerTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index 1e364ea96651..b326c8bbb021 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -2078,6 +2078,7 @@ public sealed class SocketsHttpHandlerTest_Http2 : HttpClientHandlerTest_Http2 public SocketsHttpHandlerTest_Http2(ITestOutputHelper output) : base(output) { } [ConditionalFact(nameof(SupportsAlpn))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/41078")] public async Task Http2_MultipleConnectionsEnabled_ConnectionLimitNotReached_ConcurrentRequestsSuccessfullyHandled() { const int MaxConcurrentStreams = 2; From 9ad44f8347be52df4070d85264bfe528f34ba3a0 Mon Sep 17 00:00:00 2001 From: Jon Fortescue Date: Wed, 14 Jul 2021 07:26:52 -0700 Subject: [PATCH 127/133] Turn on loc PRs (#54293) * Turn on loc PRs * oops Co-authored-by: Jeremy Koritzinsky * Keep OneLocBuild running only in main Co-authored-by: Jeremy Koritzinsky --- eng/pipelines/runtime-official.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/eng/pipelines/runtime-official.yml b/eng/pipelines/runtime-official.yml index 84b0d2fcde45..6b02686235b5 100644 --- a/eng/pipelines/runtime-official.yml +++ b/eng/pipelines/runtime-official.yml @@ -44,11 +44,12 @@ stages: # # Localization build # - - template: /eng/common/templates/job/onelocbuild.yml - parameters: - CreatePr: false - LclSource: lclFilesfromPackage - LclPackageId: 'LCL-JUNO-PROD-RUNTIME' + - ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}: + - template: /eng/common/templates/job/onelocbuild.yml + parameters: + MirrorRepo: runtime + LclSource: lclFilesfromPackage + LclPackageId: 'LCL-JUNO-PROD-RUNTIME' # # Source Index Build From d953229d5429d2ffde833740dd481aab864d3e0c Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Wed, 14 Jul 2021 10:47:03 -0400 Subject: [PATCH 128/133] [wasm] Run `Wasm.Build.Tests` against workloads (#54451) Co-authored-by: Larry Ewing --- Directory.Build.props | 4 + docs/workflow/testing/testing-workloads.md | 28 +++ eng/Subsets.props | 2 +- eng/Versions.props | 1 + eng/liveBuilds.targets | 2 +- eng/pipelines/runtime.yml | 41 +++- eng/testing/WasmRunnerTemplate.cmd | 4 +- eng/testing/linker/project.csproj.template | 1 + eng/testing/linker/trimmingTests.targets | 2 + eng/testing/tests.mobile.targets | 9 + eng/testing/tests.wasm.targets | 7 + .../Directory.Build.props | 2 +- ...Microsoft.NETCore.App.MonoCrossAOT.sfxproj | 23 +- src/libraries/Directory.Build.props | 7 + src/libraries/Directory.Build.targets | 93 +++++++ src/libraries/pretest.proj | 3 + src/libraries/sendtohelix.proj | 20 +- src/libraries/sendtohelixhelp.proj | 125 +++++++--- src/libraries/tests.proj | 9 +- .../Sdk/Sdk.props | 4 + ...T.Workload.Mono.Toolchain.Manifest.pkgproj | 1 + .../WorkloadManifest.targets.in | 2 +- src/mono/wasm/Makefile | 4 +- src/mono/wasm/README.md | 2 +- src/mono/wasm/build/EmSdkRepo.Defaults.props | 3 + src/mono/wasm/build/WasmApp.LocalBuild.props | 6 +- .../wasm/build/WasmApp.LocalBuild.targets | 4 +- src/mono/wasm/build/WasmApp.Native.targets | 25 +- src/mono/wasm/build/WasmApp.targets | 20 +- .../aot-tests/ProxyProjectForAOTOnHelix.proj | 2 +- src/mono/wasm/wasm.proj | 32 +-- src/tasks/Common/Utils.cs | 4 +- src/tasks/WasmAppBuilder/EmccCompile.cs | 4 +- .../InstallWorkloadFromArtifacts.cs | 228 ++++++++++++++++++ .../WorkloadBuildTasks/PackageInstaller.cs | 163 +++++++++++++ .../WorkloadBuildTasks.csproj | 2 + .../Wasm.Build.Tests/BlazorWasmTests.cs | 10 +- .../Wasm.Build.Tests/BuildEnvironment.cs | 29 +-- .../Wasm.Build.Tests/BuildTestBase.cs | 77 +++--- .../Wasm.Build.Tests/DotNetCommand.cs | 8 +- .../Wasm.Build.Tests/EnvironmentVariables.cs | 21 ++ .../Wasm.Build.Tests/NativeBuildTests.cs | 2 - .../BuildWasmApps/Wasm.Build.Tests/README.md | 19 ++ .../Wasm.Build.Tests/Wasm.Build.Tests.csproj | 18 +- .../Wasm.Build.Tests/WorkloadTests.cs | 12 +- .../data/Blazor.Directory.Build.targets | 6 +- .../data/Local.Directory.Build.targets | 2 +- .../data/RunScriptTemplate.cmd | 52 ++++ .../data/Workloads.Directory.Build.props | 7 + .../data/Workloads.Directory.Build.targets | 95 ++++++++ src/tests/Common/Directory.Build.targets | 4 + .../wasm-test-runner/WasmTestRunner.proj | 2 + src/tests/Directory.Build.targets | 4 + 53 files changed, 1096 insertions(+), 161 deletions(-) create mode 100644 docs/workflow/testing/testing-workloads.md create mode 100644 src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs create mode 100644 src/tasks/WorkloadBuildTasks/PackageInstaller.cs create mode 100644 src/tests/BuildWasmApps/Wasm.Build.Tests/EnvironmentVariables.cs create mode 100644 src/tests/BuildWasmApps/Wasm.Build.Tests/README.md create mode 100644 src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.cmd create mode 100644 src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.props create mode 100644 src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets diff --git a/Directory.Build.props b/Directory.Build.props index 541ad0417e40..6cab9bcee305 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -73,8 +73,10 @@ $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'AndroidAppBuilder', 'Debug', '$(NetCoreAppToolCurrent)', 'publish')) $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmAppBuilder', 'Debug', '$(NetCoreAppToolCurrent)', 'publish')) $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmBuildTasks', 'Debug', '$(NetCoreAppToolCurrent)', 'publish')) + $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WorkloadBuildTasks', 'Debug', '$(NetCoreAppToolCurrent)', 'publish')) $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'MonoAOTCompiler', 'Debug', '$(NetCoreAppToolCurrent)')) $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'RuntimeConfigParser', 'Debug', '$(NetCoreAppToolCurrent)')) + $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'JsonToItemsTaskFactory', 'Debug', '$(NetCoreAppToolCurrent)')) $([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'installer.tasks', 'Debug', '$(NetCoreAppToolCurrent)', 'installer.tasks.dll')) $([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'installer.tasks', 'Debug', 'net461', 'installer.tasks.dll')) @@ -84,8 +86,10 @@ $([MSBuild]::NormalizePath('$(AndroidAppBuilderDir)', 'AndroidAppBuilder.dll')) $([MSBuild]::NormalizePath('$(WasmAppBuilderDir)', 'WasmAppBuilder.dll')) $([MSBuild]::NormalizePath('$(WasmBuildTasksDir)', 'WasmBuildTasks.dll')) + $([MSBuild]::NormalizePath('$(WorkloadBuildTasksDir)', 'WorkloadBuildTasks.dll')) $([MSBuild]::NormalizePath('$(MonoAOTCompilerDir)', 'MonoAOTCompiler.dll')) $([MSBuild]::NormalizePath('$(RuntimeConfigParserDir)', 'RuntimeConfigParser.dll')) + $([MSBuild]::NormalizePath('$(JsonToItemsTaskFactoryDir)', 'JsonToItemsTaskFactory.dll')) $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'coreclr', '$(TargetOS).$(TargetArchitecture).$(Configuration)')) diff --git a/docs/workflow/testing/testing-workloads.md b/docs/workflow/testing/testing-workloads.md new file mode 100644 index 000000000000..cb03515554a0 --- /dev/null +++ b/docs/workflow/testing/testing-workloads.md @@ -0,0 +1,28 @@ +# Testing Workloads + +Workloads based on packages in `artifacts` can be installed, and used for testing. + +- This is done by installing a specified SDK version (`$(SdkVersionForWorkloadTesting)`) in `artifacts/bin/dotnet-workload`. +- Then the manifest for the workload in `@(WorkloadIdForTesting)` is installed + - Then workload packs are installed + - packs, or manifests not generated by `runtime`, are restored from nuget + +- The SDK is installed by `ProvisionSdkForWorkloadTesting` target +- and the workload is installed by `InstallWorkloadUsingArtifacts` target, using the `InstallWorkloadFromArtifacts` task + +- `@(WorkloadIdForTesting)`: + +Example for wasm: +```xml + +``` + +- Currently, this is used only by `src/tests/BuildWasmApps/Wasm.Build.Tests` + +## Limitations: + +- The cross compiler package is built manually from the `InstallWorkloadUsingArtifacts` target diff --git a/eng/Subsets.props b/eng/Subsets.props index 105b34254c6c..9234a8c2dde4 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -317,7 +317,7 @@ - + diff --git a/eng/Versions.props b/eng/Versions.props index 1e7be3b4cb98..2e300bfd20cf 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -161,6 +161,7 @@ 2.0.4 4.12.0 2.14.3 + 6.0.100-preview.7.21362.5 5.0.0-preview-20201009.2 diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index fb05c25a395a..7710f9d5e2f3 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -178,7 +178,7 @@ $(LibrariesNativeArtifactsPath)src\*.c; $(LibrariesNativeArtifactsPath)src\*.js; $(LibrariesNativeArtifactsPath)src\emcc-default.rsp; - $(LibrariesNativeArtifactsPath)src\Emcc.props;" + $(LibrariesNativeArtifactsPath)src\emcc-props.json;" NativeSubDirectory="src" IsNative="true" /> - @@ -305,6 +304,46 @@ jobs: eq(variables['monoContainsChange'], true), eq(variables['isFullMatrix'], true)) +# +# Build the whole product using Mono and run libraries tests, for Wasm.Build.Tests +# +- template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + helixQueuesTemplate: /eng/pipelines/libraries/helix-queues-setup.yml + buildConfig: Release + runtimeFlavor: mono + platforms: + - Browser_wasm + variables: + # map dependencies variables to local variables + - name: monoContainsChange + value: $[ dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'] ] + - name: installerContainsChange + value: $[ dependencies.evaluate_paths.outputs['SetPathVars_installer.containsChange'] ] + jobParameters: + testGroup: innerloop + nameSuffix: AllSubsets_Mono_WasmBuildTests + buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true + timeoutInMinutes: 180 + condition: >- + or( + eq(dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'], true), + eq(dependencies.evaluate_paths.outputs['SetPathVars_installer.containsChange'], true), + eq(variables['isFullMatrix'], true)) + # extra steps, run tests + extraStepsTemplate: /eng/pipelines/libraries/helix.yml + extraStepsParameters: + creator: dotnet-bot + testRunNamePrefixSuffix: Mono_$(_BuildConfig) + scenarios: + - buildwasmapps + condition: >- + or( + eq(variables['monoContainsChange'], true), + eq(variables['installerContainsChange'], true), + eq(variables['isFullMatrix'], true)) + # # Build for Browser/wasm, with EnableAggressiveTrimming=true # diff --git a/eng/testing/WasmRunnerTemplate.cmd b/eng/testing/WasmRunnerTemplate.cmd index 63af8f917b89..603199341a2b 100644 --- a/eng/testing/WasmRunnerTemplate.cmd +++ b/eng/testing/WasmRunnerTemplate.cmd @@ -5,9 +5,9 @@ set EXECUTION_DIR=%~dp0 set SCENARIO=%3 if [%HELIX_WORKITEM_UPLOAD_ROOT%] == [] ( - set XHARNESS_OUT="%EXECUTION_DIR%xharness-output" + set "XHARNESS_OUT=%EXECUTION_DIR%xharness-output" ) else ( - set XHARNESS_OUT="%HELIX_WORKITEM_UPLOAD_ROOT%\xharness-output" + set "XHARNESS_OUT=%HELIX_WORKITEM_UPLOAD_ROOT%\xharness-output" ) if [%XHARNESS_CLI_PATH%] NEQ [] ( diff --git a/eng/testing/linker/project.csproj.template b/eng/testing/linker/project.csproj.template index 38c6d68f901a..e1ba296072c8 100644 --- a/eng/testing/linker/project.csproj.template +++ b/eng/testing/linker/project.csproj.template @@ -6,6 +6,7 @@ {MonoProjectRoot} {MonoAOTCompilerTasksAssemblyPath} {WasmAppBuilderTasksAssemblyPath} + {JsonToItemsTaskFactoryTasksAssemblyPath} {MicrosoftNetCoreAppRuntimePackRidDir} {RepositoryEngineeringDir} {TestTargetFramework} diff --git a/eng/testing/linker/trimmingTests.targets b/eng/testing/linker/trimmingTests.targets index 6f084b6ddb41..649ad3e25bf2 100644 --- a/eng/testing/linker/trimmingTests.targets +++ b/eng/testing/linker/trimmingTests.targets @@ -82,8 +82,10 @@ .Replace('{AdditionalProjectReferences}', '$(_additionalProjectReferencesString)') .Replace('{RepositoryEngineeringDir}', '$(RepositoryEngineeringDir)') .Replace('{MonoAOTCompilerDir}', '$(MonoAOTCompilerDir)') + .Replace('{JsonToItemsTaskFactoryDir}', '$(JsonToItemsTaskFactoryDir)') .Replace('{MonoProjectRoot}', '$(MonoProjectRoot)') .Replace('{MonoAOTCompilerTasksAssemblyPath}', '$(MonoAOTCompilerTasksAssemblyPath)') + .Replace('{JsonToItemsTaskFactoryTasksAssemblyPath}', '$(JsonToItemsTaskFactoryTasksAssemblyPath)') .Replace('{WasmAppBuilderTasksAssemblyPath}', '$(WasmAppBuilderTasksAssemblyPath)') .Replace('{MicrosoftNetCoreAppRuntimePackRidDir}', '$(MicrosoftNetCoreAppRuntimePackRidDir)'))" Overwrite="true" /> diff --git a/eng/testing/tests.mobile.targets b/eng/testing/tests.mobile.targets index 517ebf719850..c3107ec4a060 100644 --- a/eng/testing/tests.mobile.targets +++ b/eng/testing/tests.mobile.targets @@ -7,6 +7,10 @@ true BundleTestAppleApp;BundleTestAndroidApp + + + true @@ -291,5 +295,10 @@ AfterTargets="Build" DependsOnTargets="Publish;$(BundleTestAppTargets);ArchiveTests" /> + + diff --git a/eng/testing/tests.wasm.targets b/eng/testing/tests.wasm.targets index aa4fb305dbd4..16edf35847b8 100644 --- a/eng/testing/tests.wasm.targets +++ b/eng/testing/tests.wasm.targets @@ -60,6 +60,13 @@ $(BundleTestWasmAppDependsOn);_BundleAOTTestWasmAppForHelix + + + + - + diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.MonoCrossAOT.sfxproj b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.MonoCrossAOT.sfxproj index a951a58b643d..27a590bfd08f 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.MonoCrossAOT.sfxproj +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.MonoCrossAOT.sfxproj @@ -18,14 +18,20 @@ - <_ToolFile Include="$(MonoAotCrossDir)$(TargetCrossRid)\**" /> - - - + + + + $(MonoAotCrossDir) + $(MonoAotCrossDir)$(TargetCrossRid)\ + + + <_ToolFile Include="$(AotCompilerPath)**" /> + + <_SdkPropsProperties Condition="!$([MSBuild]::IsOsPlatform('Windows'))" Include="ExeSuffix" Value="" /> <_SdkPropsProperties Condition="$([MSBuild]::IsOsPlatform('Windows'))" Include="ExeSuffix" Value=".exe" /> <_SdkPropsProperties Include="TargetRid" Value="$(TargetCrossRid)" /> @@ -36,19 +42,20 @@ <_PermissionsProperties Include="PermissionsProperties" Value="$(_PermissionsFiles)" /> + + OutputPath="$(IntermediateOutputPath)Microsoft.NETCore.App.MonoCrossAOT.UnixFilePermissions.xml" /> + OutputPath="$(IntermediateOutputPath)$(TargetCrossRid).Sdk.props" /> - + - + diff --git a/src/libraries/Directory.Build.props b/src/libraries/Directory.Build.props index 4d39fad397e5..05ea159b1bb3 100644 --- a/src/libraries/Directory.Build.props +++ b/src/libraries/Directory.Build.props @@ -163,6 +163,13 @@ --interpreter + + $(ArtifactsBinDir)dotnet-workload\ + $([MSBuild]::NormalizeDirectory($(SdkPathForWorkloadTesting))) + + $(SdkPathForWorkloadTesting)version-$(SdkVersionForWorkloadTesting).stamp + + diff --git a/src/libraries/Directory.Build.targets b/src/libraries/Directory.Build.targets index a3553ca940f1..dd17dace8e4b 100644 --- a/src/libraries/Directory.Build.targets +++ b/src/libraries/Directory.Build.targets @@ -7,6 +7,13 @@ $(TestStrongNameKeyId) + + + true + + $(SdkPathForWorkloadTesting)workload.stamp + + @@ -328,4 +335,90 @@ Text="Analyzers must only target netstandard2.0 since they run in the compiler which targets netstandard2.0. The following files were found to target '%(_AnalyzerPackFile.TargetFramework)': @(_AnalyzerPackFile)" /> + + + + + + + + + + + + + <_DotNetInstallScriptPath Condition="!$([MSBuild]::IsOSPlatform('windows'))">$(DOTNET_INSTALL_DIR)/dotnet-install.sh + <_DotNetInstallScriptPath Condition=" $([MSBuild]::IsOSPlatform('windows'))">$(RepoRoot).dotnet\dotnet-install.ps1 + + + + + + + + + + + + + + + + + + + + + + + + + <_PropsForAOTCrossBuild Include="TestingWorkloads=true" /> + <_PropsForAOTCrossBuild Include="Configuration=$(Configuration)" /> + <_PropsForAOTCrossBuild Include="TargetOS=Browser" /> + <_PropsForAOTCrossBuild Include="TargetArchitecture=wasm" /> + <_PropsForAOTCrossBuild Include="ContinuousIntegrationBuild=$(ContinuousIntegrationBuild)" /> + + <_PropsForAOTCrossBuild Include="RuntimeIdentifier=$(NETCoreSdkRuntimeIdentifier)" /> + <_PropsForAOTCrossBuild Include="TargetCrossRid=$(RuntimeIdentifier)" /> + + + + + + <_NuGetSourceForWorkloads Include="dotnet6" Value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json" /> + <_BuiltNuGets Include="$(LibrariesShippingPackagesDir)\*.nupkg" /> + + + + + <_AOTCrossNuGetPath>$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.AOT.$(NETCoreSdkRuntimeIdentifier).Cross.$(RuntimeIdentifier).$(PackageVersion).nupkg + + + + + + + + + + diff --git a/src/libraries/pretest.proj b/src/libraries/pretest.proj index 3e924e083abd..fc2fee13d5c3 100644 --- a/src/libraries/pretest.proj +++ b/src/libraries/pretest.proj @@ -22,6 +22,9 @@ + + + - <_ProjectsToBuild Include="$(PerScenarioProjectFile)"> + <_BaseProjectsToBuild Include="$(PerScenarioProjectFile)" Condition="'%(_Scenarios.Identity)' != 'buildwasmapps'"> $(_PropertiesToPass);Scenario=%(_Scenarios.Identity);TestArchiveRuntimeFile=$(TestArchiveRuntimeFile) - %(_ProjectsToBuild.AdditionalProperties);NeedsToBuildWasmAppsOnHelix=$(NeedsToBuildWasmAppsOnHelix) - + %(_BaseProjectsToBuild.AdditionalProperties);NeedsToBuildWasmAppsOnHelix=$(NeedsToBuildWasmAppsOnHelix) + + + + + + <_TestUsingWorkloadsValues Include="true;false" /> + + <_BuildWasmAppsProjectsToBuild Include="$(PerScenarioProjectFile)"> + $(_PropertiesToPass);Scenario=BuildWasmApps;TestArchiveRuntimeFile=$(TestArchiveRuntimeFile);TestUsingWorkloads=%(_TestUsingWorkloadsValues.Identity) + %(_BuildWasmAppsProjectsToBuild.AdditionalProperties);NeedsToBuildWasmAppsOnHelix=$(NeedsToBuildWasmAppsOnHelix) + + + + + <_ProjectsToBuild Include="@(_BuildWasmAppsProjectsToBuild);@(_BaseProjectsToBuild)" /> diff --git a/src/libraries/sendtohelixhelp.proj b/src/libraries/sendtohelixhelp.proj index b5f603cee4f3..572481d7ccb8 100644 --- a/src/libraries/sendtohelixhelp.proj +++ b/src/libraries/sendtohelixhelp.proj @@ -7,6 +7,7 @@ true + true $(BUILD_BUILDNUMBER) @@ -28,7 +29,7 @@ <_workItemTimeout Condition="'$(_workItemTimeout)' == '' and ('$(TargetOS)' == 'iOS' or '$(TargetOS)' == 'iOSSimulator' or '$(TargetOS)' == 'tvOS' or '$(TargetOS)' == 'tvOSSimulator' or '$(TargetOS)' == 'MacCatalyst' or '$(TargetOS)' == 'Android')">00:30:00 <_workItemTimeout Condition="'$(Scenario)' == '' and '$(_workItemTimeout)' == '' and ('$(TargetArchitecture)' == 'arm64' or '$(TargetArchitecture)' == 'arm')">00:45:00 <_workItemTimeout Condition="'$(Scenario)' != '' and '$(_workItemTimeout)' == '' and ('$(TargetArchitecture)' == 'arm64' or '$(TargetArchitecture)' == 'arm')">01:00:00 - <_workItemTimeout Condition="'$(Scenario)' == 'BuildWasmApps' and '$(_workItemTimeout)' == ''">01:00:00 + <_workItemTimeout Condition="'$(Scenario)' == 'BuildWasmApps' and '$(_workItemTimeout)' == ''">01:30:00 <_workItemTimeout Condition="'$(TargetOS)' == 'Browser' and '$(NeedsToBuildWasmAppsOnHelix)' == 'true'">01:00:00 <_workItemTimeout Condition="'$(Scenario)' == '' and '$(_workItemTimeout)' == '' and '$(Outerloop)' == 'true'">00:20:00 <_workItemTimeout Condition="'$(Scenario)' == '' and '$(_workItemTimeout)' == ''">00:15:00 @@ -53,6 +54,11 @@ $(WaitForWorkItemCompletion) true + $(RepoRoot)src\mono\wasm\emsdk\ + + true + true + true @@ -97,19 +103,29 @@ true - true + true - + - - true - true - true - + + + + false + true + + + + + true + true + true + + + + + + + + + + + + + true @@ -155,24 +183,6 @@ $([System.Text.RegularExpressions.Regex]::Match($(GlobalJsonContent), '(%3F<="dotnet": ").*(%3F=")')) - - dotnet dev-certs https && - - - powershell -command "New-SelfSignedCertificate -FriendlyName 'ASP.NET Core HTTPS development certificate' -DnsName @('localhost') -Subject 'CN = localhost' -KeyAlgorithm RSA -KeyLength 2048 -HashAlgorithm sha256 -CertStoreLocation 'Cert:\CurrentUser\My' -TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.1','1.3.6.1.4.1.311.84.1.1={hex}02','2.5.29.19={text}') -KeyUsage DigitalSignature,KeyEncipherment" && - - - $(HelixCommand)call RunTests.cmd - $(HelixCommand) --runtime-path %HELIX_CORRELATION_PAYLOAD% - - $(HelixCommand)./RunTests.sh - $(HelixCommand) --runtime-path "$HELIX_CORRELATION_PAYLOAD" - - @@ -224,6 +234,34 @@ + + @(HelixPreCommand) + $(HelixCommandPrefix) @(HelixCommandPrefixItem -> 'set "%(Identity)"', ' & ') + $(HelixCommandPrefix) @(HelixCommandPrefixItem, ' ') + true + + + + $(HelixCommandPrefix) + $(HelixCommandPrefix) & + + $(HelixCommand) dotnet dev-certs https && + + + $(HelixCommand) powershell -command "New-SelfSignedCertificate -FriendlyName 'ASP.NET Core HTTPS development certificate' -DnsName @('localhost') -Subject 'CN = localhost' -KeyAlgorithm RSA -KeyLength 2048 -HashAlgorithm sha256 -CertStoreLocation 'Cert:\CurrentUser\My' -TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.1','1.3.6.1.4.1.311.84.1.1={hex}02','2.5.29.19={text}') -KeyUsage DigitalSignature,KeyEncipherment" && + + + $(HelixCommand)call RunTests.cmd + $(HelixCommand) --runtime-path %HELIX_CORRELATION_PAYLOAD% + + $(HelixCommand)./RunTests.sh + $(HelixCommand) --runtime-path "$HELIX_CORRELATION_PAYLOAD" + + @@ -274,11 +312,13 @@ 768968 https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/$(ChromiumRevision)/chrome-linux.zip - https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/$(ChromiumRevision)/chromedriver_linux64.zip + https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/$(ChromiumRevision)/chromedriver_linux64.zip $([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'src', 'mono', 'wasm', 'emsdk')) $([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'src', 'mono', 'wasm', 'build')) $([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'bin', 'NetCoreServer', '$(NetCoreAppCurrent)-$(Configuration)')) $([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'bin', 'RemoteLoopServer', '$(NetCoreAppCurrent)-$(Configuration)')) + Workloads/ + EMSDK/ @@ -289,7 +329,7 @@ - + @@ -302,13 +342,22 @@ - + + + + + + + + + @@ -320,15 +369,15 @@ - - + + <_WorkItem Include="$(WorkItemArchiveWildCard)" Exclude="$(HelixCorrelationPayload)" /> <_WorkItem Include="$(TestArchiveRoot)runonly/**/WebAssembly.Console.*.Test.zip" Condition="'$(TargetOS)' == 'Browser' and '$(Scenario)' != 'WasmTestOnBrowser' and '$(Scenario)' != 'BuildWasmApps'" /> <_WorkItem Include="$(TestArchiveRoot)runonly/**/WebAssembly.Browser.*.Test.zip" Condition="'$(TargetOS)' == 'Browser' and '$(Scenario)' == 'WasmTestOnBrowser'" /> <_WorkItem Include="$(TestArchiveRoot)browseronly/**/*.zip" Condition="'$(TargetOS)' == 'Browser' and '$(Scenario)' == 'WasmTestOnBrowser'" /> - + %(Identity) $(HelixCommand) $(_workItemTimeout) @@ -375,7 +424,7 @@ - + @@ -393,6 +442,8 @@ + diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index b1f20442aac7..4884a9416d9f 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -347,9 +347,11 @@ Condition="'$(TestTrimming)' == 'true'" AdditionalProperties="%(AdditionalProperties);SkipTrimmingProjectsRestore=true" /> + + @@ -376,10 +378,11 @@ BuildInParallel="false" /> - + + + BuildInParallel="true" /> diff --git a/src/mono/nuget/Microsoft.NET.Runtime.MonoTargets.Sdk/Sdk/Sdk.props b/src/mono/nuget/Microsoft.NET.Runtime.MonoTargets.Sdk/Sdk/Sdk.props index d292fe15f274..a8050972730f 100644 --- a/src/mono/nuget/Microsoft.NET.Runtime.MonoTargets.Sdk/Sdk/Sdk.props +++ b/src/mono/nuget/Microsoft.NET.Runtime.MonoTargets.Sdk/Sdk/Sdk.props @@ -1,4 +1,8 @@ + + true + + diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Manifest.pkgproj b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Manifest.pkgproj index f89c42b2884d..747da02070f2 100644 --- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Manifest.pkgproj +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Manifest.pkgproj @@ -38,6 +38,7 @@ TemplateFile="WorkloadManifest.json.in" Properties="@(_WorkloadManifestValues)" OutputPath="$(WorkloadManifestPath)" /> + - + + + + diff --git a/src/mono/wasm/build/WasmApp.LocalBuild.props b/src/mono/wasm/build/WasmApp.LocalBuild.props index 69a719a0e6d1..df76040d2c72 100644 --- a/src/mono/wasm/build/WasmApp.LocalBuild.props +++ b/src/mono/wasm/build/WasmApp.LocalBuild.props @@ -37,6 +37,7 @@ $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmAppBuilder', 'Debug', '$(_NetCoreAppToolCurrent)', 'publish')) $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmBuildTasks', 'Debug', '$(_NetCoreAppToolCurrent)', 'publish')) $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'MonoAOTCompiler', 'Debug', '$(_NetCoreAppToolCurrent)')) + $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'JsonToItemsTaskFactory', 'Debug', '$(_NetCoreAppToolCurrent)')) $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'mono', '$(TargetOS).$(TargetArchitecture).$(RuntimeConfig)')) <_MonoAotCrossCompilerPath>$([MSBuild]::NormalizePath($(MonoArtifactsPath), 'cross', '$(TargetOS.ToLowerInvariant())-$(TargetArchitecture.ToLowerInvariant())', 'mono-aot-cross')) @@ -48,6 +49,7 @@ $([MSBuild]::NormalizeDirectory($(BuildBaseDir), 'microsoft.netcore.app.runtime.browser-wasm')) $([MSBuild]::NormalizeDirectory($(BuildBaseDir), 'MonoAOTCompiler')) + $([MSBuild]::NormalizeDirectory($(BuildBaseDir), 'JsonToItemsTaskFactory')) $([MSBuild]::NormalizeDirectory($(BuildBaseDir), 'WasmAppBuilder')) $([MSBuild]::NormalizeDirectory($(BuildBaseDir), 'WasmBuildTasks')) @@ -60,10 +62,12 @@ - $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackLocationToUse), 'runtimes', 'browser-wasm')) + $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackLocationToUse))) + $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackDir), 'runtimes', 'browser-wasm')) $([MSBuild]::NormalizePath('$(WasmAppBuilderDir)', 'WasmAppBuilder.dll')) $([MSBuild]::NormalizePath('$(WasmBuildTasksDir)', 'WasmBuildTasks.dll')) $([MSBuild]::NormalizePath('$(MonoAOTCompilerDir)', 'MonoAOTCompiler.dll')) + $([MSBuild]::NormalizePath('$(JsonToItemsTaskFactoryDir)', 'JsonToItemsTaskFactory.dll')) diff --git a/src/mono/wasm/build/WasmApp.LocalBuild.targets b/src/mono/wasm/build/WasmApp.LocalBuild.targets index b34d8f03bfa3..ae05dae8a558 100644 --- a/src/mono/wasm/build/WasmApp.LocalBuild.targets +++ b/src/mono/wasm/build/WasmApp.LocalBuild.targets @@ -60,10 +60,12 @@ Text="%24(RuntimeSrcDir) is set, but %24(RuntimeConfig) is not" /> - $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackLocationToUse), 'runtimes', 'browser-wasm')) + $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackLocationToUse))) + $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackDir), 'runtimes', 'browser-wasm')) $([MSBuild]::NormalizePath('$(WasmAppBuilderDir)', 'WasmAppBuilder.dll')) $([MSBuild]::NormalizePath('$(WasmBuildTasksDir)', 'WasmBuildTasks.dll')) $([MSBuild]::NormalizePath('$(MonoAOTCompilerDir)', 'MonoAOTCompiler.dll')) + $([MSBuild]::NormalizePath('$(JsonToItemsTaskFactoryDir)', 'JsonToItemsTaskFactory.dll')) .exe true - - <_WasmRuntimePackIncludeDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'include')) - <_WasmRuntimePackSrcDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'src')) - <_EmccPropsPath>$(_WasmRuntimePackSrcDir)Emcc.props - - - @@ -325,6 +315,14 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_ + + + + + + + + @@ -469,4 +467,11 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_ + + + + + + diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 034287436892..10bd9ac7973c 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -80,12 +80,6 @@ false - - $([MSBuild]::NormalizeDirectory($(NuGetPackageRoot), 'microsoft.netcore.app.runtime.mono.browser-wasm', '$(BundledNETCoreAppPackageVersion)')) - $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackDir), 'runtimes', 'browser-wasm')) - $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidDir))) - $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidDir), 'native')) - <_BeforeWasmBuildAppDependsOn /> @@ -97,6 +91,20 @@ + + + + %(ResolvedRuntimePack.PackageDirectory) + $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackDir), 'runtimes', 'browser-wasm')) + $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidDir))) + $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidDir), 'native')) + + + <_WasmRuntimePackIncludeDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'include')) + <_WasmRuntimePackSrcDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'src')) + + diff --git a/src/mono/wasm/data/aot-tests/ProxyProjectForAOTOnHelix.proj b/src/mono/wasm/data/aot-tests/ProxyProjectForAOTOnHelix.proj index cdd5e95a4e21..3a7b66fac738 100644 --- a/src/mono/wasm/data/aot-tests/ProxyProjectForAOTOnHelix.proj +++ b/src/mono/wasm/data/aot-tests/ProxyProjectForAOTOnHelix.proj @@ -22,7 +22,7 @@ - diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index b8ee77e5e273..3eacc3e1307c 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -89,7 +89,7 @@ WriteOnlyWhenDifferent="true" Overwrite="true" /> - + <_EmccVersionRaw>%(_ReversedVersionLines.Identity) - <_EmccVersionRegexPattern>^ *emcc \([^\)]+\) *([^ \(]+) *\(([^\)]+)\)$ + <_EmccVersionRegexPattern>^ *emcc \([^\)]+\) *([0-9\.]+).*\(([^\)]+)\)$ <_EmccVersion>$([System.Text.RegularExpressions.Regex]::Match($(_EmccVersionRaw), $(_EmccVersionRegexPattern)).Groups[1].Value) <_EmccVersionHash>$([System.Text.RegularExpressions.Regex]::Match($(_EmccVersionRaw), $(_EmccVersionRegexPattern)).Groups[2].Value) - <_EmccPropsContent> + <_EmccPropsJson> - - $(_EmccVersionRaw) - $(_EmccVersion) - $(_EmccVersionHash) - - +{ + "items": { + "EmccProperties": [ + { "identity": "RuntimeEmccVersion", "value": "$(_EmccVersion)" }, + { "identity": "RuntimeEmccVersionRaw", "value": "$(_EmccVersionRaw)" }, + { "identity": "RuntimeEmccVersionHash", "value": "$(_EmccVersionHash)" } + ] + } +} ]]> - + - @@ -146,7 +148,7 @@ + $(NativeBinDir)src\emcc-props.json" /> @@ -259,7 +261,7 @@ + $(NativeBinDir)src\emcc-props.json" /> diff --git a/src/tasks/Common/Utils.cs b/src/tasks/Common/Utils.cs index ea2aba607dd8..7b25107d56f9 100644 --- a/src/tasks/Common/Utils.cs +++ b/src/tasks/Common/Utils.cs @@ -185,12 +185,12 @@ internal static string CreateTemporaryBatchFile(string command) } #if NETCOREAPP - public static void DirectoryCopy(string sourceDir, string destDir, Func predicate) + public static void DirectoryCopy(string sourceDir, string destDir, Func? predicate=null) { string[] files = Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories); foreach (string file in files) { - if (!predicate(file)) + if (predicate != null && !predicate(file)) continue; string relativePath = Path.GetRelativePath(sourceDir, file); diff --git a/src/tasks/WasmAppBuilder/EmccCompile.cs b/src/tasks/WasmAppBuilder/EmccCompile.cs index 78a6a4b1faa6..2a8a2f12cbbe 100644 --- a/src/tasks/WasmAppBuilder/EmccCompile.cs +++ b/src/tasks/WasmAppBuilder/EmccCompile.cs @@ -123,7 +123,7 @@ bool ProcessSourceFile(ITaskItem srcItem) if (exitCode != 0) { - Log.LogError($"Failed to compile {srcFile} -> {objFile}: {output}"); + Log.LogError($"Failed to compile {srcFile} -> {objFile}{Environment.NewLine}{output}"); return false; } @@ -135,7 +135,7 @@ bool ProcessSourceFile(ITaskItem srcItem) } catch (Exception ex) { - Log.LogError($"Failed to compile {srcFile} -> {objFile}: {ex.Message}"); + Log.LogError($"Failed to compile {srcFile} -> {objFile}{Environment.NewLine}{ex.Message}"); return false; } } diff --git a/src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs b/src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs new file mode 100644 index 000000000000..c76659d5db3a --- /dev/null +++ b/src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs @@ -0,0 +1,228 @@ +// 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.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +#nullable enable + +namespace Microsoft.Workload.Build.Tasks +{ + public class InstallWorkloadFromArtifacts : Task + { + [Required, NotNull] + public ITaskItem? WorkloadId { get; set; } + + [Required, NotNull] + public string? VersionBand { get; set; } + + [Required, NotNull] + public string? LocalNuGetsPath { get; set; } + + [Required, NotNull] + public string? SdkDir { get; set; } + + public ITaskItem[] ExtraNuGetSources { get; set; } = Array.Empty(); + + public override bool Execute() + { + Utils.Logger = Log; + + if (!HasMetadata(WorkloadId, nameof(WorkloadId), "Version") || + !HasMetadata(WorkloadId, nameof(WorkloadId), "ManifestName")) + { + return false; + } + + if (!Directory.Exists(SdkDir)) + { + Log.LogError($"Cannot find SdkDir={SdkDir}"); + return false; + } + + Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** Installing workload manifest {WorkloadId.ItemSpec} **{Environment.NewLine}"); + + string nugetConfigContents = GetNuGetConfig(); + if (!InstallWorkloadManifest(WorkloadId.GetMetadata("ManifestName"), WorkloadId.GetMetadata("Version"), nugetConfigContents, stopOnMissing: true)) + return false; + + string nugetConfigPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.WriteAllText(nugetConfigPath, nugetConfigContents); + + Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** workload install **{Environment.NewLine}"); + (int exitCode, string output) = Utils.TryRunProcess( + Path.Combine(SdkDir, "dotnet"), + $"workload install --skip-manifest-update --no-cache --configfile \"{nugetConfigPath}\" {WorkloadId.ItemSpec}", + workingDir: Path.GetTempPath(), + silent: false, + debugMessageImportance: MessageImportance.High); + if (exitCode != 0) + { + Log.LogError($"workload install failed: {output}"); + + foreach (var dir in Directory.EnumerateDirectories(Path.Combine(SdkDir, "sdk-manifests"), "*", SearchOption.AllDirectories)) + Log.LogMessage(MessageImportance.Low, $"\t{Path.Combine(SdkDir, "sdk-manifests", dir)}"); + + foreach (var dir in Directory.EnumerateDirectories(Path.Combine(SdkDir, "packs"), "*", SearchOption.AllDirectories)) + Log.LogMessage(MessageImportance.Low, $"\t{Path.Combine(SdkDir, "packs", dir)}"); + + return false; + } + + return !Log.HasLoggedErrors; + } + + private string GetNuGetConfig() + { + StringBuilder nugetConfigBuilder = new(); + nugetConfigBuilder.AppendLine($"{Environment.NewLine}"); + + nugetConfigBuilder.AppendLine($@""); + foreach (ITaskItem source in ExtraNuGetSources) + { + string key = source.ItemSpec; + string value = source.GetMetadata("Value"); + if (string.IsNullOrEmpty(value)) + { + Log.LogWarning($"ExtraNuGetSource {key} is missing Value metadata"); + continue; + } + + nugetConfigBuilder.AppendLine($@""); + } + + nugetConfigBuilder.AppendLine($"{Environment.NewLine}"); + return nugetConfigBuilder.ToString(); + } + + private bool InstallWorkloadManifest(string name, string version, string nugetConfigContents, bool stopOnMissing) + { + Log.LogMessage(MessageImportance.High, $"Installing workload manifest for {name}/{version}"); + + // Find any existing directory with the manifest name, ignoring the case + // Multiple directories for a manifest, differing only in case causes + // workload install to fail due to duplicate manifests! + // This is applicable only on case-sensitive filesystems + string outputDir = FindSubDirIgnoringCase(Path.Combine(SdkDir, "sdk-manifests", VersionBand), name); + + PackageReference pkgRef = new(Name: $"{name}.Manifest-{VersionBand}", + Version: version, + OutputDir: outputDir, + relativeSourceDir: "data"); + + if (!PackageInstaller.Install(new[]{ pkgRef }, nugetConfigContents, Log, stopOnMissing)) + return false; + + string manifestDir = pkgRef.OutputDir; + string jsonPath = Path.Combine(manifestDir, "WorkloadManifest.json"); + if (!File.Exists(jsonPath)) + { + Log.LogError($"Could not find WorkloadManifest.json at {jsonPath}"); + return false; + } + + ManifestInformation? manifest; + try + { + manifest = JsonSerializer.Deserialize( + File.ReadAllBytes(jsonPath), + new JsonSerializerOptions(JsonSerializerDefaults.Web) + { + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip + }); + + if (manifest == null) + { + Log.LogError($"Could not parse manifest from {jsonPath}."); + return false; + } + } + catch (JsonException je) + { + Log.LogError($"Failed to read from {jsonPath}: {je.Message}"); + return false; + } + + if (manifest.DependsOn != null) + { + foreach ((string depName, string depVersion) in manifest.DependsOn) + { + if (!InstallWorkloadManifest(depName, depVersion, nugetConfigContents, stopOnMissing: false)) + { + Log.LogWarning($"Could not install manifest {depName}/{depVersion}. This can be ignored if the workload {WorkloadId.ItemSpec} doesn't depend on it."); + continue; + } + } + } + + return true; + } + + private bool HasMetadata(ITaskItem item, string itemName, string metadataName) + { + if (!string.IsNullOrEmpty(item.GetMetadata(metadataName))) + return true; + + Log.LogError($"{itemName} item ({item.ItemSpec}) is missing Name metadata"); + return false; + } + + private string FindSubDirIgnoringCase(string parentDir, string dirName) + { + IEnumerable matchingDirs = Directory.EnumerateDirectories(parentDir, + dirName, + new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }); + + string? first = matchingDirs.FirstOrDefault(); + if (matchingDirs.Count() > 1) + { + Log.LogWarning($"Found multiple directories with names that differ only in case. {string.Join(", ", matchingDirs.ToArray())}" + + $"{Environment.NewLine}Using the first one: {first}"); + } + + return first ?? Path.Combine(parentDir, dirName); + } + + private record ManifestInformation( + object Version, + string Description, + + [property: JsonPropertyName("depends-on")] + IDictionary DependsOn, + IDictionary Workloads, + IDictionary Packs, + object Data + ); + + private record WorkloadInformation( + bool Abstract, + string Kind, + string Description, + + List Packs, + List Extends, + List Platforms + ); + + private record PackVersionInformation( + string Kind, + string Version, + [property: JsonPropertyName("alias-to")] + Dictionary AliasTo + ); + } + + internal record PackageReference(string Name, + string Version, + string OutputDir, + string relativeSourceDir = ""); +} diff --git a/src/tasks/WorkloadBuildTasks/PackageInstaller.cs b/src/tasks/WorkloadBuildTasks/PackageInstaller.cs new file mode 100644 index 000000000000..f6d4e85cf76c --- /dev/null +++ b/src/tasks/WorkloadBuildTasks/PackageInstaller.cs @@ -0,0 +1,163 @@ +// 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.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +#nullable enable + +namespace Microsoft.Workload.Build.Tasks +{ + internal class PackageInstaller + { + private readonly string _tempDir; + private string _nugetConfigContents; + private TaskLoggingHelper _logger; + private string _packagesDir; + + private PackageInstaller(string nugetConfigContents, TaskLoggingHelper logger) + { + _nugetConfigContents = nugetConfigContents; + + _logger = logger; + _tempDir = Path.Combine(Path.GetTempPath(), "install-workload", Path.GetRandomFileName()); + _packagesDir = Path.Combine(_tempDir, "nuget-packages"); + } + + public static bool Install(PackageReference[] references, string nugetConfigContents, TaskLoggingHelper logger, bool stopOnMissing=true) + { + if (!references.Any()) + return true; + + return new PackageInstaller(nugetConfigContents, logger) + .InstallActual(references, stopOnMissing); + } + + private bool InstallActual(PackageReference[] references, bool stopOnMissing) + { + // Restore packages + if (Directory.Exists(_packagesDir)) + { + _logger.LogMessage(MessageImportance.Low, $"Deleting {_packagesDir}"); + Directory.Delete(_packagesDir, recursive: true); + } + + var projecDir = Path.Combine(_tempDir, "restore"); + var projectPath = Path.Combine(projecDir, "Restore.csproj"); + + Directory.CreateDirectory(projecDir); + + File.WriteAllText(Path.Combine(projecDir, "Directory.Build.props"), ""); + File.WriteAllText(Path.Combine(projecDir, "Directory.Build.targets"), ""); + File.WriteAllText(projectPath, GenerateProject(references)); + File.WriteAllText(Path.Combine(projecDir, "nuget.config"), _nugetConfigContents); + + _logger.LogMessage(MessageImportance.Low, $"Restoring packages: {string.Join(", ", references.Select(r => $"{r.Name}/{r.Version}"))}"); + + string args = $"restore \"{projectPath}\" /p:RestorePackagesPath=\"{_packagesDir}\""; + (int exitCode, string output) = Utils.TryRunProcess("dotnet", args, silent: false, debugMessageImportance: MessageImportance.Low); + if (exitCode != 0) + { + LogErrorOrWarning($"Restoring packages failed with exit code: {exitCode}. Output:{Environment.NewLine}{output}", stopOnMissing); + return false; + } + + IList<(PackageReference, string)> failedToRestore = references + .Select(r => (r, Path.Combine(_packagesDir, r.Name.ToLower(), r.Version))) + .Where(tuple => !Directory.Exists(tuple.Item2)) + .ToList(); + + if (failedToRestore.Count > 0) + { + _logger.LogMessage(MessageImportance.Normal, output); + foreach ((PackageReference pkgRef, string pkgDir) in failedToRestore) + LogErrorOrWarning($"Could not restore {pkgRef.Name}/{pkgRef.Version} (can't find {pkgDir})", stopOnMissing); + + return false; + } + + return LayoutPackages(references, stopOnMissing); + } + + private bool LayoutPackages(IEnumerable references, bool stopOnMissing) + { + foreach (var pkgRef in references) + { + var source = Path.Combine(_packagesDir, pkgRef.Name.ToLower(), pkgRef.Version, pkgRef.relativeSourceDir); + if (!Directory.Exists(source)) + { + LogErrorOrWarning($"Failed to restore {pkgRef.Name}/{pkgRef.Version} (could not find {source})", stopOnMissing); + if (stopOnMissing) + return false; + } + else + { + if (!CopyDirectoryAfresh(source, pkgRef.OutputDir) && stopOnMissing) + return false; + } + } + + return true; + } + + private static string GenerateProject(IEnumerable references) + { + StringBuilder projectFileBuilder = new(); + projectFileBuilder.Append(@" + + + net6.0 + + "); + + foreach (var reference in references) + projectFileBuilder.AppendLine($""); + + projectFileBuilder.Append(@" + + +"); + + return projectFileBuilder.ToString(); + } + + private bool CopyDirectoryAfresh(string srcDir, string destDir) + { + try + { + if (Directory.Exists(destDir)) + { + _logger.LogMessage(MessageImportance.Low, $"Deleting {destDir}"); + Directory.Delete(destDir, recursive: true); + } + + _logger.LogMessage(MessageImportance.Low, $"Copying {srcDir} to {destDir}"); + Directory.CreateDirectory(destDir); + Utils.DirectoryCopy(srcDir, destDir); + + return true; + } + catch (Exception ex) + { + _logger.LogError($"Failed while copying {srcDir} => {destDir}: {ex.Message}"); + if (ex is IOException) + return false; + + throw; + } + } + + private void LogErrorOrWarning(string msg, bool stopOnMissing) + { + if (stopOnMissing) + _logger.LogError(msg); + else + _logger.LogWarning(msg); + } + } +} diff --git a/src/tasks/WorkloadBuildTasks/WorkloadBuildTasks.csproj b/src/tasks/WorkloadBuildTasks/WorkloadBuildTasks.csproj index 328672b45145..537418ef31dc 100644 --- a/src/tasks/WorkloadBuildTasks/WorkloadBuildTasks.csproj +++ b/src/tasks/WorkloadBuildTasks/WorkloadBuildTasks.csproj @@ -6,6 +6,8 @@ $(NoWarn),CA1050 + + diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs index c58c4b55f3da..dacf67ff4f39 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs @@ -19,7 +19,8 @@ public BlazorWasmTests(ITestOutputHelper output, SharedBuildPerTestClassFixture [ConditionalFact(typeof(BuildTestBase), nameof(IsUsingWorkloads))] public void PublishTemplateProject() { - InitPaths("id"); + string id = "blazorwasm"; + InitPaths(id); if (Directory.Exists(_projectDir)) Directory.Delete(_projectDir, recursive: true); Directory.CreateDirectory(_projectDir); @@ -29,14 +30,17 @@ public void PublishTemplateProject() File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Directory.Build.props"), Path.Combine(_projectDir, "Directory.Build.props")); File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Directory.Build.targets"), Path.Combine(_projectDir, "Directory.Build.targets")); - new DotNetCommand(s_buildEnv) + string logPath = Path.Combine(s_buildEnv.LogRootPath, id); + + new DotNetCommand(s_buildEnv, useDefaultArgs: false) .WithWorkingDirectory(_projectDir) .ExecuteWithCapturedOutput("new blazorwasm") .EnsureSuccessful(); + string publishLogPath = Path.Combine(logPath, $"{id}.publish.binlog"); new DotNetCommand(s_buildEnv) .WithWorkingDirectory(_projectDir) - .ExecuteWithCapturedOutput("publish -bl -p:RunAOTCompilation=true") + .ExecuteWithCapturedOutput("publish", $"-bl:{publishLogPath}", "-p:RunAOTCompilation=true") .EnsureSuccessful(); //TODO: validate the build somehow? diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs index 231b215f88b5..a8f67d09eb68 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; namespace Wasm.Build.Tests { @@ -24,7 +25,6 @@ public class BuildEnvironment public static readonly string TestDataPath = Path.Combine(AppContext.BaseDirectory, "data"); private static string s_runtimeConfig = "Release"; - private const string s_testLogPathEnvVar = "TEST_LOG_PATH"; public BuildEnvironment() { @@ -39,12 +39,11 @@ public BuildEnvironment() solutionRoot = solutionRoot.Parent; } - string? sdkForWorkloadPath = Environment.GetEnvironmentVariable("SDK_FOR_WORKLOAD_TESTING_PATH"); + string? sdkForWorkloadPath = EnvironmentVariables.SdkForWorkloadTestingPath; if (!string.IsNullOrEmpty(sdkForWorkloadPath)) { - DotNet = Path.Combine(sdkForWorkloadPath, "dotnet"); - var workloadPacksVersion = Environment.GetEnvironmentVariable("WORKLOAD_PACKS_VER"); + var workloadPacksVersion = EnvironmentVariables.WorkloadPacksVersion; if (string.IsNullOrEmpty(workloadPacksVersion)) throw new Exception($"Cannot test with workloads without WORKLOAD_PACKS_VER environment variable being set"); @@ -63,7 +62,7 @@ public BuildEnvironment() ["PATH"] = $"{sdkForWorkloadPath}{Path.PathSeparator}{Environment.GetEnvironmentVariable("PATH")}" }; - var appRefDir = Environment.GetEnvironmentVariable("AppRefDir"); + var appRefDir = EnvironmentVariables.AppRefDir; if (string.IsNullOrEmpty(appRefDir)) throw new Exception($"Cannot test with workloads without AppRefDir environment variable being set"); @@ -75,8 +74,7 @@ public BuildEnvironment() string emsdkPath; if (solutionRoot == null) { - string? buildDir = Environment.GetEnvironmentVariable("WasmBuildSupportDir"); - + string? buildDir = EnvironmentVariables.WasmBuildSupportDir; if (buildDir == null || !Directory.Exists(buildDir)) throw new Exception($"Could not find the solution root, or a build dir: {buildDir}"); @@ -89,11 +87,10 @@ public BuildEnvironment() string artifactsBinDir = Path.Combine(solutionRoot.FullName, "artifacts", "bin"); RuntimePackDir = Path.Combine(artifactsBinDir, "microsoft.netcore.app.runtime.browser-wasm", s_runtimeConfig); - string? emsdkEnvValue = Environment.GetEnvironmentVariable("EMSDK_PATH"); - if (string.IsNullOrEmpty(emsdkEnvValue)) + if (string.IsNullOrEmpty(EnvironmentVariables.EMSDK_PATH)) emsdkPath = Path.Combine(solutionRoot.FullName, "src", "mono", "wasm", "emsdk"); else - emsdkPath = emsdkEnvValue; + emsdkPath = EnvironmentVariables.EMSDK_PATH; DefaultBuildArgs = $" /p:RuntimeSrcDir={solutionRoot.FullName} /p:RuntimeConfig={s_runtimeConfig} /p:EMSDK_PATH={emsdkPath} "; } @@ -110,11 +107,12 @@ public BuildEnvironment() } RuntimeNativeDir = Path.Combine(RuntimePackDir, "runtimes", "browser-wasm", "native"); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + DotNet += ".exe"; - string? logPathEnvVar = Environment.GetEnvironmentVariable(s_testLogPathEnvVar); - if (!string.IsNullOrEmpty(logPathEnvVar)) + if (!string.IsNullOrEmpty(EnvironmentVariables.TestLogPath)) { - LogRootPath = logPathEnvVar; + LogRootPath = EnvironmentVariables.TestLogPath; if (!Directory.Exists(LogRootPath)) { Directory.CreateDirectory(LogRootPath); @@ -126,9 +124,8 @@ public BuildEnvironment() } } - // FIXME: update these to use Workload variants of the file, with the workload support - protected static string s_directoryBuildPropsForWorkloads = File.ReadAllText(Path.Combine(TestDataPath, "Local.Directory.Build.props")); - protected static string s_directoryBuildTargetsForWorkloads = File.ReadAllText(Path.Combine(TestDataPath, "Local.Directory.Build.targets")); + protected static string s_directoryBuildPropsForWorkloads = File.ReadAllText(Path.Combine(TestDataPath, "Workloads.Directory.Build.props")); + protected static string s_directoryBuildTargetsForWorkloads = File.ReadAllText(Path.Combine(TestDataPath, "Workloads.Directory.Build.targets")); protected static string s_directoryBuildPropsForLocal = File.ReadAllText(Path.Combine(TestDataPath, "Local.Directory.Build.props")); protected static string s_directoryBuildTargetsForLocal = File.ReadAllText(Path.Combine(TestDataPath, "Local.Directory.Build.targets")); diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs index 021ce531b17b..7d211ae61891 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs @@ -8,7 +8,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using System.Reflection; +using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using Xunit; @@ -17,12 +17,12 @@ #nullable enable +// [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] + namespace Wasm.Build.Tests { public abstract class BuildTestBase : IClassFixture, IDisposable { - protected const string SkipProjectCleanupEnvVar = "SKIP_PROJECT_CLEANUP"; - protected const string XHarnessRunnerCommandEnvVar = "XHARNESS_CLI_PATH"; protected const string s_targetFramework = "net6.0"; protected static readonly bool s_skipProjectCleanup; protected static readonly string s_xharnessRunnerCommand; @@ -32,6 +32,8 @@ public abstract class BuildTestBase : IClassFixturetrue\nfalse\n"; + { + extraProperties = $"{extraProperties}\ntrue"; + extraProperties += $"\n{RuntimeInformation.IsOSPlatform(OSPlatform.Windows)}\n"; + } string projectContents = projectTemplate .Replace("##EXTRA_PROPERTIES##", extraProperties) @@ -310,7 +307,8 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp _testOutput.WriteLine($"-------- Building ---------"); _testOutput.WriteLine($"Binlog path: {logFilePath}"); Console.WriteLine($"Binlog path: {logFilePath}"); - sb.Append($" /bl:\"{logFilePath}\" /v:minimal /nologo"); + sb.Append($" /bl:\"{logFilePath}\" /nologo"); + sb.Append($" /v:diag /fl /flp:\"v:diag,LogFile={logFilePath}.log\" /v:minimal"); if (buildArgs.ExtraBuildArgs != null) sb.Append($" {buildArgs.ExtraBuildArgs} "); @@ -451,9 +449,9 @@ protected static void AssertFile(string file0, string file1, string? label=null, Assert.True(finfo0.Length != finfo1.Length, $"{label}: File sizes should not match for {file0} ({finfo0.Length}), and {file1} ({finfo1.Length})"); } - protected (int exitCode, string buildOutput) AssertBuild(string args, string label="build", bool expectSuccess=true, IDictionary? envVars=null) + protected (int exitCode, string buildOutput) AssertBuild(string args, string label="build", bool expectSuccess=true, IDictionary? envVars=null, int? timeoutMs=null) { - var result = RunProcess(s_buildEnv.DotNet, _testOutput, args, workingDir: _projectDir, label: label, envVars: envVars); + var result = RunProcess(s_buildEnv.DotNet, _testOutput, args, workingDir: _projectDir, label: label, envVars: envVars, timeoutMs: timeoutMs ?? s_defaultPerTestTimeoutMs); if (expectSuccess) Assert.True(0 == result.exitCode, $"Build process exited with non-zero exit code: {result.exitCode}"); else @@ -475,13 +473,16 @@ public static (int exitCode, string buildOutput) RunProcess(string path, IDictionary? envVars = null, string? workingDir = null, string? label = null, - bool logToXUnit = true) + bool logToXUnit = true, + int? timeoutMs = null) { _testOutput.WriteLine($"Running {path} {args}"); Console.WriteLine($"Running: {path}: {args}"); Console.WriteLine($"WorkingDirectory: {workingDir}"); _testOutput.WriteLine($"WorkingDirectory: {workingDir}"); StringBuilder outputBuilder = new (); + object syncObj = new(); + var processStartInfo = new ProcessStartInfo { FileName = path, @@ -529,8 +530,23 @@ public static (int exitCode, string buildOutput) RunProcess(string path, // process.WaitForExit doesn't work if the process exits too quickly? // resetEvent.WaitOne(); - process.WaitForExit(); - return (process.ExitCode, outputBuilder.ToString().Trim('\r', '\n')); + if (!process.WaitForExit(timeoutMs ?? s_defaultPerTestTimeoutMs)) + { + // process didn't exit + process.Kill(entireProcessTree: true); + lock (syncObj) + { + var lastLines = outputBuilder.ToString().Split('\r', '\n').TakeLast(20); + throw new XunitException($"Process timed out, output: {string.Join(Environment.NewLine, lastLines)}"); + } + + } + + lock (syncObj) + { + var exitCode = process.ExitCode; + return (process.ExitCode, outputBuilder.ToString().Trim('\r', '\n')); + } } catch (Exception ex) { @@ -540,12 +556,15 @@ public static (int exitCode, string buildOutput) RunProcess(string path, void LogData(string label, string? message) { - if (logToXUnit && message != null) + lock (syncObj) { - _testOutput.WriteLine($"{label} {message}"); - Console.WriteLine($"{label} {message}"); + if (logToXUnit && message != null) + { + _testOutput.WriteLine($"{label} {message}"); + Console.WriteLine($"{label} {message}"); + } + outputBuilder.AppendLine($"{label} {message}"); } - outputBuilder.AppendLine($"{label} {message}"); } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/DotNetCommand.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/DotNetCommand.cs index 2fefb87c0f00..4136bf8ccea4 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/DotNetCommand.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/DotNetCommand.cs @@ -6,14 +6,18 @@ namespace Wasm.Build.Tests public class DotNetCommand : ToolCommand { private BuildEnvironment _buildEnvironment; + private bool _useDefaultArgs; - public DotNetCommand(BuildEnvironment buildEnv) : base(buildEnv.DotNet) + public DotNetCommand(BuildEnvironment buildEnv, bool useDefaultArgs=true) : base(buildEnv.DotNet) { _buildEnvironment = buildEnv; + _useDefaultArgs = useDefaultArgs; WithEnvironmentVariables(buildEnv.EnvVars); } protected override string GetFullArgs(params string[] args) - => $"{_buildEnvironment.DefaultBuildArgs} {string.Join(" ", args)}"; + => _useDefaultArgs + ? $"{string.Join(" ", args)} {_buildEnvironment.DefaultBuildArgs}" + : string.Join(" ", args); } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/EnvironmentVariables.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/EnvironmentVariables.cs new file mode 100644 index 000000000000..5ab3ad4108ca --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/EnvironmentVariables.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +#nullable enable + +namespace Wasm.Build.Tests +{ + internal static class EnvironmentVariables + { + internal static readonly string? SdkForWorkloadTestingPath = Environment.GetEnvironmentVariable("SDK_FOR_WORKLOAD_TESTING_PATH"); + internal static readonly string? WorkloadPacksVersion = Environment.GetEnvironmentVariable("WORKLOAD_PACKS_VER"); + internal static readonly string? AppRefDir = Environment.GetEnvironmentVariable("AppRefDir"); + internal static readonly string? WasmBuildSupportDir = Environment.GetEnvironmentVariable("WasmBuildSupportDir"); + internal static readonly string? EMSDK_PATH = Environment.GetEnvironmentVariable("EMSDK_PATH"); + internal static readonly string? TestLogPath = Environment.GetEnvironmentVariable("TEST_LOG_PATH"); + internal static readonly string? SkipProjectCleanup = Environment.GetEnvironmentVariable("SKIP_PROJECT_CLEANUP"); + internal static readonly string? XHarnessCliPath = Environment.GetEnvironmentVariable("XHARNESS_CLI_PATH"); + } +} diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs index 130e59e0ea2a..30e487f8171c 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs @@ -27,8 +27,6 @@ public NativeBuildTests(ITestOutputHelper output, SharedBuildPerTestClassFixture public void SimpleNativeBuild(BuildArgs buildArgs, RunHost host, string id) => NativeBuild("simple_native_build", s_mainReturns42, buildArgs, host, id); - - private void NativeBuild(string projectNamePrefix, string projectContents, BuildArgs buildArgs, RunHost host, string id) { string projectName = $"{projectNamePrefix}_{buildArgs.Config}_{buildArgs.AOT}"; diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/README.md b/src/tests/BuildWasmApps/Wasm.Build.Tests/README.md new file mode 100644 index 000000000000..4f0be0c36724 --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/README.md @@ -0,0 +1,19 @@ +# Wasm.Build.Tests + +Contains tests for wasm project builds, eg. for aot, relinking, globalization +etc. The intent is to check if the build inputs result in a correct app bundle +being generated. + +- When running locally, it tests against a local workload install (based on `artifacts`) + - but this can be turned off with `/p:TestUsingWorkloads=false` + - in which case, it will run against `emsdk` from `EMSDK_PATH` + +- On CI, both workload, and emsdk cases are tested + +- Running: + +Linux/macOS: `$ make -C src/mono/wasm run-build-tests` +Windows: `.\dotnet.cmd build .\src\tests\BuildWasmApps\Wasm.Build.Tests\Wasm.Build.Tests.csproj -c Release -t:Test -p:TargetOS=Browser -p:TargetArchitecture=wasm` + +- Specific tests can be run via `XUnitClassName`, and `XUnitMethodName` + - eg. `XUnitClassName=Wasm.Build.Tests.BlazorWasmTests` diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj index 0f06b6f08dfe..d6709715ef04 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj @@ -17,7 +17,12 @@ - RunScriptTemplate.sh + + + + RunScriptTemplate.cmd + RunScriptTemplate.sh + $(MSBuildThisFileDirectory)data\$(RunScriptInputName) @@ -28,7 +33,7 @@ - + @@ -40,9 +45,8 @@ - - - + + @@ -50,6 +54,7 @@ + @@ -60,6 +65,9 @@ $(RunScriptCommand) -method $(XUnitMethodName) $(RunScriptCommand) -class $(XUnitClassName) + + + $(RunScriptCommand) -notrait category=IgnoreForCI -notrait category=failing diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/WorkloadTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/WorkloadTests.cs index 89b656ee444e..ae67ab1cb4b9 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/WorkloadTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/WorkloadTests.cs @@ -4,10 +4,12 @@ using System; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Xml; using System.Xml.Serialization; using Xunit; using Xunit.Abstractions; +using Xunit.Sdk; #nullable enable @@ -21,6 +23,7 @@ public WorkloadTests(ITestOutputHelper output, SharedBuildPerTestClassFixture bu } [ConditionalFact(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [SkipOnPlatform(TestPlatforms.Windows, "Not applicable on windows")] public void FilesInUnixFilesPermissionsXmlExist() { // not doing any project generation here @@ -54,7 +57,14 @@ public void FilesInUnixFilesPermissionsXmlExist() // We don't install the cross compiler pack from nupkg, so we don't // have the unixFilePermissions for that // Expect just the emscripten ones here for now - Assert.Equal(3, unixPermFiles.Count()); + + // linux doesn't have Emscripten.Python package, so only 2 there + int expectedPermFileCount = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? 3 : 4; + + int permFileCount = unixPermFiles.Count(); + if (permFileCount != expectedPermFileCount) + throw new XunitException($"Expected to find 3 UnixFilePermissions.xml files, but got {permFileCount}." + + $"{Environment.NewLine}Files: {string.Join(", ", unixPermFiles)}"); } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets index 1ae8c2cd6ff9..b0384d714e34 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets @@ -5,7 +5,7 @@ - + @@ -33,7 +33,7 @@ FrameworkName="Microsoft.NETCore.App" NuGetPackageId="Microsoft.NETCore.App.Runtime.Mono.browser-wasm" NuGetPackageVersion="$(RuntimePackInWorkloadVersion)" - PackageDirectory="$(WorkloadPacksDir)\Microsoft.NETCore.App.Runtime.Mono.browser-wasm\$(RuntimePackInWorkloadVersion)" + PackageDirectory="$(NetCoreTargetingPackRoot)\Microsoft.NETCore.App.Runtime.Mono.browser-wasm\$(RuntimePackInWorkloadVersion)" RuntimeIdentifier="browser-wasm" /> diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Local.Directory.Build.targets b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Local.Directory.Build.targets index 8c0b1e7e46ee..65f76e4fc858 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Local.Directory.Build.targets +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Local.Directory.Build.targets @@ -23,7 +23,7 @@ - + diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.cmd b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.cmd new file mode 100644 index 000000000000..0c2cfd89d1ee --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.cmd @@ -0,0 +1,52 @@ +@echo off +setlocal enabledelayedexpansion + +set EXECUTION_DIR=%~dp0 +set SCENARIO=%3 + +cd %EXECUTION_DIR% + +if [%HELIX_WORKITEM_UPLOAD_ROOT%] == [] ( + set XHARNESS_OUT=%EXECUTION_DIR%xharness-output +) else ( + set XHARNESS_OUT=%HELIX_WORKITEM_UPLOAD_ROOT%\xharness-output +) + +if [%XHARNESS_CLI_PATH%] NEQ [] ( + :: When running in CI, we only have the .NET runtime available + :: We need to call the XHarness CLI DLL directly via dotnet exec + set HARNESS_RUNNER=dotnet.exe exec "%XHARNESS_CLI_PATH%" +) else ( + set HARNESS_RUNNER=dotnet.exe xharness +) + +set TEST_LOG_PATH=%XHARNESS_OUT%\logs + +:: ========================= BEGIN Test Execution ============================= +echo ----- start %DATE% %TIME% =============== To repro directly: ===================================================== +echo pushd %EXECUTION_DIR% +[[RunCommandsEcho]] +echo popd +echo =========================================================================================================== +pushd %EXECUTION_DIR% +@echo on +[[RunCommands]] +set EXIT_CODE=%ERRORLEVEL% +@echo off +popd +echo ----- end %DATE% %TIME% ----- exit code %EXIT_CODE% ---------------------------------------------------------- + +echo XHarness artifacts: %XHARNESS_OUT% + +exit /b %EXIT_CODE% + +REM Functions +:SetEnvVars +if [%TEST_USING_WORKLOADS%] == [true] ( + set "PATH=%BASE_DIR%\dotnet-workload;%PATH%" + set "SDK_FOR_WORKLOAD_TESTING_PATH=%BASE_DIR%\dotnet-workload" + set "AppRefDir=%BASE_DIR%\microsoft.netcore.app.ref" +) else ( + set "WasmBuildSupportDir=%BASE_DIR%\build" +) +EXIT /b 0 diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.props b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.props new file mode 100644 index 000000000000..6d8504088b95 --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.props @@ -0,0 +1,7 @@ + + + browser-wasm + true + true + + diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets new file mode 100644 index 000000000000..19f795a01f23 --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets @@ -0,0 +1,95 @@ + + + PrepareForWasmBuild;$(WasmBuildAppDependsOn) + <_MicrosoftNetCoreAppRefDir>$(AppRefDir)\ + Microsoft.NETCore.App + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_targetingPackReferenceExclusion Include="$(TargetName)" /> + <_targetingPackReferenceExclusion Include="@(_ResolvedProjectReferencePaths->'%(Filename)')" /> + <_targetingPackReferenceExclusion Include="@(DefaultReferenceExclusion)" /> + + + + <_targetingPackReferenceWithExclusion Include="@(Reference)"> + %(_targetingPackReferenceExclusion.Identity) + + + + + diff --git a/src/tests/Common/Directory.Build.targets b/src/tests/Common/Directory.Build.targets index 31e55c12ccfc..ed56d4d011b7 100644 --- a/src/tests/Common/Directory.Build.targets +++ b/src/tests/Common/Directory.Build.targets @@ -146,6 +146,10 @@ Include="$(ArtifactsBinDir)\WasmAppBuilder\Debug\$(NetCoreAppToolCurrent)\publish\**" TargetDir="WasmAppBuilder/"/> + + diff --git a/src/tests/Common/wasm-test-runner/WasmTestRunner.proj b/src/tests/Common/wasm-test-runner/WasmTestRunner.proj index c28b704c4f3b..f9479f967462 100644 --- a/src/tests/Common/wasm-test-runner/WasmTestRunner.proj +++ b/src/tests/Common/wasm-test-runner/WasmTestRunner.proj @@ -4,6 +4,7 @@ false + $(CORE_ROOT)\runtimepack-non-existant $(CORE_ROOT)\runtimepack $(NetCoreAppCurrent) $(MSBuildThisFileDirectory)\obj\$(Configuration)\wasm @@ -12,6 +13,7 @@ $(CORE_ROOT)\WasmAppBuilder\WasmAppBuilder.dll $(CORE_ROOT)\MonoAOTCompiler\MonoAOTCompiler.dll + $(CORE_ROOT)\JsonToItemsTaskFactory\JsonToItemsTaskFactory.dll diff --git a/src/tests/Directory.Build.targets b/src/tests/Directory.Build.targets index 71494ce1d6aa..6a361c68483e 100644 --- a/src/tests/Directory.Build.targets +++ b/src/tests/Directory.Build.targets @@ -377,6 +377,10 @@ Include="$(ArtifactsBinDir)\MonoAOTCompiler\Debug\$(NetCoreAppToolCurrent)\**" TargetDir="MonoAOTCompiler/"/> + + From 8d365a0d281cbb431f92e211184f98059df667e6 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Wed, 14 Jul 2021 07:54:04 -0700 Subject: [PATCH 129/133] Migrate various parts of the bindings code to use roots (#55530) We have a lot of code that passes raw pointers to managed objects around when they should really be using roots instead (i.e. root.value) so that if the GC relocates the object, they won't be using the old address. This PR migrates a bunch of that code so that it uses root objects. A lot of code remains that does use raw objects, but I read over all of it and the cases appear to be safe because they immediately return it (or return it without calling any other functions). --- src/mono/wasm/runtime/binding_support.js | 465 +++++++++++++---------- 1 file changed, 260 insertions(+), 205 deletions(-) diff --git a/src/mono/wasm/runtime/binding_support.js b/src/mono/wasm/runtime/binding_support.js index 6f0040fe598c..916032d0db0a 100644 --- a/src/mono/wasm/runtime/binding_support.js +++ b/src/mono/wasm/runtime/binding_support.js @@ -362,8 +362,8 @@ var BindingSupportLib = { } }, - _unbox_delegate_rooted: function (mono_obj) { - var obj = this.extract_js_obj (mono_obj); + _unbox_delegate_root: function (root) { + var obj = this.extract_js_obj_root (root); obj.__mono_delegate_alive__ = true; // FIXME: Should we root the object as long as this function has not been GCd? return function () { @@ -372,11 +372,11 @@ var BindingSupportLib = { }; }, - _unbox_task_rooted: function (mono_obj) { + _unbox_task_root: function (root) { if (!this._are_promises_supported) throw new Error ("Promises are not supported thus 'System.Threading.Tasks.Task' can not work in this context."); - var obj = this.extract_js_obj (mono_obj); + var obj = this.extract_js_obj_root (root); var cont_obj = null; var promise = new Promise (function (resolve, reject) { cont_obj = { @@ -385,15 +385,16 @@ var BindingSupportLib = { }; }); - this.call_method (this.setup_js_cont, null, "mo", [ mono_obj, cont_obj ]); + // FIXME: Lifetime management/pinning? + this.call_method (this.setup_js_cont, null, "mo", [ root.value, cont_obj ]); obj.__mono_js_cont__ = cont_obj.__mono_gchandle__; cont_obj.__mono_js_task__ = obj.__mono_gchandle__; return promise; }, - _unbox_safehandle_rooted: function (mono_obj) { + _unbox_safehandle_root: function (root) { var addRef = true; - var js_handle = this.call_method(this.safehandle_get_handle, null, "mi", [ mono_obj, addRef ]); + var js_handle = this.call_method(this.safehandle_get_handle, null, "mi", [ root.value, addRef ]); var requiredObject = BINDING.mono_wasm_require_handle (js_handle); if (addRef) { @@ -405,7 +406,10 @@ var BindingSupportLib = { return requiredObject; }, - _unbox_mono_obj_rooted_with_known_nonprimitive_type: function (mono_obj, type) { + _unbox_mono_obj_root_with_known_nonprimitive_type: function (root, type) { + if (root.value === undefined) + throw new Error(`Expected a root but got ${root}`); + //See MARSHAL_TYPE_ defines in driver.c switch (type) { case 26: // int64 @@ -414,15 +418,15 @@ var BindingSupportLib = { throw new Error ("int64 not available"); case 3: // string case 29: // interned string - return this.conv_string (mono_obj); + return this.conv_string (root.value); case 4: //vts throw new Error ("no idea on how to unbox value types"); case 5: // delegate - return this._unbox_delegate_rooted (mono_obj); + return this._unbox_delegate_root (root); case 6: // Task - return this._unbox_task_rooted (mono_obj); + return this._unbox_task_root (root); case 7: // ref type - return this.extract_js_obj (mono_obj); + return this.extract_js_obj_root (root); case 10: // arrays case 11: case 12: @@ -434,29 +438,28 @@ var BindingSupportLib = { case 18: throw new Error ("Marshalling of primitive arrays are not supported. Use the corresponding TypedArray instead."); case 20: // clr .NET DateTime - var dateValue = this.call_method(this.get_date_value, null, "m", [ mono_obj ]); + var dateValue = this.call_method(this.get_date_value, null, "m", [ root.value ]); return new Date(dateValue); case 21: // clr .NET DateTimeOffset - var dateoffsetValue = this._object_to_string (mono_obj); + var dateoffsetValue = this._object_to_string (root.value); return dateoffsetValue; case 22: // clr .NET Uri - var uriValue = this._object_to_string (mono_obj); + var uriValue = this._object_to_string (root.value); return uriValue; case 23: // clr .NET SafeHandle - return this._unbox_safehandle_rooted (mono_obj); + return this._unbox_safehandle_root (root); case 30: return undefined; default: - throw new Error ("no idea on how to unbox object kind " + type + " at offset " + mono_obj); + throw new Error (`no idea on how to unbox object kind ${type} at offset ${root.value} (root address is ${root.get_address()})`); } }, _unbox_mono_obj_root: function (root) { - var mono_obj = root.value; - if (mono_obj === 0) + if (root.value === 0) return undefined; - var type = this.mono_wasm_try_unbox_primitive_and_get_type (mono_obj, this._unbox_buffer); + var type = this.mono_wasm_try_unbox_primitive_and_get_type (root.value, this._unbox_buffer); switch (type) { case 1: // int return Module.HEAP32[this._unbox_buffer / 4]; @@ -471,7 +474,7 @@ var BindingSupportLib = { case 28: // char return String.fromCharCode(Module.HEAP32[this._unbox_buffer / 4]); default: - return this._unbox_mono_obj_rooted_with_known_nonprimitive_type (mono_obj, type); + return this._unbox_mono_obj_root_with_known_nonprimitive_type (root, type); } }, @@ -788,9 +791,14 @@ var BindingSupportLib = { }, mono_method_get_call_signature: function(method, mono_obj) { - this.bindings_lazy_init (); + let instanceRoot = MONO.mono_wasm_new_root (mono_obj); + try { + this.bindings_lazy_init (); - return this.call_method (this.get_call_sig, null, "im", [ method, mono_obj ]); + return this.call_method (this.get_call_sig, null, "im", [ method, instanceRoot.value ]); + } finally { + instanceRoot.release(); + } }, get_task_and_bind: function (tcs, js_obj) { @@ -842,16 +850,27 @@ var BindingSupportLib = { }, extract_js_obj: function (mono_obj) { - if (mono_obj == 0) + if (mono_obj === 0) + return null; + var root = MONO.mono_wasm_new_root (mono_obj); + try { + return this.extract_js_obj_root (root); + } finally { + root.release(); + } + }, + + extract_js_obj_root: function (root) { + if (root.value === 0) return null; - var js_id = this.wasm_get_js_id (mono_obj); + var js_id = this.wasm_get_js_id (root.value); if (js_id > 0) return this.mono_wasm_require_handle(js_id); var gcHandle = this.mono_wasm_free_list.length ? this.mono_wasm_free_list.pop() : this.mono_wasm_ref_counter++; var js_obj = { - __mono_gchandle__: this.wasm_bind_existing(mono_obj, gcHandle + 1), + __mono_gchandle__: this.wasm_bind_existing(root.value, gcHandle + 1), is_mono_bridged_obj: true }; @@ -1396,7 +1415,7 @@ var BindingSupportLib = { } // We inline a bunch of the invoke and marshaling logic here in order to eliminate the GC pressure normally - // created by the unboxing part of the call process. Because unbox_mono_obj(_rooted) can return non-numeric + // created by the unboxing part of the call process. Because unbox_mono_obj(_root) can return non-numeric // types, v8 and spidermonkey allocate and store its result on the heap (in the nursery, to be fair). // For a bound method however, we know the result will always be the same type because C# methods have known // return types. Inlining the invoke and marshaling logic means that even though the bound method has logic @@ -1409,15 +1428,15 @@ var BindingSupportLib = { "resultRoot.value = binding_support.invoke_method (method, this_arg, buffer, exceptionRoot.get_address ());", `binding_support._handle_exception_for_call (${converterKey}, buffer, resultRoot, exceptionRoot, argsRootBuffer);`, "", - "var resultPtr = resultRoot.value, result = undefined;", + "var result = undefined;", "if (!is_result_marshaled) ", - " result = resultPtr;", - "else if (resultPtr !== 0) {", + " result = resultRoot.value;", + "else if (resultRoot.value !== 0) {", // For the common scenario where the return type is a primitive, we want to try and unbox it directly // into our existing heap allocation and then read it out of the heap. Doing this all in one operation // means that we only need to enter a gc safe region twice (instead of 3+ times with the normal, // slower check-type-and-then-unbox flow which has extra checks since unbox verifies the type). - " var resultType = binding_support.mono_wasm_try_unbox_primitive_and_get_type (resultPtr, buffer);", + " var resultType = binding_support.mono_wasm_try_unbox_primitive_and_get_type (resultRoot.value, buffer);", " switch (resultType) {", " case 1:", // int " result = Module.HEAP32[buffer / 4]; break;", @@ -1432,7 +1451,7 @@ var BindingSupportLib = { " case 28:", // char " result = String.fromCharCode(Module.HEAP32[buffer / 4]); break;", " default:", - " result = binding_support._unbox_mono_obj_rooted_with_known_nonprimitive_type (resultPtr, resultType); break;", + " result = binding_support._unbox_mono_obj_root_with_known_nonprimitive_type (resultRoot, resultType); break;", " }", "}", "", @@ -1460,11 +1479,17 @@ var BindingSupportLib = { // Check to make sure the delegate is still alive on the CLR side of things. if (typeof delegate_obj.__mono_delegate_alive__ !== "undefined") { - if (!delegate_obj.__mono_delegate_alive__) - throw new Error("The delegate target that is being invoked is no longer available. Please check if it has been prematurely GC'd."); + if (!delegate_obj.__mono_delegate_alive__) { + // HACK: It is possible (though unlikely) for a delegate to be invoked after it's been collected + // if it's being used as a JavaScript event handler and the host environment decides to fire events + // at a point where we've already disposed of the object the event handler is attached to. + // As such, we log here instead of throwing an error. We may want to not log at all... + console.log("The delegate target that is being invoked is no longer available. Please check if it has been prematurely GC'd."); + return; + } } - var [delegateRoot] = MONO.mono_wasm_new_roots ([this.extract_mono_obj (delegate_obj)]); + var delegateRoot = MONO.mono_wasm_new_root (this.extract_mono_obj (delegate_obj)); try { if (typeof delegate_obj.__mono_delegate_invoke__ === "undefined") delegate_obj.__mono_delegate_invoke__ = this.mono_wasm_get_delegate_invoke(delegateRoot.value); @@ -1476,7 +1501,7 @@ var BindingSupportLib = { return this.call_method (delegate_obj.__mono_delegate_invoke__, delegateRoot.value, delegate_obj.__mono_delegate_invoke_sig__, js_args); } finally { - MONO.mono_wasm_release_roots (delegateRoot); + delegateRoot.release(); } }, @@ -1572,22 +1597,22 @@ var BindingSupportLib = { }, // Object wrapping helper functions to handle reference handles that will // be used in managed code. - mono_wasm_register_obj: function(obj) { + mono_wasm_register_obj: function(js_obj) { var gc_handle = undefined; - if (obj !== null && typeof obj !== "undefined") + if (js_obj !== null && typeof js_obj !== "undefined") { - gc_handle = obj.__mono_gchandle__; + gc_handle = js_obj.__mono_gchandle__; if (typeof gc_handle === "undefined") { var handle = this.mono_wasm_free_list.length ? this.mono_wasm_free_list.pop() : this.mono_wasm_ref_counter++; - obj.__mono_jshandle__ = handle; + js_obj.__mono_jshandle__ = handle; // Obtain the JS -> C# type mapping. - var wasm_type = obj[Symbol.for("wasm type")]; - obj.__owns_handle__ = true; - gc_handle = obj.__mono_gchandle__ = this.wasm_binding_obj_new(handle + 1, obj.__owns_handle__, typeof wasm_type === "undefined" ? -1 : wasm_type); - this.mono_wasm_object_registry[handle] = obj; + var wasm_type = js_obj[Symbol.for("wasm type")]; + js_obj.__owns_handle__ = true; + gc_handle = js_obj.__mono_gchandle__ = this.wasm_binding_obj_new(handle + 1, js_obj.__owns_handle__, typeof wasm_type === "undefined" ? -1 : wasm_type); + this.mono_wasm_object_registry[handle] = js_obj; } } @@ -1613,8 +1638,8 @@ var BindingSupportLib = { obj.__mono_jshandle__ = undefined; // If we are unregistering a delegate then mark it as not being alive - // this will be checked in the delegate invoke and throw an appropriate - // error. + // so that attempts will not be made to invoke it even if a JS-side + // reference to it remains (registered as an event handler, etc) if (typeof obj.__mono_delegate_alive__ !== "undefined") obj.__mono_delegate_alive__ = false; @@ -1647,8 +1672,8 @@ var BindingSupportLib = { } return obj; }, - mono_wasm_parse_args : function (args) { - var js_args = this.mono_array_to_js_array(args); + mono_wasm_parse_args_root : function (argsRoot) { + var js_args = this._mono_array_root_to_js_array(argsRoot); this.mono_wasm_save_LMF(); return js_args; }, @@ -1662,7 +1687,7 @@ var BindingSupportLib = { // Release all managed objects that are loaded into the LMF if (typeof __owned_objects__ !== "undefined") { - // Look into passing the array of owned object handles in one pass. + // TODO: Look into passing the array of owned object handles in one pass. var refidx; for (refidx = 0; refidx < __owned_objects__.length; refidx++) { @@ -1680,115 +1705,130 @@ var BindingSupportLib = { }, mono_wasm_invoke_js_with_args: function(js_handle, method_name, args, is_exception) { - BINDING.bindings_lazy_init (); + let argsRoot = MONO.mono_wasm_new_root (args), nameRoot = MONO.mono_wasm_new_root (method_name); + try { + BINDING.bindings_lazy_init (); - var obj = BINDING.get_js_obj (js_handle); - if (!obj) { - setValue (is_exception, 1, "i32"); - return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); - } + var obj = BINDING.get_js_obj (js_handle); + if (!obj) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } - var js_name = BINDING.unbox_mono_obj (method_name); - if (!js_name || (typeof(js_name) !== "string")) { - setValue (is_exception, 1, "i32"); - return BINDING.js_string_to_mono_string ("Invalid method name object '" + method_name + "'"); - } + var js_name = BINDING.conv_string (nameRoot.value); + if (!js_name || (typeof(js_name) !== "string")) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid method name object '" + nameRoot.value + "'"); + } - var js_args = BINDING.mono_wasm_parse_args(args); + var js_args = BINDING.mono_wasm_parse_args_root(argsRoot); - var res; - try { - var m = obj [js_name]; - if (typeof m === "undefined") - throw new Error("Method: '" + js_name + "' not found for: '" + Object.prototype.toString.call(obj) + "'"); - var res = m.apply (obj, js_args); - return BINDING.mono_wasm_convert_return_value(res); - } catch (e) { - // make sure we release object reference counts on errors. - BINDING.mono_wasm_unwind_LMF(); - var res = e.toString (); - setValue (is_exception, 1, "i32"); - if (res === null || res === undefined) - res = "unknown exception"; - return BINDING.js_string_to_mono_string (res); + var res; + try { + var m = obj [js_name]; + if (typeof m === "undefined") + throw new Error("Method: '" + js_name + "' not found for: '" + Object.prototype.toString.call(obj) + "'"); + var res = m.apply (obj, js_args); + return BINDING.mono_wasm_convert_return_value(res); + } catch (e) { + // make sure we release object reference counts on errors. + BINDING.mono_wasm_unwind_LMF(); + var res = e.toString (); + setValue (is_exception, 1, "i32"); + if (res === null || res === undefined) + res = "unknown exception"; + return BINDING.js_string_to_mono_string (res); + } + } finally { + argsRoot.release(); + nameRoot.release(); } }, mono_wasm_get_object_property: function(js_handle, property_name, is_exception) { BINDING.bindings_lazy_init (); - var obj = BINDING.mono_wasm_require_handle (js_handle); - if (!obj) { - setValue (is_exception, 1, "i32"); - return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); - } - - var js_name = BINDING.conv_string (property_name); - if (!js_name) { - setValue (is_exception, 1, "i32"); - return BINDING.js_string_to_mono_string ("Invalid property name object '" + js_name + "'"); - } - - var res; + var nameRoot = MONO.mono_wasm_new_root (property_name); try { - var m = obj [js_name]; - if (m === Object(m) && obj.__is_mono_proxied__) - m.__is_mono_proxied__ = true; + var obj = BINDING.mono_wasm_require_handle (js_handle); + if (!obj) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } - return BINDING.js_to_mono_obj (m); - } catch (e) { - var res = e.toString (); - setValue (is_exception, 1, "i32"); - if (res === null || typeof res === "undefined") - res = "unknown exception"; - return BINDING.js_string_to_mono_string (res); + var js_name = BINDING.conv_string (nameRoot.value); + if (!js_name) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid property name object '" + nameRoot.value + "'"); + } + + var res; + try { + var m = obj [js_name]; + if (m === Object(m) && obj.__is_mono_proxied__) + m.__is_mono_proxied__ = true; + + return BINDING.js_to_mono_obj (m); + } catch (e) { + var res = e.toString (); + setValue (is_exception, 1, "i32"); + if (res === null || typeof res === "undefined") + res = "unknown exception"; + return BINDING.js_string_to_mono_string (res); + } + } finally { + nameRoot.release(); } }, mono_wasm_set_object_property: function (js_handle, property_name, value, createIfNotExist, hasOwnProperty, is_exception) { + var valueRoot = MONO.mono_wasm_new_root (value), nameRoot = MONO.mono_wasm_new_root (property_name); + try { + BINDING.bindings_lazy_init (); + var requireObject = BINDING.mono_wasm_require_handle (js_handle); + if (!requireObject) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } - BINDING.bindings_lazy_init (); - - var requireObject = BINDING.mono_wasm_require_handle (js_handle); - if (!requireObject) { - setValue (is_exception, 1, "i32"); - return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); - } - - var property = BINDING.conv_string (property_name); - if (!property) { - setValue (is_exception, 1, "i32"); - return BINDING.js_string_to_mono_string ("Invalid property name object '" + property_name + "'"); - } + var property = BINDING.conv_string (nameRoot.value); + if (!property) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid property name object '" + property_name + "'"); + } - var result = false; + var result = false; - var js_value = BINDING.unbox_mono_obj(value); - BINDING.mono_wasm_save_LMF(); + var js_value = BINDING._unbox_mono_obj_root(valueRoot); + BINDING.mono_wasm_save_LMF(); - if (createIfNotExist) { - requireObject[property] = js_value; - result = true; - } - else { - result = false; - if (!createIfNotExist) - { - if (!requireObject.hasOwnProperty(property)) - return false; - } - if (hasOwnProperty === true) { - if (requireObject.hasOwnProperty(property)) { - requireObject[property] = js_value; - result = true; - } - } - else { - requireObject[property] = js_value; - result = true; - } + if (createIfNotExist) { + requireObject[property] = js_value; + result = true; + } + else { + result = false; + if (!createIfNotExist) + { + if (!requireObject.hasOwnProperty(property)) + return false; + } + if (hasOwnProperty === true) { + if (requireObject.hasOwnProperty(property)) { + requireObject[property] = js_value; + result = true; + } + } + else { + requireObject[property] = js_value; + result = true; + } + } + BINDING.mono_wasm_unwind_LMF(); + return BINDING._box_js_bool (result); + } finally { + nameRoot.release(); + valueRoot.release(); } - BINDING.mono_wasm_unwind_LMF(); - return BINDING._box_js_bool (result); }, mono_wasm_get_by_index: function(js_handle, property_index, is_exception) { BINDING.bindings_lazy_init (); @@ -1811,49 +1851,59 @@ var BindingSupportLib = { } }, mono_wasm_set_by_index: function(js_handle, property_index, value, is_exception) { - BINDING.bindings_lazy_init (); + var valueRoot = MONO.mono_wasm_new_root (value); + try { + BINDING.bindings_lazy_init (); - var obj = BINDING.mono_wasm_require_handle (js_handle); - if (!obj) { - setValue (is_exception, 1, "i32"); - return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); - } + var obj = BINDING.mono_wasm_require_handle (js_handle); + if (!obj) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } - var js_value = BINDING.unbox_mono_obj(value); - BINDING.mono_wasm_save_LMF(); + var js_value = BINDING._unbox_mono_obj_root(valueRoot); + BINDING.mono_wasm_save_LMF(); - try { - obj [property_index] = js_value; - BINDING.mono_wasm_unwind_LMF(); - return true; - } catch (e) { - var res = e.toString (); - setValue (is_exception, 1, "i32"); - if (res === null || typeof res === "undefined") - res = "unknown exception"; - return BINDING.js_string_to_mono_string (res); + try { + obj [property_index] = js_value; + BINDING.mono_wasm_unwind_LMF(); + return true; + } catch (e) { + var res = e.toString (); + setValue (is_exception, 1, "i32"); + if (res === null || typeof res === "undefined") + res = "unknown exception"; + return BINDING.js_string_to_mono_string (res); + } + } finally { + valueRoot.release(); } }, mono_wasm_get_global_object: function(global_name, is_exception) { - BINDING.bindings_lazy_init (); + var nameRoot = MONO.mono_wasm_new_root (global_name); + try { + BINDING.bindings_lazy_init (); - var js_name = BINDING.conv_string (global_name); + var js_name = BINDING.conv_string (nameRoot.value); - var globalObj; + var globalObj; - if (!js_name) { - globalObj = globalThis; - } - else { - globalObj = globalThis[js_name]; - } + if (!js_name) { + globalObj = globalThis; + } + else { + globalObj = globalThis[js_name]; + } - if (globalObj === null || typeof globalObj === undefined) { - setValue (is_exception, 1, "i32"); - return BINDING.js_string_to_mono_string ("Global object '" + js_name + "' not found."); - } + if (globalObj === null || typeof globalObj === undefined) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Global object '" + js_name + "' not found."); + } - return BINDING.js_to_mono_obj (globalObj); + return BINDING.js_to_mono_obj (globalObj); + } finally { + nameRoot.release(); + } }, mono_wasm_release_handle: function(js_handle, is_exception) { BINDING.bindings_lazy_init (); @@ -1893,50 +1943,55 @@ var BindingSupportLib = { return gc_handle; }, mono_wasm_new: function (core_name, args, is_exception) { - BINDING.bindings_lazy_init (); + var argsRoot = MONO.mono_wasm_new_root (args), nameRoot = MONO.mono_wasm_new_root (core_name); + try { + BINDING.bindings_lazy_init (); - var js_name = BINDING.conv_string (core_name); + var js_name = BINDING.conv_string (nameRoot.value); - if (!js_name) { - setValue (is_exception, 1, "i32"); - return BINDING.js_string_to_mono_string ("Core object '" + js_name + "' not found."); - } + if (!js_name) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid name @" + nameRoot.value); + } - var coreObj = globalThis[js_name]; + var coreObj = globalThis[js_name]; - if (coreObj === null || typeof coreObj === "undefined") { - setValue (is_exception, 1, "i32"); - return BINDING.js_string_to_mono_string ("JavaScript host object '" + js_name + "' not found."); - } + if (coreObj === null || typeof coreObj === "undefined") { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("JavaScript host object '" + js_name + "' not found."); + } - var js_args = BINDING.mono_wasm_parse_args(args); + var js_args = BINDING.mono_wasm_parse_args_root(argsRoot); - try { + try { - // This is all experimental !!!!!! - var allocator = function(constructor, js_args) { - // Not sure if we should be checking for anything here - var argsList = new Array(); - argsList[0] = constructor; - if (js_args) - argsList = argsList.concat (js_args); - var tempCtor = constructor.bind.apply (constructor, argsList); - var obj = new tempCtor (); - return obj; - }; + // This is all experimental !!!!!! + var allocator = function(constructor, js_args) { + // Not sure if we should be checking for anything here + var argsList = new Array(); + argsList[0] = constructor; + if (js_args) + argsList = argsList.concat (js_args); + var tempCtor = constructor.bind.apply (constructor, argsList); + var obj = new tempCtor (); + return obj; + }; - var res = allocator(coreObj, js_args); - var gc_handle = BINDING.mono_wasm_free_list.length ? BINDING.mono_wasm_free_list.pop() : BINDING.mono_wasm_ref_counter++; - BINDING.mono_wasm_object_registry[gc_handle] = res; - return BINDING.mono_wasm_convert_return_value(gc_handle + 1); - } catch (e) { - var res = e.toString (); - setValue (is_exception, 1, "i32"); - if (res === null || res === undefined) - res = "Error allocating object."; - return BINDING.js_string_to_mono_string (res); + var res = allocator(coreObj, js_args); + var gc_handle = BINDING.mono_wasm_free_list.length ? BINDING.mono_wasm_free_list.pop() : BINDING.mono_wasm_ref_counter++; + BINDING.mono_wasm_object_registry[gc_handle] = res; + return BINDING.mono_wasm_convert_return_value(gc_handle + 1); + } catch (e) { + var res = e.toString (); + setValue (is_exception, 1, "i32"); + if (res === null || res === undefined) + res = "Error allocating object."; + return BINDING.js_string_to_mono_string (res); + } + } finally { + argsRoot.release(); + nameRoot.release(); } - }, mono_wasm_typed_array_to_array: function(js_handle, is_exception) { From 654dbc453e9bb59e1152662f598b731f0fb6e68b Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 14 Jul 2021 17:10:20 +0200 Subject: [PATCH 130/133] enable working websocket subprotocol test --- src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs index 296bffbc620a..43b119eaedc7 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs @@ -170,7 +170,6 @@ public async Task ConnectAsync_CookieHeaders_Success(Uri server) [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/45583", TestPlatforms.Browser)] public async Task ConnectAsync_PassNoSubProtocol_ServerRequires_ThrowsWebSocketException(Uri server) { const string AcceptedProtocol = "CustomProtocol"; From fa779e8cb2b5868a0ac2fd4215f39ffb91f0dab0 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 14 Jul 2021 11:33:50 -0400 Subject: [PATCH 131/133] Raise the max size poolable by ArrayPool (#55621) We previously set an arbitrary cut-off of 2MB max array size. That was before we would trim arrays in response to memory pressure. This now raises the limit as high as we can while maintaining the current power-of-two-based scheme. --- .../tests/ArrayPool/UnitTests.cs | 47 +++++++++++++++---- .../Compression/WebSocketDeflater.cs | 6 +-- .../Compression/WebSocketInflater.cs | 5 +- .../TlsOverPerCoreLockedStacksArrayPool.cs | 3 +- .../System/Globalization/CompareInfo.Icu.cs | 4 +- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/libraries/System.Buffers/tests/ArrayPool/UnitTests.cs b/src/libraries/System.Buffers/tests/ArrayPool/UnitTests.cs index 7456e1b46fe6..5673d22d3639 100644 --- a/src/libraries/System.Buffers/tests/ArrayPool/UnitTests.cs +++ b/src/libraries/System.Buffers/tests/ArrayPool/UnitTests.cs @@ -8,6 +8,7 @@ using System.Numerics; using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; +using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace System.Buffers.ArrayPool.Tests @@ -240,15 +241,11 @@ public static void NewDefaultArrayPoolWithSmallBufferSizeRoundsToOurSmallestSupp } [Fact] - public static void ReturningABufferGreaterThanMaxSizeDoesNotThrow() + public static void ReturningToCreatePoolABufferGreaterThanMaxSizeDoesNotThrow() { ArrayPool pool = ArrayPool.Create(maxArrayLength: 16, maxArraysPerBucket: 1); byte[] rented = pool.Rent(32); pool.Return(rented); - - ArrayPool.Shared.Return(new byte[3 * 1024 * 1024]); - ArrayPool.Shared.Return(new char[3 * 1024 * 1024]); - ArrayPool.Shared.Return(new string[3 * 1024 * 1024]); } [Fact] @@ -292,11 +289,11 @@ public static void CanRentManySizedBuffers(ArrayPool pool) [InlineData(1024, 1024)] [InlineData(4096, 4096)] [InlineData(1024 * 1024, 1024 * 1024)] - [InlineData(1024 * 1024 + 1, 1024 * 1024 + 1)] + [InlineData(1024 * 1024 + 1, 1024 * 1024 * 2)] [InlineData(1024 * 1024 * 2, 1024 * 1024 * 2)] public static void RentingSpecificLengthsYieldsExpectedLengths(int requestedMinimum, int expectedLength) { - foreach (ArrayPool pool in new[] { ArrayPool.Create(), ArrayPool.Shared }) + foreach (ArrayPool pool in new[] { ArrayPool.Create((int)BitOperations.RoundUpToPowerOf2((uint)requestedMinimum), 1), ArrayPool.Shared }) { byte[] buffer1 = pool.Rent(requestedMinimum); byte[] buffer2 = pool.Rent(requestedMinimum); @@ -313,7 +310,7 @@ public static void RentingSpecificLengthsYieldsExpectedLengths(int requestedMini pool.Return(buffer1); } - foreach (ArrayPool pool in new[] { ArrayPool.Create(), ArrayPool.Shared }) + foreach (ArrayPool pool in new[] { ArrayPool.Create((int)BitOperations.RoundUpToPowerOf2((uint)requestedMinimum), 1), ArrayPool.Shared }) { char[] buffer1 = pool.Rent(requestedMinimum); char[] buffer2 = pool.Rent(requestedMinimum); @@ -330,7 +327,7 @@ public static void RentingSpecificLengthsYieldsExpectedLengths(int requestedMini pool.Return(buffer1); } - foreach (ArrayPool pool in new[] { ArrayPool.Create(), ArrayPool.Shared }) + foreach (ArrayPool pool in new[] { ArrayPool.Create((int)BitOperations.RoundUpToPowerOf2((uint)requestedMinimum), 1), ArrayPool.Shared }) { string[] buffer1 = pool.Rent(requestedMinimum); string[] buffer2 = pool.Rent(requestedMinimum); @@ -348,6 +345,38 @@ public static void RentingSpecificLengthsYieldsExpectedLengths(int requestedMini } } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.Is64BitProcess))] + [InlineData(1024 * 1024 * 1024 - 1, true)] + [InlineData(1024 * 1024 * 1024, true)] + [InlineData(1024 * 1024 * 1024 + 1, false)] + [InlineData(0X7FFFFFC7 /* Array.MaxLength */, false)] + [OuterLoop] + public static void RentingGiganticArraySucceeds(int length, bool expectPooled) + { + var options = new RemoteInvokeOptions(); + options.StartInfo.UseShellExecute = false; + options.StartInfo.EnvironmentVariables.Add(TrimSwitchName, "false"); + + RemoteExecutor.Invoke((lengthStr, expectPooledStr) => + { + int length = int.Parse(lengthStr); + byte[] array; + try + { + array = ArrayPool.Shared.Rent(length); + } + catch (OutOfMemoryException) + { + return; + } + + Assert.InRange(array.Length, length, int.MaxValue); + ArrayPool.Shared.Return(array); + + Assert.Equal(bool.Parse(expectPooledStr), ReferenceEquals(array, ArrayPool.Shared.Rent(length))); + }, length.ToString(), expectPooled.ToString(), options).Dispose(); + } + [Fact] public static void RentingAfterPoolExhaustionReturnsSizeForCorrespondingBucket_SmallerThanLimit() { diff --git a/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/Compression/WebSocketDeflater.cs b/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/Compression/WebSocketDeflater.cs index e7f180728424..f3ecf278886d 100644 --- a/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/Compression/WebSocketDeflater.cs +++ b/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/Compression/WebSocketDeflater.cs @@ -46,15 +46,11 @@ public ReadOnlySpan Deflate(ReadOnlySpan payload, bool endOfMessage) { Debug.Assert(_buffer is null, "Invalid state, ReleaseBuffer not called."); - // Do not try to rent more than 1MB initially, because it will actually allocate - // instead of renting. Be optimistic that what we're sending is actually going to fit. - const int MaxInitialBufferLength = 1024 * 1024; - // For small payloads there might actually be overhead in the compression and the resulting // output might be larger than the payload. This is why we rent at least 4KB initially. const int MinInitialBufferLength = 4 * 1024; - _buffer = ArrayPool.Shared.Rent(Math.Clamp(payload.Length, MinInitialBufferLength, MaxInitialBufferLength)); + _buffer = ArrayPool.Shared.Rent(Math.Max(payload.Length, MinInitialBufferLength)); int position = 0; while (true) diff --git a/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/Compression/WebSocketInflater.cs b/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/Compression/WebSocketInflater.cs index 6ade12d539a4..d47e646fa60e 100644 --- a/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/Compression/WebSocketInflater.cs +++ b/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/Compression/WebSocketInflater.cs @@ -82,10 +82,9 @@ public void Prepare(long payloadLength, int userBufferLength) } else { - // Rent a buffer as close to the size of the user buffer as possible, - // but not try to rent anything above 1MB because the array pool will allocate. + // Rent a buffer as close to the size of the user buffer as possible. // If the payload is smaller than the user buffer, rent only as much as we need. - _buffer = ArrayPool.Shared.Rent(Math.Min(userBufferLength, (int)Math.Min(payloadLength, 1024 * 1024))); + _buffer = ArrayPool.Shared.Rent((int)Math.Min(userBufferLength, payloadLength)); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs index 59cb6556ad7a..438ca8e9cb20 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs @@ -24,12 +24,11 @@ internal sealed partial class TlsOverPerCoreLockedStacksArrayPool : ArrayPool // TODO https://github.com/dotnet/coreclr/pull/7747: "Investigate optimizing ArrayPool heuristics" // - Explore caching in TLS more than one array per size per thread, and moving stale buffers to the global queue. // - Explore changing the size of each per-core bucket, potentially dynamically or based on other factors like array size. - // - Explore changing number of buckets and what sizes of arrays are cached. // - Investigate whether false sharing is causing any issues, in particular on LockedStack's count and the contents of its array. // ... /// The number of buckets (array sizes) in the pool, one for each array length, starting from length 16. - private const int NumBuckets = 17; // Utilities.SelectBucketIndex(2*1024*1024) + private const int NumBuckets = 27; // Utilities.SelectBucketIndex(1024 * 1024 * 1024 + 1) /// Maximum number of per-core stacks to use per array size. private const int MaxPerCorePerArraySizeStacks = 64; // selected to avoid needing to worry about processor groups /// The maximum number of buffers to store in a bucket's global queue. diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs index 4d27d9e881e4..a9b9274952ae 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs @@ -722,7 +722,9 @@ private unsafe int IcuGetHashCodeOfString(ReadOnlySpan source, CompareOpti // according to ICU User Guide the performance of ucol_getSortKey is worse when it is called with null output buffer // the solution is to try to fill the sort key in a temporary buffer of size equal 4 x string length - // 1MB is the biggest array that can be rented from ArrayPool.Shared without memory allocation + // (The ArrayPool used to have a limit on the length of buffers it would cache; this code was avoiding + // exceeding that limit to avoid a per-operation allocation, and the performance implications here + // were not re-evaluated when the limit was lifted.) int sortKeyLength = (source.Length > 1024 * 1024 / 4) ? 0 : 4 * source.Length; byte[]? borrowedArray = null; From cd0f72d67081f5de5b6abe8a702ccfafc531986d Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Wed, 14 Jul 2021 10:25:30 -0700 Subject: [PATCH 132/133] Fix build breaks --- .../System.Private.CoreLib/src/Resources/Strings.resx | 6 ++++++ .../src/System.Private.CoreLib.Shared.projitems | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Resources/Strings.resx b/src/coreclr/nativeaot/System.Private.CoreLib/src/Resources/Strings.resx index eda296f916ff..502bd0ca33e1 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Resources/Strings.resx @@ -3202,6 +3202,12 @@ The specified directory '{0}' cannot be created. + + The source '{0}' and destination '{1}' are the same file. + + + The specified path '{0}' is not a file. + Source and destination path must be different. diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 2955343aced2..59a6bc137abf 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1245,6 +1245,8 @@ + + From be7debac130da1814818d20ffa0784470e057109 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Wed, 14 Jul 2021 12:41:06 -0700 Subject: [PATCH 133/133] Disable tests against https://github.com/dotnet/runtimelab/issues/1323 --- .../tests/System/Reflection/NullabilityInfoContextTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs index 07f244e9172f..7a57039efc84 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs @@ -10,6 +10,7 @@ namespace System.Reflection.Tests { + [ActiveIssue("https://github.com/dotnet/runtimelab/issues/1323" /* NativeAot */)] public class NullabilityInfoContextTests { private static readonly NullabilityInfoContext nullabilityContext = new NullabilityInfoContext();