diff --git a/docs/area-owners.md b/docs/area-owners.md index 4188ea7d982a6..740404f771aa2 100644 --- a/docs/area-owners.md +++ b/docs/area-owners.md @@ -79,6 +79,7 @@ Note: Editing this file doesn't update the mapping used by the `@msftbot` issue | area-System.Diagnostics-mono | @lewing | @thaystg @radical | | | area-System.Diagnostics.Activity | @tommcdon | @tarekgh | | | area-System.Diagnostics.EventLog | @ericstj | @Anipik @ViktorHofer | | +| area-System.Diagnostics.Metric | @tommcdon | @noahfalk | | | area-System.Diagnostics.PerformanceCounter | @ericstj | @Anipik @ViktorHofer | | | area-System.Diagnostics.Process | @jeffhandley | @adamsitnik @carlossanlop @jozkee | | | area-System.Diagnostics.Tracing | @tommcdon | @noahfalk @tommcdon @Anipik @ViktorHofer @tarekgh | Included: | diff --git a/docs/design/mono/wasm-aot.md b/docs/design/mono/wasm-aot.md new file mode 100644 index 0000000000000..667d124ed9c6e --- /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. diff --git a/docs/project/list-of-diagnostics.md b/docs/project/list-of-diagnostics.md index 2d16853231758..cfb593d70b54d 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 ` + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 2389066e4b33e..58a2c18606c85 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,190 +1,190 @@ - + https://github.com/dotnet/icu - e7626ad8c04b150de635f920b5e8dede0aafaf73 + 1782cd21854f8cb2b60355f4773714a8e0130696 https://github.com/dotnet/msquic d7db669b70f4dd67ec001c192f9809c218cab88b - + https://github.com/dotnet/emsdk - 5c9145289bd4d4e14b18a544dda60a185f66f688 + 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 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 - 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 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 + 664e78edc72dd0a48e6f55e352051b6ba61bba9a https://github.com/dotnet/xharness @@ -194,33 +194,33 @@ https://github.com/dotnet/xharness c6d444eaf7e95339589ceef371cbef0a90a4add5 - + https://github.com/dotnet/arcade - 286d98094b830b8dad769542b2669cb1b75f7097 + 55262f114b0c1b82f0b081bca0d919b657ba24c5 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - a89f052e97fec59a2d0148c08d3b4801567ec200 + a83e4392b6d3f377239726eedc19b6dc85b85496 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - a89f052e97fec59a2d0148c08d3b4801567ec200 + a83e4392b6d3f377239726eedc19b6dc85b85496 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - a89f052e97fec59a2d0148c08d3b4801567ec200 + a83e4392b6d3f377239726eedc19b6dc85b85496 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - a89f052e97fec59a2d0148c08d3b4801567ec200 + a83e4392b6d3f377239726eedc19b6dc85b85496 - + https://github.com/dotnet/hotreload-utils - 3960ef9a8980181e840b5c1d64ed0b234711e850 + 33219d2b3fbc957e05f8e52a33363cf9b858bb08 - + 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 7be0ee173f2f3..1e7be3b4cb980 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 @@ -52,28 +52,28 @@ 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 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.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.21355.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.21357.1 + 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 4b25520324931..2df0909937d10 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 05ca99c6b2813..828119be411b3 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/eng/pipelines/libraries/helix-queues-setup.yml b/eng/pipelines/libraries/helix-queues-setup.yml index 7d33d26edb012..75d6cbd212217 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 diff --git a/eng/targetingpacks.targets b/eng/targetingpacks.targets index f80b26fb98445..0f44b205eb6a0 100644 --- a/eng/targetingpacks.targets +++ b/eng/targetingpacks.targets @@ -11,6 +11,7 @@ $(MicrosoftNetCoreAppFrameworkName) + true diff --git a/global.json b/global.json index 99807111962c2..e25c47c09f713 100644 --- a/global.json +++ b/global.json @@ -12,13 +12,13 @@ "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.21355.1" + "Microsoft.NET.Sdk.IL": "6.0.0-preview.7.21361.10" } } diff --git a/src/coreclr/CMakeLists.txt b/src/coreclr/CMakeLists.txt index 78aa969473525..b4a4859342702 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/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index c1a968ed9f994..83be00cf5e03f 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/build-runtime.cmd b/src/coreclr/build-runtime.cmd index 731c255468e83..ab805f370aa20 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 0b39613aac369..1d9881f281b18 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="$?" diff --git a/src/coreclr/clr.featuredefines.props b/src/coreclr/clr.featuredefines.props index 08fca8de6dd64..184e885470f63 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/coreclr/clrdefinitions.cmake b/src/coreclr/clrdefinitions.cmake index eeb421cac4c2f..0485ff99a99eb 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/createdump/CMakeLists.txt b/src/coreclr/debug/createdump/CMakeLists.txt index 5d766d53da185..f0093b7cb6660 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 bc444815415cf..807036fd20082 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 f315b98dd2877..199144e17540e 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 4461d2a8c96a5..ad9c247e37dfd 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 2cc1eb6c688fb..2f9605554ad1f 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 9cac899968f88..40d8dbdba767a 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 ef77bfcac5592..e5f0f618d944f 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 95f63f460e4af..f588867c7926f 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 954ff5328b4e7..792947bafe217 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 57249d83f7e6c..afd403212b247 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 ac4dc10fd2f13..6da55da2f1375 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 6be2aa7742b60..704ea08488214 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 f07e50d592f08..2876562fba2dd 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 90db2697b812b..75e20d93120c0 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 99284ed040247..2107c6c1bafb7 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 1a690157908c3..7ce0df5f1ec18 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 3ea9151a6494f..92b9339088fca 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 c1e5ca1154cca..90a4c8ab9ffe5 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 b16f85d8773ef..c3143bf243b71 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/debug/ee/arm64/arm64walker.cpp b/src/coreclr/debug/ee/arm64/arm64walker.cpp index ae6e8c1fc2933..6c4dee9349700 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 b17ae8f115002..f9304d16ab070 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 12b1106f7a4b2..6996439c31fba 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 53ee5555ace43..e4563a31757f4 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 f16f8cd6d9d9d..5503de2459099 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 d8d14b24b5425..9d363938519c7 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 c4e2d28602e56..269281eb006be 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 4f4c3f7bcd8f2..05c03c7b3094f 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 313b42c5a1970..2f228b3a3a9a1 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 fae55ecdc3ea5..9b8e4b649864d 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/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 268fffcd4d7ff..6cf5283476aba 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 09bd97d189188..d3f3526925d9b 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 diff --git a/src/coreclr/inc/CrstTypes.def b/src/coreclr/inc/CrstTypes.def index c48872a0b9424..c7266df7dbb01 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 0d2a1db98e471..e2f1a63a20fec 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/corprof.idl b/src/coreclr/inc/corprof.idl index 8fc965a84f6b5..d1c58f96cf97c 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/crsttypes.h b/src/coreclr/inc/crsttypes.h index a1bab2ecb906c..7be482c48bb55 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 ce0c6c22f890e..101178f9a4ef0 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 fb65ea9fa613c..3c42f0850850b 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/profilepriv.inl b/src/coreclr/inc/profilepriv.inl index 08ba58f5623f1..e99591c5ffd18 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/inc/utilcode.h b/src/coreclr/inc/utilcode.h index a47034ee2e05c..77df9dfa94d2a 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/inc/xclrdata.idl b/src/coreclr/inc/xclrdata.idl index 818915a29ccec..aeddf9529bee4 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/inc/yieldprocessornormalized.h b/src/coreclr/inc/yieldprocessornormalized.h index ba349bb83ad56..121e60b033356 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/jit/block.cpp b/src/coreclr/jit/block.cpp index 128c4e033d9fa..5df8c1e4358c2 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/block.h b/src/coreclr/jit/block.h index 0cbe51281b353..5d42a1626536f 100644 --- a/src/coreclr/jit/block.h +++ b/src/coreclr/jit/block.h @@ -29,7 +29,10 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #include "jithashtable.h" /*****************************************************************************/ -typedef BitVec EXPSET_TP; +typedef BitVec EXPSET_TP; +typedef BitVec_ValArg_T EXPSET_VALARG_TP; +typedef BitVec_ValRet_T EXPSET_VALRET_TP; + #if LARGE_EXPSET #define EXPSET_SZ 64 #else diff --git a/src/coreclr/jit/clrjit.natvis b/src/coreclr/jit/clrjit.natvis index 90a9ff703a471..b3c187474900f 100644 --- a/src/coreclr/jit/clrjit.natvis +++ b/src/coreclr/jit/clrjit.natvis @@ -5,6 +5,13 @@ Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. --> + @@ -183,4 +190,48 @@ The .NET Foundation licenses this file to you under the MIT license. + + size={m_nSize,d} capacity={m_nCapacity,d} + Empty + + + m_nSize + m_pArray + + + + + + size={m_size,d} + Empty + + + m_size + m_members + + + + + + size={m_size,d} used={m_used,d} + Empty + + + m_used + m_members + + + + + + + + + {optType,en} + + (LcJaggedArrayOptInfo*)this,nd + (LcMdArrayOptInfo*)this,nd + + + diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 132a763c06b01..626cb3e5b7bd6 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -216,6 +216,7 @@ class CodeGen final : public CodeGenInterface unsigned genCurDispOffset; static const char* genInsName(instruction ins); + const char* genInsDisplayName(emitter::instrDesc* id); #endif // DEBUG //------------------------------------------------------------------------- @@ -1503,10 +1504,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX void instGen_Store_Reg_Into_Lcl(var_types dstType, regNumber srcReg, int varNum, int offs); -#ifdef DEBUG - void __cdecl instDisp(instruction ins, bool noNL, const char* fmt, ...); -#endif - #ifdef TARGET_XARCH instruction genMapShiftInsToShiftByConstantIns(instruction ins, int shiftByValue); #endif // TARGET_XARCH diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index aeecb0553b989..d2e08159ec104 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1070,7 +1070,7 @@ void CodeGen::genDefineTempLabel(BasicBlock* label) { genLogLabel(label); label->bbEmitCookie = GetEmitter()->emitAddLabel(gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, - gcInfo.gcRegByrefSetCur, false DEBUG_ARG(label->bbNum)); + gcInfo.gcRegByrefSetCur, false DEBUG_ARG(label)); } // genDefineInlineTempLabel: Define an inline label that does not affect the GC @@ -2064,9 +2064,8 @@ void CodeGen::genInsertNopForUnwinder(BasicBlock* block) // block starts an EH region. If we pointed the existing bbEmitCookie here, then the NOP // would be executed, which we would prefer not to do. - block->bbUnwindNopEmitCookie = - GetEmitter()->emitAddLabel(gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, - false DEBUG_ARG(block->bbNum)); + block->bbUnwindNopEmitCookie = GetEmitter()->emitAddLabel(gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, + gcInfo.gcRegByrefSetCur, false DEBUG_ARG(block)); instGen(INS_nop); } diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index b822f233e66db..aa2fb0f58955f 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -356,7 +356,7 @@ void CodeGen::genCodeForBBlist() // Mark a label and update the current set of live GC refs block->bbEmitCookie = GetEmitter()->emitAddLabel(gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, - gcInfo.gcRegByrefSetCur, false DEBUG_ARG(block->bbNum)); + gcInfo.gcRegByrefSetCur, false DEBUG_ARG(block)); } if (block == compiler->fgFirstColdBlock) diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 7019d8afad6c5..fe5734213afcc 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -2988,6 +2988,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) opts.disAsmSpilled = false; opts.disDiffable = false; opts.disAddr = false; + opts.disAlignment = false; opts.dspCode = false; opts.dspEHTable = false; opts.dspDebugInfo = false; @@ -3136,6 +3137,11 @@ void Compiler::compInitOptions(JitFlags* jitFlags) opts.disAddr = true; } + if (JitConfig.JitDasmWithAlignmentBoundaries() != 0) + { + opts.disAlignment = true; + } + if (JitConfig.JitLongAddress() != 0) { opts.compLongAddress = true; @@ -6266,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 1383b57a7dd3a..9027a5d233529 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 @@ -6237,9 +6238,9 @@ class Compiler public: void optInit(); - GenTree* Compiler::optRemoveRangeCheck(GenTreeBoundsChk* check, GenTree* comma, Statement* stmt); - GenTree* Compiler::optRemoveStandaloneRangeCheck(GenTreeBoundsChk* check, Statement* stmt); - void Compiler::optRemoveCommaBasedRangeCheck(GenTree* comma, Statement* stmt); + GenTree* optRemoveRangeCheck(GenTreeBoundsChk* check, GenTree* comma, Statement* stmt); + GenTree* optRemoveStandaloneRangeCheck(GenTreeBoundsChk* check, Statement* stmt); + void optRemoveCommaBasedRangeCheck(GenTree* comma, Statement* stmt); bool optIsRangeCheckRemovable(GenTree* tree); protected: @@ -6728,7 +6729,7 @@ class Compiler // BitVec trait information for computing CSE availability using the CSE_DataFlow algorithm. // Two bits are allocated per CSE candidate to compute CSE availability // plus an extra bit to handle the initial unvisited case. - // (See CSE_DataFlow::EndMerge for an explaination of why this is necessary) + // (See CSE_DataFlow::EndMerge for an explanation of why this is necessary.) // // The two bits per CSE candidate have the following meanings: // 11 - The CSE is available, and is also available when considering calls as killing availability. @@ -6738,6 +6739,37 @@ class Compiler // BitVecTraits* cseLivenessTraits; + //----------------------------------------------------------------------------------------------------------------- + // getCSEnum2bit: Return the normalized index to use in the EXPSET_TP for the CSE with the given CSE index. + // Each GenTree has a `gtCSEnum` field. Zero is reserved to mean this node is not a CSE, positive values indicate + // CSE uses, and negative values indicate CSE defs. The caller must pass a non-zero positive value, as from + // GET_CSE_INDEX(). + // + static unsigned genCSEnum2bit(unsigned CSEnum) + { + assert((CSEnum > 0) && (CSEnum <= MAX_CSE_CNT)); + return CSEnum - 1; + } + + //----------------------------------------------------------------------------------------------------------------- + // getCSEAvailBit: Return the bit used by CSE dataflow sets (bbCseGen, etc.) for the availability bit for a CSE. + // + static unsigned getCSEAvailBit(unsigned CSEnum) + { + return genCSEnum2bit(CSEnum) * 2; + } + + //----------------------------------------------------------------------------------------------------------------- + // getCSEAvailCrossCallBit: Return the bit used by CSE dataflow sets (bbCseGen, etc.) for the availability bit + // for a CSE considering calls as killing availability bit (see description above). + // + static unsigned getCSEAvailCrossCallBit(unsigned CSEnum) + { + return getCSEAvailBit(CSEnum) + 1; + } + + void optPrintCSEDataFlowSet(EXPSET_VALARG_TP cseDataFlowSet, bool includeBits = true); + EXPSET_TP cseCallKillsMask; // Computed once - A mask that is used to kill available CSEs at callsites /* Generic list of nodes - used by the CSE logic */ @@ -6872,9 +6904,12 @@ class Compiler return (enckey & ~TARGET_SIGN_BIT) << CSE_CONST_SHARED_LOW_BITS; } - /************************************************************************** - * Value Number based CSEs - *************************************************************************/ +/************************************************************************** + * Value Number based CSEs + *************************************************************************/ + +// String to use for formatting CSE numbers. Note that this is the positive number, e.g., from GET_CSE_INDEX(). +#define FMT_CSE "CSE #%02u" public: void optOptimizeValnumCSEs(); @@ -6882,16 +6917,15 @@ class Compiler protected: void optValnumCSE_Init(); unsigned optValnumCSE_Index(GenTree* tree, Statement* stmt); - unsigned optValnumCSE_Locate(); - void optValnumCSE_InitDataFlow(); - void optValnumCSE_DataFlow(); - void optValnumCSE_Availablity(); - void optValnumCSE_Heuristic(); + bool optValnumCSE_Locate(); + void optValnumCSE_InitDataFlow(); + void optValnumCSE_DataFlow(); + void optValnumCSE_Availablity(); + void optValnumCSE_Heuristic(); bool optDoCSE; // True when we have found a duplicate CSE tree - bool optValnumCSE_phase; // True when we are executing the optValnumCSE_phase - unsigned optCSECandidateTotal; // Grand total of CSE candidates for both Lexical and ValNum - unsigned optCSECandidateCount; // Count of CSE's candidates, reset for Lexical and ValNum CSE's + bool optValnumCSE_phase; // True when we are executing the optOptimizeValnumCSEs() phase + unsigned optCSECandidateCount; // Count of CSE's candidates unsigned optCSEstart; // The first local variable number that is a CSE unsigned optCSEcount; // The total count of CSE's introduced. BasicBlock::weight_t optCSEweight; // The weight of the current block when we are doing PerformCSE @@ -6916,6 +6950,7 @@ class Compiler bool optConfigDisableCSE(); bool optConfigDisableCSE2(); #endif + void optOptimizeCSEs(); struct isVarAssgDsc @@ -9337,6 +9372,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX bool disasmWithGC; // Display GC info interleaved with disassembly. bool disDiffable; // Makes the Disassembly code 'diff-able' bool disAddr; // Display process address next to each instruction in disassembly code + bool disAlignment; // Display alignment boundaries in disassembly code bool disAsm2; // Display native code after it is generated using external disassembler bool dspOrder; // Display names of each of the methods that we ngen/jit bool dspUnwind; // Display the unwind info output diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index d05bfc6bbeb9f..ca9626ee11c1f 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -763,25 +763,6 @@ inline double getR8LittleEndian(const BYTE* ptr) return *(double*)&val; } -/***************************************************************************** - * - * Return the normalized index to use in the EXPSET_TP for the CSE with - * the given CSE index. - * Each GenTree has the following field: - * signed char gtCSEnum; // 0 or the CSE index (negated if def) - * So zero is reserved to mean this node is not a CSE - * and postive values indicate CSE uses and negative values indicate CSE defs. - * The caller of this method must pass a non-zero postive value. - * This precondition is checked by the assert on the first line of this method. - */ - -inline unsigned int genCSEnum2bit(unsigned index) -{ - assert((index > 0) && (index <= EXPSET_SZ)); - - return (index - 1); -} - #ifdef DEBUG const char* genES2str(BitVecTraits* traits, EXPSET_TP set); const char* refCntWtd2str(BasicBlock::weight_t refCntWtd); diff --git a/src/coreclr/jit/emit.cpp b/src/coreclr/jit/emit.cpp index 913951568fd1b..21e37879eed7e 100644 --- a/src/coreclr/jit/emit.cpp +++ b/src/coreclr/jit/emit.cpp @@ -773,7 +773,7 @@ insGroup* emitter::emitSavIG(bool emitAdd) memcpy(id, emitCurIGfreeBase, sz); #ifdef DEBUG - if (false && emitComp->verbose) // this is not useful in normal dumps (hence it is normally under if (false) + if (false && emitComp->verbose) // this is not useful in normal dumps (hence it is normally under if (false)) { // If there's an error during emission, we may want to connect the post-copy address // of an instrDesc with the pre-copy address (the one that was originally created). This @@ -843,7 +843,7 @@ insGroup* emitter::emitSavIG(bool emitAdd) #ifdef DEBUG if (emitComp->opts.dspCode) { - printf("\n G_M%03u_IG%02u:", emitComp->compMethodID, ig->igNum); + printf("\n %s:", emitLabelString(ig)); if (emitComp->verbose) { printf(" ; offs=%06XH, funclet=%02u, bbWeight=%s", ig->igOffs, ig->igFuncIdx, @@ -1184,7 +1184,7 @@ int emitter::instrDesc::idAddrUnion::iiaGetJitDataOffset() const //---------------------------------------------------------------------------------------- // insEvaluateExecutionCost: -// Returns the estimate execution cost fortyhe current instruction +// Returns the estimated execution cost for the current instruction // // Arguments: // id - The current instruction descriptor to be evaluated @@ -1193,8 +1193,6 @@ int emitter::instrDesc::idAddrUnion::iiaGetJitDataOffset() const // calls getInsExecutionCharacteristics and uses the result // to compute an estimated execution cost // -// Notes: -// float emitter::insEvaluateExecutionCost(instrDesc* id) { insExecutionCharacteristics result = getInsExecutionCharacteristics(id); @@ -1202,8 +1200,10 @@ float emitter::insEvaluateExecutionCost(instrDesc* id) float latency = result.insLatency; unsigned memAccessKind = result.insMemoryAccessKind; - // Check for PERFSCORE_THROUGHPUT_ILLEGAL and PERFSCORE_LATENCY_ILLEGAL - assert(throughput > 0.0); + // Check for PERFSCORE_THROUGHPUT_ILLEGAL and PERFSCORE_LATENCY_ILLEGAL. + // Note that 0.0 throughput is allowed for pseudo-instructions in the instrDesc list that won't actually + // generate code. + assert(throughput >= 0.0); assert(latency >= 0.0); if (memAccessKind == PERFSCORE_MEMORY_WRITE) @@ -1241,7 +1241,7 @@ float emitter::insEvaluateExecutionCost(instrDesc* id) void emitter::perfScoreUnhandledInstruction(instrDesc* id, insExecutionCharacteristics* pResult) { #ifdef DEBUG - printf("PerfScore: unhandled instruction: %s, format %s", codeGen->genInsName(id->idIns()), + printf("PerfScore: unhandled instruction: %s, format %s", codeGen->genInsDisplayName(id), emitIfName(id->idInsFmt())); assert(!"PerfScore: unhandled instruction"); #endif @@ -1524,6 +1524,14 @@ void* emitter::emitAllocAnyInstr(size_t sz, emitAttr opsz) emitCurIGinsCnt++; +#ifdef DEBUG + if (emitComp->compCurBB != emitCurIG->lastGeneratedBlock) + { + emitCurIG->igBlocks.push_back(emitComp->compCurBB); + emitCurIG->lastGeneratedBlock = emitComp->compCurBB; + } +#endif // DEBUG + return id; } @@ -2504,7 +2512,7 @@ bool emitter::emitNoGChelper(CORINFO_METHOD_HANDLE methHnd) void* emitter::emitAddLabel(VARSET_VALARG_TP GCvars, regMaskTP gcrefRegs, regMaskTP byrefRegs, - bool isFinallyTarget DEBUG_ARG(unsigned bbNum)) + bool isFinallyTarget DEBUG_ARG(BasicBlock* block)) { /* Create a new IG if the current one is non-empty */ @@ -2533,7 +2541,7 @@ void* emitter::emitAddLabel(VARSET_VALARG_TP GCvars, #endif // defined(FEATURE_EH_FUNCLETS) && defined(TARGET_ARM) #ifdef DEBUG - JITDUMP("Mapped " FMT_BB " to G_M%03u_IG%02u\n", bbNum, emitComp->compMethodID, emitCurIG->igNum); + JITDUMP("Mapped " FMT_BB " to %s\n", block->bbNum, emitLabelString(emitCurIG)); if (EMIT_GC_VERBOSE) { @@ -2548,6 +2556,7 @@ void* emitter::emitAddLabel(VARSET_VALARG_TP GCvars, printf("\n"); } #endif + return emitCurIG; } @@ -2561,6 +2570,40 @@ void* emitter::emitAddInlineLabel() return emitCurIG; } +#ifdef DEBUG + +//----------------------------------------------------------------------------- +// emitPrintLabel: Print the assembly label for an insGroup. We could use emitter::emitLabelString() +// to be consistent, but that seems silly. +// +void emitter::emitPrintLabel(insGroup* ig) +{ + printf("G_M%03u_IG%02u", emitComp->compMethodID, ig->igNum); +} + +//----------------------------------------------------------------------------- +// emitLabelString: Return label string for an insGroup, for use in debug output. +// This can be called up to four times in a single 'printf' before the static buffers +// get reused. +// +// Returns: +// String with insGroup label +// +const char* emitter::emitLabelString(insGroup* ig) +{ + const int TEMP_BUFFER_LEN = 40; + static unsigned curBuf = 0; + static char buf[4][TEMP_BUFFER_LEN]; + const char* retbuf; + + sprintf_s(buf[curBuf], TEMP_BUFFER_LEN, "G_M%03u_IG%02u", emitComp->compMethodID, ig->igNum); + retbuf = buf[curBuf]; + curBuf = (curBuf + 1) % 4; + return retbuf; +} + +#endif // DEBUG + #ifdef TARGET_ARMARCH // Does the argument location point to an IG at the end of a function or funclet? @@ -3502,7 +3545,7 @@ void emitter::emitDispIG(insGroup* ig, insGroup* igPrev, bool verbose) const int TEMP_BUFFER_LEN = 40; char buff[TEMP_BUFFER_LEN]; - sprintf_s(buff, TEMP_BUFFER_LEN, "G_M%03u_IG%02u: ", emitComp->compMethodID, ig->igNum); + sprintf_s(buff, TEMP_BUFFER_LEN, "%s: ", emitLabelString(ig)); printf("%s; ", buff); // We dump less information when we're only interleaving GC info with a disassembly listing, @@ -3599,9 +3642,10 @@ void emitter::emitDispIG(insGroup* ig, insGroup* igPrev, bool verbose) else { const char* separator = ""; + if (jitdump) { - printf("offs=%06XH, size=%04XH", ig->igOffs, ig->igSize); + printf("%soffs=%06XH, size=%04XH", separator, ig->igOffs, ig->igSize); separator = ", "; } @@ -3642,6 +3686,15 @@ void emitter::emitDispIG(insGroup* ig, insGroup* igPrev, bool verbose) } #endif // FEATURE_LOOP_ALIGN + if (jitdump && !ig->igBlocks.empty()) + { + for (auto block : ig->igBlocks) + { + printf("%s%s", separator, block->dspToString()); + separator = ", "; + } + } + emitDispIGflags(ig->igFlags); if (ig == emitCurIG) @@ -3733,10 +3786,6 @@ size_t emitter::emitIssue1Instr(insGroup* ig, instrDesc* id, BYTE** dp) { size_t is; -#ifdef DEBUG - size_t beforeAddr = (size_t)*dp; -#endif - /* Record the beginning offset of the instruction */ BYTE* curInsAdr = *dp; @@ -3822,52 +3871,7 @@ size_t emitter::emitIssue1Instr(insGroup* ig, instrDesc* id, BYTE** dp) id->idDebugOnlyInfo()->idNum, is, emitSizeOfInsDsc(id)); assert(is == emitSizeOfInsDsc(id)); } - - // Print the alignment boundary - if ((emitComp->opts.disAsm || emitComp->verbose) && emitComp->opts.disAddr) - { - size_t currAddr = (size_t)*dp; - size_t lastBoundaryAddr = currAddr & ~((size_t)emitComp->opts.compJitAlignLoopBoundary - 1); - - // draw boundary if beforeAddr was before the lastBoundary. - if (beforeAddr < lastBoundaryAddr) - { - printf("; "); - instruction currIns = id->idIns(); - -#if defined(TARGET_XARCH) - - // https://www.intel.com/content/dam/support/us/en/documents/processors/mitigations-jump-conditional-code-erratum.pdf - bool isJccAffectedIns = - ((currIns >= INS_i_jmp && currIns < INS_align) || (currIns == INS_call) || (currIns == INS_ret)); - - instrDesc* nextId = id; - castto(nextId, BYTE*) += is; - instruction nextIns = nextId->idIns(); - if ((currIns == INS_cmp) || (currIns == INS_test) || (currIns == INS_add) || (currIns == INS_sub) || - (currIns == INS_and) || (currIns == INS_inc) || (currIns == INS_dec)) - { - isJccAffectedIns |= (nextIns >= INS_i_jmp && nextIns < INS_align); - } -#else - bool isJccAffectedIns = false; -#endif - - // Indicate if instruction is at at 32B boundary or is splitted - unsigned bytesCrossedBoundary = (currAddr & (emitComp->opts.compJitAlignLoopBoundary - 1)); - if ((bytesCrossedBoundary != 0) || (isJccAffectedIns && bytesCrossedBoundary == 0)) - { - printf("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (%s: %d)", codeGen->genInsName(id->idIns()), - bytesCrossedBoundary); - } - else - { - printf("..............................."); - } - printf(" %dB boundary ...............................\n", (emitComp->opts.compJitAlignLoopBoundary)); - } - } -#endif +#endif // DEBUG return is; } @@ -4229,7 +4233,7 @@ void emitter::emitJumpDistBind() { if (tgtIG) { - printf(" to G_M%03u_IG%02u\n", emitComp->compMethodID, tgtIG->igNum); + printf(" to %s\n", emitLabelString(tgtIG)); } else { @@ -4687,8 +4691,7 @@ void emitter::emitLoopAlignment() // all IGs that follows this IG and participate in a loop. emitCurIG->igFlags |= IGF_LOOP_ALIGN; - JITDUMP("Adding 'align' instruction of %d bytes in G_M%03u_IG%02u.\n", paddingBytes, emitComp->compMethodID, - emitCurIG->igNum); + JITDUMP("Adding 'align' instruction of %d bytes in %s.\n", paddingBytes, emitLabelString(emitCurIG)); #ifdef DEBUG emitComp->loopAlignCandidates++; @@ -5027,10 +5030,10 @@ void emitter::emitLoopAlignAdjustments() alignInstr = prevAlignInstr; } - JITDUMP("Adjusted alignment of G_M%03u_IG%02u from %u to %u.\n", emitComp->compMethodID, alignIG->igNum, - estimatedPaddingNeeded, actualPaddingNeeded); - JITDUMP("Adjusted size of G_M%03u_IG%02u from %u to %u.\n", emitComp->compMethodID, alignIG->igNum, - (alignIG->igSize + diff), alignIG->igSize); + JITDUMP("Adjusted alignment of %s from %u to %u.\n", emitLabelString(alignIG), estimatedPaddingNeeded, + actualPaddingNeeded); + JITDUMP("Adjusted size of %s from %u to %u.\n", emitLabelString(alignIG), (alignIG->igSize + diff), + alignIG->igSize); } // Adjust the offset of all IGs starting from next IG until we reach the IG having the next @@ -5039,8 +5042,8 @@ void emitter::emitLoopAlignAdjustments() insGroup* adjOffUptoIG = alignInstr->idaNext != nullptr ? alignInstr->idaNext->idaIG : emitIGlast; while ((adjOffIG != nullptr) && (adjOffIG->igNum <= adjOffUptoIG->igNum)) { - JITDUMP("Adjusted offset of G_M%03u_IG%02u from %04X to %04X\n", emitComp->compMethodID, adjOffIG->igNum, - adjOffIG->igOffs, (adjOffIG->igOffs - alignBytesRemoved)); + JITDUMP("Adjusted offset of %s from %04X to %04X\n", emitLabelString(adjOffIG), adjOffIG->igOffs, + (adjOffIG->igOffs - alignBytesRemoved)); adjOffIG->igOffs -= alignBytesRemoved; adjOffIG = adjOffIG->igNext; } @@ -5099,8 +5102,8 @@ unsigned emitter::emitCalculatePaddingForLoopAlignment(insGroup* ig, size_t offs // No padding if loop is already aligned if ((offset & (alignmentBoundary - 1)) == 0) { - JITDUMP(";; Skip alignment: 'Loop at G_M%03u_IG%02u already aligned at %dB boundary.'\n", - emitComp->compMethodID, ig->igNext->igNum, alignmentBoundary); + JITDUMP(";; Skip alignment: 'Loop at %s already aligned at %dB boundary.'\n", emitLabelString(ig->igNext), + alignmentBoundary); return 0; } @@ -5124,8 +5127,8 @@ unsigned emitter::emitCalculatePaddingForLoopAlignment(insGroup* ig, size_t offs // No padding if loop is big if (loopSize > maxLoopSize) { - JITDUMP(";; Skip alignment: 'Loop at G_M%03u_IG%02u is big. LoopSize= %d, MaxLoopSize= %d.'\n", - emitComp->compMethodID, ig->igNext->igNum, loopSize, maxLoopSize); + JITDUMP(";; Skip alignment: 'Loop at %s is big. LoopSize= %d, MaxLoopSize= %d.'\n", emitLabelString(ig->igNext), + loopSize, maxLoopSize); return 0; } @@ -5151,17 +5154,16 @@ unsigned emitter::emitCalculatePaddingForLoopAlignment(insGroup* ig, size_t offs if (nPaddingBytes == 0) { skipPadding = true; - JITDUMP(";; Skip alignment: 'Loop at G_M%03u_IG%02u already aligned at %uB boundary.'\n", - emitComp->compMethodID, ig->igNext->igNum, alignmentBoundary); + JITDUMP(";; Skip alignment: 'Loop at %s already aligned at %uB boundary.'\n", + emitLabelString(ig->igNext), alignmentBoundary); } // Check if the alignment exceeds new maxPadding limit else if (nPaddingBytes > nMaxPaddingBytes) { skipPadding = true; - JITDUMP(";; Skip alignment: 'Loop at G_M%03u_IG%02u PaddingNeeded= %d, MaxPadding= %d, LoopSize= %d, " + JITDUMP(";; Skip alignment: 'Loop at %s PaddingNeeded= %d, MaxPadding= %d, LoopSize= %d, " "AlignmentBoundary= %dB.'\n", - emitComp->compMethodID, ig->igNext->igNum, nPaddingBytes, nMaxPaddingBytes, loopSize, - alignmentBoundary); + emitLabelString(ig->igNext), nPaddingBytes, nMaxPaddingBytes, loopSize, alignmentBoundary); } } @@ -5183,8 +5185,8 @@ unsigned emitter::emitCalculatePaddingForLoopAlignment(insGroup* ig, size_t offs else { // Otherwise, the loop just fits in minBlocksNeededForLoop and so can skip alignment. - JITDUMP(";; Skip alignment: 'Loop at G_M%03u_IG%02u is aligned to fit in %d blocks of %d chunks.'\n", - emitComp->compMethodID, ig->igNext->igNum, minBlocksNeededForLoop, alignmentBoundary); + JITDUMP(";; Skip alignment: 'Loop at %s is aligned to fit in %d blocks of %d chunks.'\n", + emitLabelString(ig->igNext), minBlocksNeededForLoop, alignmentBoundary); } } } @@ -5212,13 +5214,13 @@ unsigned emitter::emitCalculatePaddingForLoopAlignment(insGroup* ig, size_t offs else { // Otherwise, the loop just fits in minBlocksNeededForLoop and so can skip alignment. - JITDUMP(";; Skip alignment: 'Loop at G_M%03u_IG%02u is aligned to fit in %d blocks of %d chunks.'\n", - emitComp->compMethodID, ig->igNext->igNum, minBlocksNeededForLoop, alignmentBoundary); + JITDUMP(";; Skip alignment: 'Loop at %s is aligned to fit in %d blocks of %d chunks.'\n", + emitLabelString(ig->igNext), minBlocksNeededForLoop, alignmentBoundary); } } - JITDUMP(";; Calculated padding to add %d bytes to align G_M%03u_IG%02u at %dB boundary.\n", paddingToAdd, - emitComp->compMethodID, ig->igNext->igNum, alignmentBoundary); + JITDUMP(";; Calculated padding to add %d bytes to align %s at %dB boundary.\n", paddingToAdd, + emitLabelString(ig->igNext), alignmentBoundary); // Either no padding is added because it is too expensive or the offset gets aligned // to the alignment boundary @@ -5900,7 +5902,7 @@ unsigned emitter::emitEndCodeGen(Compiler* comp, } else { - printf("\nG_M%03u_IG%02u:", emitComp->compMethodID, ig->igNum); + printf("\n%s:", emitLabelString(ig)); if (!emitComp->opts.disDiffable) { printf(" ;; offset=%04XH", emitCurCodeOffs(cp)); @@ -6017,9 +6019,97 @@ unsigned emitter::emitEndCodeGen(Compiler* comp, emitCurIG = ig; - for (unsigned cnt = ig->igInsCnt; cnt; cnt--) + for (unsigned cnt = ig->igInsCnt; cnt > 0; cnt--) { +#ifdef DEBUG + size_t curInstrAddr = (size_t)cp; + instrDesc* curInstrDesc = id; +#endif + castto(id, BYTE*) += emitIssue1Instr(ig, id, &cp); + +#ifdef DEBUG + // Print the alignment boundary + if ((emitComp->opts.disAsm || emitComp->verbose) && (emitComp->opts.disAddr || emitComp->opts.disAlignment)) + { + size_t afterInstrAddr = (size_t)cp; + instruction curIns = curInstrDesc->idIns(); + bool isJccAffectedIns = false; + +#if defined(TARGET_XARCH) + + // Determine if this instruction is part of a set that matches the Intel jcc erratum characteristic + // described here: + // https://www.intel.com/content/dam/support/us/en/documents/processors/mitigations-jump-conditional-code-erratum.pdf + // This is the case when a jump instruction crosses a 32-byte boundary, or ends on a 32-byte boundary. + // "Jump instruction" in this case includes conditional jump (jcc), macro-fused op-jcc (where 'op' is + // one of cmp, test, add, sub, and, inc, or dec), direct unconditional jump, indirect jump, + // direct/indirect call, and return. + + size_t jccAlignBoundary = 32; + size_t jccAlignBoundaryMask = jccAlignBoundary - 1; + size_t jccLastBoundaryAddr = afterInstrAddr & ~jccAlignBoundaryMask; + + if (curInstrAddr < jccLastBoundaryAddr) + { + isJccAffectedIns = IsJccInstruction(curIns) || IsJmpInstruction(curIns) || (curIns == INS_call) || + (curIns == INS_ret); + + // For op-Jcc there are two cases: (1) curIns is the jcc, in which case the above condition + // already covers us. (2) curIns is the `op` and the next instruction is the `jcc`. Note that + // we will never have a `jcc` as the first instruction of a group, so we don't need to worry + // about looking ahead to the next group after a an `op` of `op-Jcc`. + + if (!isJccAffectedIns && (cnt > 1)) + { + // The current `id` is valid, namely, there is another instruction in this group. + instruction nextIns = id->idIns(); + if (((curIns == INS_cmp) || (curIns == INS_test) || (curIns == INS_add) || + (curIns == INS_sub) || (curIns == INS_and) || (curIns == INS_inc) || + (curIns == INS_dec)) && + IsJccInstruction(nextIns)) + { + isJccAffectedIns = true; + } + } + + if (isJccAffectedIns) + { + unsigned bytesCrossedBoundary = (unsigned)(afterInstrAddr & jccAlignBoundaryMask); + printf("; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (%s: %d ; jcc erratum) %dB boundary " + "...............................\n", + codeGen->genInsDisplayName(curInstrDesc), bytesCrossedBoundary, jccAlignBoundary); + } + } + +#endif // TARGET_XARCH + + // Jcc affected instruction boundaries were printed above; handle other cases here. + if (!isJccAffectedIns) + { + size_t alignBoundaryMask = (size_t)emitComp->opts.compJitAlignLoopBoundary - 1; + size_t lastBoundaryAddr = afterInstrAddr & ~alignBoundaryMask; + + // draw boundary if beforeAddr was before the lastBoundary. + if (curInstrAddr < lastBoundaryAddr) + { + // Indicate if instruction is at the alignment boundary or is split + unsigned bytesCrossedBoundary = (unsigned)(afterInstrAddr & alignBoundaryMask); + if (bytesCrossedBoundary != 0) + { + printf("; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (%s: %d)", + codeGen->genInsDisplayName(curInstrDesc), bytesCrossedBoundary); + } + else + { + printf("; ..............................."); + } + printf(" %dB boundary ...............................\n", + emitComp->opts.compJitAlignLoopBoundary); + } + } + } +#endif // DEBUG } #ifdef DEBUG @@ -6862,11 +6952,8 @@ void emitter::emitDispDataSec(dataSecDsc* section) BasicBlock* block = reinterpret_cast(data->dsCont)[i]; insGroup* ig = static_cast(emitCodeGetCookie(block)); - const char* blockLabelFormat = "G_M%03u_IG%02u"; - char blockLabel[64]; - char firstLabel[64]; - sprintf_s(blockLabel, _countof(blockLabel), blockLabelFormat, emitComp->compMethodID, ig->igNum); - sprintf_s(firstLabel, _countof(firstLabel), blockLabelFormat, emitComp->compMethodID, igFirst->igNum); + const char* blockLabel = emitLabelString(ig); + const char* firstLabel = emitLabelString(igFirst); if (isRelative) { @@ -7986,11 +8073,6 @@ insGroup* emitter::emitAllocIG() ig->igSelf = ig; #endif -#if defined(DEBUG) || defined(LATE_DISASM) - ig->igWeight = getCurrentBlockWeight(); - ig->igPerfScore = 0.0; -#endif - #if EMITTER_STATS emitTotalIGcnt += 1; emitTotalIGsize += sz; @@ -8028,6 +8110,11 @@ void emitter::emitInitIG(insGroup* ig) ig->igFlags = 0; +#if defined(DEBUG) || defined(LATE_DISASM) + ig->igWeight = getCurrentBlockWeight(); + ig->igPerfScore = 0.0; +#endif + /* Zero out some fields to avoid printing garbage in JitDumps. These really only need to be set in DEBUG, but do it in all cases to make sure we act the same in non-DEBUG builds. @@ -8040,6 +8127,12 @@ void emitter::emitInitIG(insGroup* ig) #if FEATURE_LOOP_ALIGN ig->igLoopBackEdge = nullptr; #endif + +#ifdef DEBUG + ig->lastGeneratedBlock = nullptr; + // Explicitly call init, since IGs don't actually have a constructor. + ig->igBlocks.jitstd::list::init(emitComp->getAllocator(CMK_LoopOpt)); +#endif } /***************************************************************************** @@ -8104,6 +8197,11 @@ void emitter::emitNxtIG(bool extend) // We've created a new IG; no need to force another one. emitForceNewIG = false; + +#ifdef DEBUG + // We haven't written any code into the IG yet, so clear our record of the last block written to the IG. + emitCurIG->lastGeneratedBlock = nullptr; +#endif } /***************************************************************************** @@ -8638,7 +8736,7 @@ const char* emitter::emitOffsetToLabel(unsigned offs) if (ig->igOffs == offs) { // Found it! - sprintf_s(buf[curBuf], TEMP_BUFFER_LEN, "G_M%03u_IG%02u", emitComp->compMethodID, ig->igNum); + sprintf_s(buf[curBuf], TEMP_BUFFER_LEN, "%s", emitLabelString(ig)); retbuf = buf[curBuf]; curBuf = (curBuf + 1) % 4; return retbuf; diff --git a/src/coreclr/jit/emit.h b/src/coreclr/jit/emit.h index 2748acf463766..ef67148ea962a 100644 --- a/src/coreclr/jit/emit.h +++ b/src/coreclr/jit/emit.h @@ -247,6 +247,11 @@ struct insGroup double igPerfScore; // The PerfScore for this insGroup #endif +#ifdef DEBUG + BasicBlock* lastGeneratedBlock; // The last block that generated code into this insGroup. + jitstd::list igBlocks; // All the blocks that generated code into this insGroup. +#endif + UNATIVE_OFFSET igNum; // for ordering (and display) purposes UNATIVE_OFFSET igOffs; // offset of this group within method unsigned int igFuncIdx; // Which function/funclet does this belong to? (Index into Compiler::compFuncInfos array.) @@ -1229,6 +1234,8 @@ class emitter #define PERFSCORE_THROUGHPUT_ILLEGAL -1024.0f +#define PERFSCORE_THROUGHPUT_ZERO 0.0f // Only used for pseudo-instructions that don't generate code + #define PERFSCORE_THROUGHPUT_6X (1.0f / 6.0f) // Hextuple issue #define PERFSCORE_THROUGHPUT_5X 0.20f // Pentuple issue #define PERFSCORE_THROUGHPUT_4X 0.25f // Quad issue @@ -1902,13 +1909,18 @@ class emitter void* emitAddLabel(VARSET_VALARG_TP GCvars, regMaskTP gcrefRegs, regMaskTP byrefRegs, - bool isFinallyTarget = false DEBUG_ARG(unsigned bbNum = 0)); + bool isFinallyTarget = false DEBUG_ARG(BasicBlock* block = nullptr)); // Same as above, except the label is added and is conceptually "inline" in // the current block. Thus it extends the previous block and the emitter // continues to track GC info as if there was no label. void* emitAddInlineLabel(); +#ifdef DEBUG + void emitPrintLabel(insGroup* ig); + const char* emitLabelString(insGroup* ig); +#endif + #ifdef TARGET_ARMARCH void emitGetInstrDescs(insGroup* ig, instrDesc** id, int* insCnt); diff --git a/src/coreclr/jit/emitarm.cpp b/src/coreclr/jit/emitarm.cpp index 6953df5b49a7c..5b01adbd14a5d 100644 --- a/src/coreclr/jit/emitarm.cpp +++ b/src/coreclr/jit/emitarm.cpp @@ -7292,7 +7292,7 @@ void emitter::emitDispInsHelp( lab = (insGroup*)emitCodeGetCookie(*bbp++); assert(lab); - printf("\n DD G_M%03u_IG%02u", emitComp->compMethodID, lab->igNum); + printf("\n DD %s", emitLabelString(lab)); } while (--cnt); } } @@ -7601,7 +7601,7 @@ void emitter::emitDispInsHelp( case IF_T2_M1: // Load Label emitDispReg(id->idReg1(), attr, true); if (id->idIsBound()) - printf("G_M%03u_IG%02u", emitComp->compMethodID, id->idAddr()->iiaIGlabel->igNum); + emitPrintLabel(id->idAddr()->iiaIGlabel); else printf("L_M%03u_" FMT_BB, emitComp->compMethodID, id->idAddr()->iiaBBlabel->bbNum); break; @@ -7646,7 +7646,7 @@ void emitter::emitDispInsHelp( } } else if (id->idIsBound()) - printf("G_M%03u_IG%02u", emitComp->compMethodID, id->idAddr()->iiaIGlabel->igNum); + emitPrintLabel(id->idAddr()->iiaIGlabel); else printf("L_M%03u_" FMT_BB, emitComp->compMethodID, id->idAddr()->iiaBBlabel->bbNum); } diff --git a/src/coreclr/jit/emitarm64.cpp b/src/coreclr/jit/emitarm64.cpp index 37e19a219d265..84c49d94723d4 100644 --- a/src/coreclr/jit/emitarm64.cpp +++ b/src/coreclr/jit/emitarm64.cpp @@ -12286,7 +12286,7 @@ void emitter::emitDispIns( } else if (id->idIsBound()) { - printf("G_M%03u_IG%02u", emitComp->compMethodID, id->idAddr()->iiaIGlabel->igNum); + emitPrintLabel(id->idAddr()->iiaIGlabel); } else { @@ -12324,7 +12324,7 @@ void emitter::emitDispIns( emitDispReg(id->idReg1(), size, true); if (id->idIsBound()) { - printf("G_M%03u_IG%02u", emitComp->compMethodID, id->idAddr()->iiaIGlabel->igNum); + emitPrintLabel(id->idAddr()->iiaIGlabel); } else { @@ -12338,7 +12338,7 @@ void emitter::emitDispIns( emitDispImm(emitGetInsSC(id), true); if (id->idIsBound()) { - printf("G_M%03u_IG%02u", emitComp->compMethodID, id->idAddr()->iiaIGlabel->igNum); + emitPrintLabel(id->idAddr()->iiaIGlabel); } else { @@ -12463,7 +12463,7 @@ void emitter::emitDispIns( } else if (id->idIsBound()) { - printf("G_M%03u_IG%02u", emitComp->compMethodID, id->idAddr()->iiaIGlabel->igNum); + emitPrintLabel(id->idAddr()->iiaIGlabel); } else { diff --git a/src/coreclr/jit/emitxarch.cpp b/src/coreclr/jit/emitxarch.cpp index 217f5f15aafb2..c35a6675fe757 100644 --- a/src/coreclr/jit/emitxarch.cpp +++ b/src/coreclr/jit/emitxarch.cpp @@ -24,37 +24,37 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #include "emit.h" #include "codegen.h" -bool IsSSEInstruction(instruction ins) +bool emitter::IsSSEInstruction(instruction ins) { return (ins >= INS_FIRST_SSE_INSTRUCTION) && (ins <= INS_LAST_SSE_INSTRUCTION); } -bool IsSSEOrAVXInstruction(instruction ins) +bool emitter::IsSSEOrAVXInstruction(instruction ins) { return (ins >= INS_FIRST_SSE_INSTRUCTION) && (ins <= INS_LAST_AVX_INSTRUCTION); } -bool IsAVXOnlyInstruction(instruction ins) +bool emitter::IsAVXOnlyInstruction(instruction ins) { return (ins >= INS_FIRST_AVX_INSTRUCTION) && (ins <= INS_LAST_AVX_INSTRUCTION); } -bool IsFMAInstruction(instruction ins) +bool emitter::IsFMAInstruction(instruction ins) { return (ins >= INS_FIRST_FMA_INSTRUCTION) && (ins <= INS_LAST_FMA_INSTRUCTION); } -bool IsAVXVNNIInstruction(instruction ins) +bool emitter::IsAVXVNNIInstruction(instruction ins) { return (ins >= INS_FIRST_AVXVNNI_INSTRUCTION) && (ins <= INS_LAST_AVXVNNI_INSTRUCTION); } -bool IsBMIInstruction(instruction ins) +bool emitter::IsBMIInstruction(instruction ins) { return (ins >= INS_FIRST_BMI_INSTRUCTION) && (ins <= INS_LAST_BMI_INSTRUCTION); } -regNumber getBmiRegNumber(instruction ins) +regNumber emitter::getBmiRegNumber(instruction ins) { switch (ins) { @@ -81,7 +81,7 @@ regNumber getBmiRegNumber(instruction ins) } } -regNumber getSseShiftRegNumber(instruction ins) +regNumber emitter::getSseShiftRegNumber(instruction ins) { switch (ins) { @@ -123,7 +123,7 @@ regNumber getSseShiftRegNumber(instruction ins) } } -bool emitter::IsAVXInstruction(instruction ins) +bool emitter::IsAVXInstruction(instruction ins) const { return UseVEXEncoding() && IsSSEOrAVXInstruction(ins); } @@ -445,7 +445,7 @@ bool emitter::Is4ByteSSEInstruction(instruction ins) // Returns true if this instruction requires a VEX prefix // All AVX instructions require a VEX prefix -bool emitter::TakesVexPrefix(instruction ins) +bool emitter::TakesVexPrefix(instruction ins) const { // special case vzeroupper as it requires 2-byte VEX prefix // special case the fencing, movnti and the prefetch instructions as they never take a VEX prefix @@ -521,7 +521,7 @@ emitter::code_t emitter::AddVexPrefix(instruction ins, code_t code, emitAttr att } // Returns true if this instruction, for the given EA_SIZE(attr), will require a REX.W prefix -bool TakesRexWPrefix(instruction ins, emitAttr attr) +bool emitter::TakesRexWPrefix(instruction ins, emitAttr attr) { // Because the current implementation of AVX does not have a way to distinguish between the register // size specification (128 vs. 256 bits) and the operand size specification (32 vs. 64 bits), where both are @@ -4299,6 +4299,38 @@ bool emitter::IsMovInstruction(instruction ins) } } +//------------------------------------------------------------------------ +// IsJccInstruction: Determine if an instruction is a conditional jump instruction. +// +// Arguments: +// ins -- The instruction being checked +// +// Return Value: +// true if the instruction qualifies; otherwise, false +// +bool emitter::IsJccInstruction(instruction ins) +{ + return ((ins >= INS_jo) && (ins <= INS_jg)) || ((ins >= INS_l_jo) && (ins <= INS_l_jg)); +} + +//------------------------------------------------------------------------ +// IsJmpInstruction: Determine if an instruction is a jump instruction but NOT a conditional jump instruction. +// +// Arguments: +// ins -- The instruction being checked +// +// Return Value: +// true if the instruction qualifies; otherwise, false +// +bool emitter::IsJmpInstruction(instruction ins) +{ + return +#ifdef TARGET_AMD64 + (ins == INS_rex_jmp) || +#endif + (ins == INS_i_jmp) || (ins == INS_jmp) || (ins == INS_l_jmp); +} + //---------------------------------------------------------------------------------------- // IsRedundantMov: // Check if the current `mov` instruction is redundant and can be omitted. @@ -8459,7 +8491,7 @@ void emitter::emitDispAddrMode(instrDesc* id, bool noDetail) lab = (insGroup*)emitCodeGetCookie(*bbp++); assert(lab); - printf("\n D" SIZE_LETTER " G_M%03u_IG%02u", emitComp->compMethodID, lab->igNum); + printf("\n D" SIZE_LETTER " %s", emitLabelString(lab)); } while (--cnt); } } @@ -8691,22 +8723,16 @@ void emitter::emitDispIns( /* Display the instruction name */ - sstr = codeGen->genInsName(ins); + sstr = codeGen->genInsDisplayName(id); + printf(" %-9s", sstr); - if (IsAVXInstruction(ins) && !IsBMIInstruction(ins)) - { - printf(" v%-8s", sstr); - } - else - { - printf(" %-9s", sstr); - } #ifndef HOST_UNIX - if (strnlen_s(sstr, 10) >= 8) + if (strnlen_s(sstr, 10) >= 9) #else // HOST_UNIX - if (strnlen(sstr, 10) >= 8) + if (strnlen(sstr, 10) >= 9) #endif // HOST_UNIX { + // Make sure there's at least one space after the instruction name, for very long instruction names. printf(" "); } @@ -9656,7 +9682,7 @@ void emitter::emitDispIns( } else { - printf("G_M%03u_IG%02u", emitComp->compMethodID, id->idAddr()->iiaIGlabel->igNum); + emitPrintLabel(id->idAddr()->iiaIGlabel); } } else @@ -14676,6 +14702,17 @@ emitter::insExecutionCharacteristics emitter::getInsExecutionCharacteristics(ins switch (ins) { case INS_align: +#if FEATURE_LOOP_ALIGN + if (id->idCodeSize() == 0) + { + // We're not going to generate any instruction, so it doesn't count for PerfScore. + result.insThroughput = PERFSCORE_THROUGHPUT_ZERO; + result.insLatency = PERFSCORE_LATENCY_ZERO; + break; + } +#endif + FALLTHROUGH; + case INS_nop: case INS_int3: assert(memFmt == IF_NONE); diff --git a/src/coreclr/jit/emitxarch.h b/src/coreclr/jit/emitxarch.h index 8260445686be0..f952a6f649f44 100644 --- a/src/coreclr/jit/emitxarch.h +++ b/src/coreclr/jit/emitxarch.h @@ -83,7 +83,15 @@ code_t insEncodeOpreg(instruction ins, regNumber reg, emitAttr size); unsigned insSSval(unsigned scale); -bool IsAVXInstruction(instruction ins); +static bool IsSSEInstruction(instruction ins); +static bool IsSSEOrAVXInstruction(instruction ins); +static bool IsAVXOnlyInstruction(instruction ins); +static bool IsFMAInstruction(instruction ins); +static bool IsAVXVNNIInstruction(instruction ins); +static bool IsBMIInstruction(instruction ins); +static regNumber getBmiRegNumber(instruction ins); +static regNumber getSseShiftRegNumber(instruction ins); +bool IsAVXInstruction(instruction ins) const; code_t insEncodeMIreg(instruction ins, regNumber reg, emitAttr size, code_t code); code_t AddRexWPrefix(instruction ins, code_t code); @@ -98,6 +106,9 @@ static bool IsMovInstruction(instruction ins); bool IsRedundantMov( instruction ins, insFormat fmt, emitAttr size, regNumber dst, regNumber src, bool canIgnoreSideEffects); +static bool IsJccInstruction(instruction ins); +static bool IsJmpInstruction(instruction ins); + bool AreUpper32BitsZero(regNumber reg); bool AreFlagsSetToZeroCmp(regNumber reg, emitAttr opSize, genTreeOps treeOps); @@ -116,7 +127,8 @@ bool hasRexPrefix(code_t code) #define VEX_PREFIX_MASK_3BYTE 0xFF000000000000ULL #define VEX_PREFIX_CODE_3BYTE 0xC4000000000000ULL -bool TakesVexPrefix(instruction ins); +bool TakesVexPrefix(instruction ins) const; +static bool TakesRexWPrefix(instruction ins, emitAttr attr); // Returns true if the instruction encoding already contains VEX prefix bool hasVexPrefix(code_t code) @@ -142,7 +154,7 @@ code_t AddVexPrefixIfNeededAndNotPresent(instruction ins, code_t code, emitAttr } bool useVEXEncodings; -bool UseVEXEncoding() +bool UseVEXEncoding() const { return useVEXEncodings; } diff --git a/src/coreclr/jit/fgbasic.cpp b/src/coreclr/jit/fgbasic.cpp index 62a9467da63fe..6c65586fbca1c 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/fgopt.cpp b/src/coreclr/jit/fgopt.cpp index 3b1b1739919d5..92a82c346a545 100644 --- a/src/coreclr/jit/fgopt.cpp +++ b/src/coreclr/jit/fgopt.cpp @@ -1620,6 +1620,9 @@ void Compiler::fgCompactBlocks(BasicBlock* block, BasicBlock* bNext) } } bNext->bbPreds = nullptr; + + // `block` can no longer be a loop pre-header (if it was before). + block->bbFlags &= ~BBF_LOOP_PREHEADER; } else { diff --git a/src/coreclr/jit/fgprofile.cpp b/src/coreclr/jit/fgprofile.cpp index 1659b2d390529..31d7c208ebaf0 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/gentree.cpp b/src/coreclr/jit/gentree.cpp index 9422225c5d3de..7f16e6cd40048 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -10294,7 +10294,7 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, __in __in_z _ { if (IS_CSE_INDEX(tree->gtCSEnum)) { - printf("CSE #%02d (%s)", GET_CSE_INDEX(tree->gtCSEnum), (IS_CSE_USE(tree->gtCSEnum) ? "use" : "def")); + printf(FMT_CSE " (%s)", GET_CSE_INDEX(tree->gtCSEnum), (IS_CSE_USE(tree->gtCSEnum) ? "use" : "def")); } else { @@ -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/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index c12e46f02bdab..4b7374573ef73 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -494,6 +494,7 @@ enum GenTreeFlags : unsigned int GTF_INX_RNGCHK = 0x80000000, // GT_INDEX/GT_INDEX_ADDR -- the array reference should be range-checked. GTF_INX_STRING_LAYOUT = 0x40000000, // GT_INDEX -- this uses the special string array layout + GTF_INX_NOFAULT = 0x20000000, // GT_INDEX -- the INDEX does not throw an exception (morph to GTF_IND_NONFAULTING) GTF_IND_TGT_NOT_HEAP = 0x80000000, // GT_IND -- the target is not on the heap GTF_IND_VOLATILE = 0x40000000, // GT_IND -- the load or store must use volatile sematics (this is a nop on X86) @@ -6800,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/importer.cpp b/src/coreclr/jit/importer.cpp index d49a12ffd13dd..b3857cdec583a 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; } @@ -13258,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"); @@ -19071,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); } /***************************************************************************** @@ -19243,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); @@ -19465,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 a16d82888b64a..cbd85ff240dea 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 bc08dd8110d2a..6a39b2d9cb5d0 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 fdf4ae19341c2..e84ff2858a011 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 7d0d83bd73646..466e17fe0e112 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/instr.cpp b/src/coreclr/jit/instr.cpp index 9ce10458fb08e..752626f20184d 100644 --- a/src/coreclr/jit/instr.cpp +++ b/src/coreclr/jit/instr.cpp @@ -24,11 +24,12 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX /*****************************************************************************/ #ifdef DEBUG -/***************************************************************************** - * - * Returns the string representation of the given CPU instruction. - */ - +//----------------------------------------------------------------------------- +// genInsName: Returns the string representation of the given CPU instruction, as +// it exists in the instruction table. Note that some architectures don't encode the +// name completely in the table: xarch sometimes prepends a "v", and arm sometimes +// appends a "s". Use `genInsDisplayName()` to get a fully-formed name. +// const char* CodeGen::genInsName(instruction ins) { // clang-format off @@ -77,36 +78,36 @@ const char* CodeGen::genInsName(instruction ins) return insNames[ins]; } -void __cdecl CodeGen::instDisp(instruction ins, bool noNL, const char* fmt, ...) +//----------------------------------------------------------------------------- +// genInsDisplayName: Get a fully-formed instruction display name. This only handles +// the xarch case of prepending a "v", not the arm case of appending an "s". +// This can be called up to four times in a single 'printf' before the static buffers +// get reused. +// +// Returns: +// String with instruction name +// +const char* CodeGen::genInsDisplayName(emitter::instrDesc* id) { - if (compiler->opts.dspCode) - { - /* Display the instruction offset within the emit block */ - - // printf("[%08X:%04X]", GetEmitter().emitCodeCurBlock(), GetEmitter().emitCodeOffsInBlock()); - - /* Display the FP stack depth (before the instruction is executed) */ - - // printf("[FP=%02u] ", genGetFPstkLevel()); + instruction ins = id->idIns(); + const char* insName = genInsName(ins); - /* Display the instruction mnemonic */ - printf(" "); - - printf(" %-8s", genInsName(ins)); - - if (fmt) - { - va_list args; - va_start(args, fmt); - vprintf(fmt, args); - va_end(args); - } +#ifdef TARGET_XARCH + const int TEMP_BUFFER_LEN = 40; + static unsigned curBuf = 0; + static char buf[4][TEMP_BUFFER_LEN]; + const char* retbuf; - if (!noNL) - { - printf("\n"); - } + if (GetEmitter()->IsAVXInstruction(ins) && !GetEmitter()->IsBMIInstruction(ins)) + { + sprintf_s(buf[curBuf], TEMP_BUFFER_LEN, "v%s", insName); + retbuf = buf[curBuf]; + curBuf = (curBuf + 1) % 4; + return retbuf; } +#endif // TARGET_XARCH + + return insName; } /*****************************************************************************/ diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 7254e205fbc48..cd685c2cc1032 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -68,6 +68,9 @@ CONFIG_INTEGER(JitAlignLoopAdaptive, W("JitAlignLoopAdaptive"), 1) // If set, perform adaptive loop alignment that limits number of padding based on loop size. +// Print the alignment boundaries in disassembly. +CONFIG_INTEGER(JitDasmWithAlignmentBoundaries, W("JitDasmWithAlignmentBoundaries"), 0) + CONFIG_INTEGER(JitDirectAlloc, W("JitDirectAlloc"), 0) CONFIG_INTEGER(JitDoubleAlign, W("JitDoubleAlign"), 1) CONFIG_INTEGER(JitDumpASCII, W("JitDumpASCII"), 1) // Uses only ASCII characters in tree dumps @@ -460,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: @@ -552,7 +556,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/jitstd/algorithm.h b/src/coreclr/jit/jitstd/algorithm.h index 000639a5a1de8..9fa6fbb94dd54 100644 --- a/src/coreclr/jit/jitstd/algorithm.h +++ b/src/coreclr/jit/jitstd/algorithm.h @@ -102,9 +102,9 @@ void quick_sort(RandomAccessIterator first, RandomAccessIterator last, Less less // // It's not possible for newFirst to go past the end of the sort range: // - If newFirst reaches the pivot before newLast then the pivot is - // swapped to the right and we'll stop again when we reach it. + // swapped to the right and we'll stop again when we reach it. // - If newLast reaches the pivot before newFirst then the pivot is - // swapped to the left and the value at newFirst will take its place + // swapped to the left and the value at newFirst will take its place // to the right so less(newFirst, pivot) will again be false when the // old pivot's position is reached. do diff --git a/src/coreclr/jit/jitstd/list.h b/src/coreclr/jit/jitstd/list.h index 070d94361f2aa..b573a3952fb17 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&); diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 33c5abaa30df7..75bcbfafbf290 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/loopcloning.cpp b/src/coreclr/jit/loopcloning.cpp index 0fe22420f783e..c3991e663e1d9 100644 --- a/src/coreclr/jit/loopcloning.cpp +++ b/src/coreclr/jit/loopcloning.cpp @@ -12,6 +12,40 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #include "jitpch.h" +#ifdef DEBUG + +//-------------------------------------------------------------------------------------------------- +// ArrIndex::Print - debug print an ArrIndex struct in form: `V01[V02][V03]`. +// +// Arguments: +// dim (Optional) Print up to but not including this dimension. Default: print all dimensions. +// +void ArrIndex::Print(unsigned dim /* = -1 */) +{ + printf("V%02d", arrLcl); + for (unsigned i = 0; i < ((dim == (unsigned)-1) ? rank : dim); ++i) + { + printf("[V%02d]", indLcls.Get(i)); + } +} + +//-------------------------------------------------------------------------------------------------- +// ArrIndex::PrintBoundsCheckNodes - debug print an ArrIndex struct bounds check node tree ids in +// form: `[000125][000113]`. +// +// Arguments: +// dim (Optional) Print up to but not including this dimension. Default: print all dimensions. +// +void ArrIndex::PrintBoundsCheckNodes(unsigned dim /* = -1 */) +{ + for (unsigned i = 0; i < ((dim == (unsigned)-1) ? rank : dim); ++i) + { + Compiler::printTreeID(bndsChks.Get(i)); + } +} + +#endif // DEBUG + //-------------------------------------------------------------------------------------------------- // ToGenTree - Convert an arrLen operation into a gentree node. // @@ -39,11 +73,26 @@ GenTree* LC_Array::ToGenTree(Compiler* comp, BasicBlock* bb) { arr = comp->gtNewIndexRef(TYP_REF, arr, comp->gtNewLclvNode(arrIndex->indLcls[i], comp->lvaTable[arrIndex->indLcls[i]].lvType)); + + // Clear the range check flag and mark the index as non-faulting: we guarantee that all necessary range + // checking has already been done by the time this array index expression is invoked. + arr->gtFlags &= ~(GTF_INX_RNGCHK | GTF_EXCEPT); + arr->gtFlags |= GTF_INX_NOFAULT; } // If asked for arrlen invoke arr length operator. if (oper == ArrLen) { GenTree* arrLen = comp->gtNewArrLen(TYP_INT, arr, OFFSETOF__CORINFO_Array__length, bb); + + // We already guaranteed (by a sequence of preceding checks) that the array length operator will not + // throw an exception because we null checked the base array. + // So, we should be able to do the following: + // arrLen->gtFlags &= ~GTF_EXCEPT; + // arrLen->gtFlags |= GTF_IND_NONFAULTING; + // However, we then end up with a mix of non-faulting array length operators as well as normal faulting + // array length operators in the slow-path of the cloned loops. CSE doesn't keep these separate, so bails + // out on creating CSEs on this very useful type of CSE, leading to CQ losses in the cloned loop fast path. + // TODO-CQ: fix this. return arrLen; } else @@ -395,26 +444,30 @@ void LoopCloneContext::PrintBlockConditions(unsigned loopNum) { printf("Block conditions:\n"); - JitExpandArrayStack*>* levelCond = blockConditions[loopNum]; - if (levelCond == nullptr || levelCond->Size() == 0) + JitExpandArrayStack*>* blockConds = blockConditions[loopNum]; + if (blockConds == nullptr || blockConds->Size() == 0) { printf("No block conditions\n"); return; } - for (unsigned i = 0; i < levelCond->Size(); ++i) + for (unsigned i = 0; i < blockConds->Size(); ++i) + { + PrintBlockLevelConditions(i, (*blockConds)[i]); + } +} +void LoopCloneContext::PrintBlockLevelConditions(unsigned level, JitExpandArrayStack* levelCond) +{ + printf("%d = ", level); + for (unsigned j = 0; j < levelCond->Size(); ++j) { - printf("%d = {", i); - for (unsigned j = 0; j < ((*levelCond)[i])->Size(); ++j) + if (j != 0) { - if (j != 0) - { - printf(" & "); - } - (*((*levelCond)[i]))[j].Print(); + printf(" & "); } - printf("}\n"); + (*levelCond)[j].Print(); } + printf("\n"); } #endif @@ -650,19 +703,19 @@ void LoopCloneContext::PrintConditions(unsigned loopNum) { if (conditions[loopNum] == nullptr) { - JITDUMP("NO conditions"); + printf("NO conditions"); return; } if (conditions[loopNum]->Size() == 0) { - JITDUMP("Conditions were optimized away! Will always take cloned path."); + printf("Conditions were optimized away! Will always take cloned path."); return; } for (unsigned i = 0; i < conditions[loopNum]->Size(); ++i) { if (i != 0) { - JITDUMP(" & "); + printf(" & "); } (*conditions[loopNum])[i].Print(); } @@ -711,6 +764,9 @@ void LoopCloneContext::CondToStmtInBlock(Compiler* comp comp->fgInsertStmtAtEnd(block, stmt); // Remorph. + JITDUMP("Loop cloning condition tree before morphing:\n"); + DBEXEC(comp->verbose, comp->gtDispTree(jmpTrueTree)); + JITDUMP("\n"); comp->fgMorphBlockStmt(block, stmt DEBUGARG("Loop cloning condition")); } @@ -965,16 +1021,15 @@ bool Compiler::optDeriveLoopCloningConditions(unsigned loopNum, LoopCloneContext for (unsigned i = 0; i < optInfos->Size(); ++i) { - LcOptInfo* optInfo = optInfos->GetRef(i); + LcOptInfo* optInfo = optInfos->Get(i); switch (optInfo->GetOptType()) { case LcOptInfo::LcJaggedArray: { // limit <= arrLen LcJaggedArrayOptInfo* arrIndexInfo = optInfo->AsLcJaggedArrayOptInfo(); - LC_Array arrLen(LC_Array::Jagged, &arrIndexInfo->arrIndex, arrIndexInfo->dim, LC_Array::ArrLen); - LC_Ident arrLenIdent = LC_Ident(arrLen); - + LC_Array arrLen(LC_Array::Jagged, &arrIndexInfo->arrIndex, arrIndexInfo->dim, LC_Array::ArrLen); + LC_Ident arrLenIdent = LC_Ident(arrLen); LC_Condition cond(GT_LE, LC_Expr(ident), LC_Expr(arrLenIdent)); context->EnsureConditions(loopNum)->Push(cond); @@ -1000,9 +1055,9 @@ bool Compiler::optDeriveLoopCloningConditions(unsigned loopNum, LoopCloneContext return false; } } - JITDUMP("Conditions: ("); + JITDUMP("Conditions: "); DBEXEC(verbose, context->PrintConditions(loopNum)); - JITDUMP(")\n"); + JITDUMP("\n"); return true; } return false; @@ -1164,10 +1219,6 @@ bool Compiler::optComputeDerefConditions(unsigned loopNum, LoopCloneContext* con printf("Deref condition tree:\n"); for (unsigned i = 0; i < nodes.Size(); ++i) { - if (i != 0) - { - printf(","); - } nodes[i]->Print(); printf("\n"); } @@ -1176,6 +1227,7 @@ bool Compiler::optComputeDerefConditions(unsigned loopNum, LoopCloneContext* con if (maxRank == -1) { + JITDUMP("> maxRank undefined\n"); return false; } @@ -1184,12 +1236,13 @@ bool Compiler::optComputeDerefConditions(unsigned loopNum, LoopCloneContext* con // So add 1 after rank * 2. unsigned condBlocks = (unsigned)maxRank * 2 + 1; - // Heuristic to not create too many blocks. - // REVIEW: due to the definition of `condBlocks`, above, the effective max is 3 blocks, meaning - // `maxRank` of 1. Question: should the heuristic allow more blocks to be created in some situations? - // REVIEW: make this based on a COMPlus configuration? - if (condBlocks > 4) + // Heuristic to not create too many blocks. Defining as 3 allows, effectively, loop cloning on + // doubly-nested loops. + // REVIEW: make this based on a COMPlus configuration, at least for debug? + const unsigned maxAllowedCondBlocks = 3; + if (condBlocks > maxAllowedCondBlocks) { + JITDUMP("> Too many condition blocks (%u > %u)\n", condBlocks, maxAllowedCondBlocks); return false; } @@ -1254,14 +1307,60 @@ void Compiler::optPerformStaticOptimizations(unsigned loopNum, LoopCloneContext* JitExpandArrayStack* optInfos = context->GetLoopOptInfo(loopNum); for (unsigned i = 0; i < optInfos->Size(); ++i) { - LcOptInfo* optInfo = optInfos->GetRef(i); + LcOptInfo* optInfo = optInfos->Get(i); switch (optInfo->GetOptType()) { case LcOptInfo::LcJaggedArray: { LcJaggedArrayOptInfo* arrIndexInfo = optInfo->AsLcJaggedArrayOptInfo(); compCurBB = arrIndexInfo->arrIndex.useBlock; - optRemoveCommaBasedRangeCheck(arrIndexInfo->arrIndex.bndsChks[arrIndexInfo->dim], arrIndexInfo->stmt); + + // Remove all bounds checks for this array up to (and including) `arrIndexInfo->dim`. So, if that is 1, + // Remove rank 0 and 1 bounds checks. + + for (unsigned dim = 0; dim <= arrIndexInfo->dim; dim++) + { + GenTree* bndsChkNode = arrIndexInfo->arrIndex.bndsChks[dim]; + +#ifdef DEBUG + if (verbose) + { + printf("Remove bounds check "); + printTreeID(bndsChkNode->gtGetOp1()); + printf(" for " FMT_STMT ", dim% d, ", arrIndexInfo->stmt->GetID(), dim); + arrIndexInfo->arrIndex.Print(); + printf(", bounds check nodes: "); + arrIndexInfo->arrIndex.PrintBoundsCheckNodes(); + printf("\n"); + } +#endif // DEBUG + + if (bndsChkNode->gtGetOp1()->OperIsBoundsCheck()) + { + // This COMMA node will only represent a bounds check if we've haven't already removed this + // bounds check in some other nesting cloned loop. For example, consider: + // for (i = 0; i < x; i++) + // for (j = 0; j < y; j++) + // a[i][j] = i + j; + // If the outer loop is cloned first, it will remove the a[i] bounds check from the optimized + // path. Later, when the inner loop is cloned, we want to remove the a[i][j] bounds check. If + // we clone the inner loop, we know that the a[i] bounds check isn't required because we'll add + // it to the loop cloning conditions. On the other hand, we can clone a loop where we get rid of + // the nested bounds check but nobody has gotten rid of the outer bounds check. As before, we + // know the outer bounds check is not needed because it's been added to the cloning conditions, + // so we can get rid of the bounds check here. + // + optRemoveCommaBasedRangeCheck(bndsChkNode, arrIndexInfo->stmt); + } + else + { + JITDUMP(" Bounds check already removed\n"); + + // If the bounds check node isn't there, it better have been converted to a GT_NOP. + assert(bndsChkNode->gtGetOp1()->OperIs(GT_NOP)); + } + } + DBEXEC(dynamicPath, optDebugLogLoopCloning(arrIndexInfo->arrIndex.useBlock, arrIndexInfo->stmt)); } break; @@ -1474,8 +1573,9 @@ BasicBlock* Compiler::optInsertLoopChoiceConditions(LoopCloneContext* context, BasicBlock* head, BasicBlock* slowHead) { - JITDUMP("Inserting loop cloning conditions\n"); + JITDUMP("Inserting loop " FMT_LP " loop choice conditions\n", loopNum); assert(context->HasBlockConditions(loopNum)); + assert(head->bbJumpKind == BBJ_COND); BasicBlock* curCond = head; JitExpandArrayStack*>* levelCond = context->GetBlockConditions(loopNum); @@ -1484,10 +1584,15 @@ BasicBlock* Compiler::optInsertLoopChoiceConditions(LoopCloneContext* context, bool isHeaderBlock = (curCond == head); // Flip the condition if header block. + JITDUMP("Adding loop " FMT_LP " level %u block conditions to " FMT_BB "\n ", loopNum, i, curCond->bbNum); + DBEXEC(verbose, context->PrintBlockLevelConditions(i, (*levelCond)[i])); context->CondToStmtInBlock(this, *((*levelCond)[i]), curCond, /*reverse*/ isHeaderBlock); // Create each condition block ensuring wiring between them. - BasicBlock* tmp = fgNewBBafter(BBJ_COND, isHeaderBlock ? slowHead : curCond, /*extendRegion*/ true); + BasicBlock* tmp = fgNewBBafter(BBJ_COND, isHeaderBlock ? slowHead : curCond, /*extendRegion*/ true); + tmp->inheritWeight(head); + tmp->bbNatLoopNum = head->bbNatLoopNum; + curCond->bbJumpDest = isHeaderBlock ? tmp : slowHead; JITDUMP("Adding " FMT_BB " -> " FMT_BB "\n", curCond->bbNum, curCond->bbJumpDest->bbNum); @@ -1500,13 +1605,13 @@ BasicBlock* Compiler::optInsertLoopChoiceConditions(LoopCloneContext* context, } curCond = tmp; - - curCond->inheritWeight(head); - curCond->bbNatLoopNum = head->bbNatLoopNum; - JITDUMP("Created new " FMT_BB " for new level %u\n", curCond->bbNum, i); } // Finally insert cloning conditions after all deref conditions have been inserted. + JITDUMP("Adding loop " FMT_LP " cloning conditions to " FMT_BB "\n", loopNum, curCond->bbNum); + JITDUMP(" "); + DBEXEC(verbose, context->PrintConditions(loopNum)); + JITDUMP("\n"); context->CondToStmtInBlock(this, *(context->GetConditions(loopNum)), curCond, /*reverse*/ false); return curCond; } @@ -2222,10 +2327,12 @@ Compiler::fgWalkResult Compiler::optCanOptimizeByLoopCloning(GenTree* tree, Loop #ifdef DEBUG if (verbose) { - printf("Found ArrIndex at tree "); + printf("Found ArrIndex at " FMT_BB " " FMT_STMT " tree ", arrIndex.useBlock->bbNum, info->stmt->GetID()); printTreeID(tree); printf(" which is equivalent to: "); arrIndex.Print(); + printf(", bounds check nodes: "); + arrIndex.PrintBoundsCheckNodes(); printf("\n"); } #endif @@ -2233,6 +2340,7 @@ Compiler::fgWalkResult Compiler::optCanOptimizeByLoopCloning(GenTree* tree, Loop // Check that the array object local variable is invariant within the loop body. if (!optIsStackLocalInvariant(info->loopNum, arrIndex.arrLcl)) { + JITDUMP("V%02d is not loop invariant\n", arrIndex.arrLcl); return WALK_SKIP_SUBTREES; } diff --git a/src/coreclr/jit/loopcloning.h b/src/coreclr/jit/loopcloning.h index 6cc921c520db1..c5ed53c31bad8 100644 --- a/src/coreclr/jit/loopcloning.h +++ b/src/coreclr/jit/loopcloning.h @@ -220,14 +220,8 @@ struct ArrIndex } #ifdef DEBUG - void Print(unsigned dim = -1) - { - printf("V%02d", arrLcl); - for (unsigned i = 0; i < ((dim == (unsigned)-1) ? rank : dim); ++i) - { - printf("[V%02d]", indLcls.GetRef(i)); - } - } + void Print(unsigned dim = -1); + void PrintBoundsCheckNodes(unsigned dim = -1); #endif }; @@ -260,9 +254,8 @@ struct LcOptInfo #include "loopcloningopts.h" }; - void* optInfo; OptType optType; - LcOptInfo(void* optInfo, OptType optType) : optInfo(optInfo), optType(optType) + LcOptInfo(OptType optType) : optType(optType) { } @@ -270,6 +263,7 @@ struct LcOptInfo { return optType; } + #define LC_OPT(en) \ en##OptInfo* As##en##OptInfo() \ { \ @@ -292,7 +286,7 @@ struct LcMdArrayOptInfo : public LcOptInfo ArrIndex* index; // "index" cached computation in the form of an ArrIndex representation. LcMdArrayOptInfo(GenTreeArrElem* arrElem, unsigned dim) - : LcOptInfo(this, LcMdArray), arrElem(arrElem), dim(dim), index(nullptr) + : LcOptInfo(LcMdArray), arrElem(arrElem), dim(dim), index(nullptr) { } @@ -325,7 +319,7 @@ struct LcJaggedArrayOptInfo : public LcOptInfo Statement* stmt; // "stmt" where the optimization opportunity occurs. LcJaggedArrayOptInfo(ArrIndex& arrIndex, unsigned dim, Statement* stmt) - : LcOptInfo(this, LcJaggedArray), dim(dim), arrIndex(arrIndex), stmt(stmt) + : LcOptInfo(LcJaggedArray), dim(dim), arrIndex(arrIndex), stmt(stmt) { } }; @@ -678,30 +672,24 @@ struct LoopCloneContext CompAllocator alloc; // The allocator // The array of optimization opportunities found in each loop. (loop x optimization-opportunities) - JitExpandArrayStack** optInfo; + jitstd::vector*> optInfo; // The array of conditions that influence which path to take for each loop. (loop x cloning-conditions) - JitExpandArrayStack** conditions; + jitstd::vector*> conditions; // The array of dereference conditions found in each loop. (loop x deref-conditions) - JitExpandArrayStack** derefs; + jitstd::vector*> derefs; // The array of block levels of conditions for each loop. (loop x level x conditions) - JitExpandArrayStack*>** blockConditions; + jitstd::vector*>*> blockConditions; - LoopCloneContext(unsigned loopCount, CompAllocator alloc) : alloc(alloc) + LoopCloneContext(unsigned loopCount, CompAllocator alloc) + : alloc(alloc), optInfo(alloc), conditions(alloc), derefs(alloc), blockConditions(alloc) { - optInfo = new (alloc) JitExpandArrayStack*[loopCount]; - conditions = new (alloc) JitExpandArrayStack*[loopCount]; - derefs = new (alloc) JitExpandArrayStack*[loopCount]; - blockConditions = new (alloc) JitExpandArrayStack*>*[loopCount]; - for (unsigned i = 0; i < loopCount; ++i) - { - optInfo[i] = nullptr; - conditions[i] = nullptr; - derefs[i] = nullptr; - blockConditions[i] = nullptr; - } + optInfo.resize(loopCount, nullptr); + conditions.resize(loopCount, nullptr); + derefs.resize(loopCount, nullptr); + blockConditions.resize(loopCount, nullptr); } // Evaluate conditions into a JTRUE stmt and put it in the block. Reverse condition if 'reverse' is true. @@ -739,6 +727,7 @@ struct LoopCloneContext #ifdef DEBUG // Print the block conditions for the loop. void PrintBlockConditions(unsigned loopNum); + void PrintBlockLevelConditions(unsigned level, JitExpandArrayStack* levelCond); #endif // Does the loop have block conditions? diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 9499ac5d81782..a5b0ba16fdc6c 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 7f7c9d3760aa1..a0d897e74da16 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 7edf8c7103f15..134b77281f681 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 ed889f7f383bc..43c0df6204236 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/lsra.cpp b/src/coreclr/jit/lsra.cpp index 4bbb877fda8c3..f382b1dcf4634 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); diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index f5f4d781d759e..da7cb15b7cd08 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/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 526a996e1b40c..354b1e4a35749 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5581,8 +5581,9 @@ GenTree* Compiler::fgMorphArrayIndex(GenTree* tree) GenTree* arrRef = asIndex->Arr(); GenTree* index = asIndex->Index(); - bool chkd = ((tree->gtFlags & GTF_INX_RNGCHK) != 0); // if false, range checking will be disabled - bool nCSE = ((tree->gtFlags & GTF_DONT_CSE) != 0); + bool chkd = ((tree->gtFlags & GTF_INX_RNGCHK) != 0); // if false, range checking will be disabled + bool indexNonFaulting = ((tree->gtFlags & GTF_INX_NOFAULT) != 0); // if true, mark GTF_IND_NONFAULTING + bool nCSE = ((tree->gtFlags & GTF_DONT_CSE) != 0); GenTree* arrRefDefn = nullptr; // non-NULL if we need to allocate a temp for the arrRef expression GenTree* indexDefn = nullptr; // non-NULL if we need to allocate a temp for the index expression @@ -5742,9 +5743,9 @@ GenTree* Compiler::fgMorphArrayIndex(GenTree* tree) this->compFloatingPointUsed = true; } - // We've now consumed the GTF_INX_RNGCHK, and the node + // We've now consumed the GTF_INX_RNGCHK and GTF_INX_NOFAULT, and the node // is no longer a GT_INDEX node. - tree->gtFlags &= ~GTF_INX_RNGCHK; + tree->gtFlags &= ~(GTF_INX_RNGCHK | GTF_INX_NOFAULT); tree->AsOp()->gtOp1 = addr; @@ -5752,7 +5753,7 @@ GenTree* Compiler::fgMorphArrayIndex(GenTree* tree) tree->gtFlags |= GTF_IND_ARR_INDEX; // If there's a bounds check, the indir won't fault. - if (bndsChk) + if (bndsChk || indexNonFaulting) { tree->gtFlags |= GTF_IND_NONFAULTING; } @@ -12070,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(); @@ -12081,28 +12083,13 @@ 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(); } } - 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 */ @@ -12344,9 +12331,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); @@ -12481,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 ? + + GenTree* andMask = op1->AsOp()->gtOp2; - /* Now we know that we can cast AsOp()->gtOp1 of AND to int */ + 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; diff --git a/src/coreclr/jit/optcse.cpp b/src/coreclr/jit/optcse.cpp index b4137ec52ebbf..587349e489b55 100644 --- a/src/coreclr/jit/optcse.cpp +++ b/src/coreclr/jit/optcse.cpp @@ -166,7 +166,8 @@ Compiler::fgWalkResult Compiler::optCSE_MaskHelper(GenTree** pTree, fgWalkData* if (IS_CSE_INDEX(tree->gtCSEnum)) { unsigned cseIndex = GET_CSE_INDEX(tree->gtCSEnum); - unsigned cseBit = genCSEnum2bit(cseIndex); + // Note that we DO NOT use getCSEAvailBit() here, for the CSE_defMask/CSE_useMask + unsigned cseBit = genCSEnum2bit(cseIndex); if (IS_CSE_DEF(tree->gtCSEnum)) { BitVecOps::AddElemD(comp->cseMaskTraits, pUserData->CSE_defMask, cseBit); @@ -424,16 +425,16 @@ unsigned Compiler::optValnumCSE_Index(GenTree* tree, Statement* stmt) ValueNum vnLibNorm = vnStore->VNNormalValue(vnLib); // We use the normal value number because we want the CSE candidate to - // represent all expressions that produce the same normal value number + // represent all expressions that produce the same normal value number. // We will handle the case where we have different exception sets when // promoting the candidates. // // We do this because a GT_IND will usually have a NullPtrExc entry in its // exc set, but we may have cleared the GTF_EXCEPT flag and if so, it won't - // have an NullPtrExc, or we may have assigned the value of an GT_IND + // have an NullPtrExc, or we may have assigned the value of an GT_IND // into a LCL_VAR and then read it back later. // - // When we are promoting the CSE candidates we insure that any CSE + // When we are promoting the CSE candidates we ensure that any CSE // uses that we promote have an exc set that is the same as the CSE defs // or have an empty set. And that all of the CSE defs produced the required // set of exceptions for the CSE uses. @@ -502,7 +503,7 @@ unsigned Compiler::optValnumCSE_Index(GenTree* tree, Statement* stmt) key = vnLibNorm; } - // Make sure that the result of Is_Shared_Const_CSE(key) matches isSharedConst + // Make sure that the result of Is_Shared_Const_CSE(key) matches isSharedConst. // Note that when isSharedConst is true then we require that the TARGET_SIGN_BIT is set in the key // and otherwise we require that we never create a ValueNumber with the TARGET_SIGN_BIT set. // @@ -709,7 +710,6 @@ unsigned Compiler::optValnumCSE_Index(GenTree* tree, Statement* stmt) C_ASSERT((signed char)MAX_CSE_CNT == MAX_CSE_CNT); unsigned CSEindex = ++optCSECandidateCount; - // EXPSET_TP CSEmask = genCSEnum2bit(CSEindex); /* Record the new CSE index in the hashDsc */ hashDsc->csdIndex = CSEindex; @@ -746,16 +746,14 @@ unsigned Compiler::optValnumCSE_Index(GenTree* tree, Statement* stmt) } } -/***************************************************************************** - * - * Locate CSE candidates and assign indices to them - * return 0 if no CSE candidates were found - */ - -unsigned Compiler::optValnumCSE_Locate() +//------------------------------------------------------------------------ +// optValnumCSE_Locate: Locate CSE candidates and assign them indices. +// +// Returns: +// true if there are any CSE candidates, false otherwise +// +bool Compiler::optValnumCSE_Locate() { - // Locate CSE candidates and assign them indices - bool enableConstCSE = true; int configValue = JitConfig.JitConstCSE(); @@ -871,14 +869,14 @@ unsigned Compiler::optValnumCSE_Locate() if (!optDoCSE) { - return 0; + return false; } /* We're finished building the expression lookup table */ optCSEstop(); - return 1; + return true; } //------------------------------------------------------------------------ @@ -890,7 +888,7 @@ unsigned Compiler::optValnumCSE_Locate() // // Arguments: // compare - The compare node to check - +// void Compiler::optCseUpdateCheckedBoundMap(GenTree* compare) { assert(compare->OperIsCompare()); @@ -999,9 +997,9 @@ void Compiler::optValnumCSE_InitDataFlow() // Init traits and cseCallKillsMask bitvectors. cseLivenessTraits = new (getAllocator(CMK_CSE)) BitVecTraits(bitCount, this); cseCallKillsMask = BitVecOps::MakeEmpty(cseLivenessTraits); - for (unsigned inx = 0; inx < optCSECandidateCount; inx++) + for (unsigned inx = 1; inx <= optCSECandidateCount; inx++) { - unsigned cseAvailBit = inx * 2; + unsigned cseAvailBit = getCSEAvailBit(inx); // a one preserves availability and a zero kills the availability // we generate this kind of bit pattern: 101010101010 @@ -1047,7 +1045,7 @@ void Compiler::optValnumCSE_InitDataFlow() block->bbCseGen = BitVecOps::MakeEmpty(cseLivenessTraits); } - // We walk the set of CSE candidates and set the bit corresponsing to the CSEindex + // We walk the set of CSE candidates and set the bit corresponding to the CSEindex // in the block's bbCseGen bitset // for (unsigned inx = 0; inx < optCSECandidateCount; inx++) @@ -1060,16 +1058,16 @@ void Compiler::optValnumCSE_InitDataFlow() while (lst != nullptr) { BasicBlock* block = lst->tslBlock; - unsigned CseAvailBit = genCSEnum2bit(CSEindex) * 2; - unsigned cseAvailCrossCallBit = CseAvailBit + 1; + unsigned cseAvailBit = getCSEAvailBit(CSEindex); + unsigned cseAvailCrossCallBit = getCSEAvailCrossCallBit(CSEindex); - // This CSE is generated in 'block', we always set the CseAvailBit + // This CSE is generated in 'block', we always set the cseAvailBit // If this block does not contain a call, we also set cseAvailCrossCallBit // // If we have a call in this block then in the loop below we walk the trees // backwards to find any CSEs that are generated after the last call in the block. // - BitVecOps::AddElemD(cseLivenessTraits, block->bbCseGen, CseAvailBit); + BitVecOps::AddElemD(cseLivenessTraits, block->bbCseGen, cseAvailBit); if ((block->bbFlags & BBF_HAS_CALL) == 0) { BitVecOps::AddElemD(cseLivenessTraits, block->bbCseGen, cseAvailCrossCallBit); @@ -1113,7 +1111,7 @@ void Compiler::optValnumCSE_InitDataFlow() if (IS_CSE_INDEX(tree->gtCSEnum)) { unsigned CSEnum = GET_CSE_INDEX(tree->gtCSEnum); - unsigned cseAvailCrossCallBit = (genCSEnum2bit(CSEnum) * 2) + 1; + unsigned cseAvailCrossCallBit = getCSEAvailCrossCallBit(CSEnum); BitVecOps::AddElemD(cseLivenessTraits, block->bbCseGen, cseAvailCrossCallBit); } if (tree->OperGet() == GT_CALL) @@ -1142,15 +1140,16 @@ void Compiler::optValnumCSE_InitDataFlow() bool headerPrinted = false; for (BasicBlock* const block : Blocks()) { - if (block->bbCseGen != nullptr) + if (!BitVecOps::IsEmpty(cseLivenessTraits, block->bbCseGen)) { if (!headerPrinted) { printf("\nBlocks that generate CSE def/uses\n"); headerPrinted = true; } - printf(FMT_BB, block->bbNum); - printf(" cseGen = %s\n", genES2str(cseLivenessTraits, block->bbCseGen)); + printf(FMT_BB " cseGen = ", block->bbNum); + optPrintCSEDataFlowSet(block->bbCseGen); + printf("\n"); } } } @@ -1184,6 +1183,7 @@ class CSE_DataFlow // BitVecOps::Assign(m_comp->cseLivenessTraits, m_preMergeOut, block->bbCseOut); +#if 0 #ifdef DEBUG if (m_comp->verbose) { @@ -1191,11 +1191,13 @@ class CSE_DataFlow printf(" :: cseOut = %s\n", genES2str(m_comp->cseLivenessTraits, block->bbCseOut)); } #endif // DEBUG +#endif // 0 } // Merge: perform the merging of each of the predecessor's liveness values (since this is a forward analysis) void Merge(BasicBlock* block, BasicBlock* predBlock, unsigned dupCount) { +#if 0 #ifdef DEBUG if (m_comp->verbose) { @@ -1204,15 +1206,18 @@ class CSE_DataFlow printf(" :: cseOut = %s\n", genES2str(m_comp->cseLivenessTraits, block->bbCseOut)); } #endif // DEBUG +#endif // 0 BitVecOps::IntersectionD(m_comp->cseLivenessTraits, block->bbCseIn, predBlock->bbCseOut); +#if 0 #ifdef DEBUG if (m_comp->verbose) { printf(" => cseIn = %s\n", genES2str(m_comp->cseLivenessTraits, block->bbCseIn)); } #endif // DEBUG +#endif // 0 } //------------------------------------------------------------------------ @@ -1272,6 +1277,7 @@ class CSE_DataFlow // bool notDone = !BitVecOps::Equal(m_comp->cseLivenessTraits, block->bbCseOut, m_preMergeOut); +#if 0 #ifdef DEBUG if (m_comp->verbose) { @@ -1288,6 +1294,7 @@ class CSE_DataFlow notDone ? "true" : "false"); } #endif // DEBUG +#endif // 0 return notDone; } @@ -1330,10 +1337,12 @@ void Compiler::optValnumCSE_DataFlow() for (BasicBlock* const block : Blocks()) { - printf(FMT_BB, block->bbNum); - printf(" cseIn = %s,", genES2str(cseLivenessTraits, block->bbCseIn)); - printf(" cseGen = %s,", genES2str(cseLivenessTraits, block->bbCseGen)); - printf(" cseOut = %s", genES2str(cseLivenessTraits, block->bbCseOut)); + printf(FMT_BB " in gen out\n", block->bbNum); + optPrintCSEDataFlowSet(block->bbCseIn); + printf("\n"); + optPrintCSEDataFlowSet(block->bbCseGen); + printf("\n"); + optPrintCSEDataFlowSet(block->bbCseOut); printf("\n"); } @@ -1347,17 +1356,17 @@ void Compiler::optValnumCSE_DataFlow() // // Using the information computed by CSE_DataFlow determine for each // CSE whether the CSE is a definition (if the CSE was not available) -// or if the CSE is a use (if the CSE was previously made available) -// The implementation iterates of all blocks setting 'available_cses' +// or if the CSE is a use (if the CSE was previously made available). +// The implementation iterates over all blocks setting 'available_cses' // to the CSEs that are available at input to the block. // When a CSE expression is encountered it is classified as either // as a definition (if the CSE is not in the 'available_cses' set) or -// as a use (if the CSE is in the 'available_cses' set). If the CSE +// as a use (if the CSE is in the 'available_cses' set). If the CSE // is a definition then it is added to the 'available_cses' set. // // This algorithm uncovers the defs and uses gradually and as it does // so it also builds the exception set that all defs make: 'defExcSetCurrent' -// and the exception set that the uses we have seen depend upon: 'defExcSetPromise' +// and the exception set that the uses we have seen depend upon: 'defExcSetPromise'. // // Typically expressions with the same normal ValueNum generate exactly the // same exception sets. There are two way that we can get different exception @@ -1371,12 +1380,11 @@ void Compiler::optValnumCSE_DataFlow() // 2. We stored an expression into a LclVar or into Memory and read it later // e.g. t = p.a; // e1 = (t + q.b) :: e1 has one NullPtrExc and e2 has two. -// e2 = (p.a + q.b) but both compute the same normal value// +// e2 = (p.a + q.b) but both compute the same normal value // e.g. m.a = p.a; // e1 = (m.a + q.b) :: e1 and e2 have different exception sets. // e2 = (p.a + q.b) but both compute the same normal value // -// void Compiler::optValnumCSE_Availablity() { #ifdef DEBUG @@ -1411,12 +1419,12 @@ void Compiler::optValnumCSE_Availablity() if (IS_CSE_INDEX(tree->gtCSEnum)) { unsigned CSEnum = GET_CSE_INDEX(tree->gtCSEnum); - unsigned CseAvailBit = genCSEnum2bit(CSEnum) * 2; - unsigned cseAvailCrossCallBit = CseAvailBit + 1; + unsigned cseAvailBit = getCSEAvailBit(CSEnum); + unsigned cseAvailCrossCallBit = getCSEAvailCrossCallBit(CSEnum); CSEdsc* desc = optCSEfindDsc(CSEnum); BasicBlock::weight_t stmw = block->getBBWeight(this); - isUse = BitVecOps::IsMember(cseLivenessTraits, available_cses, CseAvailBit); + isUse = BitVecOps::IsMember(cseLivenessTraits, available_cses, cseAvailBit); isDef = !isUse; // If is isn't a CSE use, it is a CSE def // Is this a "use", that we haven't yet marked as live across a call @@ -1446,7 +1454,7 @@ void Compiler::optValnumCSE_Availablity() printf(FMT_BB " ", block->bbNum); printTreeID(tree); - printf(" %s of CSE #%02u [weight=%s]%s\n", isUse ? "Use" : "Def", CSEnum, refCntWtd2str(stmw), + printf(" %s of " FMT_CSE " [weight=%s]%s\n", isUse ? "Use" : "Def", CSEnum, refCntWtd2str(stmw), madeLiveAcrossCall ? " *** Now Live Across Call ***" : ""); } #endif // DEBUG @@ -1477,7 +1485,7 @@ void Compiler::optValnumCSE_Availablity() // Is defExcSetCurrent still set to the uninit marker value of VNForNull() ? if (desc->defExcSetCurrent == vnStore->VNForNull()) { - // This is the first time visited, so record this defs exeception set + // This is the first time visited, so record this defs exception set desc->defExcSetCurrent = theLiberalExcSet; } @@ -1589,7 +1597,7 @@ void Compiler::optValnumCSE_Availablity() tree->gtCSEnum = TO_CSE_DEF(tree->gtCSEnum); // This CSE becomes available after this def - BitVecOps::AddElemD(cseLivenessTraits, available_cses, CseAvailBit); + BitVecOps::AddElemD(cseLivenessTraits, available_cses, cseAvailBit); BitVecOps::AddElemD(cseLivenessTraits, available_cses, cseAvailCrossCallBit); } else // We are visiting a CSE use @@ -1636,7 +1644,7 @@ void Compiler::optValnumCSE_Availablity() if (!vnStore->VNExcIsSubset(desc->defExcSetPromise, theLiberalExcSet)) { // We can't safely make this into a CSE use, because this - // CSE use has an exeception set item that is not promised + // CSE use has an exception set item that is not promised // by all of our CSE defs. // // We will omit this CSE use from the graph and proceed, @@ -1660,7 +1668,7 @@ void Compiler::optValnumCSE_Availablity() // In order to determine if a CSE is live across a call, we model availablity using two bits and // kill all of the cseAvailCrossCallBit for each CSE whenever we see a GT_CALL (unless the call - // generates A cse) + // generates a CSE). // if (tree->OperGet() == GT_CALL) { @@ -1690,7 +1698,7 @@ void Compiler::optValnumCSE_Availablity() // available_cses // unsigned CSEnum = GET_CSE_INDEX(tree->gtCSEnum); - unsigned cseAvailCrossCallBit = (genCSEnum2bit(CSEnum) * 2) + 1; + unsigned cseAvailCrossCallBit = getCSEAvailCrossCallBit(CSEnum); BitVecOps::AddElemD(cseLivenessTraits, available_cses, cseAvailCrossCallBit); } @@ -2027,14 +2035,14 @@ class CSE_Heuristic if (!Compiler::Is_Shared_Const_CSE(dsc->csdHashKey)) { - printf("CSE #%02u, {$%-3x, $%-3x} useCnt=%d: [def=%3f, use=%3f, cost=%3u%s]\n :: ", + printf(FMT_CSE ", {$%-3x, $%-3x} useCnt=%d: [def=%3f, use=%3f, cost=%3u%s]\n :: ", dsc->csdIndex, dsc->csdHashKey, dsc->defExcSetPromise, dsc->csdUseCount, def, use, cost, dsc->csdLiveAcrossCall ? ", call" : " "); } else { size_t kVal = Compiler::Decode_Shared_Const_CSE_Value(dsc->csdHashKey); - printf("CSE #%02u, {K_%p} useCnt=%d: [def=%3f, use=%3f, cost=%3u%s]\n :: ", dsc->csdIndex, + printf(FMT_CSE ", {K_%p} useCnt=%d: [def=%3f, use=%3f, cost=%3u%s]\n :: ", dsc->csdIndex, dspPtr(kVal), dsc->csdUseCount, def, use, cost, dsc->csdLiveAcrossCall ? ", call" : " "); } @@ -2814,7 +2822,7 @@ class CSE_Heuristic if (dsc->csdDefCount == 1) { - JITDUMP("CSE #%02u is single-def, so associated CSE temp V%02u will be in SSA\n", dsc->csdIndex, + JITDUMP(FMT_CSE " is single-def, so associated CSE temp V%02u will be in SSA\n", dsc->csdIndex, cseLclVarNum); m_pCompiler->lvaTable[cseLclVarNum].lvInSsa = true; @@ -2931,7 +2939,7 @@ class CSE_Heuristic if (IS_CSE_INDEX(lst->tslTree->gtCSEnum)) { ValueNum currVN = m_pCompiler->vnStore->VNLiberalNormalValue(lst->tslTree->gtVNPair); - printf("0x%x(%s " FMT_VN ") ", lst->tslTree, + printf("[%06d](%s " FMT_VN ") ", m_pCompiler->dspTreeID(lst->tslTree), IS_CSE_USE(lst->tslTree->gtCSEnum) ? "use" : "def", currVN); } lst = lst->tslNext; @@ -2996,7 +3004,7 @@ class CSE_Heuristic #ifdef DEBUG if (m_pCompiler->verbose) { - printf("\nWorking on the replacement of the CSE #%02u use at ", exp->gtCSEnum); + printf("\nWorking on the replacement of the " FMT_CSE " use at ", exp->gtCSEnum); Compiler::printTreeID(exp); printf(" in " FMT_BB "\n", blk->bbNum); } @@ -3164,7 +3172,7 @@ class CSE_Heuristic #ifdef DEBUG if (m_pCompiler->verbose) { - printf("\nCSE #%02u def at ", GET_CSE_INDEX(exp->gtCSEnum)); + printf("\n" FMT_CSE " def at ", GET_CSE_INDEX(exp->gtCSEnum)); Compiler::printTreeID(exp); printf(" replaced in " FMT_BB " with def of V%02u\n", blk->bbNum, cseLclVarNum); } @@ -3314,13 +3322,13 @@ class CSE_Heuristic if (dsc->defExcSetPromise == ValueNumStore::NoVN) { - JITDUMP("Abandoned CSE #%02u because we had defs with different Exc sets\n", candidate.CseIndex()); + JITDUMP("Abandoned " FMT_CSE " because we had defs with different Exc sets\n", candidate.CseIndex()); continue; } if (dsc->csdStructHndMismatch) { - JITDUMP("Abandoned CSE #%02u because we had mismatching struct handles\n", candidate.CseIndex()); + JITDUMP("Abandoned " FMT_CSE " because we had mismatching struct handles\n", candidate.CseIndex()); continue; } @@ -3328,7 +3336,7 @@ class CSE_Heuristic if (candidate.UseCount() == 0) { - JITDUMP("Skipped CSE #%02u because use count is 0\n", candidate.CseIndex()); + JITDUMP("Skipped " FMT_CSE " because use count is 0\n", candidate.CseIndex()); continue; } @@ -3337,14 +3345,14 @@ class CSE_Heuristic { if (!Compiler::Is_Shared_Const_CSE(dsc->csdHashKey)) { - printf("\nConsidering CSE #%02u {$%-3x, $%-3x} [def=%3f, use=%3f, cost=%3u%s]\n", + printf("\nConsidering " FMT_CSE " {$%-3x, $%-3x} [def=%3f, use=%3f, cost=%3u%s]\n", candidate.CseIndex(), dsc->csdHashKey, dsc->defExcSetPromise, candidate.DefCount(), candidate.UseCount(), candidate.Cost(), dsc->csdLiveAcrossCall ? ", call" : " "); } else { size_t kVal = Compiler::Decode_Shared_Const_CSE_Value(dsc->csdHashKey); - printf("\nConsidering CSE #%02u {K_%p} [def=%3f, use=%3f, cost=%3u%s]\n", candidate.CseIndex(), + printf("\nConsidering " FMT_CSE " {K_%p} [def=%3f, use=%3f, cost=%3u%s]\n", candidate.CseIndex(), dspPtr(kVal), candidate.DefCount(), candidate.UseCount(), candidate.Cost(), dsc->csdLiveAcrossCall ? ", call" : " "); } @@ -3428,11 +3436,6 @@ void Compiler::optValnumCSE_Heuristic() void Compiler::optOptimizeValnumCSEs() { #ifdef DEBUG - if (verbose) - { - printf("\n*************** In optOptimizeValnumCSEs()\n"); - } - if (optConfigDisableCSE()) { return; // Disabled by JitNoCSE @@ -3441,22 +3444,13 @@ void Compiler::optOptimizeValnumCSEs() optValnumCSE_phase = true; - /* Initialize the expression tracking logic */ - optValnumCSE_Init(); - /* Locate interesting expressions and assign indices to them */ - - if (optValnumCSE_Locate() > 0) + if (optValnumCSE_Locate()) { - optCSECandidateTotal += optCSECandidateCount; - optValnumCSE_InitDataFlow(); - optValnumCSE_DataFlow(); - optValnumCSE_Availablity(); - optValnumCSE_Heuristic(); } @@ -3807,21 +3801,11 @@ bool Compiler::optConfigDisableCSE2() void Compiler::optOptimizeCSEs() { -#ifdef DEBUG - if (verbose) - { - printf("\n*************** In optOptimizeCSEs()\n"); - printf("Blocks/Trees at start of optOptimizeCSE phase\n"); - fgDispBasicBlocks(true); - } -#endif // DEBUG - optCSECandidateCount = 0; optCSEstart = lvaCount; INDEBUG(optEnsureClearCSEInfo()); optOptimizeValnumCSEs(); - EndPhase(PHASE_OPTIMIZE_VALNUM_CSES); } /***************************************************************************** @@ -3873,4 +3857,38 @@ void Compiler::optEnsureClearCSEInfo() } } +//------------------------------------------------------------------------ +// optPrintCSEDataFlowSet: Print out one of the CSE dataflow sets bbCseGen, bbCseIn, bbCseOut, +// interpreting the bits in a more useful way for the dump. +// +// Arguments: +// cseDataFlowSet - One of the dataflow sets to display +// includeBits - Display the actual bits of the set as well +// +void Compiler::optPrintCSEDataFlowSet(EXPSET_VALARG_TP cseDataFlowSet, bool includeBits /* = true */) +{ + if (includeBits) + { + printf("%s ", genES2str(cseLivenessTraits, cseDataFlowSet)); + } + + bool first = true; + for (unsigned cseIndex = 1; cseIndex <= optCSECandidateCount; cseIndex++) + { + unsigned cseAvailBit = getCSEAvailBit(cseIndex); + unsigned cseAvailCrossCallBit = getCSEAvailCrossCallBit(cseIndex); + + if (BitVecOps::IsMember(cseLivenessTraits, cseDataFlowSet, cseAvailBit)) + { + if (!first) + { + printf(", "); + } + const bool isAvailCrossCall = BitVecOps::IsMember(cseLivenessTraits, cseDataFlowSet, cseAvailCrossCallBit); + printf(FMT_CSE "%s", cseIndex, isAvailCrossCall ? ".c" : ""); + first = false; + } + } +} + #endif // DEBUG diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index a7df5b95905a2..748d5feabb562 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -38,7 +38,6 @@ void Compiler::optInit() optNativeCallCount = 0; optAssertionCount = 0; optAssertionDep = nullptr; - optCSECandidateTotal = 0; optCSEstart = UINT_MAX; optCSEcount = 0; } @@ -5627,8 +5626,8 @@ void Compiler::optHoistLoopCode() #endif #if 0 - // The code in this #if has been useful in debugging loop cloning issues, by - // enabling selective enablement of the loop cloning optimization according to + // The code in this #if has been useful in debugging loop hoisting issues, by + // enabling selective enablement of the loop hoisting optimization according to // method hash. #ifdef DEBUG unsigned methHash = info.compMethodHash(); @@ -5650,7 +5649,7 @@ void Compiler::optHoistLoopCode() return; printf("Doing loop hoisting in %s (0x%x).\n", info.compFullName, methHash); #endif // DEBUG -#endif // 0 -- debugging loop cloning issues +#endif // 0 -- debugging loop hoisting issues #ifdef DEBUG if (verbose) @@ -5899,6 +5898,8 @@ void Compiler::optHoistThisLoop(unsigned lnum, LoopHoistContext* hoistCtxt) printf("\n LOOPV-FP(%d)=", pLoopDsc->lpLoopVarFPCount); lvaDispVarSet(loopFPVars); + + printf("\n"); } #endif } diff --git a/src/coreclr/jit/phase.cpp b/src/coreclr/jit/phase.cpp index 530f8e74cbba0..f08134bf11532 100644 --- a/src/coreclr/jit/phase.cpp +++ b/src/coreclr/jit/phase.cpp @@ -84,8 +84,9 @@ void Phase::PrePhase() // // Currently the list is just the set of phases that have custom // derivations from the Phase class. - static Phases s_allowlist[] = {PHASE_BUILD_SSA, PHASE_RATIONALIZE, PHASE_LOWERING, PHASE_STACK_LEVEL_SETTER}; - bool doPrePhase = false; + static Phases s_allowlist[] = {PHASE_BUILD_SSA, PHASE_OPTIMIZE_VALNUM_CSES, PHASE_RATIONALIZE, PHASE_LOWERING, + PHASE_STACK_LEVEL_SETTER}; + bool doPrePhase = false; for (size_t i = 0; i < sizeof(s_allowlist) / sizeof(Phases); i++) { diff --git a/src/coreclr/minipal/CMakeLists.txt b/src/coreclr/minipal/CMakeLists.txt new file mode 100644 index 0000000000000..3096237d2a2fe --- /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 0000000000000..b56b5017d375f --- /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 0000000000000..6e0278e3ccd69 --- /dev/null +++ b/src/coreclr/minipal/Unix/doublemapping.cpp @@ -0,0 +1,208 @@ +// 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 +#if defined(TARGET_LINUX) && !defined(MFD_CLOEXEC) +#include +#include // __NR_memfd_create +#define memfd_create(...) syscall(__NR_memfd_create, __VA_ARGS__) +#endif // TARGET_LINUX && !MFD_CLOEXEC +#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 + +#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 0000000000000..b56b5017d375f --- /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 0000000000000..e265f1d139ad0 --- /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 0000000000000..39098f9bc1295 --- /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/pal/prebuilt/inc/corprof.h b/src/coreclr/pal/prebuilt/inc/corprof.h index 85ce86870bf8c..e82623d0c09f0 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/pal/prebuilt/inc/xclrdata.h b/src/coreclr/pal/prebuilt/inc/xclrdata.h index dbee62b9b1225..b28463dc86c78 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] */ diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.cs b/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.cs index 46c53157b465a..f78e4df8a29e7 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(); diff --git a/src/coreclr/utilcode/CMakeLists.txt b/src/coreclr/utilcode/CMakeLists.txt index 1ae433adbfd89..8c57742cb6315 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 0000000000000..ac4c326c83784 --- /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 adaf07d8f5825..b3b381b2f9bef 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 0026d1f619f14..e7b1755b2b1c4 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/utilcode/yieldprocessornormalized.cpp b/src/coreclr/utilcode/yieldprocessornormalized.cpp index 4242f82792b47..020d8d7cc79e4 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 1d682d2a428bb..f31e5a3ca12a6 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} ) @@ -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/ClrEtwAll.man b/src/coreclr/vm/ClrEtwAll.man index 45895f16fce48..0eed049c17ba4 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 4ac4fe405d9da..9c5738ef43dbc 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/amd64/JitHelpers_Fast.asm b/src/coreclr/vm/amd64/JitHelpers_Fast.asm index 82a301bb0cbd1..219597eb350c2 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 a13afb4878511..8109886d0c969 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 38bff78a54cb0..02b023777b8a9 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 79317263b2223..f9e718ae5420e 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 930395b56dc7e..3faa8fe36846e 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 d20540e62090e..82596e66693dc 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 88d0c6802b69d..425c286558432 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 aac3e25b18146..6e62df2370338 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 d45925311a33e..6c1764647c9f2 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 e6b47d07b2b0c..8ef66586cd22c 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 ffbeb9fd1acb3..17d3a676940bd 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 83e56cfb9f9b9..0641d89ff1a91 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 54cf1c4927548..12d56ddb9867e 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 cdc5925234af9..b60aac924d2e2 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 02feec829a76b..5c5004f56860a 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 37220786fedda..78721292a3e9f 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 8b95dac8cdd77..499880dc16dde 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 2581ddf832fd5..0f1e4b878e4c9 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 b6c17260a1302..03cecd838023b 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); @@ -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; } } diff --git a/src/coreclr/vm/comsynchronizable.cpp b/src/coreclr/vm/comsynchronizable.cpp index 39f00d0674193..15a33c711e7a9 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 e9968201b8bc2..cfab18d901070 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/custommarshalerinfo.cpp b/src/coreclr/vm/custommarshalerinfo.cpp index 67acbff136ec3..0af48f8e71576 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/coreclr/vm/dllimportcallback.cpp b/src/coreclr/vm/dllimportcallback.cpp index 4a88f81df5210..4f3cf879d10a4 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 9dae86aca9377..541d88dc16885 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/ecalllist.h b/src/coreclr/vm/ecalllist.h index f77dc75c80b5c..ea3f65d72917d 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/eeprofinterfaces.inl b/src/coreclr/vm/eeprofinterfaces.inl index 250b3700f801a..da6e978832968 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/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h index 607286d048bab..7ee83ae8c5f84 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/coreclr/vm/eventtrace.cpp b/src/coreclr/vm/eventtrace.cpp index ac7be2a94398f..aded74deda619 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/excep.cpp b/src/coreclr/vm/excep.cpp index a1fdf255a5ce0..6bf5efcc8028c 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 7fff234ca85ef..4af702fab1499 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/finalizerthread.cpp b/src/coreclr/vm/finalizerthread.cpp index 1e4dbf913c898..e8370315e6665 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/gccover.cpp b/src/coreclr/vm/gccover.cpp index be856dbe1a63a..9ce0cc676f7a7 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/gchelpers.cpp b/src/coreclr/vm/gchelpers.cpp index 0cecfc624a744..01ffd5305d9f0 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/coreclr/vm/i386/jithelp.S b/src/coreclr/vm/i386/jithelp.S index facce7cacd3ef..dc56da1d1779e 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 3743ac3cbe02f..3650b3f2afd6d 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 0e366bdbd1a8b..0467f347aaacb 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 61c5dfd90cbfc..564363053fc6a 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 af5244d077193..564c999975e7c 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 a1e4d93d881de..882e2c29cef04 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 ca9d03c2141d3..e071d0717d179 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 4f222be4a2c03..0a77e4445f06f 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 a826675ccc93c..993732d4010f8 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 bd3984d8697cd..db308ab208a8e 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/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index 834d3cfc8c40b..8bdc5f0c2a892 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 4063e50b7b61c..df712a378d4ec 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 a3adc702cbe3d..b1af313a29556 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 89926ffdaae02..154c642cf40d7 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/coreclr/vm/precode.cpp b/src/coreclr/vm/precode.cpp index 80731c191e737..0bd2bd657f9ad 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 0971334af4d31..e61802b984950 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 04a33e3982613..304cb4fb35b44 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 94326f9962ea7..9613fd48f687d 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 fa93110399d39..c6485b86d59c7 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. @@ -1131,57 +1150,85 @@ 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); _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); @@ -7145,6 +7192,7 @@ BOOL Thread::HaveExtraWorkForFinalizer() || Thread::CleanupNeededForFinalizedThread() || (m_DetachCount > 0) || SystemDomain::System()->RequireAppDomainCleanup() + || YieldProcessorNormalization::IsMeasurementScheduled() || ThreadStore::s_pThreadStore->ShouldTriggerGCForDeadThreads(); } @@ -7191,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/threads.h b/src/coreclr/vm/threads.h index d18b21d58f95a..7d600dab5edac 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/threadstatics.cpp b/src/coreclr/vm/threadstatics.cpp index 2644d7ad5fc91..bac0f082fb8ed 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 1755bf7230d20..ddb59b5cbc20d 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) diff --git a/src/coreclr/vm/virtualcallstub.cpp b/src/coreclr/vm/virtualcallstub.cpp index 95d568d641c73..3af4c52afc9bb 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, diff --git a/src/coreclr/vm/yieldprocessornormalized.cpp b/src/coreclr/vm/yieldprocessornormalized.cpp index 91547923310fb..2c51e73b678d8 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/installer/tests/Assets/TestProjects/RuntimeProperties/Program.cs b/src/installer/tests/Assets/TestProjects/RuntimeProperties/Program.cs index d22677aa617d8..96fa3b2adbad2 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 2a99ab5681f75..0a8d54559e227 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 6d8949c4132a5..01b5b415ecd26 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/libraries/Common/src/Internal/Cryptography/Helpers.cs b/src/libraries/Common/src/Internal/Cryptography/Helpers.cs index 2ee5ffc39c4f7..b1777c1c1e585 100644 --- a/src/libraries/Common/src/Internal/Cryptography/Helpers.cs +++ b/src/libraries/Common/src/Internal/Cryptography/Helpers.cs @@ -30,17 +30,12 @@ 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) + internal static bool TryCopyToDestination(this ReadOnlySpan source, Span destination, out int bytesWritten) { if (source.TryCopyTo(destination)) { 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 eeb472a19e5d7..8be9f32cbedee 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 5dc24477beef5..22b06128926fa 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 0ca179b736eca..a85ec0a5f4ffb 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/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 862760aa2c8de..c93319994bbd5 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/Unix/System.Native/Interop.GetGroups.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetGroups.cs new file mode 100644 index 0000000000000..2fd31563f1745 --- /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 cbc54d4072c42..8248077deaf50 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/Common/src/Interop/Unix/System.Native/Interop.PosixSignal.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PosixSignal.cs index b42b5138ec622..65e3ca35f4844 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/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 a2c6255541e28..f334479290011 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.EvpPkey.Rsa.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs index c8849d343d9ce..6437c1a7addc4 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs @@ -11,6 +11,24 @@ internal static partial class Interop { internal static partial class Crypto { + [DllImport(Libraries.CryptoNative)] + private static extern SafeEvpPKeyHandle CryptoNative_EvpPKeyCreateRsa(IntPtr rsa); + + internal static SafeEvpPKeyHandle EvpPKeyCreateRsa(IntPtr rsa) + { + Debug.Assert(rsa != IntPtr.Zero); + + SafeEvpPKeyHandle pkey = CryptoNative_EvpPKeyCreateRsa(rsa); + + if (pkey.IsInvalid) + { + pkey.Dispose(); + throw CreateOpenSslCryptographicException(); + } + + return pkey; + } + [DllImport(Libraries.CryptoNative)] private static extern SafeEvpPKeyHandle CryptoNative_RsaGenerateKey(int keySize); @@ -171,16 +189,5 @@ ref MemoryMarshal.GetReference(signature), Debug.Assert(ret == -1); throw CreateOpenSslCryptographicException(); } - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyGetRsa")] - internal static extern SafeRsaHandle EvpPkeyGetRsa(SafeEvpPKeyHandle pkey); - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeySetRsa")] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool EvpPkeySetRsa(SafeEvpPKeyHandle pkey, SafeRsaHandle rsa); - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeySetRsa")] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool EvpPkeySetRsa(SafeEvpPKeyHandle pkey, IntPtr rsa); } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs index 8a3fc840b1610..924c2da14d4b5 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -12,6 +13,30 @@ internal static partial class Crypto [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyCreate")] internal static extern SafeEvpPKeyHandle EvpPkeyCreate(); + [DllImport(Libraries.CryptoNative)] + private static extern SafeEvpPKeyHandle CryptoNative_EvpPKeyDuplicate( + SafeEvpPKeyHandle currentKey, + EvpAlgorithmId algorithmId); + + internal static SafeEvpPKeyHandle EvpPKeyDuplicate( + SafeEvpPKeyHandle currentKey, + EvpAlgorithmId algorithmId) + { + Debug.Assert(!currentKey.IsInvalid); + + SafeEvpPKeyHandle pkey = CryptoNative_EvpPKeyDuplicate( + currentKey, + algorithmId); + + if (pkey.IsInvalid) + { + pkey.Dispose(); + throw CreateOpenSslCryptographicException(); + } + + return pkey; + } + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyDestroy")] internal static extern void EvpPkeyDestroy(IntPtr pkey); @@ -20,5 +45,173 @@ internal static partial class Crypto [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_UpRefEvpPkey")] internal static extern int UpRefEvpPkey(SafeEvpPKeyHandle handle); + + [DllImport(Libraries.CryptoNative)] + private static extern unsafe SafeEvpPKeyHandle CryptoNative_DecodeSubjectPublicKeyInfo( + byte* buf, + int len, + int algId); + + [DllImport(Libraries.CryptoNative)] + private static extern unsafe SafeEvpPKeyHandle CryptoNative_DecodePkcs8PrivateKey( + byte* buf, + int len, + int algId); + + internal static unsafe SafeEvpPKeyHandle DecodeSubjectPublicKeyInfo( + ReadOnlySpan source, + EvpAlgorithmId algorithmId) + { + SafeEvpPKeyHandle handle; + + fixed (byte* sourcePtr = source) + { + handle = CryptoNative_DecodeSubjectPublicKeyInfo( + sourcePtr, + source.Length, + (int)algorithmId); + } + + if (handle.IsInvalid) + { + handle.Dispose(); + throw CreateOpenSslCryptographicException(); + } + + return handle; + } + + internal static unsafe SafeEvpPKeyHandle DecodePkcs8PrivateKey( + ReadOnlySpan source, + EvpAlgorithmId algorithmId) + { + SafeEvpPKeyHandle handle; + + fixed (byte* sourcePtr = source) + { + handle = CryptoNative_DecodePkcs8PrivateKey( + sourcePtr, + source.Length, + (int)algorithmId); + } + + if (handle.IsInvalid) + { + handle.Dispose(); + throw CreateOpenSslCryptographicException(); + } + + return handle; + } + + [DllImport(Libraries.CryptoNative)] + private static extern int CryptoNative_GetPkcs8PrivateKeySize(IntPtr pkey); + + private static int GetPkcs8PrivateKeySize(IntPtr pkey) + { + int ret = CryptoNative_GetPkcs8PrivateKeySize(pkey); + + if (ret < 0) + { + throw CreateOpenSslCryptographicException(); + } + + return ret; + } + + [DllImport(Libraries.CryptoNative)] + private static extern unsafe int CryptoNative_EncodePkcs8PrivateKey(IntPtr pkey, byte* buf); + + internal static ArraySegment RentEncodePkcs8PrivateKey(SafeEvpPKeyHandle pkey) + { + bool addedRef = false; + + try + { + pkey.DangerousAddRef(ref addedRef); + IntPtr handle = pkey.DangerousGetHandle(); + + int size = GetPkcs8PrivateKeySize(handle); + byte[] rented = CryptoPool.Rent(size); + int written; + + unsafe + { + fixed (byte* buf = rented) + { + written = CryptoNative_EncodePkcs8PrivateKey(handle, buf); + } + } + + Debug.Assert(written == size); + return new ArraySegment(rented, 0, written); + } + finally + { + if (addedRef) + { + pkey.DangerousRelease(); + } + } + } + + [DllImport(Libraries.CryptoNative)] + private static extern int CryptoNative_GetSubjectPublicKeyInfoSize(IntPtr pkey); + + private static int GetSubjectPublicKeyInfoSize(IntPtr pkey) + { + int ret = CryptoNative_GetSubjectPublicKeyInfoSize(pkey); + + if (ret < 0) + { + throw CreateOpenSslCryptographicException(); + } + + return ret; + } + + [DllImport(Libraries.CryptoNative)] + private static extern unsafe int CryptoNative_EncodeSubjectPublicKeyInfo(IntPtr pkey, byte* buf); + + internal static ArraySegment RentEncodeSubjectPublicKeyInfo(SafeEvpPKeyHandle pkey) + { + bool addedRef = false; + + try + { + pkey.DangerousAddRef(ref addedRef); + IntPtr handle = pkey.DangerousGetHandle(); + + int size = GetSubjectPublicKeyInfoSize(handle); + byte[] rented = CryptoPool.Rent(size); + int written; + + unsafe + { + fixed (byte* buf = rented) + { + written = CryptoNative_EncodeSubjectPublicKeyInfo(handle, buf); + } + } + + Debug.Assert(written == size); + return new ArraySegment(rented, 0, written); + } + finally + { + if (addedRef) + { + pkey.DangerousRelease(); + } + } + } + + internal enum EvpAlgorithmId + { + Unknown = 0, + RSA = 6, + DSA = 116, + ECC = 408, + } } } 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 604ac8b4c7980..9dd310326b0ad 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; @@ -295,7 +309,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 +363,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/Unix/System.Security.Cryptography.Native/Interop.Rsa.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Rsa.cs deleted file mode 100644 index 105258194845c..0000000000000 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Rsa.cs +++ /dev/null @@ -1,136 +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; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using Microsoft.Win32.SafeHandles; - -internal static partial class Interop -{ - internal static partial class Crypto - { - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_RsaCreate")] - internal static extern SafeRsaHandle RsaCreate(); - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_RsaUpRef")] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool RsaUpRef(IntPtr rsa); - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_RsaDestroy")] - internal static extern void RsaDestroy(IntPtr rsa); - - internal static SafeRsaHandle DecodeRsaPublicKey(ReadOnlySpan buf) => - DecodeRsaPublicKey(ref MemoryMarshal.GetReference(buf), buf.Length); - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_DecodeRsaPublicKey")] - private static extern SafeRsaHandle DecodeRsaPublicKey(ref byte buf, int len); - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_RsaSize")] - internal static extern int RsaSize(SafeRsaHandle rsa); - - internal static RSAParameters ExportRsaParameters(SafeEvpPKeyHandle key, bool includePrivateParameters) - { - using (SafeRsaHandle rsa = EvpPkeyGetRsa(key)) - { - return ExportRsaParameters(rsa, includePrivateParameters); - } - } - - internal static RSAParameters ExportRsaParameters(SafeRsaHandle key, bool includePrivateParameters) - { - Debug.Assert( - key != null && !key.IsInvalid, - "Callers should check the key is invalid and throw an exception with a message"); - - if (key == null || key.IsInvalid) - { - throw new CryptographicException(); - } - - bool addedRef = false; - - try - { - key.DangerousAddRef(ref addedRef); - - IntPtr n, e, d, p, dmp1, q, dmq1, iqmp; - if (!GetRsaParameters(key, out n, out e, out d, out p, out dmp1, out q, out dmq1, out iqmp)) - { - throw new CryptographicException(); - } - - int modulusSize = Crypto.RsaSize(key); - - // RSACryptoServiceProvider expects P, DP, Q, DQ, and InverseQ to all - // be padded up to half the modulus size. - int halfModulus = modulusSize / 2; - - RSAParameters rsaParameters = new RSAParameters - { - Modulus = Crypto.ExtractBignum(n, modulusSize)!, - Exponent = Crypto.ExtractBignum(e, 0)!, - }; - - if (includePrivateParameters) - { - rsaParameters.D = Crypto.ExtractBignum(d, modulusSize); - rsaParameters.P = Crypto.ExtractBignum(p, halfModulus); - rsaParameters.DP = Crypto.ExtractBignum(dmp1, halfModulus); - rsaParameters.Q = Crypto.ExtractBignum(q, halfModulus); - rsaParameters.DQ = Crypto.ExtractBignum(dmq1, halfModulus); - rsaParameters.InverseQ = Crypto.ExtractBignum(iqmp, halfModulus); - } - - return rsaParameters; - } - finally - { - if (addedRef) - key.DangerousRelease(); - } - } - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetRsaParameters")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool GetRsaParameters( - SafeRsaHandle key, - out IntPtr n, - out IntPtr e, - out IntPtr d, - out IntPtr p, - out IntPtr dmp1, - out IntPtr q, - out IntPtr dmq1, - out IntPtr iqmp); - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SetRsaParameters")] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool SetRsaParameters( - SafeRsaHandle key, - byte[]? n, - int nLength, - byte[]? e, - int eLength, - byte[]? d, - int dLength, - byte[]? p, - int pLength, - byte[]? dmp1, - int dmp1Length, - byte[]? q, - int qLength, - byte[]? dmq1, - int dmq1Length, - byte[]? iqmp, - int iqmpLength); - - internal enum RsaPadding : int - { - Pkcs1 = 0, - OaepSHA1 = 1, - NoPadding = 2, - } - } -} 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 d080cf2f0d7b4..29154b77da632 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/Common/src/Interop/Windows/HttpApi/Interop.HttpApi.cs b/src/libraries/Common/src/Interop/Windows/HttpApi/Interop.HttpApi.cs index e0982d907c687..27c22800a42d5 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/Ole32/Interop.CoGetObjectContext.cs b/src/libraries/Common/src/Interop/Windows/Ole32/Interop.CoGetObjectContext.cs index 4326327034382..c73b065ea5953 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 1b153d786dfba..d65814a2f51c6 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/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs index b675f7f022473..1694ca539a86a 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/Common/src/Interop/Windows/SspiCli/SecurityPackageInfoClass.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SecurityPackageInfoClass.cs index ea65be9600eba..ada41ba189fc5 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/Interop/Windows/SspiCli/SecuritySafeHandles.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs index 0f2cb05603aca..43b718524d1ca 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/Interop/Windows/WinHttp/Interop.winhttp.cs b/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp.cs index 7ac85b3155bbc..82a8218338ca5 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/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs index 193533551bcea..f8b26951cf226 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 0c6a8304620ad..20eeb6dfbf818 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/SafeRsaHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeRsaHandle.Unix.cs deleted file mode 100644 index aef516adb3a30..0000000000000 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeRsaHandle.Unix.cs +++ /dev/null @@ -1,47 +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; -using System.Diagnostics; -using System.Security; -using System.Runtime.InteropServices; - -namespace Microsoft.Win32.SafeHandles -{ - internal sealed class SafeRsaHandle : SafeHandle - { - public SafeRsaHandle() : - base(IntPtr.Zero, ownsHandle: true) - { - } - - protected override bool ReleaseHandle() - { - Interop.Crypto.RsaDestroy(handle); - SetHandle(IntPtr.Zero); - return true; - } - - public override bool IsInvalid - { - get { return handle == IntPtr.Zero; } - } - - internal static SafeRsaHandle DuplicateHandle(IntPtr handle) - { - Debug.Assert(handle != IntPtr.Zero); - - // Reliability: Allocate the SafeHandle before calling RSA_up_ref so - // that we don't lose a tracked reference in low-memory situations. - SafeRsaHandle safeHandle = new SafeRsaHandle(); - - if (!Interop.Crypto.RsaUpRef(handle)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - safeHandle.SetHandle(handle); - return safeHandle; - } - } -} diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeUnicodeStringHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeUnicodeStringHandle.cs index e58bfd19695ea..0c27a20049577 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/CodeDom/CodeTypeReference.cs b/src/libraries/Common/src/System/CodeDom/CodeTypeReference.cs index 281691f7907f6..4ddf9950f5cc7 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 8630d366a866e..f716210055d63 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 80a092104ae50..54df9fa779826 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 d1caed09be28d..b8fc7866abe24 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 a6bfd96daf413..571aa9afd9e8a 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 4e4e0c672b496..a145951254a76 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/src/System/Net/Security/SecurityContextTokenHandle.cs b/src/libraries/Common/src/System/Net/Security/SecurityContextTokenHandle.cs index 8e889f852afc3..4f4c9951a72ed 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 123a28ace0f49..2e5a0b8560027 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 9b27cc0d969fd..3f550d36d7f43 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 c5fa820ab9279..ca7438a33b483 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 58d1942829e52..fef571d5f7dbd 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 8867027d07042..ff378ab425139 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/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs index d06ce8117fc5a..8de1e2a0f4ce3 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 de0465a51c6c7..50e7db176e2f1 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 0000000000000..a61f47a0fa458 --- /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/Common/src/System/Obsoletions.cs b/src/libraries/Common/src/System/Obsoletions.cs index 613d51a5ba783..5548be60e6934 100644 --- a/src/libraries/Common/src/System/Obsoletions.cs +++ b/src/libraries/Common/src/System/Obsoletions.cs @@ -101,5 +101,8 @@ internal static class Obsoletions internal const string UseManagedSha1Message = "HMACSHA1 always uses the algorithm implementation provided by the platform. Use a constructor without the useManagedSha1 parameter."; internal const string UseManagedSha1DiagId = "SYSLIB0030"; + + internal const string CryptoConfigEncodeOIDMessage = "EncodeOID is obsolete. Use the ASN.1 functionality provided in System.Formats.Asn1."; + internal const string CryptoConfigEncodeOIDDiagId = "SYSLIB0031"; } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1/AlgorithmIdentifierAsn.manual.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1/AlgorithmIdentifierAsn.manual.cs index c7a4c5784ab91..058093035af1e 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1/AlgorithmIdentifierAsn.manual.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1/AlgorithmIdentifierAsn.manual.cs @@ -32,7 +32,7 @@ internal bool Equals(ref AlgorithmIdentifierAsn other) return Parameters!.Value.Span.SequenceEqual(other.Parameters!.Value.Span); } - internal bool HasNullEquivalentParameters() + internal readonly bool HasNullEquivalentParameters() { return RepresentsNull(Parameters); } diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnValueReader.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnValueReader.cs index 6e22b91fb3687..a16f66c40c89e 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnValueReader.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnValueReader.cs @@ -227,5 +227,12 @@ internal static void WriteObjectIdentifierForCrypto( throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); } } + + internal static ArraySegment RentAndEncode(this AsnWriter writer) + { + byte[] rented = CryptoPool.Rent(writer.GetEncodedLength()); + int written = writer.Encode(rented); + return new ArraySegment(rented, 0, written); + } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.Encrypted.cs b/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.Encrypted.cs new file mode 100644 index 0000000000000..b4d07d7366aff --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.Encrypted.cs @@ -0,0 +1,393 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Diagnostics; +using System.Formats.Asn1; +using System.Runtime.InteropServices; +using System.Security.Cryptography.Asn1; + +namespace System.Security.Cryptography +{ + internal static partial class KeyFormatHelper + { + internal static unsafe void ReadEncryptedPkcs8( + string[] validOids, + ReadOnlySpan source, + ReadOnlySpan password, + KeyReader keyReader, + out int bytesRead, + out TRet ret) + { + fixed (byte* ptr = &MemoryMarshal.GetReference(source)) + { + using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) + { + ReadEncryptedPkcs8(validOids, manager.Memory, password, keyReader, out bytesRead, out ret); + } + } + } + + internal static unsafe void ReadEncryptedPkcs8( + string[] validOids, + ReadOnlySpan source, + ReadOnlySpan passwordBytes, + KeyReader keyReader, + out int bytesRead, + out TRet ret) + { + fixed (byte* ptr = &MemoryMarshal.GetReference(source)) + { + using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) + { + ReadEncryptedPkcs8( + validOids, + manager.Memory, + passwordBytes, + keyReader, + out bytesRead, + out ret); + } + } + } + + private static void ReadEncryptedPkcs8( + string[] validOids, + ReadOnlyMemory source, + ReadOnlySpan password, + KeyReader keyReader, + out int bytesRead, + out TRet ret) + { + ReadEncryptedPkcs8( + validOids, + source, + password, + ReadOnlySpan.Empty, + keyReader, + out bytesRead, + out ret); + } + + private static void ReadEncryptedPkcs8( + string[] validOids, + ReadOnlyMemory source, + ReadOnlySpan passwordBytes, + KeyReader keyReader, + out int bytesRead, + out TRet ret) + { + ReadEncryptedPkcs8( + validOids, + source, + ReadOnlySpan.Empty, + passwordBytes, + keyReader, + out bytesRead, + out ret); + } + + private static void ReadEncryptedPkcs8( + string[] validOids, + ReadOnlyMemory source, + ReadOnlySpan password, + ReadOnlySpan passwordBytes, + KeyReader keyReader, + out int bytesRead, + out TRet ret) + { + int read; + EncryptedPrivateKeyInfoAsn epki; + + try + { + AsnValueReader reader = new AsnValueReader(source.Span, AsnEncodingRules.BER); + read = reader.PeekEncodedValue().Length; + EncryptedPrivateKeyInfoAsn.Decode(ref reader, source, out epki); + } + catch (AsnContentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + + // No supported encryption algorithms produce more bytes of decryption output than there + // were of decryption input. + byte[] decrypted = CryptoPool.Rent(epki.EncryptedData.Length); + Memory decryptedMemory = decrypted; + + try + { + int decryptedBytes = PasswordBasedEncryption.Decrypt( + epki.EncryptionAlgorithm, + password, + passwordBytes, + epki.EncryptedData.Span, + decrypted); + + decryptedMemory = decryptedMemory.Slice(0, decryptedBytes); + + ReadPkcs8( + validOids, + decryptedMemory, + keyReader, + out int innerRead, + out ret); + + if (innerRead != decryptedMemory.Length) + { + ret = default!; + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + bytesRead = read; + } + catch (CryptographicException e) + { + throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e); + } + finally + { + CryptographicOperations.ZeroMemory(decryptedMemory.Span); + CryptoPool.Return(decrypted, clearSize: 0); + } + } + + internal static AsnWriter WriteEncryptedPkcs8( + ReadOnlySpan password, + AsnWriter pkcs8Writer, + PbeParameters pbeParameters) + { + return WriteEncryptedPkcs8( + password, + ReadOnlySpan.Empty, + pkcs8Writer, + pbeParameters); + } + + internal static AsnWriter WriteEncryptedPkcs8( + ReadOnlySpan passwordBytes, + AsnWriter pkcs8Writer, + PbeParameters pbeParameters) + { + return WriteEncryptedPkcs8( + ReadOnlySpan.Empty, + passwordBytes, + pkcs8Writer, + pbeParameters); + } + + private static AsnWriter WriteEncryptedPkcs8( + ReadOnlySpan password, + ReadOnlySpan passwordBytes, + AsnWriter pkcs8Writer, + PbeParameters pbeParameters) + { + PasswordBasedEncryption.InitiateEncryption( + pbeParameters, + out SymmetricAlgorithm cipher, + out string hmacOid, + out string encryptionAlgorithmOid, + out bool isPkcs12); + + byte[]? encryptedRent = null; + Span encryptedSpan = default; + AsnWriter? writer = null; + + try + { + Debug.Assert(cipher.BlockSize <= 128, $"Encountered unexpected block size: {cipher.BlockSize}"); + Span iv = stackalloc byte[cipher.BlockSize / 8]; + Span salt = stackalloc byte[16]; + + // We need at least one block size beyond the input data size. + encryptedRent = CryptoPool.Rent( + checked(pkcs8Writer.GetEncodedLength() + (cipher.BlockSize / 8))); + + RandomNumberGenerator.Fill(salt); + + int written = PasswordBasedEncryption.Encrypt( + password, + passwordBytes, + cipher, + isPkcs12, + pkcs8Writer, + pbeParameters, + salt, + encryptedRent, + iv); + + encryptedSpan = encryptedRent.AsSpan(0, written); + + writer = new AsnWriter(AsnEncodingRules.DER); + + // PKCS8 EncryptedPrivateKeyInfo + writer.PushSequence(); + + // EncryptedPrivateKeyInfo.encryptionAlgorithm + PasswordBasedEncryption.WritePbeAlgorithmIdentifier( + writer, + isPkcs12, + encryptionAlgorithmOid, + salt, + pbeParameters.IterationCount, + hmacOid, + iv); + + // encryptedData + writer.WriteOctetString(encryptedSpan); + writer.PopSequence(); + + return writer; + } + finally + { + CryptographicOperations.ZeroMemory(encryptedSpan); + CryptoPool.Return(encryptedRent!, clearSize: 0); + + cipher.Dispose(); + } + } + + internal static ArraySegment DecryptPkcs8( + ReadOnlySpan inputPassword, + ReadOnlyMemory source, + out int bytesRead) + { + return DecryptPkcs8( + inputPassword, + ReadOnlySpan.Empty, + source, + out bytesRead); + } + + internal static ArraySegment DecryptPkcs8( + ReadOnlySpan inputPasswordBytes, + ReadOnlyMemory source, + out int bytesRead) + { + return DecryptPkcs8( + ReadOnlySpan.Empty, + inputPasswordBytes, + source, + out bytesRead); + } + + private static ArraySegment DecryptPkcs8( + ReadOnlySpan inputPassword, + ReadOnlySpan inputPasswordBytes, + ReadOnlyMemory source, + out int bytesRead) + { + int localRead; + EncryptedPrivateKeyInfoAsn epki; + + try + { + AsnValueReader reader = new AsnValueReader(source.Span, AsnEncodingRules.BER); + localRead = reader.PeekEncodedValue().Length; + EncryptedPrivateKeyInfoAsn.Decode(ref reader, source, out epki); + } + catch (AsnContentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + + // No supported encryption algorithms produce more bytes of decryption output than there + // were of decryption input. + byte[] decrypted = CryptoPool.Rent(epki.EncryptedData.Length); + + try + { + int decryptedBytes = PasswordBasedEncryption.Decrypt( + epki.EncryptionAlgorithm, + inputPassword, + inputPasswordBytes, + epki.EncryptedData.Span, + decrypted); + + bytesRead = localRead; + + return new ArraySegment(decrypted, 0, decryptedBytes); + } + catch (CryptographicException e) + { + CryptoPool.Return(decrypted); + throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e); + } + } + + internal static AsnWriter ReencryptPkcs8( + ReadOnlySpan inputPassword, + ReadOnlyMemory current, + ReadOnlySpan newPassword, + PbeParameters pbeParameters) + { + ArraySegment decrypted = DecryptPkcs8( + inputPassword, + current, + out int bytesRead); + + try + { + if (bytesRead != current.Length) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + AsnWriter pkcs8Writer = new AsnWriter(AsnEncodingRules.BER); + pkcs8Writer.WriteEncodedValueForCrypto(decrypted); + + return WriteEncryptedPkcs8( + newPassword, + pkcs8Writer, + pbeParameters); + } + catch (CryptographicException e) + { + throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e); + } + finally + { + CryptographicOperations.ZeroMemory(decrypted); + CryptoPool.Return(decrypted.Array!, clearSize: 0); + } + } + + internal static AsnWriter ReencryptPkcs8( + ReadOnlySpan inputPassword, + ReadOnlyMemory current, + ReadOnlySpan newPasswordBytes, + PbeParameters pbeParameters) + { + ArraySegment decrypted = DecryptPkcs8( + inputPassword, + current, + out int bytesRead); + + try + { + if (bytesRead != current.Length) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + AsnWriter pkcs8Writer = new AsnWriter(AsnEncodingRules.BER); + pkcs8Writer.WriteEncodedValueForCrypto(decrypted); + + return WriteEncryptedPkcs8( + newPasswordBytes, + pkcs8Writer, + pbeParameters); + } + catch (CryptographicException e) + { + throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e); + } + finally + { + CryptographicOperations.ZeroMemory(decrypted); + CryptoPool.Return(decrypted.Array!, clearSize: 0); + } + } + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs b/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs index 66981c2bd4126..e175342014a98 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs @@ -9,7 +9,7 @@ namespace System.Security.Cryptography { - internal static class KeyFormatHelper + internal static partial class KeyFormatHelper { internal delegate void KeyReader(ReadOnlyMemory key, in AlgorithmIdentifierAsn algId, out TRet ret); @@ -158,147 +158,6 @@ private static void ReadPkcs8( } } - internal static unsafe void ReadEncryptedPkcs8( - string[] validOids, - ReadOnlySpan source, - ReadOnlySpan password, - KeyReader keyReader, - out int bytesRead, - out TRet ret) - { - fixed (byte* ptr = &MemoryMarshal.GetReference(source)) - { - using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) - { - ReadEncryptedPkcs8(validOids, manager.Memory, password, keyReader, out bytesRead, out ret); - } - } - } - - internal static unsafe void ReadEncryptedPkcs8( - string[] validOids, - ReadOnlySpan source, - ReadOnlySpan passwordBytes, - KeyReader keyReader, - out int bytesRead, - out TRet ret) - { - fixed (byte* ptr = &MemoryMarshal.GetReference(source)) - { - using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) - { - ReadEncryptedPkcs8( - validOids, - manager.Memory, - passwordBytes, - keyReader, - out bytesRead, - out ret); - } - } - } - - private static void ReadEncryptedPkcs8( - string[] validOids, - ReadOnlyMemory source, - ReadOnlySpan password, - KeyReader keyReader, - out int bytesRead, - out TRet ret) - { - ReadEncryptedPkcs8( - validOids, - source, - password, - ReadOnlySpan.Empty, - keyReader, - out bytesRead, - out ret); - } - - private static void ReadEncryptedPkcs8( - string[] validOids, - ReadOnlyMemory source, - ReadOnlySpan passwordBytes, - KeyReader keyReader, - out int bytesRead, - out TRet ret) - { - ReadEncryptedPkcs8( - validOids, - source, - ReadOnlySpan.Empty, - passwordBytes, - keyReader, - out bytesRead, - out ret); - } - - private static void ReadEncryptedPkcs8( - string[] validOids, - ReadOnlyMemory source, - ReadOnlySpan password, - ReadOnlySpan passwordBytes, - KeyReader keyReader, - out int bytesRead, - out TRet ret) - { - int read; - EncryptedPrivateKeyInfoAsn epki; - - try - { - AsnValueReader reader = new AsnValueReader(source.Span, AsnEncodingRules.BER); - read = reader.PeekEncodedValue().Length; - EncryptedPrivateKeyInfoAsn.Decode(ref reader, source, out epki); - } - catch (AsnContentException e) - { - throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); - } - - // No supported encryption algorithms produce more bytes of decryption output than there - // were of decryption input. - byte[] decrypted = CryptoPool.Rent(epki.EncryptedData.Length); - Memory decryptedMemory = decrypted; - - try - { - int decryptedBytes = PasswordBasedEncryption.Decrypt( - epki.EncryptionAlgorithm, - password, - passwordBytes, - epki.EncryptedData.Span, - decrypted); - - decryptedMemory = decryptedMemory.Slice(0, decryptedBytes); - - ReadPkcs8( - validOids, - decryptedMemory, - keyReader, - out int innerRead, - out ret); - - if (innerRead != decryptedMemory.Length) - { - ret = default!; - throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); - } - - bytesRead = read; - } - catch (CryptographicException e) - { - throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e); - } - finally - { - CryptographicOperations.ZeroMemory(decryptedMemory.Span); - CryptoPool.Return(decrypted, clearSize: 0); - } - } - internal static AsnWriter WritePkcs8( AsnWriter algorithmIdentifierWriter, AsnWriter privateKeyWriter, @@ -345,243 +204,5 @@ internal static AsnWriter WritePkcs8( writer.PopSequence(); return writer; } - - internal static AsnWriter WriteEncryptedPkcs8( - ReadOnlySpan password, - AsnWriter pkcs8Writer, - PbeParameters pbeParameters) - { - return WriteEncryptedPkcs8( - password, - ReadOnlySpan.Empty, - pkcs8Writer, - pbeParameters); - } - - internal static AsnWriter WriteEncryptedPkcs8( - ReadOnlySpan passwordBytes, - AsnWriter pkcs8Writer, - PbeParameters pbeParameters) - { - return WriteEncryptedPkcs8( - ReadOnlySpan.Empty, - passwordBytes, - pkcs8Writer, - pbeParameters); - } - - private static AsnWriter WriteEncryptedPkcs8( - ReadOnlySpan password, - ReadOnlySpan passwordBytes, - AsnWriter pkcs8Writer, - PbeParameters pbeParameters) - { - PasswordBasedEncryption.InitiateEncryption( - pbeParameters, - out SymmetricAlgorithm cipher, - out string hmacOid, - out string encryptionAlgorithmOid, - out bool isPkcs12); - - byte[]? encryptedRent = null; - Span encryptedSpan = default; - AsnWriter? writer = null; - - try - { - Debug.Assert(cipher.BlockSize <= 128, $"Encountered unexpected block size: {cipher.BlockSize}"); - Span iv = stackalloc byte[cipher.BlockSize / 8]; - Span salt = stackalloc byte[16]; - - // We need at least one block size beyond the input data size. - encryptedRent = CryptoPool.Rent( - checked(pkcs8Writer.GetEncodedLength() + (cipher.BlockSize / 8))); - - RandomNumberGenerator.Fill(salt); - - int written = PasswordBasedEncryption.Encrypt( - password, - passwordBytes, - cipher, - isPkcs12, - pkcs8Writer, - pbeParameters, - salt, - encryptedRent, - iv); - - encryptedSpan = encryptedRent.AsSpan(0, written); - - writer = new AsnWriter(AsnEncodingRules.DER); - - // PKCS8 EncryptedPrivateKeyInfo - writer.PushSequence(); - - // EncryptedPrivateKeyInfo.encryptionAlgorithm - PasswordBasedEncryption.WritePbeAlgorithmIdentifier( - writer, - isPkcs12, - encryptionAlgorithmOid, - salt, - pbeParameters.IterationCount, - hmacOid, - iv); - - // encryptedData - writer.WriteOctetString(encryptedSpan); - writer.PopSequence(); - - return writer; - } - finally - { - CryptographicOperations.ZeroMemory(encryptedSpan); - CryptoPool.Return(encryptedRent!, clearSize: 0); - - cipher.Dispose(); - } - } - - internal static ArraySegment DecryptPkcs8( - ReadOnlySpan inputPassword, - ReadOnlyMemory source, - out int bytesRead) - { - return DecryptPkcs8( - inputPassword, - ReadOnlySpan.Empty, - source, - out bytesRead); - } - - internal static ArraySegment DecryptPkcs8( - ReadOnlySpan inputPasswordBytes, - ReadOnlyMemory source, - out int bytesRead) - { - return DecryptPkcs8( - ReadOnlySpan.Empty, - inputPasswordBytes, - source, - out bytesRead); - } - - private static ArraySegment DecryptPkcs8( - ReadOnlySpan inputPassword, - ReadOnlySpan inputPasswordBytes, - ReadOnlyMemory source, - out int bytesRead) - { - int localRead; - EncryptedPrivateKeyInfoAsn epki; - - try - { - AsnValueReader reader = new AsnValueReader(source.Span, AsnEncodingRules.BER); - localRead = reader.PeekEncodedValue().Length; - EncryptedPrivateKeyInfoAsn.Decode(ref reader, source, out epki); - } - catch (AsnContentException e) - { - throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); - } - - // No supported encryption algorithms produce more bytes of decryption output than there - // were of decryption input. - byte[] decrypted = CryptoPool.Rent(epki.EncryptedData.Length); - - try - { - int decryptedBytes = PasswordBasedEncryption.Decrypt( - epki.EncryptionAlgorithm, - inputPassword, - inputPasswordBytes, - epki.EncryptedData.Span, - decrypted); - - bytesRead = localRead; - - return new ArraySegment(decrypted, 0, decryptedBytes); - } - catch (CryptographicException e) - { - CryptoPool.Return(decrypted); - throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e); - } - } - - internal static AsnWriter ReencryptPkcs8( - ReadOnlySpan inputPassword, - ReadOnlyMemory current, - ReadOnlySpan newPassword, - PbeParameters pbeParameters) - { - ArraySegment decrypted = DecryptPkcs8( - inputPassword, - current, - out int bytesRead); - - try - { - if (bytesRead != current.Length) - { - throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); - } - - AsnWriter pkcs8Writer = new AsnWriter(AsnEncodingRules.BER); - pkcs8Writer.WriteEncodedValueForCrypto(decrypted); - - return WriteEncryptedPkcs8( - newPassword, - pkcs8Writer, - pbeParameters); - } - catch (CryptographicException e) - { - throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e); - } - finally - { - CryptographicOperations.ZeroMemory(decrypted); - CryptoPool.Return(decrypted.Array!, clearSize: 0); - } - } - - internal static AsnWriter ReencryptPkcs8( - ReadOnlySpan inputPassword, - ReadOnlyMemory current, - ReadOnlySpan newPasswordBytes, - PbeParameters pbeParameters) - { - ArraySegment decrypted = DecryptPkcs8( - inputPassword, - current, - out int bytesRead); - - try - { - if (bytesRead != current.Length) - { - throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); - } - - AsnWriter pkcs8Writer = new AsnWriter(AsnEncodingRules.BER); - pkcs8Writer.WriteEncodedValueForCrypto(decrypted); - - return WriteEncryptedPkcs8( - newPasswordBytes, - pkcs8Writer, - pbeParameters); - } - catch (CryptographicException e) - { - throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e); - } - finally - { - CryptographicOperations.ZeroMemory(decrypted); - CryptoPool.Return(decrypted.Array!, clearSize: 0); - } - } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSAKeyFormatHelper.Encrypted.cs b/src/libraries/Common/src/System/Security/Cryptography/RSAKeyFormatHelper.Encrypted.cs new file mode 100644 index 0000000000000..0ce3c2e5037d6 --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAKeyFormatHelper.Encrypted.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Security.Cryptography +{ + internal static partial class RSAKeyFormatHelper + { + internal static void ReadEncryptedPkcs8( + ReadOnlySpan source, + ReadOnlySpan password, + out int bytesRead, + out RSAParameters key) + { + KeyFormatHelper.ReadEncryptedPkcs8( + s_validOids, + source, + password, + FromPkcs1PrivateKey, + out bytesRead, + out key); + } + + internal static void ReadEncryptedPkcs8( + ReadOnlySpan source, + ReadOnlySpan passwordBytes, + out int bytesRead, + out RSAParameters key) + { + KeyFormatHelper.ReadEncryptedPkcs8( + s_validOids, + source, + passwordBytes, + FromPkcs1PrivateKey, + out bytesRead, + out key); + } + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSAKeyFormatHelper.cs b/src/libraries/Common/src/System/Security/Cryptography/RSAKeyFormatHelper.cs index e7285c21f94f8..e5c776da09299 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSAKeyFormatHelper.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAKeyFormatHelper.cs @@ -1,13 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Diagnostics; using System.Formats.Asn1; using System.Security.Cryptography.Asn1; namespace System.Security.Cryptography { - internal static class RSAKeyFormatHelper + internal static partial class RSAKeyFormatHelper { private static readonly string[] s_validOids = { @@ -111,6 +112,25 @@ internal static ReadOnlyMemory ReadSubjectPublicKeyInfo( out bytesRead); } + /// + /// Checks that a SubjectPublicKeyInfo represents an RSA key. + /// + /// The number of bytes read from . + internal static unsafe int CheckSubjectPublicKeyInfo(ReadOnlySpan source) + { + int bytesRead; + + fixed (byte* ptr = source) + { + using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) + { + _ = ReadSubjectPublicKeyInfo(manager.Memory, out bytesRead); + } + } + + return bytesRead; + } + public static void ReadPkcs8( ReadOnlySpan source, out int bytesRead, @@ -134,34 +154,23 @@ internal static ReadOnlyMemory ReadPkcs8( out bytesRead); } - internal static void ReadEncryptedPkcs8( - ReadOnlySpan source, - ReadOnlySpan password, - out int bytesRead, - out RSAParameters key) + /// + /// Checks that a Pkcs8PrivateKeyInfo represents an RSA key. + /// + /// The number of bytes read from . + internal static unsafe int CheckPkcs8(ReadOnlySpan source) { - KeyFormatHelper.ReadEncryptedPkcs8( - s_validOids, - source, - password, - FromPkcs1PrivateKey, - out bytesRead, - out key); - } + int bytesRead; - internal static void ReadEncryptedPkcs8( - ReadOnlySpan source, - ReadOnlySpan passwordBytes, - out int bytesRead, - out RSAParameters key) - { - KeyFormatHelper.ReadEncryptedPkcs8( - s_validOids, - source, - passwordBytes, - FromPkcs1PrivateKey, - out bytesRead, - out key); + fixed (byte* ptr = source) + { + using (MemoryManager manager = new PointerMemoryManager(ptr, source.Length)) + { + _ = ReadPkcs8(manager.Memory, out bytesRead); + } + } + + return bytesRead; } internal static AsnWriter WriteSubjectPublicKeyInfo(ReadOnlySpan pkcs1PublicKey) diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs index 40381076b0321..d6d66c78ae1d4 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Formats.Asn1; using System.IO; +using System.Security.Cryptography.Asn1; using Microsoft.Win32.SafeHandles; using Internal.Cryptography; @@ -278,81 +279,211 @@ private static bool TryEncrypt( return true; } - public override RSAParameters ExportParameters(bool includePrivateParameters) + private delegate T ExportPrivateKeyFunc(ReadOnlyMemory pkcs8, ReadOnlyMemory pkcs1); + + private delegate ReadOnlyMemory TryExportPrivateKeySelector( + ReadOnlyMemory pkcs8, + ReadOnlyMemory pkcs1); + + private T ExportPrivateKey(ExportPrivateKeyFunc exporter) { // It's entirely possible that this line will cause the key to be generated in the first place. SafeEvpPKeyHandle key = GetKey(); - RSAParameters rsaParameters = Interop.Crypto.ExportRsaParameters(key, includePrivateParameters); - bool hasPrivateKey = rsaParameters.D != null; + ArraySegment p8 = Interop.Crypto.RentEncodePkcs8PrivateKey(key); - if (hasPrivateKey != includePrivateParameters || !HasConsistentPrivateKey(ref rsaParameters)) + try { - throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); + ReadOnlyMemory pkcs1 = VerifyPkcs8(p8); + return exporter(p8, pkcs1); + } + finally + { + CryptoPool.Return(p8); } + } + + private bool TryExportPrivateKey(TryExportPrivateKeySelector selector, Span destination, out int bytesWritten) + { + // It's entirely possible that this line will cause the key to be generated in the first place. + SafeEvpPKeyHandle key = GetKey(); + + ArraySegment p8 = Interop.Crypto.RentEncodePkcs8PrivateKey(key); - return rsaParameters; + try + { + ReadOnlyMemory pkcs1 = VerifyPkcs8(p8); + ReadOnlyMemory selected = selector(p8, pkcs1); + return selected.Span.TryCopyToDestination(destination, out bytesWritten); + } + finally + { + CryptoPool.Return(p8); + } } - public override void ImportParameters(RSAParameters parameters) + private T ExportPublicKey(Func, T> exporter) { - ValidateParameters(ref parameters); - ThrowIfDisposed(); + // It's entirely possible that this line will cause the key to be generated in the first place. + SafeEvpPKeyHandle key = GetKey(); + + ArraySegment spki = Interop.Crypto.RentEncodeSubjectPublicKeyInfo(key); + + try + { + return exporter(spki); + } + finally + { + CryptoPool.Return(spki); + } + } - SafeRsaHandle key = Interop.Crypto.RsaCreate(); - SafeEvpPKeyHandle pkey = Interop.Crypto.EvpPkeyCreate(); - bool imported = false; + private bool TryExportPublicKey( + Func, ReadOnlyMemory>? transform, + Span destination, + out int bytesWritten) + { + // It's entirely possible that this line will cause the key to be generated in the first place. + SafeEvpPKeyHandle key = GetKey(); - Interop.Crypto.CheckValidOpenSslHandle(key); + ArraySegment spki = Interop.Crypto.RentEncodeSubjectPublicKeyInfo(key); try { - if (!Interop.Crypto.SetRsaParameters( - key, - parameters.Modulus, - parameters.Modulus != null ? parameters.Modulus.Length : 0, - parameters.Exponent, - parameters.Exponent != null ? parameters.Exponent.Length : 0, - parameters.D, - parameters.D != null ? parameters.D.Length : 0, - parameters.P, - parameters.P != null ? parameters.P.Length : 0, - parameters.DP, - parameters.DP != null ? parameters.DP.Length : 0, - parameters.Q, - parameters.Q != null ? parameters.Q.Length : 0, - parameters.DQ, - parameters.DQ != null ? parameters.DQ.Length : 0, - parameters.InverseQ, - parameters.InverseQ != null ? parameters.InverseQ.Length : 0)) + ReadOnlyMemory data = spki; + + if (transform != null) { - throw Interop.Crypto.CreateOpenSslCryptographicException(); + data = transform(data); } - imported = true; + return data.Span.TryCopyToDestination(destination, out bytesWritten); } finally { - if (!imported) - { - key.Dispose(); - } + CryptoPool.Return(spki); } + } + + public override bool TryExportPkcs8PrivateKey(Span destination, out int bytesWritten) + { + return TryExportPrivateKey(static (pkcs8, pkcs1) => pkcs8, destination, out bytesWritten); + } + + public override byte[] ExportPkcs8PrivateKey() + { + return ExportPrivateKey(static (pkcs8, pkcs1) => pkcs8.ToArray()); + } + + public override bool TryExportRSAPrivateKey(Span destination, out int bytesWritten) + { + return TryExportPrivateKey(static (pkcs8, pkcs1) => pkcs1, destination, out bytesWritten); + } + + public override byte[] ExportRSAPrivateKey() + { + return ExportPrivateKey(static (pkcs8, pkcs1) => pkcs1.ToArray()); + } + + public override byte[] ExportRSAPublicKey() + { + return ExportPublicKey( + static spki => + { + ReadOnlyMemory pkcs1 = RSAKeyFormatHelper.ReadSubjectPublicKeyInfo(spki, out int read); + Debug.Assert(read == spki.Length); + return pkcs1.ToArray(); + }); + } + + public override bool TryExportRSAPublicKey(Span destination, out int bytesWritten) + { + return TryExportPublicKey( + spki => + { + ReadOnlyMemory pkcs1 = RSAKeyFormatHelper.ReadSubjectPublicKeyInfo(spki, out int read); + Debug.Assert(read == spki.Length); + return pkcs1; + }, + destination, + out bytesWritten); + } + + public override byte[] ExportSubjectPublicKeyInfo() + { + return ExportPublicKey(static spki => spki.ToArray()); + } + + public override bool TryExportSubjectPublicKeyInfo(Span destination, out int bytesWritten) + { + return TryExportPublicKey( + transform: null, + destination, + out bytesWritten); + } - if (!Interop.Crypto.EvpPkeySetRsa(pkey, key)) + public override RSAParameters ExportParameters(bool includePrivateParameters) + { + if (includePrivateParameters) { - pkey.Dispose(); - key.Dispose(); - throw Interop.Crypto.CreateOpenSslCryptographicException(); + return ExportPrivateKey( + static (pkcs8, pkcs1) => + { + AlgorithmIdentifierAsn algId = default; + RSAParameters ret; + RSAKeyFormatHelper.FromPkcs1PrivateKey(pkcs1, in algId, out ret); + return ret; + }); } - key.Dispose(); - FreeKey(); - _key = new Lazy(pkey); + return ExportPublicKey( + static spki => + { + RSAParameters ret; + RSAKeyFormatHelper.ReadSubjectPublicKeyInfo( + spki.Span, + out int read, + out ret); + + Debug.Assert(read == spki.Length); + return ret; + }); + } - // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere - // with the already loaded key. - ForceSetKeySize(BitsPerByte * Interop.Crypto.EvpPKeySize(pkey)); + public override void ImportParameters(RSAParameters parameters) + { + ValidateParameters(ref parameters); + ThrowIfDisposed(); + + if (parameters.D != null) + { + AsnWriter writer = RSAKeyFormatHelper.WritePkcs8PrivateKey(parameters); + ArraySegment pkcs8 = writer.RentAndEncode(); + + try + { + ImportPkcs8PrivateKey(pkcs8, checkAlgorithm: false, out _); + } + finally + { + CryptoPool.Return(pkcs8); + } + } + else + { + AsnWriter writer = RSAKeyFormatHelper.WriteSubjectPublicKeyInfo(parameters); + ArraySegment spki = writer.RentAndEncode(); + + try + { + ImportSubjectPublicKeyInfo(spki, checkAlgorithm: false, out _); + } + finally + { + CryptoPool.Return(spki); + } + } } public override void ImportRSAPublicKey(ReadOnlySpan source, out int bytesRead) @@ -375,27 +506,52 @@ public override void ImportRSAPublicKey(ReadOnlySpan source, out int bytes throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); } - SafeEvpPKeyHandle pkey = Interop.Crypto.EvpPkeyCreate(); - SafeRsaHandle key = Interop.Crypto.DecodeRsaPublicKey(source.Slice(0, read)); - - Interop.Crypto.CheckValidOpenSslHandle(key); + AsnWriter writer = RSAKeyFormatHelper.WriteSubjectPublicKeyInfo(source.Slice(0, read)); + ArraySegment spki = writer.RentAndEncode(); - if (!Interop.Crypto.EvpPkeySetRsa(pkey, key)) + try + { + ImportSubjectPublicKeyInfo(spki, checkAlgorithm: false, out _); + } + finally { - key.Dispose(); - pkey.Dispose(); - throw Interop.Crypto.CreateOpenSslCryptographicException(); + CryptoPool.Return(spki); } - key.Dispose(); + bytesRead = read; + } - FreeKey(); - _key = new Lazy(pkey); + public override void ImportSubjectPublicKeyInfo( + ReadOnlySpan source, + out int bytesRead) + { + ThrowIfDisposed(); - // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere - // with the already loaded key. - ForceSetKeySize(BitsPerByte * Interop.Crypto.EvpPKeySize(pkey)); + ImportSubjectPublicKeyInfo(source, checkAlgorithm: true, out bytesRead); + } + + private void ImportSubjectPublicKeyInfo( + ReadOnlySpan source, + bool checkAlgorithm, + out int bytesRead) + { + int read; + + if (checkAlgorithm) + { + read = RSAKeyFormatHelper.CheckSubjectPublicKeyInfo(source); + } + else + { + read = source.Length; + } + + SafeEvpPKeyHandle newKey = Interop.Crypto.DecodeSubjectPublicKeyInfo( + source.Slice(0, read), + Interop.Crypto.EvpAlgorithmId.RSA); + Debug.Assert(!newKey.IsInvalid); + SetKey(newKey); bytesRead = read; } @@ -417,6 +573,70 @@ public override void ImportEncryptedPkcs8PrivateKey( base.ImportEncryptedPkcs8PrivateKey(password, source, out bytesRead); } + public override void ImportPkcs8PrivateKey(ReadOnlySpan source, out int bytesRead) + { + ThrowIfDisposed(); + + ImportPkcs8PrivateKey(source, checkAlgorithm: true, out bytesRead); + } + + private void ImportPkcs8PrivateKey(ReadOnlySpan source, bool checkAlgorithm, out int bytesRead) + { + int read; + + if (checkAlgorithm) + { + read = RSAKeyFormatHelper.CheckPkcs8(source); + } + else + { + read = source.Length; + } + + SafeEvpPKeyHandle newKey = Interop.Crypto.DecodePkcs8PrivateKey( + source.Slice(0, read), + Interop.Crypto.EvpAlgorithmId.RSA); + + Debug.Assert(!newKey.IsInvalid); + SetKey(newKey); + bytesRead = read; + } + + public override void ImportRSAPrivateKey(ReadOnlySpan source, out int bytesRead) + { + ThrowIfDisposed(); + + int read; + + try + { + AsnDecoder.ReadEncodedValue( + source, + AsnEncodingRules.BER, + out _, + out _, + out read); + } + catch (AsnContentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + + AsnWriter writer = RSAKeyFormatHelper.WritePkcs8PrivateKey(source.Slice(0, read)); + ArraySegment pkcs8 = writer.RentAndEncode(); + + try + { + ImportPkcs8PrivateKey(pkcs8, checkAlgorithm: false, out _); + } + finally + { + CryptoPool.Return(pkcs8); + } + + bytesRead = read; + } + protected override void Dispose(bool disposing) { if (disposing) @@ -437,6 +657,18 @@ private void FreeKey() } } + [System.Diagnostics.CodeAnalysis.MemberNotNull(nameof(_key))] + private void SetKey(SafeEvpPKeyHandle newKey) + { + Debug.Assert(!newKey.IsInvalid); + FreeKey(); + _key = new Lazy(newKey); + + // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere + // with the already loaded key. + ForceSetKeySize(BitsPerByte * Interop.Crypto.EvpPKeySize(newKey)); + } + private static void ValidateParameters(ref RSAParameters parameters) { if (parameters.Modulus == null || parameters.Exponent == null) @@ -646,6 +878,26 @@ public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan sign signature); } + private static ReadOnlyMemory VerifyPkcs8(ReadOnlyMemory pkcs8) + { + // OpenSSL 1.1.1 will export RSA public keys as a PKCS#8, but this makes a broken structure. + // + // So, crack it back open. If we can walk the payload it's valid, otherwise throw the + // "there's no private key" exception. + + try + { + ReadOnlyMemory pkcs1Priv = RSAKeyFormatHelper.ReadPkcs8(pkcs8, out int read); + Debug.Assert(read == pkcs8.Length); + _ = RSAPrivateKeyAsn.Decode(pkcs1Priv, AsnEncodingRules.BER); + return pkcs1Priv; + } + catch (CryptographicException) + { + throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); + } + } + private static void ValidatePadding(RSAEncryptionPadding padding) { if (padding == null) diff --git a/src/libraries/Common/tests/StaticTestGenerator/Program.cs b/src/libraries/Common/tests/StaticTestGenerator/Program.cs index 1c25d5ccd4cdd..2274463eba964 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/StaticTestGenerator/StaticTestGenerator.csproj b/src/libraries/Common/tests/StaticTestGenerator/StaticTestGenerator.csproj index b4107546efdd2..035387e854df3 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/Common/tests/System/Net/Http/HttpClientHandlerTest.RemoteServer.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.RemoteServer.cs index 36d732d79ff9d..316a58cb33f3f 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/Common/tests/System/Net/Http/HttpProtocolTests.cs b/src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs index ec7bb197e81a6..b90cffafeab5d 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/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs index 21ee6346eff8e..8e8951179e863 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 cb7ce16290771..596b8be559e4b 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 ae039a3c38f73..73cb4bba880f5 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 2cffa413a7597..cba65e49d5209 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 c0d0d2e1dee69..e7a89febbc25d 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; } 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 77d5e0cf77823..bc109f9bd03d5 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 465958943f8c2..2144dad5aaf1c 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 d775ffb9f128e..0a050f842736c 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 d23a48a2851cd..707db864be13f 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 5b191ca5ea05f..fd7a2f626e412 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 411aadaf347e6..24d2befc125c4 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 89e6b5d285deb..42a9b584366d9 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 d5706706858c9..1d9e69c0a87e8 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 64b3203b4b7cc..fc627effbc78d 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/Common/tests/System/Security/Cryptography/ByteUtils.cs b/src/libraries/Common/tests/System/Security/Cryptography/ByteUtils.cs index 997b09af0cbfa..8a1a95bb0984b 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 a96b25090b703..d51c5e3cf8719 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 c765bf4a589a5..23cfda1c710f8 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 6496568c75bc9..c7155243400bb 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/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 5038c4f01977e..90f21d02f5c49 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; @@ -70,8 +71,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) { @@ -242,14 +243,9 @@ public static string GetDistroVersionString() private static bool GetStaticNonPublicBooleanPropertyValue(string typeName, string propertyName) { - Type globalizationMode = Type.GetType(typeName); - if (globalizationMode != null) + if (Type.GetType(typeName)?.GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Static)?.GetMethod is MethodInfo mi) { - MethodInfo methodInfo = globalizationMode.GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Static)?.GetMethod; - if (methodInfo != null) - { - return (bool)methodInfo.Invoke(null, null); - } + return (bool)mi.Invoke(null, null); } return false; @@ -290,6 +286,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/Microsoft.CSharp/src/Microsoft.CSharp.csproj b/src/libraries/Microsoft.CSharp/src/Microsoft.CSharp.csproj index 322c4c10680fe..8f9de151dfdab 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 289a053145845..9d2373f0b69f3 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.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 fc47c82f88ab5..36aaba499cb1f 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/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs b/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs index c3cc43f7eff56..feef64fc2dde1 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 0000000000000..9578f3c334ac3 --- /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 455efdb4176bf..cfe56a20978e5 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 0000000000000..6e7a4919b25e4 --- /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; + } + + } +} 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 bf78cbc03c926..e3159637dc7ea 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 16b3d1aecfbf1..7e9998e9516cf 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/Microsoft.XmlSerializer.Generator/pkg/build/Microsoft.XmlSerializer.Generator.targets b/src/libraries/Microsoft.XmlSerializer.Generator/pkg/build/Microsoft.XmlSerializer.Generator.targets index a66dd7eb7a24d..6d90592074090 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 @@ - + diff --git a/src/libraries/Native/Unix/System.Native/entrypoints.c b/src/libraries/Native/Unix/System.Native/entrypoints.c index bf6f98953f9a9..c8b678ec08340 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_signal.c b/src/libraries/Native/Unix/System.Native/pal_signal.c index 4c4017f654746..ee6ccd833c0de 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 138f37835a8ca..146fdabbfceb1 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/Native/Unix/System.Native/pal_uid.c b/src/libraries/Native/Unix/System.Native/pal_uid.c index 2807d06b77d6e..6571fb1e600cb 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 03d347a61e32f..d0b16eef97a03 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/Native/Unix/System.Security.Cryptography.Native/CMakeLists.txt b/src/libraries/Native/Unix/System.Security.Cryptography.Native/CMakeLists.txt index f6f12e2d7f5da..37a7f5ccc5b0d 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/CMakeLists.txt +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/CMakeLists.txt @@ -37,7 +37,6 @@ set(NATIVECRYPTO_SOURCES pal_hmac.c pal_ocsp.c pal_pkcs7.c - pal_rsa.c pal_ssl.c pal_x509.c pal_x509_name.c 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 daf13002b1185..15df621f7a7df 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); @@ -818,4 +825,63 @@ int local_RSA_test_flags(const RSA *r, int flags) return r->flags & flags; } +int local_EVP_PKEY_check(EVP_PKEY_CTX* ctx) +{ + EVP_PKEY* pkey = EVP_PKEY_CTX_get0_pkey(ctx); + + if (pkey == NULL) + { + ERR_put_error(ERR_LIB_EVP, 0, EVP_R_INPUT_NOT_INITIALIZED, __FILE__, __LINE__); + return -1; + } + + int id = EVP_PKEY_get_base_id(pkey); + + switch (id) + { + case NID_rsaEncryption: + { + const RSA* rsa = EVP_PKEY_get0_RSA(pkey); + + if (rsa != NULL) + { + return RSA_check_key(rsa); + } + + break; + } + default: + ERR_put_error(ERR_LIB_EVP, 0, EVP_R_UNSUPPORTED_ALGORITHM, __FILE__, __LINE__); + return -1; + } + + ERR_put_error(ERR_LIB_EVP, 0, EVP_R_NO_KEY_SET, __FILE__, __LINE__); + return -1; +} + +int local_EVP_PKEY_public_check(EVP_PKEY_CTX* ctx) +{ + EVP_PKEY* pkey = EVP_PKEY_CTX_get0_pkey(ctx); + + if (pkey == NULL) + { + ERR_put_error(ERR_LIB_EVP, 0, EVP_R_INPUT_NOT_INITIALIZED, __FILE__, __LINE__); + return -1; + } + + int id = EVP_PKEY_get_base_id(pkey); + + switch (id) + { + case NID_rsaEncryption: + { + ERR_put_error(ERR_LIB_EVP, 0, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE, __FILE__, __LINE__); + return -2; + } + default: + ERR_put_error(ERR_LIB_EVP, 0, EVP_R_UNSUPPORTED_ALGORITHM, __FILE__, __LINE__); + return -1; + } +} + #endif 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 1b866bc4474d2..3998f1abbf3a4 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/apibridge.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/apibridge.h @@ -15,7 +15,9 @@ int32_t local_DSA_set0_pqg(DSA* dsa, BIGNUM* bnP, BIGNUM* bnQ, BIGNUM* bnG); void local_EVP_CIPHER_CTX_free(EVP_CIPHER_CTX* ctx); EVP_CIPHER_CTX* local_EVP_CIPHER_CTX_new(void); int32_t local_EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX* ctx); +int local_EVP_PKEY_check(EVP_PKEY_CTX* ctx); RSA* local_EVP_PKEY_get0_RSA(EVP_PKEY* pkey); +int local_EVP_PKEY_public_check(EVP_PKEY_CTX* ctx); int32_t local_EVP_PKEY_up_ref(EVP_PKEY* pkey); void local_HMAC_CTX_free(HMAC_CTX* ctx); HMAC_CTX* local_HMAC_CTX_new(void); @@ -32,6 +34,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 996921777f6f4..bf68f23be3633 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c @@ -23,7 +23,6 @@ #include "pal_hmac.h" #include "pal_ocsp.h" #include "pal_pkcs7.h" -#include "pal_rsa.h" #include "pal_ssl.h" #include "pal_x509.h" #include "pal_x509ext.h" @@ -57,7 +56,8 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_DecodeExtendedKeyUsage) DllImportEntry(CryptoNative_DecodeOcspResponse) DllImportEntry(CryptoNative_DecodePkcs7) - DllImportEntry(CryptoNative_DecodeRsaPublicKey) + DllImportEntry(CryptoNative_DecodePkcs8PrivateKey) + DllImportEntry(CryptoNative_DecodeSubjectPublicKeyInfo) DllImportEntry(CryptoNative_DecodeX509) DllImportEntry(CryptoNative_DecodeX509BasicConstraints2Extension) DllImportEntry(CryptoNative_DecodeX509Crl) @@ -84,6 +84,8 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EncodeAsn1Integer) DllImportEntry(CryptoNative_EncodeOcspRequest) DllImportEntry(CryptoNative_EncodePkcs7) + DllImportEntry(CryptoNative_EncodePkcs8PrivateKey) + DllImportEntry(CryptoNative_EncodeSubjectPublicKeyInfo) DllImportEntry(CryptoNative_EncodeX509) DllImportEntry(CryptoNative_EncodeX509SubjectPublicKeyInfo) DllImportEntry(CryptoNative_ErrClearError) @@ -144,16 +146,16 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EvpMdCtxDestroy) DllImportEntry(CryptoNative_EvpMdSize) DllImportEntry(CryptoNative_EvpPkeyCreate) + DllImportEntry(CryptoNative_EvpPKeyCreateRsa) DllImportEntry(CryptoNative_EvpPKeyCtxCreate) DllImportEntry(CryptoNative_EvpPKeyCtxDestroy) DllImportEntry(CryptoNative_EvpPKeyDeriveSecretAgreement) DllImportEntry(CryptoNative_EvpPkeyDestroy) + DllImportEntry(CryptoNative_EvpPKeyDuplicate) DllImportEntry(CryptoNative_EvpPkeyGetDsa) DllImportEntry(CryptoNative_EvpPkeyGetEcKey) - DllImportEntry(CryptoNative_EvpPkeyGetRsa) DllImportEntry(CryptoNative_EvpPkeySetDsa) DllImportEntry(CryptoNative_EvpPkeySetEcKey) - DllImportEntry(CryptoNative_EvpPkeySetRsa) DllImportEntry(CryptoNative_EvpPKeySize) DllImportEntry(CryptoNative_EvpRC2Cbc) DllImportEntry(CryptoNative_EvpRC2Ecb) @@ -174,8 +176,9 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_GetOcspRequestDerSize) DllImportEntry(CryptoNative_GetPkcs7Certificates) DllImportEntry(CryptoNative_GetPkcs7DerSize) + DllImportEntry(CryptoNative_GetPkcs8PrivateKeySize) DllImportEntry(CryptoNative_GetRandomBytes) - DllImportEntry(CryptoNative_GetRsaParameters) + DllImportEntry(CryptoNative_GetSubjectPublicKeyInfoSize) DllImportEntry(CryptoNative_GetX509CrlNextUpdate) DllImportEntry(CryptoNative_GetX509DerSize) DllImportEntry(CryptoNative_GetX509EkuField) @@ -227,16 +230,11 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_ReadX509AsDerFromBio) DllImportEntry(CryptoNative_RecursiveFreeX509Stack) DllImportEntry(CryptoNative_RegisterLegacyAlgorithms) - DllImportEntry(CryptoNative_RsaCreate) DllImportEntry(CryptoNative_RsaDecrypt) - DllImportEntry(CryptoNative_RsaDestroy) DllImportEntry(CryptoNative_RsaEncrypt) DllImportEntry(CryptoNative_RsaGenerateKey) DllImportEntry(CryptoNative_RsaSignHash) - DllImportEntry(CryptoNative_RsaSize) - DllImportEntry(CryptoNative_RsaUpRef) DllImportEntry(CryptoNative_RsaVerifyHash) - DllImportEntry(CryptoNative_SetRsaParameters) DllImportEntry(CryptoNative_UpRefEvpPkey) DllImportEntry(CryptoNative_X509ChainBuildOcspRequest) DllImportEntry(CryptoNative_X509ChainGetCachedOcspStatus) @@ -286,6 +284,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 09d8351288c6e..69c95b532ade0 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h @@ -203,6 +203,8 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); REQUIRED_FUNCTION(d2i_PKCS12_fp) \ REQUIRED_FUNCTION(d2i_PKCS7) \ REQUIRED_FUNCTION(d2i_PKCS7_bio) \ + REQUIRED_FUNCTION(d2i_PKCS8_PRIV_KEY_INFO) \ + REQUIRED_FUNCTION(d2i_PUBKEY) \ REQUIRED_FUNCTION(d2i_RSAPublicKey) \ REQUIRED_FUNCTION(d2i_X509) \ REQUIRED_FUNCTION(d2i_X509_bio) \ @@ -318,7 +320,9 @@ 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_PKCS82PKEY) \ + REQUIRED_FUNCTION(EVP_PKEY2PKCS8) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_ctrl) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_free) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_get0_pkey) \ @@ -329,7 +333,7 @@ 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) \ + FALLBACK_FUNCTION(EVP_PKEY_check) \ REQUIRED_FUNCTION(EVP_PKEY_decrypt) \ REQUIRED_FUNCTION(EVP_PKEY_decrypt_init) \ REQUIRED_FUNCTION(EVP_PKEY_derive_set_peer) \ @@ -338,6 +342,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) \ @@ -345,12 +351,12 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); REQUIRED_FUNCTION(EVP_PKEY_keygen) \ REQUIRED_FUNCTION(EVP_PKEY_keygen_init) \ REQUIRED_FUNCTION(EVP_PKEY_new) \ + FALLBACK_FUNCTION(EVP_PKEY_public_check) \ REQUIRED_FUNCTION(EVP_PKEY_set1_DSA) \ REQUIRED_FUNCTION(EVP_PKEY_set1_EC_KEY) \ 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) \ @@ -376,6 +382,8 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); REQUIRED_FUNCTION(i2d_OCSP_REQUEST) \ REQUIRED_FUNCTION(i2d_OCSP_RESPONSE) \ REQUIRED_FUNCTION(i2d_PKCS7) \ + REQUIRED_FUNCTION(i2d_PKCS8_PRIV_KEY_INFO) \ + REQUIRED_FUNCTION(i2d_PUBKEY) \ REQUIRED_FUNCTION(i2d_X509) \ REQUIRED_FUNCTION(i2d_X509_PUBKEY) \ REQUIRED_FUNCTION(OBJ_ln2nid) \ @@ -412,6 +420,7 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); RENAMED_FUNCTION(OPENSSL_sk_value, sk_value) \ FALLBACK_FUNCTION(OpenSSL_version_num) \ LIGHTUP_FUNCTION(OSSL_PROVIDER_try_load) \ + REQUIRED_FUNCTION(PKCS8_PRIV_KEY_INFO_free) \ REQUIRED_FUNCTION(PEM_read_bio_PKCS7) \ REQUIRED_FUNCTION(PEM_read_bio_X509) \ REQUIRED_FUNCTION(PEM_read_bio_X509_AUX) \ @@ -458,7 +467,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 +492,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) \ @@ -639,6 +651,8 @@ FOR_ALL_OPENSSL_FUNCTIONS #define d2i_PKCS12_fp d2i_PKCS12_fp_ptr #define d2i_PKCS7 d2i_PKCS7_ptr #define d2i_PKCS7_bio d2i_PKCS7_bio_ptr +#define d2i_PKCS8_PRIV_KEY_INFO d2i_PKCS8_PRIV_KEY_INFO_ptr +#define d2i_PUBKEY d2i_PUBKEY_ptr #define d2i_RSAPublicKey d2i_RSAPublicKey_ptr #define d2i_X509 d2i_X509_ptr #define d2i_X509_bio d2i_X509_bio_ptr @@ -754,7 +768,9 @@ 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_PKCS82PKEY EVP_PKCS82PKEY_ptr +#define EVP_PKEY2PKCS8 EVP_PKEY2PKCS8_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 +781,7 @@ 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_check EVP_PKEY_check_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 +790,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 @@ -781,12 +799,12 @@ FOR_ALL_OPENSSL_FUNCTIONS #define EVP_PKEY_keygen EVP_PKEY_keygen_ptr #define EVP_PKEY_keygen_init EVP_PKEY_keygen_init_ptr #define EVP_PKEY_new EVP_PKEY_new_ptr +#define EVP_PKEY_public_check EVP_PKEY_public_check_ptr #define EVP_PKEY_set1_DSA EVP_PKEY_set1_DSA_ptr #define EVP_PKEY_set1_EC_KEY EVP_PKEY_set1_EC_KEY_ptr #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 @@ -812,6 +830,8 @@ FOR_ALL_OPENSSL_FUNCTIONS #define i2d_OCSP_REQUEST i2d_OCSP_REQUEST_ptr #define i2d_OCSP_RESPONSE i2d_OCSP_RESPONSE_ptr #define i2d_PKCS7 i2d_PKCS7_ptr +#define i2d_PKCS8_PRIV_KEY_INFO i2d_PKCS8_PRIV_KEY_INFO_ptr +#define i2d_PUBKEY i2d_PUBKEY_ptr #define i2d_X509 i2d_X509_ptr #define i2d_X509_PUBKEY i2d_X509_PUBKEY_ptr #define OBJ_ln2nid OBJ_ln2nid_ptr @@ -848,6 +868,7 @@ FOR_ALL_OPENSSL_FUNCTIONS #define OPENSSL_sk_value OPENSSL_sk_value_ptr #define OpenSSL_version_num OpenSSL_version_num_ptr #define OSSL_PROVIDER_try_load OSSL_PROVIDER_try_load_ptr +#define PKCS8_PRIV_KEY_INFO_free PKCS8_PRIV_KEY_INFO_free_ptr #define PEM_read_bio_PKCS7 PEM_read_bio_PKCS7_ptr #define PEM_read_bio_X509 PEM_read_bio_X509_ptr #define PEM_read_bio_X509_AUX PEM_read_bio_X509_AUX_ptr @@ -895,7 +916,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 +942,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 @@ -1078,6 +1104,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 @@ -1098,7 +1127,9 @@ FOR_ALL_OPENSSL_FUNCTIONS #define EVP_CIPHER_CTX_free local_EVP_CIPHER_CTX_free #define EVP_CIPHER_CTX_new local_EVP_CIPHER_CTX_new #define EVP_CIPHER_CTX_reset local_EVP_CIPHER_CTX_reset +#define EVP_PKEY_check local_EVP_PKEY_check #define EVP_PKEY_get0_RSA local_EVP_PKEY_get0_RSA +#define EVP_PKEY_public_check local_EVP_PKEY_public_check #define EVP_PKEY_up_ref local_EVP_PKEY_up_ref #define HMAC_CTX_free local_HMAC_CTX_free #define HMAC_CTX_new local_HMAC_CTX_new 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 e9a1b4939bab3..59f0fc5f59d5d 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; @@ -30,6 +31,8 @@ int32_t EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX* ctx); void EVP_MD_CTX_free(EVP_MD_CTX* ctx); EVP_MD_CTX* EVP_MD_CTX_new(void); RSA* EVP_PKEY_get0_RSA(EVP_PKEY* pkey); +int EVP_PKEY_check(EVP_PKEY_CTX* ctx); +int EVP_PKEY_public_check(EVP_PKEY_CTX* ctx); int32_t EVP_PKEY_up_ref(EVP_PKEY* pkey); void HMAC_CTX_free(HMAC_CTX* ctx); HMAC_CTX* HMAC_CTX_new(void); @@ -56,6 +59,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/osslcompat_30.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/osslcompat_30.h index bb529df51ee03..dba69f1382d2f 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 a119f8178f001..9cf042a29e9fc 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 83b18f7621f12..6839d4c2fe04e 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 @@ -9,6 +9,54 @@ EVP_PKEY* CryptoNative_EvpPkeyCreate() return EVP_PKEY_new(); } +EVP_PKEY* CryptoNative_EvpPKeyDuplicate(EVP_PKEY* currentKey, int32_t algId) +{ + assert(currentKey != NULL); + + int currentAlgId = EVP_PKEY_get_base_id(currentKey); + + if (algId != NID_undef && algId != currentAlgId) + { + ERR_put_error(ERR_LIB_EVP, 0, EVP_R_DIFFERENT_KEY_TYPES, __FILE__, __LINE__); + return NULL; + } + + EVP_PKEY* newKey = EVP_PKEY_new(); + + if (newKey == NULL) + { + return NULL; + } + + bool success = true; + + if (currentAlgId == EVP_PKEY_RSA) + { + const RSA* rsa = EVP_PKEY_get0_RSA(currentKey); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-qual" + if (rsa == NULL || !EVP_PKEY_set1_RSA(newKey, (RSA*)rsa)) +#pragma clang diagnostic pop + { + success = false; + } + } + else + { + ERR_put_error(ERR_LIB_EVP, 0, EVP_R_UNSUPPORTED_ALGORITHM, __FILE__, __LINE__); + success = false; + } + + if (!success) + { + EVP_PKEY_free(newKey); + newKey = NULL; + } + + return newKey; +} + void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey) { if (pkey != NULL) @@ -20,7 +68,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) @@ -32,3 +80,122 @@ int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey) return EVP_PKEY_up_ref(pkey); } + +static bool CheckKey(EVP_PKEY* key, int32_t algId, int32_t (*check_func)(EVP_PKEY_CTX*)) +{ + if (algId != NID_undef && EVP_PKEY_get_base_id(key) != algId) + { + ERR_put_error(ERR_LIB_EVP, 0, EVP_R_UNSUPPORTED_ALGORITHM, __FILE__, __LINE__); + return false; + } + + EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(key, NULL); + + if (ctx == NULL) + { + // The malloc error should have already been set. + return false; + } + + int check = check_func(ctx); + EVP_PKEY_CTX_free(ctx); + + // 1: Success + // -2: The key object had no check routine available. + if (check == 1 || check == -2) + { + // We need to clear for -2, doesn't hurt for 1. + ERR_clear_error(); + return true; + } + + return false; +} + +EVP_PKEY* CryptoNative_DecodeSubjectPublicKeyInfo(const uint8_t* buf, int32_t len, int32_t algId) +{ + assert(buf != NULL); + assert(len > 0); + + EVP_PKEY* key = d2i_PUBKEY(NULL, &buf, len); + + if (key != NULL && !CheckKey(key, algId, EVP_PKEY_public_check)) + { + EVP_PKEY_free(key); + key = NULL; + } + + return key; +} + +EVP_PKEY* CryptoNative_DecodePkcs8PrivateKey(const uint8_t* buf, int32_t len, int32_t algId) +{ + assert(buf != NULL); + assert(len > 0); + + PKCS8_PRIV_KEY_INFO* p8info = d2i_PKCS8_PRIV_KEY_INFO(NULL, &buf, len); + + if (p8info == NULL) + { + return NULL; + } + + EVP_PKEY* key = EVP_PKCS82PKEY(p8info); + PKCS8_PRIV_KEY_INFO_free(p8info); + + if (key != NULL && !CheckKey(key, algId, EVP_PKEY_check)) + { + EVP_PKEY_free(key); + key = NULL; + } + + return key; +} + +int32_t CryptoNative_GetPkcs8PrivateKeySize(EVP_PKEY* pkey) +{ + assert(pkey != NULL); + + PKCS8_PRIV_KEY_INFO* p8 = EVP_PKEY2PKCS8(pkey); + + if (p8 == NULL) + { + return -1; + } + + int ret = i2d_PKCS8_PRIV_KEY_INFO(p8, NULL); + PKCS8_PRIV_KEY_INFO_free(p8); + return ret; +} + +int32_t CryptoNative_EncodePkcs8PrivateKey(EVP_PKEY* pkey, uint8_t* buf) +{ + assert(pkey != NULL); + assert(buf != NULL); + + PKCS8_PRIV_KEY_INFO* p8 = EVP_PKEY2PKCS8(pkey); + + if (p8 == NULL) + { + return -1; + } + + int ret = i2d_PKCS8_PRIV_KEY_INFO(p8, &buf); + PKCS8_PRIV_KEY_INFO_free(p8); + return ret; +} + +int32_t CryptoNative_GetSubjectPublicKeyInfoSize(EVP_PKEY* pkey) +{ + assert(pkey != NULL); + + return i2d_PUBKEY(pkey, NULL); +} + +int32_t CryptoNative_EncodeSubjectPublicKeyInfo(EVP_PKEY* pkey, uint8_t* buf) +{ + assert(pkey != NULL); + assert(buf != NULL); + + return i2d_PUBKEY(pkey, &buf); +} diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.h index 98139a8c417d7..012ac98e03db2 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.h @@ -12,6 +12,12 @@ Returns the new EVP_PKEY instance. */ PALEXPORT EVP_PKEY* CryptoNative_EvpPkeyCreate(void); +/* +Create a new EVP_PKEY that has the same interior key as currentKey, +optionally verifying that the key has the correct algorithm. +*/ +PALEXPORT EVP_PKEY* CryptoNative_EvpPKeyDuplicate(EVP_PKEY* currentKey, int32_t algId); + /* Cleans up and deletes a EVP_PKEY instance. @@ -36,3 +42,45 @@ Returns the number (as of this call) of references to the EVP_PKEY. Anything les 2 is an error, because the key is already in the process of being freed. */ PALEXPORT int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey); + +/* +Decodes an X.509 SubjectPublicKeyInfo into an EVP_PKEY*, verifying the interpreted algorithm type. + +Requres a non-null buf, and len > 0. +*/ +PALEXPORT EVP_PKEY* CryptoNative_DecodeSubjectPublicKeyInfo(const uint8_t* buf, int32_t len, int32_t algId); + +/* +Decodes an Pkcs8PrivateKeyInfo into an EVP_PKEY*, verifying the interpreted algorithm type. + +Requres a non-null buf, and len > 0. +*/ +PALEXPORT EVP_PKEY* CryptoNative_DecodePkcs8PrivateKey(const uint8_t* buf, int32_t len, int32_t algId); + +/* +Reports the number of bytes rqeuired to encode an EVP_PKEY* as a Pkcs8PrivateKeyInfo, or a negative value on error. +*/ +PALEXPORT int32_t CryptoNative_GetPkcs8PrivateKeySize(EVP_PKEY* pkey); + +/* +Encodes the EVP_PKEY* as a Pkcs8PrivateKeyInfo, writing the encoded value to buf. + +buf must be big enough, or an out of bounds write may occur. + +Returns the number of bytes written. +*/ +PALEXPORT int32_t CryptoNative_EncodePkcs8PrivateKey(EVP_PKEY* pkey, uint8_t* buf); + +/* +Reports the number of bytes rqeuired to encode an EVP_PKEY* as an X.509 SubjectPublicKeyInfo, or a negative value on error. +*/ +PALEXPORT int32_t CryptoNative_GetSubjectPublicKeyInfoSize(EVP_PKEY* pkey); + +/* +Encodes the EVP_PKEY* as an X.509 SubjectPublicKeyInfo, writing the encoded value to buf. + +buf must be big enough, or an out of bounds write may occur. + +Returns the number of bytes written. +*/ +PALEXPORT int32_t CryptoNative_EncodeSubjectPublicKeyInfo(EVP_PKEY* pkey, uint8_t* buf); 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 fa88420cf9670..36924abb50581 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 @@ -7,6 +7,26 @@ static int HasNoPrivateKey(const RSA* rsa); +EVP_PKEY* CryptoNative_EvpPKeyCreateRsa(RSA* currentKey) +{ + assert(currentKey != NULL); + + EVP_PKEY* pkey = EVP_PKEY_new(); + + if (pkey == NULL) + { + return NULL; + } + + if (!EVP_PKEY_set1_RSA(pkey, currentKey)) + { + EVP_PKEY_free(pkey); + return NULL; + } + + return pkey; +} + EVP_PKEY* CryptoNative_RsaGenerateKey(int keySize) { EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); @@ -277,7 +297,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; @@ -294,16 +314,6 @@ int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, return ret; } -RSA* CryptoNative_EvpPkeyGetRsa(EVP_PKEY* pkey) -{ - return EVP_PKEY_get1_RSA(pkey); -} - -int32_t CryptoNative_EvpPkeySetRsa(EVP_PKEY* pkey, RSA* rsa) -{ - return EVP_PKEY_set1_RSA(pkey, rsa); -} - static int HasNoPrivateKey(const RSA* rsa) { if (rsa == NULL) diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h index d913bc3554b93..2d3c58787650a 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h @@ -15,6 +15,11 @@ typedef enum RsaPaddingOaepOrPss, } RsaPaddingMode; +/* +Create a new EVP_PKEY* wrapping an existing RSA key. +*/ +PALEXPORT EVP_PKEY* CryptoNative_EvpPKeyCreateRsa(RSA* currentKey); + /* Creates an RSA key of the requested size. */ @@ -74,17 +79,3 @@ PALEXPORT int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, const uint8_t* signature, int32_t signatureLen); -/* -Shims the EVP_PKEY_get1_RSA method. - -Returns the RSA instance for the EVP_PKEY. -*/ -PALEXPORT RSA* CryptoNative_EvpPkeyGetRsa(EVP_PKEY* pkey); - -/* -Shims the EVP_PKEY_set1_RSA method to set the RSA -instance on the EVP_KEY. - -Returns 1 upon success, otherwise 0. -*/ -PALEXPORT int32_t CryptoNative_EvpPkeySetRsa(EVP_PKEY* pkey, RSA* rsa); diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_rsa.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_rsa.c deleted file mode 100644 index 197f8bfd9bb44..0000000000000 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_rsa.c +++ /dev/null @@ -1,249 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include "pal_rsa.h" -#include "pal_utilities.h" - -RSA* CryptoNative_RsaCreate() -{ - return RSA_new(); -} - -int32_t CryptoNative_RsaUpRef(RSA* rsa) -{ - return RSA_up_ref(rsa); -} - -void CryptoNative_RsaDestroy(RSA* rsa) -{ - if (rsa != NULL) - { - RSA_free(rsa); - } -} - -RSA* CryptoNative_DecodeRsaPublicKey(const uint8_t* buf, int32_t len) -{ - if (!buf || !len) - { - return NULL; - } - - return d2i_RSAPublicKey(NULL, &buf, len); -} - -int32_t CryptoNative_RsaSize(RSA* rsa) -{ - return RSA_size(rsa); -} - -int32_t CryptoNative_GetRsaParameters(const RSA* rsa, - const BIGNUM** n, - const BIGNUM** e, - const BIGNUM** d, - const BIGNUM** p, - const BIGNUM** dmp1, - const BIGNUM** q, - const BIGNUM** dmq1, - const BIGNUM** iqmp) -{ - if (!rsa || !n || !e || !d || !p || !dmp1 || !q || !dmq1 || !iqmp) - { - assert(false); - - // since these parameters are 'out' parameters in managed code, ensure they are initialized - if (n) - *n = NULL; - if (e) - *e = NULL; - if (d) - *d = NULL; - if (p) - *p = NULL; - if (dmp1) - *dmp1 = NULL; - if (q) - *q = NULL; - if (dmq1) - *dmq1 = NULL; - if (iqmp) - *iqmp = NULL; - - return 0; - } - - RSA_get0_key(rsa, n, e, d); - RSA_get0_factors(rsa, p, q); - RSA_get0_crt_params(rsa, dmp1, dmq1, iqmp); - - return 1; -} - -static BIGNUM* MakeBignum(uint8_t* buffer, int32_t bufferLength) -{ - if (buffer && bufferLength) - { - return BN_bin2bn(buffer, bufferLength, NULL); - } - - return NULL; -} - -static int32_t ValidatePrivateRsaParameters(BIGNUM* bnN, - BIGNUM* bnE, - BIGNUM* bnD, - BIGNUM* bnP, - BIGNUM* bnQ, - BIGNUM* bnDmp1, - BIGNUM* bnDmq1, - BIGNUM* bnIqmp) -{ - if (!bnN || !bnE || !bnD || !bnP || !bnQ || - !bnDmp1 || !bnDmq1 || !bnIqmp) - { - assert(false); - return 0; - } - - // This is shared and should not be freed. - const RSA_METHOD* openssl_method = RSA_PKCS1_OpenSSL(); - - RSA* rsa = RSA_new(); - - if (!rsa) - { - return 0; - } - - // RSA_check_key only works when it has access to the - // internal values of the RSA*. If the process - // has changed the default ENGINE, the function - // may fail. For purposes of validation, we always - // do it using the openssl methods. - if (!RSA_set_method(rsa, openssl_method)) - { - RSA_free(rsa); - return 0; - } - - BIGNUM* bnNdup = BN_dup(bnN); - BIGNUM* bnEdup = BN_dup(bnE); - BIGNUM* bnDdup = BN_dup(bnD); - - if (!RSA_set0_key(rsa, bnNdup, bnEdup, bnDdup)) - { - BN_free(bnNdup); - BN_free(bnEdup); - BN_clear_free(bnDdup); - RSA_free(rsa); - return 0; - } - - BIGNUM* bnPdup = BN_dup(bnP); - BIGNUM* bnQdup = BN_dup(bnQ); - - if (!RSA_set0_factors(rsa, bnPdup, bnQdup)) - { - BN_clear_free(bnPdup); - BN_clear_free(bnQdup); - RSA_free(rsa); - return 0; - } - - BIGNUM* bnDmp1dup = BN_dup(bnDmp1); - BIGNUM* bnDmq1dup = BN_dup(bnDmq1); - BIGNUM* bnIqmpdup = BN_dup(bnIqmp); - - if (!RSA_set0_crt_params(rsa, bnDmp1dup, bnDmq1dup, bnIqmpdup)) - { - BN_clear_free(bnDmp1dup); - BN_clear_free(bnDmq1dup); - BN_clear_free(bnIqmpdup); - RSA_free(rsa); - return 0; - } - - if (RSA_check_key(rsa) != 1) - { - RSA_free(rsa); - return 0; - } - - RSA_free(rsa); - return 1; -} - -int32_t CryptoNative_SetRsaParameters(RSA* rsa, - uint8_t* n, - int32_t nLength, - uint8_t* e, - int32_t eLength, - uint8_t* d, - int32_t dLength, - uint8_t* p, - int32_t pLength, - uint8_t* dmp1, - int32_t dmp1Length, - uint8_t* q, - int32_t qLength, - uint8_t* dmq1, - int32_t dmq1Length, - uint8_t* iqmp, - int32_t iqmpLength) -{ - if (!rsa) - { - assert(false); - return 0; - } - - BIGNUM* bnN = MakeBignum(n, nLength); - BIGNUM* bnE = MakeBignum(e, eLength); - BIGNUM* bnD = MakeBignum(d, dLength); - - if (!RSA_set0_key(rsa, bnN, bnE, bnD)) - { - // BN_free handles NULL input - BN_free(bnN); - BN_free(bnE); - BN_free(bnD); - return 0; - } - - if (bnD != NULL) - { - BIGNUM* bnP = MakeBignum(p, pLength); - BIGNUM* bnQ = MakeBignum(q, qLength); - BIGNUM* bnDmp1 = MakeBignum(dmp1, dmp1Length); - BIGNUM* bnDmq1 = MakeBignum(dmq1, dmq1Length); - BIGNUM* bnIqmp = MakeBignum(iqmp, iqmpLength); - - if (!ValidatePrivateRsaParameters(bnN, - bnE, - bnD, - bnP, - bnQ, - bnDmp1, - bnDmq1, - bnIqmp) - || !RSA_set0_factors(rsa, bnP, bnQ)) - { - BN_free(bnP); - BN_free(bnQ); - BN_free(bnDmp1); - BN_free(bnDmq1); - BN_free(bnIqmp); - return 0; - } - - if (!RSA_set0_crt_params(rsa, bnDmp1, bnDmq1, bnIqmp)) - { - BN_free(bnDmp1); - BN_free(bnDmq1); - BN_free(bnIqmp); - return 0; - } - } - - return 1; -} diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_rsa.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_rsa.h deleted file mode 100644 index bebcaa3443943..0000000000000 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_rsa.h +++ /dev/null @@ -1,79 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include "pal_types.h" -#include "pal_compiler.h" -#include "opensslshim.h" - -/* -Shims the RSA_new method. - -Returns the new RSA instance. -*/ -PALEXPORT RSA* CryptoNative_RsaCreate(void); - -/* -Shims the RSA_up_ref method. - -Returns 1 upon success, otherwise 0. -*/ -PALEXPORT int32_t CryptoNative_RsaUpRef(RSA* rsa); - -/* -Cleans up and deletes a RSA instance. - -Implemented by calling RSA_free - -No-op if rsa is null. -The given RSA pointer is invalid after this call. -Always succeeds. -*/ -PALEXPORT void CryptoNative_RsaDestroy(RSA* rsa); - -/* -Shims the d2i_RSAPublicKey method and makes it easier to invoke from managed code. -*/ -PALEXPORT RSA* CryptoNative_DecodeRsaPublicKey(const uint8_t* buf, int32_t len); - -/* -Shims the RSA_size method. - -Returns the RSA modulus size in bytes. -*/ -PALEXPORT int32_t CryptoNative_RsaSize(RSA* rsa); - -/* -Gets all the parameters from the RSA instance. - -Returns 1 upon success, otherwise 0. -*/ -PALEXPORT int32_t CryptoNative_GetRsaParameters(const RSA* rsa, - const BIGNUM** n, - const BIGNUM** e, - const BIGNUM** d, - const BIGNUM** p, - const BIGNUM** dmp1, - const BIGNUM** q, - const BIGNUM** dmq1, - const BIGNUM** iqmp); - -/* -Sets all the parameters on the RSA instance. -*/ -PALEXPORT int32_t CryptoNative_SetRsaParameters(RSA* rsa, - uint8_t* n, - int32_t nLength, - uint8_t* e, - int32_t eLength, - uint8_t* d, - int32_t dLength, - uint8_t* p, - int32_t pLength, - uint8_t* dmp1, - int32_t dmp1Length, - uint8_t* q, - int32_t qLength, - uint8_t* dmq1, - int32_t dmq1Length, - uint8_t* iqmp, - int32_t iqmpLength); 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 524179cdcb20a..77ac385a79905 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 @@ -5,7 +5,6 @@ #include "openssl.h" #include "pal_evp_pkey.h" #include "pal_evp_pkey_rsa.h" -#include "pal_rsa.h" #include "pal_x509.h" #include @@ -368,8 +367,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; } @@ -661,7 +688,7 @@ static int MakeSelfSignedCertificate(X509 * cert, EVP_PKEY* evp) if (rsa != NULL) { - if (CryptoNative_EvpPkeySetRsa(evp, rsa) == 1) + if (EVP_PKEY_set1_RSA(evp, rsa) == 1) { rsa = NULL; } 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 d7b995c5a19e9..79c6cbe22f955 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.CodeDom/src/Microsoft/CSharp/CSharpCodeGenerator.cs b/src/libraries/System.CodeDom/src/Microsoft/CSharp/CSharpCodeGenerator.cs index 0d7bc48f361b8..356529d80d2b4 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 a25eb3ed7f4fa..5c55dfc06506c 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 1433e38cf9568..3d494454c0f2b 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.Collections/src/System/Collections/BitArray.cs b/src/libraries/System.Collections/src/System/Collections/BitArray.cs index 6921b13347be4..9f8531a9e818a 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.ComponentModel.Composition/src/System/ComponentModel/Composition/ReflectionModel/GenericServices.cs b/src/libraries/System.ComponentModel.Composition/src/System/ComponentModel/Composition/ReflectionModel/GenericServices.cs index 907b838a8f70f..999a67dc43974 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 5640c431c5eb0..827acc9c4e839 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 f41b9fca2a130..ef3cc90c16dbb 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 9094bc6c44904..69017ea3367eb 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 eb545649d3667..19e94551a6f59 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.Console/tests/Helpers.cs b/src/libraries/System.Console/tests/Helpers.cs index f1d6205dc9b19..8c057a1c32dff 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 + + + + 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 393abfc15c492..0000000000000 --- 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/Common/DbConnectionStringBuilder.cs b/src/libraries/System.Data.Common/src/System/Data/Common/DbConnectionStringBuilder.cs index 27ffe33faa3b4..1b6c5fe1b7eb9 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 301075bf54304..91409b4aa8c26 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/DataSet.cs b/src/libraries/System.Data.Common/src/System/Data/DataSet.cs index 33c265766cee4..6974aafce602c 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 bd8257122e233..22ace6fce6404 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: 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 0dca43b20ca62..edefa695a40c7 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 79577f945b94e..2202a8c9f811c 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 ded0f3982a6c8..ea2cc9de54a81 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 1d7ee94a478cf..39d130e4ff0f4 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 5a4c7b1179f05..616acba1372b0 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 3792151f36d95..a757be446e73a 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 fd41a8fb1912b..64e7815e2945c 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 df76c1da336d6..a025c3f7f2bea 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 1094e6ed536e9..ab2fef64dbc51 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 46ad2ac3a8062..a65a2798e5f6f 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 d5943b3da388c..f2d3d31cdfe72 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/DatabaseSetupInstructions.md b/src/libraries/System.Data.Odbc/src/DatabaseSetupInstructions.md index 4c3b16f43fc53..25bcc7225760b 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/libraries/System.Data.Odbc/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.Data.Odbc/src/ILLink/ILLink.Suppressions.xml index 6ea564dbbf68d..c6e3fb619c20c 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 6cd776f00fa2f..8339b330196f6 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 @@ -261,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; - } } } 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 77900ae013f17..a5432b8240539 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/OdbcParameter.cs b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcParameter.cs index 08c95dc69d086..7bcba69eb243d 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/src/System/Data/Odbc/OdbcStatementHandle.cs b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcStatementHandle.cs index 871478cae5014..f8384b7a20b47 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 370bb9771dc83..6df1b3c9ec9d5 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.Odbc/tests/TestCommon/DataTestUtility.cs b/src/libraries/System.Data.Odbc/tests/TestCommon/DataTestUtility.cs index 5055dfb97ce08..b563a7e7b8584 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 8eca4d6280676..344ccae3b8ab1 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 2d47d32c4d9f2..afce7a7dbc3a3 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 756c4612c3f1f..c0b719c737372 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 6417087aa6230..8d78f2ac49323 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 e50ba5a23b1d0..e305bfd04167e 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 69a0e4807db78..0c7dd5ad35c84 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/OleDbTransaction.cs b/src/libraries/System.Data.OleDb/src/OleDbTransaction.cs index 2181a6cbe75af..4a169181f2b59 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 23ef6c6b0d495..bc458f68223e2 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/OleDb_Util.cs b/src/libraries/System.Data.OleDb/src/OleDb_Util.cs index b0a39dea96c73..1426320311c41 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/PropertyIDSet.cs b/src/libraries/System.Data.OleDb/src/PropertyIDSet.cs index 52b854cd0dc6f..5aca3735e5591 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 60b44bab7de48..67f95789eb04a 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 7f94bf563d5f1..75312475fcdd7 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 d435f7f45bcd1..edd87806063a0 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/Common/AdapterUtil.cs b/src/libraries/System.Data.OleDb/src/System/Data/Common/AdapterUtil.cs index f5bacfd623917..473789ae89fb2 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.Data.OleDb/src/System/Data/ProviderBase/DbConnectionPool.cs b/src/libraries/System.Data.OleDb/src/System/Data/ProviderBase/DbConnectionPool.cs index e411fc550101f..521ea909f11da 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.Diagnostics.Debug/tests/DebugTestsNoListeners.Interpolation.cs b/src/libraries/System.Diagnostics.Debug/tests/DebugTestsNoListeners.Interpolation.cs new file mode 100644 index 0000000000000..cd05cdfdc8bd1 --- /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 cabe5a7f4ec9e..4277946a7a29b 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 6e8c0ad320d51..e842a465e60cd 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.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs index 482a58bb414ea..68fa62b8be9be 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 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); + 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 DistributedContextPropagator Current { get; set; } + public static DistributedContextPropagator CreateDefaultPropagator() { throw null; } + public static DistributedContextPropagator CreatePassThroughPropagator() { throw null; } + public static DistributedContextPropagator 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 e1dca02a19d4a..dab222792b1bb 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj @@ -39,6 +39,10 @@ + + + + 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 72f7572313f90..d2ee34fe439d9 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/src/System/Diagnostics/DistributedContextPropagator.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DistributedContextPropagator.cs new file mode 100644 index 0000000000000..9c54baf9c251c --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DistributedContextPropagator.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 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. + /// DistributedContextPropagator inject values into and extracts values from carriers as string key/value pairs. + /// + public abstract class DistributedContextPropagator + { + 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. + /// + /// 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 DistributedContextPropagator. + 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 DistributedContextPropagator 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 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 DistributedContextPropagator CreatePassThroughPropagator() => PassThroughPropagator.Instance; + + /// + /// Returns a propagator which does not transmit any distributed context information in outbound network messages. + /// + public static DistributedContextPropagator 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/src/System/Diagnostics/LegacyPropagator.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LegacyPropagator.cs new file mode 100644 index 0000000000000..9a3c909bb177b --- /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 : DistributedContextPropagator + { + internal static DistributedContextPropagator 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/Metrics/AggregationManager.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/AggregationManager.cs index 41bb991cda7ca..d181fbdf9a666 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 80baa185a09fe..38c4216bdcddf 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/src/System/Diagnostics/NoOutputPropagator.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/NoOutputPropagator.cs new file mode 100644 index 0000000000000..1655ef466e000 --- /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 : DistributedContextPropagator + { + internal static DistributedContextPropagator 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 0000000000000..12515555fcf46 --- /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 : DistributedContextPropagator + { + internal static DistributedContextPropagator 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/tests/DiagnosticSourceEventSourceBridgeTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs index 2a59c8b48e4f6..ecf73aa7f06cb 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. /// diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs index c63aaa1a8712f..ce68dcf98f815 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,34 @@ 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); + } + + [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) @@ -746,8 +792,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); } } @@ -814,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 @@ -906,7 +963,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; } 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 0000000000000..5935ad4770e26 --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/PropagatorTests.cs @@ -0,0 +1,672 @@ +// 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(DistributedContextPropagator.Current); + + // + // Default Propagator + // + + Assert.Same(DistributedContextPropagator.CreateDefaultPropagator(), DistributedContextPropagator.Current); + + TestDefaultPropagatorUsingW3CActivity( + DistributedContextPropagator.Current, + "Legacy1=true", + new List>() { new KeyValuePair(" LegacyKey1 ", " LegacyValue1 ") }); + + TestDefaultPropagatorUsingHierarchicalActivity( + DistributedContextPropagator.Current, + "Legacy2=true", + new List>() { new KeyValuePair("LegacyKey2", "LegacyValue2") }); + + TestFields(DistributedContextPropagator.Current); + + // + // NoOutput Propagator + // + + DistributedContextPropagator.Current = DistributedContextPropagator.CreateNoOutputPropagator(); + Assert.NotNull(DistributedContextPropagator.Current); + TestNoOutputPropagatorUsingHierarchicalActivity( + DistributedContextPropagator.Current, + "ActivityState=1", + new List>() { new KeyValuePair("B1", "V1"), new KeyValuePair(" B2 ", " V2 ")}); + + TestNoOutputPropagatorUsingHierarchicalActivity( + DistributedContextPropagator.Current, + "ActivityState=2", + null); + + TestNoOutputPropagatorUsingW3CActivity( + DistributedContextPropagator.Current, + "ActivityState=1", + new List>() { new KeyValuePair(" B3 ", " V3"), new KeyValuePair(" B4 ", " V4 "), new KeyValuePair("B5", "V5")}); + + TestNoOutputPropagatorUsingW3CActivity( + DistributedContextPropagator.Current, + "ActivityState=2", + null); + + TestFields(DistributedContextPropagator.Current); + + // + // Pass Through Propagator + // + + DistributedContextPropagator.Current = DistributedContextPropagator.CreatePassThroughPropagator(); + Assert.NotNull(DistributedContextPropagator.Current); + TestPassThroughPropagatorUsingHierarchicalActivityWithParentChain( + DistributedContextPropagator.Current, + "PassThrough=true", + new List>() { new KeyValuePair("PassThroughKey1", "PassThroughValue1"), new KeyValuePair("PassThroughKey2", "PassThroughValue2")}); + + TestPassThroughPropagatorUsingHierarchicalActivityWithParentId( + DistributedContextPropagator.Current, + "PassThrough1=true", + new List>() { new KeyValuePair("PassThroughKey3", "PassThroughValue3"), new KeyValuePair(" PassThroughKey4 ", " PassThroughValue4 ")}); + + TestPassThroughPropagatorUsingW3CActivity( + DistributedContextPropagator.Current, + "PassThrough2=1", + new List>() { new KeyValuePair(" PassThroughKey4 ", " PassThroughValue4 ") }); + + TestPassThroughPropagatorWithNullCurrent(DistributedContextPropagator.Current); + + TestFields(DistributedContextPropagator.Current); + + // + // Test Current + // + + Assert.Throws(() => DistributedContextPropagator.Current = null); + + }).Dispose(); + } + + 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); + + Assert.NotSame(Activity.Current, a); + + TestDefaultPropagatorUsing(a, propagator, state, baggage); + + Assert.Same(Activity.Current, b); + + TestDefaultPropagatorUsing(Activity.Current, propagator, state, 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); + + 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, DistributedContextPropagator 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(DistributedContextPropagator 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(DistributedContextPropagator 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(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") } ); + 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(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") } ); + 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(DistributedContextPropagator 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(DistributedContextPropagator 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(DistributedContextPropagator 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(DistributedContextPropagator 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(DistributedContextPropagator 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; + } + + [Fact] + public void TestBuiltInPropagatorsAreCached() + { + Assert.Same(DistributedContextPropagator.CreateDefaultPropagator(), DistributedContextPropagator.CreateDefaultPropagator()); + Assert.Same(DistributedContextPropagator.CreateNoOutputPropagator(), DistributedContextPropagator.CreateNoOutputPropagator()); + Assert.Same(DistributedContextPropagator.CreatePassThroughPropagator(), DistributedContextPropagator.CreatePassThroughPropagator()); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void TestCustomPropagator() + { + RemoteExecutor.Invoke(() => { + + 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); + + DistributedContextPropagator.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"); + }); + + DistributedContextPropagator.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 = DistributedContextPropagator.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 : DistributedContextPropagator + { + 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 3d38a94931789..24c84423e6638 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 @@ + 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 d92bd9471ba6a..ce77b52cc6785 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.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index 882b1beb85ffc..e4f10fa90630d 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 cce32e3760109..6c1268ebf5064 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 a4ff703332afc..63d7f3d6d02b3 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.Diagnostics.TextWriterTraceListener/src/System/Diagnostics/TextWriterTraceListener.cs b/src/libraries/System.Diagnostics.TextWriterTraceListener/src/System/Diagnostics/TextWriterTraceListener.cs index 9c4015842b11d..28325cc356a9b 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 3935b1609fa1a..a8441268574b1 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.DirectoryServices.AccountManagement/src/System/DirectoryServices/AccountManagement/AuthZSet.cs b/src/libraries/System.DirectoryServices.AccountManagement/src/System/DirectoryServices/AccountManagement/AuthZSet.cs index 4c6491929ad38..56edd76877c0f 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 3dc2a589a8d5d..4f2ac1c3086cf 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/Brush.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Brush.cs index af4e81ea8af7e..de964a2b13d33 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 a1c04e7d2f5c1..f0a1bd9aec3b4 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 4b4135665eed7..8e83841c4f6c5 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 beb8d42bb6ff5..410f4478913ba 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 @@ -44,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 91a932b60dc7b..8163d0eefb15e 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 6634ee788416a..0a738e0e3e864 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 5bb82adbc808c..ace1758b4d577 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 6e0438be88587..89b80a06cbcf0 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 929ea19c4fad8..8dd2ad15b22d5 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 ccea5ff42d52e..c359e7af7fdd3 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 5fc556efad7fc..44d36d291cb3b 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 5831014b1e60f..dca1674b2d536 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 705fad55ee57e..ca4e867fcba28 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 9d2343d83b598..8645b31c10cfd 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 606f8cc57af37..ca5d045000dd4 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 39691a9ad5e63..05764ce8f21a1 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 a7e8c1dbdbcf6..a79fb02f0a283 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 8ac8e9ce247ac..f0e41dbc667db 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 34b12d52c9dbc..4b9b49ae33d06 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 9db9042105084..5a321cc4d81f3 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 25ea2116850df..2d3306ed8a2f6 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 3d0b09a7321d7..eaa73074f8728 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 d3e567777cde1..4043ec7221345 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 1889516f87ad4..d750390d07d6b 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 d87c739d8ca48..379fc61e2e154 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 6a9059182008a..cd0a7d074d1c1 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 8541e3069c2f2..609ccae35b8de 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 f711d9a5fe662..a7f116edada78 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 36c56ce1d0c0b..678cecd313e2d 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 47ae16677be5c..1a35a72826c49 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 7d728a1fea562..e4f7500ced2c6 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 508cd1252dbd5..96f75808613bb 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 bd140b7e08019..c23a66d91423a 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 4d0eb252dd675..d386f4dad859f 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 29e2f2ce89c7b..938c758bd5707 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 0f52f67c6b545..48f73a4527a38 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 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Specified file length was too large for the file system. + Zip entry name ends in directory separator character but contains data. Extracting Zip entry would have resulted in a file outside the specified destination directory. - \ No newline at end of file + + The file '{0}' already exists. + + + Unable to find the specified file. + + + Could not find file '{0}'. + + + 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. + + + The path '{0}' is too long, or a component of the specified path is too long. + + + 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. + + + Access to the path is denied. + + + Access to the path '{0}' is denied. + + diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj b/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj index 93eb3e71933e8..2110ae810ea63 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj +++ b/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj @@ -1,7 +1,7 @@ true - $(NetCoreAppCurrent) + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser enable @@ -14,10 +14,26 @@ + + + + + + + + + + + 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 0000000000000..d2e4f44a6751a --- /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 00d82c242b0a6..c9199a97f2a89 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 0000000000000..4da8b87b43ea3 --- /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 cad1a12c5a485..a74aca915faaf 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 bf9e78ab74285..9fbde72eef7b4 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 3ba4081397b7b..18768b5a592fc 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 0000000000000..ab61ae92443d5 --- /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; + } + } +} diff --git a/src/libraries/System.IO.FileSystem/System.IO.FileSystem.sln b/src/libraries/System.IO.FileSystem/System.IO.FileSystem.sln index 93f9297296e4e..fd94f8bfdcac8 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/src/Resources/Strings.resx b/src/libraries/System.IO.FileSystem/src/Resources/Strings.resx new file mode 100644 index 0000000000000..3c391ca6bcde4 --- /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/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs index 6f9a1ace40045..0682f8ce16e5b 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 { 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 0000000000000..736991961ac96 --- /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 0000000000000..87afaa0fb079f --- /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 0000000000000..c118f2a0a0a0b --- /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 eb2686807bc1c..643d2a98cf22a 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 bf0417dbfe061..fb7f4be892c74 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 7f13447f37d92..54c7643766335 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 111ecb545adea..748c01dbffb8f 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 c0ba0787d577a..99c94b450790f 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 76d1541d60c5e..b2a1288639db3 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 d24a8445e6d7f..1dbe303849658 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 3481bff97b4f4..3ad76c272fff6 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/File/Replace.cs b/src/libraries/System.IO.FileSystem/tests/File/Replace.cs index 2cd2a3011a3b7..8bebeef1bb042 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.IO.FileSystem/tests/FileStream/Position.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/Position.cs index 8157f6cc91efd..32817dcee2667 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 6971eb49927e4..963a1fddff57d 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 67944a457716c..3a3f547d070e9 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 60e3cd49ea71c..47e3b49237316 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)); } } } 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 386b2992ee3e8..205e2940e7ec3 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 ae93555bbe831..6ee1c6fc95c8b 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 dd67a63fed17a..745ff60368b2e 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.IO.FileSystem/tests/PortedCommon/CommonUtilities.cs b/src/libraries/System.IO.FileSystem/tests/PortedCommon/CommonUtilities.cs index 03737fa30d425..b413b599f2150 100644 --- a/src/libraries/System.IO.FileSystem/tests/PortedCommon/CommonUtilities.cs +++ b/src/libraries/System.IO.FileSystem/tests/PortedCommon/CommonUtilities.cs @@ -288,7 +288,7 @@ private void CreateFileSystem() for (int k = 0; k < numOfDirPerDir; k++) { string dirName = GetNonExistingDir(dir, DirPrefixName); - Debug.Assert(!Directory.Exists(dirName), string.Format("ERR_93472g! Directory exists: {0}", dirName)); + Debug.Assert(!Directory.Exists(dirName), $"ERR_93472g! Directory exists: {dirName}"); tempDirsForOneLevel.Add(dirName, new List()); _listOfAllDirs.Add(dirName); Directory.CreateDirectory(dirName); 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 dcc7d47924f61..cee09fe2d62b3 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 585335c4cdcb1..d897cc7b8be25 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 3cac633f53f76..8a687dc6012de 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 b5c2399f9642b..3700328398206 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 5af52f0c61a17..0f28b7c9c68b6 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 d6bd235efb745..f89cfd3b4fc62 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.IO.Hashing/tests/NonCryptoHashTestDriver.cs b/src/libraries/System.IO.Hashing/tests/NonCryptoHashTestDriver.cs index 979e60a28065f..3cec2c0983621 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 170ce2feb9725..1ad1801c27052 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 174b91bb219a1..389ab072d9a6c 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/FlushAsyncTests.cs b/src/libraries/System.IO.Pipelines/tests/FlushAsyncTests.cs index 9647a47055b18..fa3db4c91ae4d 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.IO.Pipelines/tests/Infrastructure/TestMemoryPool.cs b/src/libraries/System.IO.Pipelines/tests/Infrastructure/TestMemoryPool.cs index 9567b03e903ad..f990345a9bcf2 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 39f47249a691e..dc2180b39b703 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 0e46efe6998d1..85eb2f4eaf8a5 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 8376f729aa550..99be124701c04 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/DebugViewWriter.cs b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/DebugViewWriter.cs index af967733a5a7a..2f2ecf4b38435 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)) 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 0c3ef9769ae44..585d5901506bc 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 0fbabd6289f36..cf1f973204a36 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 784baac2de9a2..b5753f67732f1 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 3956e67b29a24..77b399b366d8d 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 83177991b77ac..d078d82a597f4 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 0a3586ed514da..53e3fa831e174 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.Management/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.Management/src/ILLink/ILLink.Suppressions.xml index 54f262023c9d4..fbf2a09e7eb93 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 9fac8892aed3a..2d353115180a8 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" /> - - - - diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index 7318b4a0d3c57..1ddd8df16a6b5 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 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; } + 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/src/System/Buffers/Text/Base64.cs b/src/libraries/System.Memory/src/System/Buffers/Text/Base64.cs index 60d1558a50f50..7506cbe1eb014 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 b6c639781c2c2..e71b3b7f2d2fd 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 0ee99479128bc..99add7b72e8a9 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 - }; } } 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 0000000000000..b3d7987bfb655 --- /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 shouldAppend; + + 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 shouldAppend); + Assert.True(shouldAppend); + + if (literalLength > 0) + { + 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 shouldAppend); + Assert.True(shouldAppend); + + 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 shouldAppend); + Assert.False(shouldAppend); + } + } + } + + [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 shouldAppend); + Assert.True(shouldAppend); + + 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 shouldAppend); + Assert.True(shouldAppend); + + 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 shouldAppend); + Assert.True(shouldAppend); + + // 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 5ab1670c7df70..e6638ed601f92 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.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs index 36785e95bd05d..8049845e1a218 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.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj b/src/libraries/System.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj index 525d50608ff7d..99e54a8c729c9 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 f659c9c2f07e2..b0b6d86c67541 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 da4baadb35903..472116113faca 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 5e862724d0e7f..9d3dbc8145367 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,15 +5,16 @@ 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 { private int _size; - private string _cachedToString; + private string? _cachedToString; internal WinHttpChannelBinding(SafeWinHttpHandle requestHandle) { @@ -55,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 ff0ac8798aeed..739995f5e732c 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 9ce26b2716d24..93781b803cc75 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 3b2212ec42aa7..f4a8f6eef82ec 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 93595b1d12af6..366acfa61ad76 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. @@ -175,7 +177,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/WinHttpRequestStream.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpRequestStream.cs index 301e5a6a21fc8..5e79072f44ba8 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 02ce3184f8fe4..f2e3292321ced 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 9f324ada27a0f..053091aadbbf8 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 d7432541b94b9..74f0abbbb8050 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 104808b472a9f..096dc180c72fe 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,17 +9,17 @@ 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()); 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); } - 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 1c3d5eea71799..ab8ee83c1a3e2 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) 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 4b6596d143222..47dbdd201719e 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 >? 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 ae6a8158fa04e..1b20e03c7905a 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.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index 49bc41d756e4b..0bbe56c0d879b 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" /> 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/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs index 4805010002b22..1cd0dbdb7a645 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 21b161f9fbb4a..75954afccab50 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 0fa57394c1cc3..cd91daaed3cbc 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/GlobalHttpSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/GlobalHttpSettings.cs index 7382f4ca0da13..664d296dcb6d1 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/Headers/AltSvcHeaderValue.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderValue.cs index ce534b0e1d542..f04f569b72554 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 b38c3396ca9dd..e8186404aa4f6 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 fd1074f16f939..d52a92875a6a8 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 cba711f89a2d8..91262983d8cdf 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 814a053f5ad61..377a2113dfa88 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 c232ba8371df8..caa70684ec7df 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/HttpClientHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs index 2e3289643cfbe..fce5166f279ed 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/HttpContent.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpContent.cs index afb7082a57ab1..eabc0efe68e0a 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/HttpRequestMessage.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs index d0c88d60f7714..daeda85fff79a 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/AuthenticationHelper.NtAuth.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs index 74abe97e80de9..e9468042f64e1 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/ConnectHelper.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs index 97e8478fa7487..84d6ff8b345bb 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/Http3Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs index ce4f3e74169b5..4667b7bec32f4 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 00a1a376799e2..480724dd8eb5d 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()); @@ -728,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/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index 03cb3e9a40624..410acc16b69ad 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; @@ -67,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 : @@ -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 856b4e61da394..5d4b4846a37eb 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 42361fba08085..68fbd071e7d64 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 1fb6fd925fd33..49e4b0a384806 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)); 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 d4d806f688136..b3551199a2994 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.Http/tests/FunctionalTests/HttpClientTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs index 61727faa43307..26a01e5a13385 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(); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index d8b7ec4e712cd..1e364ea966510 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; @@ -2119,6 +2118,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; 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 b3413cbef3053..667b35e9a8afe 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/PerformanceTests/HPackHuffmanBenchmark/HPackHuffmanBenchmark.csproj b/src/libraries/System.Net.Http/tests/PerformanceTests/HPackHuffmanBenchmark/HPackHuffmanBenchmark.csproj index 7cdd7dc929803..b3bebf988203f 100644 --- a/src/libraries/System.Net.Http/tests/PerformanceTests/HPackHuffmanBenchmark/HPackHuffmanBenchmark.csproj +++ b/src/libraries/System.Net.Http/tests/PerformanceTests/HPackHuffmanBenchmark/HPackHuffmanBenchmark.csproj @@ -1,11 +1,11 @@ - + Exe - net5.0 + net6.0 ../../../src/Resources/Strings.resx enable - 9.0 + preview 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 db333cc634f6c..80b4aa21b108b 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.Http/tests/StressTests/HttpStress/NuGet.config b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/NuGet.config deleted file mode 100644 index 0992c432038a9..0000000000000 --- a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/NuGet.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs index 407c0acd0db25..b411d637a54f1 100644 --- a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs +++ b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs @@ -17,209 +17,212 @@ [assembly:SupportedOSPlatform("windows")] [assembly:SupportedOSPlatform("linux")] -/// -/// Simple HttpClient stress app that launches Kestrel in-proc and runs many concurrent requests of varying types against it. -/// -public static class Program +namespace HttpStress { - public enum ExitCode { Success = 0, StressError = 1, CliError = 2 }; - - public static async Task Main(string[] args) + /// + /// Simple HttpClient stress app that launches Kestrel in-proc and runs many concurrent requests of varying types against it. + /// + public static class Program { - if (!TryParseCli(args, out Configuration? config)) - { - return (int) ExitCode.CliError; - } + public enum ExitCode { Success = 0, StressError = 1, CliError = 2 }; - return (int) await Run(config); - } - - private static bool TryParseCli(string[] args, [NotNullWhen(true)] out Configuration? config) - { - var cmd = new RootCommand(); - cmd.AddOption(new Option("-n", "Max number of requests to make concurrently.") { Argument = new Argument("numWorkers", Environment.ProcessorCount) }); - cmd.AddOption(new Option("-serverUri", "Stress suite server uri.") { Argument = new Argument("serverUri", "https://localhost:5001") }); - cmd.AddOption(new Option("-runMode", "Stress suite execution mode. Defaults to Both.") { Argument = new Argument("runMode", RunMode.both) }); - cmd.AddOption(new Option("-maxExecutionTime", "Maximum stress execution time, in minutes. Defaults to infinity.") { Argument = new Argument("minutes", null) }); - cmd.AddOption(new Option("-maxContentLength", "Max content length for request and response bodies.") { Argument = new Argument("numBytes", 1000) }); - cmd.AddOption(new Option("-maxRequestUriSize", "Max query string length support by the server.") { Argument = new Argument("numChars", 5000) }); - cmd.AddOption(new Option("-maxRequestHeaderCount", "Maximum number of headers to place in request") { Argument = new Argument("numHeaders", 90) }); - cmd.AddOption(new Option("-maxRequestHeaderTotalSize", "Max request header total size.") { Argument = new Argument("numBytes", 1000) }); - cmd.AddOption(new Option("-http", "HTTP version (1.1 or 2.0)") { Argument = new Argument("version", HttpVersion.Version20) }); - cmd.AddOption(new Option("-connectionLifetime", "Max connection lifetime length (milliseconds).") { Argument = new Argument("connectionLifetime", null) }); - cmd.AddOption(new Option("-ops", "Indices of the operations to use") { Argument = new Argument("space-delimited indices", null) }); - cmd.AddOption(new Option("-xops", "Indices of the operations to exclude") { Argument = new Argument("space-delimited indices", null) }); - cmd.AddOption(new Option("-trace", "Enable System.Net.Http.InternalDiagnostics (client) and/or ASP.NET dignostics (server) tracing.") { Argument = new Argument("enable", false) }); - cmd.AddOption(new Option("-aspnetlog", "Enable ASP.NET warning and error logging.") { Argument = new Argument("enable", false) }); - cmd.AddOption(new Option("-listOps", "List available options.") { Argument = new Argument("enable", false) }); - cmd.AddOption(new Option("-seed", "Seed for generating pseudo-random parameters for a given -n argument.") { Argument = new Argument("seed", null) }); - cmd.AddOption(new Option("-numParameters", "Max number of query parameters or form fields for a request.") { Argument = new Argument("queryParameters", 1) }); - cmd.AddOption(new Option("-cancelRate", "Number between 0 and 1 indicating rate of client-side request cancellation attempts. Defaults to 0.1.") { Argument = new Argument("probability", 0.1) }); - cmd.AddOption(new Option("-httpSys", "Use http.sys instead of Kestrel.") { Argument = new Argument("enable", false) }); - cmd.AddOption(new Option("-winHttp", "Use WinHttpHandler for the stress client.") { Argument = new Argument("enable", false) }); - cmd.AddOption(new Option("-displayInterval", "Client stats display interval in seconds. Defaults to 5 seconds.") { Argument = new Argument("seconds", 5) }); - cmd.AddOption(new Option("-clientTimeout", "Default HttpClient timeout in seconds. Defaults to 60 seconds.") { Argument = new Argument("seconds", 60) }); - cmd.AddOption(new Option("-serverMaxConcurrentStreams", "Overrides kestrel max concurrent streams per connection.") { Argument = new Argument("streams", null) }); - cmd.AddOption(new Option("-serverMaxFrameSize", "Overrides kestrel max frame size setting.") { Argument = new Argument("bytes", null) }); - cmd.AddOption(new Option("-serverInitialConnectionWindowSize", "Overrides kestrel initial connection window size setting.") { Argument = new Argument("bytes", null) }); - cmd.AddOption(new Option("-serverMaxRequestHeaderFieldSize", "Overrides kestrel max request header field size.") { Argument = new Argument("bytes", null) }); - - ParseResult cmdline = cmd.Parse(args); - if (cmdline.Errors.Count > 0) + public static async Task Main(string[] args) { - foreach (ParseError error in cmdline.Errors) + if (!TryParseCli(args, out Configuration? config)) { - Console.WriteLine(error); + return (int)ExitCode.CliError; } - Console.WriteLine(); - new HelpBuilder(new SystemConsole()).Write(cmd); - config = null; - return false; + + return (int)await Run(config); } - config = new Configuration() + private static bool TryParseCli(string[] args, [NotNullWhen(true)] out Configuration? config) { - RunMode = cmdline.ValueForOption("-runMode"), - ServerUri = cmdline.ValueForOption("-serverUri"), - ListOperations = cmdline.ValueForOption("-listOps"), - - HttpVersion = cmdline.ValueForOption("-http"), - UseWinHttpHandler = cmdline.ValueForOption("-winHttp"), - ConcurrentRequests = cmdline.ValueForOption("-n"), - RandomSeed = cmdline.ValueForOption("-seed") ?? new Random().Next(), - MaxContentLength = cmdline.ValueForOption("-maxContentLength"), - MaxRequestUriSize = cmdline.ValueForOption("-maxRequestUriSize"), - MaxRequestHeaderCount = cmdline.ValueForOption("-maxRequestHeaderCount"), - MaxRequestHeaderTotalSize = cmdline.ValueForOption("-maxRequestHeaderTotalSize"), - OpIndices = cmdline.ValueForOption("-ops"), - ExcludedOpIndices = cmdline.ValueForOption("-xops"), - MaxParameters = cmdline.ValueForOption("-numParameters"), - DisplayInterval = TimeSpan.FromSeconds(cmdline.ValueForOption("-displayInterval")), - DefaultTimeout = TimeSpan.FromSeconds(cmdline.ValueForOption("-clientTimeout")), - ConnectionLifetime = cmdline.ValueForOption("-connectionLifetime").Select(TimeSpan.FromMilliseconds), - CancellationProbability = Math.Max(0, Math.Min(1, cmdline.ValueForOption("-cancelRate"))), - MaximumExecutionTime = cmdline.ValueForOption("-maxExecutionTime").Select(TimeSpan.FromMinutes), - - UseHttpSys = cmdline.ValueForOption("-httpSys"), - LogAspNet = cmdline.ValueForOption("-aspnetlog"), - Trace = cmdline.ValueForOption("-trace"), - ServerMaxConcurrentStreams = cmdline.ValueForOption("-serverMaxConcurrentStreams"), - ServerMaxFrameSize = cmdline.ValueForOption("-serverMaxFrameSize"), - ServerInitialConnectionWindowSize = cmdline.ValueForOption("-serverInitialConnectionWindowSize"), - ServerMaxRequestHeaderFieldSize = cmdline.ValueForOption("-serverMaxRequestHeaderFieldSize"), - }; - - return true; - } - - private static async Task Run(Configuration config) - { - (string name, Func op)[] clientOperations = - ClientOperations.Operations - // annotate the operation name with its index - .Select((op, i) => ($"{i.ToString().PadLeft(2)}: {op.name}", op.operation)) - .ToArray(); + var cmd = new RootCommand(); + cmd.AddOption(new Option("-n", "Max number of requests to make concurrently.") { Argument = new Argument("numWorkers", Environment.ProcessorCount) }); + cmd.AddOption(new Option("-serverUri", "Stress suite server uri.") { Argument = new Argument("serverUri", "https://localhost:5001") }); + cmd.AddOption(new Option("-runMode", "Stress suite execution mode. Defaults to Both.") { Argument = new Argument("runMode", RunMode.both) }); + cmd.AddOption(new Option("-maxExecutionTime", "Maximum stress execution time, in minutes. Defaults to infinity.") { Argument = new Argument("minutes", null) }); + cmd.AddOption(new Option("-maxContentLength", "Max content length for request and response bodies.") { Argument = new Argument("numBytes", 1000) }); + cmd.AddOption(new Option("-maxRequestUriSize", "Max query string length support by the server.") { Argument = new Argument("numChars", 5000) }); + cmd.AddOption(new Option("-maxRequestHeaderCount", "Maximum number of headers to place in request") { Argument = new Argument("numHeaders", 90) }); + cmd.AddOption(new Option("-maxRequestHeaderTotalSize", "Max request header total size.") { Argument = new Argument("numBytes", 1000) }); + cmd.AddOption(new Option("-http", "HTTP version (1.1 or 2.0)") { Argument = new Argument("version", HttpVersion.Version20) }); + cmd.AddOption(new Option("-connectionLifetime", "Max connection lifetime length (milliseconds).") { Argument = new Argument("connectionLifetime", null) }); + cmd.AddOption(new Option("-ops", "Indices of the operations to use") { Argument = new Argument("space-delimited indices", null) }); + cmd.AddOption(new Option("-xops", "Indices of the operations to exclude") { Argument = new Argument("space-delimited indices", null) }); + cmd.AddOption(new Option("-trace", "Enable System.Net.Http.InternalDiagnostics (client) and/or ASP.NET dignostics (server) tracing.") { Argument = new Argument("enable", false) }); + cmd.AddOption(new Option("-aspnetlog", "Enable ASP.NET warning and error logging.") { Argument = new Argument("enable", false) }); + cmd.AddOption(new Option("-listOps", "List available options.") { Argument = new Argument("enable", false) }); + cmd.AddOption(new Option("-seed", "Seed for generating pseudo-random parameters for a given -n argument.") { Argument = new Argument("seed", null) }); + cmd.AddOption(new Option("-numParameters", "Max number of query parameters or form fields for a request.") { Argument = new Argument("queryParameters", 1) }); + cmd.AddOption(new Option("-cancelRate", "Number between 0 and 1 indicating rate of client-side request cancellation attempts. Defaults to 0.1.") { Argument = new Argument("probability", 0.1) }); + cmd.AddOption(new Option("-httpSys", "Use http.sys instead of Kestrel.") { Argument = new Argument("enable", false) }); + cmd.AddOption(new Option("-winHttp", "Use WinHttpHandler for the stress client.") { Argument = new Argument("enable", false) }); + cmd.AddOption(new Option("-displayInterval", "Client stats display interval in seconds. Defaults to 5 seconds.") { Argument = new Argument("seconds", 5) }); + cmd.AddOption(new Option("-clientTimeout", "Default HttpClient timeout in seconds. Defaults to 60 seconds.") { Argument = new Argument("seconds", 60) }); + cmd.AddOption(new Option("-serverMaxConcurrentStreams", "Overrides kestrel max concurrent streams per connection.") { Argument = new Argument("streams", null) }); + cmd.AddOption(new Option("-serverMaxFrameSize", "Overrides kestrel max frame size setting.") { Argument = new Argument("bytes", null) }); + cmd.AddOption(new Option("-serverInitialConnectionWindowSize", "Overrides kestrel initial connection window size setting.") { Argument = new Argument("bytes", null) }); + cmd.AddOption(new Option("-serverMaxRequestHeaderFieldSize", "Overrides kestrel max request header field size.") { Argument = new Argument("bytes", null) }); + + ParseResult cmdline = cmd.Parse(args); + if (cmdline.Errors.Count > 0) + { + foreach (ParseError error in cmdline.Errors) + { + Console.WriteLine(error); + } + Console.WriteLine(); + new HelpBuilder(new SystemConsole()).Write(cmd); + config = null; + return false; + } - if ((config.RunMode & RunMode.both) == 0) - { - Console.Error.WriteLine("Must specify a valid run mode"); - return ExitCode.CliError; + config = new Configuration() + { + RunMode = cmdline.ValueForOption("-runMode"), + ServerUri = cmdline.ValueForOption("-serverUri"), + ListOperations = cmdline.ValueForOption("-listOps"), + + HttpVersion = cmdline.ValueForOption("-http"), + UseWinHttpHandler = cmdline.ValueForOption("-winHttp"), + ConcurrentRequests = cmdline.ValueForOption("-n"), + RandomSeed = cmdline.ValueForOption("-seed") ?? new Random().Next(), + MaxContentLength = cmdline.ValueForOption("-maxContentLength"), + MaxRequestUriSize = cmdline.ValueForOption("-maxRequestUriSize"), + MaxRequestHeaderCount = cmdline.ValueForOption("-maxRequestHeaderCount"), + MaxRequestHeaderTotalSize = cmdline.ValueForOption("-maxRequestHeaderTotalSize"), + OpIndices = cmdline.ValueForOption("-ops"), + ExcludedOpIndices = cmdline.ValueForOption("-xops"), + MaxParameters = cmdline.ValueForOption("-numParameters"), + DisplayInterval = TimeSpan.FromSeconds(cmdline.ValueForOption("-displayInterval")), + DefaultTimeout = TimeSpan.FromSeconds(cmdline.ValueForOption("-clientTimeout")), + ConnectionLifetime = cmdline.ValueForOption("-connectionLifetime").Select(TimeSpan.FromMilliseconds), + CancellationProbability = Math.Max(0, Math.Min(1, cmdline.ValueForOption("-cancelRate"))), + MaximumExecutionTime = cmdline.ValueForOption("-maxExecutionTime").Select(TimeSpan.FromMinutes), + + UseHttpSys = cmdline.ValueForOption("-httpSys"), + LogAspNet = cmdline.ValueForOption("-aspnetlog"), + Trace = cmdline.ValueForOption("-trace"), + ServerMaxConcurrentStreams = cmdline.ValueForOption("-serverMaxConcurrentStreams"), + ServerMaxFrameSize = cmdline.ValueForOption("-serverMaxFrameSize"), + ServerInitialConnectionWindowSize = cmdline.ValueForOption("-serverInitialConnectionWindowSize"), + ServerMaxRequestHeaderFieldSize = cmdline.ValueForOption("-serverMaxRequestHeaderFieldSize"), + }; + + return true; } - if (!config.ServerUri.StartsWith("http")) + private static async Task Run(Configuration config) { - Console.Error.WriteLine("Invalid server uri"); - return ExitCode.CliError; - } + (string name, Func op)[] clientOperations = + ClientOperations.Operations + // annotate the operation name with its index + .Select((op, i) => ($"{i.ToString().PadLeft(2)}: {op.name}", op.operation)) + .ToArray(); - if (config.ListOperations) - { - for (int i = 0; i < clientOperations.Length; i++) + if ((config.RunMode & RunMode.both) == 0) { - Console.WriteLine(clientOperations[i].name); + Console.Error.WriteLine("Must specify a valid run mode"); + return ExitCode.CliError; } - return ExitCode.Success; - } - // derive client operations based on arguments - (string name, Func op)[] usedClientOperations = (config.OpIndices, config.ExcludedOpIndices) switch - { - (null, null) => clientOperations, - (int[] incl, null) => incl.Select(i => clientOperations[i]).ToArray(), - (_, int[] excl) => - Enumerable - .Range(0, clientOperations.Length) - .Except(excl) - .Select(i => clientOperations[i]) - .ToArray(), - }; - - string GetAssemblyInfo(Assembly assembly) => $"{assembly.Location}, modified {new FileInfo(assembly.Location).LastWriteTime}"; - - Console.WriteLine(" .NET Core: " + GetAssemblyInfo(typeof(object).Assembly)); - Console.WriteLine(" ASP.NET Core: " + GetAssemblyInfo(typeof(WebHost).Assembly)); - Console.WriteLine(" System.Net.Http: " + GetAssemblyInfo(typeof(System.Net.Http.HttpClient).Assembly)); - Console.WriteLine(" Server: " + (config.UseHttpSys ? "http.sys" : "Kestrel")); - Console.WriteLine(" Server URL: " + config.ServerUri); - Console.WriteLine(" Client Tracing: " + (config.Trace && config.RunMode.HasFlag(RunMode.client) ? "ON (client.log)" : "OFF")); - Console.WriteLine(" Server Tracing: " + (config.Trace && config.RunMode.HasFlag(RunMode.server) ? "ON (server.log)" : "OFF")); - Console.WriteLine(" ASP.NET Log: " + config.LogAspNet); - Console.WriteLine(" Concurrency: " + config.ConcurrentRequests); - Console.WriteLine(" Content Length: " + config.MaxContentLength); - Console.WriteLine(" HTTP Version: " + config.HttpVersion); - Console.WriteLine(" Lifetime: " + (config.ConnectionLifetime.HasValue ? $"{config.ConnectionLifetime.Value.TotalMilliseconds}ms" : "(infinite)")); - Console.WriteLine(" Operations: " + string.Join(", ", usedClientOperations.Select(o => o.name))); - Console.WriteLine(" Random Seed: " + config.RandomSeed); - Console.WriteLine(" Cancellation: " + 100 * config.CancellationProbability + "%"); - Console.WriteLine("Max Content Size: " + config.MaxContentLength); - Console.WriteLine("Query Parameters: " + config.MaxParameters); - Console.WriteLine(); - - - StressServer? server = null; - if (config.RunMode.HasFlag(RunMode.server)) - { - // Start the Kestrel web server in-proc. - Console.WriteLine($"Starting {(config.UseHttpSys ? "http.sys" : "Kestrel")} server."); - server = new StressServer(config); - Console.WriteLine($"Server started at {server.ServerUri}"); - } + if (!config.ServerUri.StartsWith("http")) + { + Console.Error.WriteLine("Invalid server uri"); + return ExitCode.CliError; + } - StressClient? client = null; - if (config.RunMode.HasFlag(RunMode.client)) - { - // Start the client. - Console.WriteLine($"Starting {config.ConcurrentRequests} client workers."); + if (config.ListOperations) + { + for (int i = 0; i < clientOperations.Length; i++) + { + Console.WriteLine(clientOperations[i].name); + } + return ExitCode.Success; + } - client = new StressClient(usedClientOperations, config); - client.Start(); - } + // derive client operations based on arguments + (string name, Func op)[] usedClientOperations = (config.OpIndices, config.ExcludedOpIndices) switch + { + (null, null) => clientOperations, + (int[] incl, null) => incl.Select(i => clientOperations[i]).ToArray(), + (_, int[] excl) => + Enumerable + .Range(0, clientOperations.Length) + .Except(excl) + .Select(i => clientOperations[i]) + .ToArray(), + }; + + string GetAssemblyInfo(Assembly assembly) => $"{assembly.Location}, modified {new FileInfo(assembly.Location).LastWriteTime}"; + + Console.WriteLine(" .NET Core: " + GetAssemblyInfo(typeof(object).Assembly)); + Console.WriteLine(" ASP.NET Core: " + GetAssemblyInfo(typeof(WebHost).Assembly)); + Console.WriteLine(" System.Net.Http: " + GetAssemblyInfo(typeof(System.Net.Http.HttpClient).Assembly)); + Console.WriteLine(" Server: " + (config.UseHttpSys ? "http.sys" : "Kestrel")); + Console.WriteLine(" Server URL: " + config.ServerUri); + Console.WriteLine(" Client Tracing: " + (config.Trace && config.RunMode.HasFlag(RunMode.client) ? "ON (client.log)" : "OFF")); + Console.WriteLine(" Server Tracing: " + (config.Trace && config.RunMode.HasFlag(RunMode.server) ? "ON (server.log)" : "OFF")); + Console.WriteLine(" ASP.NET Log: " + config.LogAspNet); + Console.WriteLine(" Concurrency: " + config.ConcurrentRequests); + Console.WriteLine(" Content Length: " + config.MaxContentLength); + Console.WriteLine(" HTTP Version: " + config.HttpVersion); + Console.WriteLine(" Lifetime: " + (config.ConnectionLifetime.HasValue ? $"{config.ConnectionLifetime.Value.TotalMilliseconds}ms" : "(infinite)")); + Console.WriteLine(" Operations: " + string.Join(", ", usedClientOperations.Select(o => o.name))); + Console.WriteLine(" Random Seed: " + config.RandomSeed); + Console.WriteLine(" Cancellation: " + 100 * config.CancellationProbability + "%"); + Console.WriteLine("Max Content Size: " + config.MaxContentLength); + Console.WriteLine("Query Parameters: " + config.MaxParameters); + Console.WriteLine(); - await WaitUntilMaxExecutionTimeElapsedOrKeyboardInterrupt(config.MaximumExecutionTime); - client?.Stop(); - client?.PrintFinalReport(); + StressServer? server = null; + if (config.RunMode.HasFlag(RunMode.server)) + { + // Start the Kestrel web server in-proc. + Console.WriteLine($"Starting {(config.UseHttpSys ? "http.sys" : "Kestrel")} server."); + server = new StressServer(config); + Console.WriteLine($"Server started at {server.ServerUri}"); + } - // return nonzero status code if there are stress errors - return client?.TotalErrorCount == 0 ? ExitCode.Success : ExitCode.StressError; - } + StressClient? client = null; + if (config.RunMode.HasFlag(RunMode.client)) + { + // Start the client. + Console.WriteLine($"Starting {config.ConcurrentRequests} client workers."); - private static async Task WaitUntilMaxExecutionTimeElapsedOrKeyboardInterrupt(TimeSpan? maxExecutionTime = null) - { - var tcs = new TaskCompletionSource(); - Console.CancelKeyPress += (sender,args) => { Console.Error.WriteLine("Keyboard interrupt"); args.Cancel = true; tcs.TrySetResult(false); }; - if (maxExecutionTime.HasValue) - { - Console.WriteLine($"Running for a total of {maxExecutionTime.Value.TotalMinutes:0.##} minutes"); - var cts = new System.Threading.CancellationTokenSource(delay: maxExecutionTime.Value); - cts.Token.Register(() => { Console.WriteLine("Max execution time elapsed"); tcs.TrySetResult(false); }); + client = new StressClient(usedClientOperations, config); + client.Start(); + } + + await WaitUntilMaxExecutionTimeElapsedOrKeyboardInterrupt(config.MaximumExecutionTime); + + client?.Stop(); + client?.PrintFinalReport(); + + // return nonzero status code if there are stress errors + return client?.TotalErrorCount == 0 ? ExitCode.Success : ExitCode.StressError; } - await tcs.Task; - } + private static async Task WaitUntilMaxExecutionTimeElapsedOrKeyboardInterrupt(TimeSpan? maxExecutionTime = null) + { + var tcs = new TaskCompletionSource(); + Console.CancelKeyPress += (sender, args) => { Console.Error.WriteLine("Keyboard interrupt"); args.Cancel = true; tcs.TrySetResult(false); }; + if (maxExecutionTime.HasValue) + { + Console.WriteLine($"Running for a total of {maxExecutionTime.Value.TotalMinutes:0.##} minutes"); + var cts = new System.Threading.CancellationTokenSource(delay: maxExecutionTime.Value); + cts.Token.Register(() => { Console.WriteLine("Max execution time elapsed"); tcs.TrySetResult(false); }); + } - private static S? Select(this T? value, Func mapper) where T : struct where S : struct - { - return value is null ? null : new S?(mapper(value.Value)); + await tcs.Task; + } + + private static S? Select(this T? value, Func mapper) where T : struct where S : struct + { + return value is null ? null : new S?(mapper(value.Value)); + } } } 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 968adb53345d0..249e579d47d1b 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 61f05ac248e93..f078b5bdbee6a 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 13562495cb46b..63c96497909f3 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 3eadb3cf7e5e4..ecbadd5943c9f 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 5a41b0ddeed6a..d41ff445938cd 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/HttpServerSessionHandle.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpServerSessionHandle.cs index 671eae6386b5d..f180fefecd115 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.HttpListener/src/System/Net/Windows/ListenerAsyncResult.Windows.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/ListenerAsyncResult.Windows.cs index b36252e9de822..0b57602b1b746 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 ee844910e4de3..9b9c501746de2 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 84d4222f8cd11..9355cd36ccb25 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 838963d98d8d8..712b0d7f39d66 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.csproj b/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj index a90d602beadd4..60d0a7259e2e8 100644 --- a/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj +++ b/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj @@ -204,6 +204,8 @@ Link="Common\Interop\Windows\Crypt32\Interop.DATA_BLOB.cs" /> + 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 e98a031e77ca2..34079edf51c8f 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 c62b7f83500f5..e12d731640074 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 cceff5d15fa1e..415c1e6e972d9 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 bb300289dfa8c..4365cee08cb91 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.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj b/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj index d63a2501e8a46..2132010f6a7d4 100644 --- a/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj +++ b/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj @@ -183,6 +183,8 @@ + + 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 46858edea5dfa..f18ee68a35a93 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; } } @@ -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/GetHostByNameTest.cs b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostByNameTest.cs index 94dba8bdb7fcd..2b62cb6c77192 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 6fa40dd80c46a..bfb8b48cc4e70 100644 --- a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs +++ b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs @@ -6,7 +6,8 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; - +using Microsoft.DotNet.RemoteExecutor; +using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace System.Net.NameResolution.Tests @@ -20,14 +21,28 @@ public async Task Dns_GetHostEntryAsync_IPAddress_Ok() await TestGetHostEntryAsync(() => Dns.GetHostEntryAsync(localIPAddress)); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)] - - [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 && + // [ActiveIssue("https://github.com/dotnet/runtime/issues/55271")] + !PlatformDetection.IsSLES; + + [ConditionalTheory(nameof(GetHostEntryWorks))] [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))); @@ -71,13 +86,19 @@ public async Task Dns_GetHostEntry_HostString_Ok(string hostName) } } - [ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)] - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArm64Process))] // [ActiveIssue("https://github.com/dotnet/runtime/issues/27622")] + [ConditionalTheory(nameof(GetHostEntryWorks))] [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() => @@ -98,6 +119,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 7b2b9ee2c9fd6..3b9a81c4ee5d3 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" /> + 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 d53d00eb5887e..ca042d47b7492 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.Ping/src/System.Net.Ping.csproj b/src/libraries/System.Net.Ping/src/System.Net.Ping.csproj index d7becfa7963de..571583be6c379 100644 --- a/src/libraries/System.Net.Ping/src/System.Net.Ping.csproj +++ b/src/libraries/System.Net.Ping/src/System.Net.Ping.csproj @@ -21,6 +21,8 @@ Link="Common\System\Net\IPAddressParserStatics.cs" /> + 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 3893ae10651ac..8ed63ef9f97b3 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 212e3b6e0941f..bb7801cbebb82 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.Quic/src/System.Net.Quic.csproj b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj index 6d8de7f5d6c8a..1cd17da1d873e 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/Internal/MsQuicApi.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs index 2bf1b9b1955e8..b691d3c5c14da 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; @@ -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/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 0000000000000..33ef7b948c9b6 --- /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/Interop/SafeMsQuicConfigurationHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConfigurationHandle.cs index f80f33f53ae49..df48e0db377ae 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 74f806c9818b0..9354ce04a18e9 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 87e59b69a2205..f0f75556921c3 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 d9c6e7c70adc4..798636bd7e431 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 3037f60956a70..179bdd3d15e1c 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.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs index dd651ad0ab515..3eea4d19c692b 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,6 +85,7 @@ public void RemoveStream(MsQuicStream? stream) if (releaseHandles) { + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"{TraceId} releasing handle after last stream."); Handle?.Dispose(); } } @@ -125,6 +128,8 @@ public void SetClosing() } } + 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) { @@ -160,9 +165,10 @@ public MsQuicConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, Saf throw; } + _state.TraceId = MsQuicTraceHelper.GetTraceId(_state.Handle); if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(_state, $"[Connection#{_state.GetHashCode()}] inbound connection created"); + NetEventSource.Info(_state, $"{TraceId()} Inbound connection created"); } } @@ -199,9 +205,10 @@ public MsQuicConnection(QuicClientConnectionOptions options) throw; } + _state.TraceId = MsQuicTraceHelper.GetTraceId(_state.Handle); if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(_state, $"[Connection#{_state.GetHashCode()}] outbound connection created"); + NetEventSource.Info(_state, $"{TraceId()} Outbound connection created"); } } @@ -382,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 @@ -411,19 +418,23 @@ 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, $"[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; @@ -621,7 +632,7 @@ private static uint NativeCallbackHandler( if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(state, $"[Connection#{state.GetHashCode()}] received event {connectionEvent.Type}"); + NetEventSource.Info(state, $"{state.TraceId} Connection received event {connectionEvent.Type}"); } try @@ -650,10 +661,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. @@ -697,6 +708,8 @@ private void Dispose(bool disposing) return; } + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(_state, $"{TraceId()} Stream disposing {disposing}"); + bool releaseHandles = false; lock (_state) { @@ -716,6 +729,8 @@ private void Dispose(bool disposing) _configuration?.Dispose(); if (releaseHandles) { + 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 c5ef5d3fdce36..391d26a93ddd3 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 fed61e11e5695..585aca851b3f0 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 @@ -36,13 +33,23 @@ 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; + + // 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; @@ -53,7 +60,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; @@ -69,6 +76,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(); @@ -79,13 +88,14 @@ public void Cleanup() } } + internal string TraceId() => _state.TraceId; + // inbound. internal MsQuicStream(MsQuicConnection.State connectionState, SafeMsQuicStreamHandle streamHandle, QUIC_STREAM_OPEN_FLAGS flags) { _state.Handle = streamHandle; _canRead = true; _canWrite = !flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL); - _started = true; if (!_canWrite) { _state.SendState = SendState.Closed; @@ -113,12 +123,13 @@ internal MsQuicStream(MsQuicConnection.State connectionState, SafeMsQuicStreamHa _state.ConnectionState = connectionState; + _state.TraceId = MsQuicTraceHelper.GetTraceId(_state.Handle); if (NetEventSource.Log.IsEnabled()) { 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 connection {_state.ConnectionState.TraceId}."); } } @@ -166,12 +177,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, - $"[Stream#{_state.GetHashCode()}] outbound {(flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL) ? "uni" : "bi")}directional stream created " + - $"in Connection#{_state.ConnectionState.GetHashCode()}."); + $"{_state.TraceId} Outbound {(flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL) ? "uni" : "bi")}directional stream created " + + $"in connection {_state.ConnectionState.TraceId}."); } } @@ -208,7 +220,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); @@ -224,7 +236,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); @@ -235,14 +247,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) { @@ -268,14 +280,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 @@ -344,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(); @@ -353,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 System.OperationCanceledException(cancellationToken); - } - 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."); } + 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 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 actual; + 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); @@ -647,6 +694,9 @@ private void Dispose(bool disposing) return; } + + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(_state, $"{TraceId()} Stream disposing {disposing}"); + bool callShutdown = false; bool abortRead = false; bool releaseHandles = false; @@ -698,15 +748,13 @@ 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()} Stream disposed"); } 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( @@ -726,17 +774,17 @@ 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} Stream received event {evt.Type}"); } 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); @@ -769,12 +817,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, $"{state.TraceId} Exception occurred during handling Stream {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($"{state.TraceId} Exception occurred during handling Stream {evt.Type} event: {ex}"); return MsQuicStatusCodes.InternalError; } @@ -782,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) - { - shouldComplete = true; - } - if (state.ReadState != ReadState.ConnectionClosed) + switch (state.ReadState) { - 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) @@ -831,22 +926,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; } @@ -887,14 +970,15 @@ 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) + 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; } @@ -942,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; @@ -966,14 +1052,15 @@ 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) + 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; } @@ -1256,7 +1343,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}" : "")); } @@ -1267,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) { @@ -1322,18 +1409,35 @@ 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 { /// /// The stream is open, but there is no data available. /// - None, + None = 0, /// /// Data is available in . /// IndividualReadComplete, + /// + /// User called ReadAsync() + /// + PendingRead, + + // following states are final: + /// /// The peer has gracefully shutdown their sends / our receives; the stream's reads are complete. /// @@ -1357,7 +1461,7 @@ private enum ReadState private enum ShutdownWriteState { - None, + None = 0, Canceled, Finished, ConnectionClosed @@ -1365,7 +1469,7 @@ private enum ShutdownWriteState private enum ShutdownState { - None, + None = 0, Canceled, Pending, Finished, @@ -1374,10 +1478,12 @@ private enum ShutdownState private enum SendState { - None, + None = 0, Pending, - Aborted, Finished, + + // Terminal states + Aborted, ConnectionClosed, Closed } diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs index b8097a43a6c6d..884c18947568c 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); @@ -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; @@ -476,7 +477,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 +500,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/QuicConnectionTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs index 8c81ce800dc84..41b8d549ed748 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; diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs index 7b40903651824..4890ebea5f352 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(); }); @@ -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 4d9ab4de6a3aa..da2cfb37f412c 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; @@ -171,40 +207,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 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 2a17894a5555a..583b2b6c4861e 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 + + + diff --git a/src/libraries/System.Net.Security/ref/System.Net.Security.cs b/src/libraries/System.Net.Security/ref/System.Net.Security.cs index 4e277cecdb575..d31416123ca8e 100644 --- a/src/libraries/System.Net.Security/ref/System.Net.Security.cs +++ b/src/libraries/System.Net.Security/ref/System.Net.Security.cs @@ -130,10 +130,25 @@ public readonly struct SslClientHelloInfo public static bool operator !=(System.Net.Security.SslApplicationProtocol left, System.Net.Security.SslApplicationProtocol right) { throw null; } public override string ToString() { throw null; } } + + public sealed partial class SslCertificateTrust + { + public static SslCertificateTrust CreateForX509Store( + System.Security.Cryptography.X509Certificates.X509Store store, + bool sendTrustInHandshake = false) { throw null; } + [System.Runtime.Versioning.UnsupportedOSPlatform("windows")] + public static SslCertificateTrust CreateForX509Collection( + System.Security.Cryptography.X509Certificates.X509Certificate2Collection trustList, + bool sendTrustInHandshake = false) { throw null; } + private SslCertificateTrust() { throw null; } + } + public sealed partial class SslStreamCertificateContext { internal SslStreamCertificateContext() { throw null; } - public static SslStreamCertificateContext Create(System.Security.Cryptography.X509Certificates.X509Certificate2 target, System.Security.Cryptography.X509Certificates.X509Certificate2Collection? additionalCertificates, bool offline = false) { throw null; } + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public static SslStreamCertificateContext Create(System.Security.Cryptography.X509Certificates.X509Certificate2 target, System.Security.Cryptography.X509Certificates.X509Certificate2Collection? additionalCertificates, bool offline) { throw null; } + public static SslStreamCertificateContext Create(System.Security.Cryptography.X509Certificates.X509Certificate2 target, System.Security.Cryptography.X509Certificates.X509Certificate2Collection? additionalCertificates, bool offline = false, SslCertificateTrust? trust = null) { throw null; } } public partial class SslClientAuthenticationOptions { diff --git a/src/libraries/System.Net.Security/src/Resources/Strings.resx b/src/libraries/System.Net.Security/src/Resources/Strings.resx index 8181afecaaa62..e33b4122a7d53 100644 --- a/src/libraries/System.Net.Security/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Security/src/Resources/Strings.resx @@ -455,7 +455,16 @@ Received data during renegotiation. + + Client stream needs to be drained before renegotiation. + 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 100115a0920a2..ca89fca29fa5b 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 @@ + 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; @@ -922,7 +922,7 @@ internal SecurityStatusPal Decrypt(Span 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 0000000000000..982d206c85935 --- /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 17858bb0e68b6..f0f0b954d4a8f 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); @@ -664,7 +683,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; @@ -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 75c2938d444af..276652dbcb94f 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/SslStreamCertificateContext.Linux.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Linux.cs index f5e9dd2114d61..facb437f02b42 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 4579f15407fde..9cef66806ac06 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 fcb84b2dda6a0..699a5c8c01239 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 4ae127aadcc15..c585d7e033de0 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.Unix.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs index 020456131702f..ed0bcaaccd1b9 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/src/System/Net/Security/SslStreamPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs index 4060296c4ed87..29ab3bed0ee8c 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/src/System/Security/Authentication/ExtendedProtection/ExtendedProtectionPolicy.cs b/src/libraries/System.Net.Security/src/System/Security/Authentication/ExtendedProtection/ExtendedProtectionPolicy.cs index fe69152dc10a6..6a59d81957a4a 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.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs index e2e8314249d36..f135095476320 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()) diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs index 6ff3afb23f3a4..b3a5f5e21f46c 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 a7dbdc9451ea4..4bd1b0ee30f9a 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 4e146153626b3..ee160631b400f 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() { 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 35d10bb679f16..496c628956f8c 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 3e2886baf52b7..9ed625aecd0ff 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() { 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 0c32b6d906d2a..27289121acd00 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 368cf805542bb..9959c66918e3c 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/BrowserWebSockets/BrowserWebSocket.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs index 0eae170576a7d..b052020b11e0f 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/src/System/Net/WebSockets/WebSocketHandle.Managed.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs index 0af0cd5fb145c..f19f9a572772f 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.Net.WebSockets.Client/tests/AbortTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs index 6e9a60f064793..209a6f10ff23b 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 0fb1314a6068e..296bffbc620ae 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); 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 d6a0cb1c6930e..bf6c663b56432 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 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 f73025d545b72..68eab6c5bbac6 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/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs index cd4e18e7486db..367184e9a455e 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 49638af80f34f..abb6238b52139 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/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs index 10dd1eabee010..ee1def85b9e02 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. diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 23a96351e049c..a9910865f0065 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.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index a1e270dbde97d..6c1182acbd19f 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 @@ + + @@ -837,6 +839,9 @@ + + + @@ -1235,6 +1240,7 @@ + @@ -1281,7 +1287,6 @@ - @@ -1651,6 +1656,9 @@ Common\Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs + + Common\Interop\Windows\Interop.SetConsoleCtrlHandler.cs + Common\Interop\Windows\Kernel32\Interop.SetCurrentDirectory.cs @@ -1833,6 +1841,7 @@ + @@ -1946,7 +1955,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 @@ -2129,6 +2138,10 @@ Common\Interop\Unix\System.Native\Interop.GetPid.cs + + + + + @@ -2304,4 +2320,4 @@ - + \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System/ApplicationId.cs b/src/libraries/System.Private.CoreLib/src/System/ApplicationId.cs index 30542d17ece7f..11f807986d31f 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/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 ee2e046901fe1..07723d04810f7 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 0b9cca720b425..dd4572afaf7fa 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; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Byte.cs b/src/libraries/System.Private.CoreLib/src/System/Byte.cs index 39bfeae2a7e44..3d5b448159a63 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 2f58b54b7460a..26ff3399fe10e 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/Collections/Generic/KeyValuePair.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/KeyValuePair.cs index 83f4fda598eb2..61325d2058f2e 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/Decimal.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs index e5f26f2ff0148..5c4f30795cd6d 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/Diagnostics/Debug.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debug.cs index da744f6d7bee0..317e319e3f90a 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/Diagnostics/Tracing/ActivityTracker.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/ActivityTracker.cs index a1f724612d467..8aebbc4f7aaee 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 8a53d56908210..0c4460facc30f 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 d6093b4fb66bb..4800a2cde4736 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 fee9867d2c9fe..ed9697d245022 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/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs deleted file mode 100644 index bfd8d0ff989dd..0000000000000 --- 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 - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index 85d5f8c36f11d..10178510f6332 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/Environment.Win32.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Win32.cs index a499d8984371e..8e8e8c5e6898b 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 e9e3618bfc60d..593a9ab1e4fb0 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 ce6e23b8c8560..b4796fd92e63d 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 4a6aef540a5af..2f9d5f729e459 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/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index ea64c10f83538..c70cb712bfe63 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 a93b439625e98..89ec036bb147a 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/IO/BufferedStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/BufferedStream.cs index 3834bbeeffe78..14da1aa038b78 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/FileSystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs index c80e585eda865..0151499893aa8 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 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 41de56fcbea7e..b82627112f35a 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 63639cc8503a7..8711ce23e9d2c 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 4d768cad53eda..ad190fe2f75e6 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/BufferedFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs index 11d21121beea6..9aaa16ff51941 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/IO/Strategies/OSFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs index b49b8ce2f33f8..7652fc768c1f3 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 c88665bf0b8d8..6f0f9f9562dab 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/Int16.cs b/src/libraries/System.Private.CoreLib/src/System/Int16.cs index f42c84de2bd87..909b37f77b340 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 d8514bc89f7e7..68c6a70cdb043 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 c0420f5052e5b..1646cf96457ef 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 f64effb167adf..a9592617b2eb2 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/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 31608d643a292..f7acdcd21d075 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,418 @@ 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 shouldAppend) + { + _destination = destination; + _provider = null; + _pos = 0; + _success = shouldAppend = 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 shouldAppend) + { + _destination = destination; + _provider = provider; + _pos = 0; + _success = shouldAppend = 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) + { + 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/Numerics/Plane.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Plane.cs index aaab1903dc476..74f9b93fa672e 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 0b483517b97bf..ce70c49dfc147 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 @@ -75,9 +76,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; @@ -101,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 @@ -172,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.Private.CoreLib/src/System/Random.cs b/src/libraries/System.Private.CoreLib/src/System/Random.cs index b09da359a628c..396af2e5d6cae 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/Reflection/NullabilityInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfo.cs new file mode 100644 index 0000000000000..2da593d762863 --- /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 0000000000000..3d8b3766961c1 --- /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.Private.CoreLib/src/System/Resources/ResourceReader.cs b/src/libraries/System.Private.CoreLib/src/System/Resources/ResourceReader.cs index 78ead82418ca1..16e2b161fc0db 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/CompilerServices/DefaultInterpolatedStringHandler.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/DefaultInterpolatedStringHandler.cs index efad54ee6041c..ae6fb22f32c80 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/Runtime/InteropServices/COMException.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/COMException.cs index 5713153e7b00c..5fddcf9737294 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 33c8855e751c2..13b85e9913934 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.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 52% 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 4bfd2eae0be67..e726194c0c0e7 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,22 +1,18 @@ // 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() { } - public static partial PosixSignalRegistration Create(PosixSignal signal, Action handler) - { - if (handler is null) - { - throw new ArgumentNullException(nameof(handler)); - } - + [DynamicDependency("#ctor")] // Prevent the private ctor and the IDisposable implementation from getting linked away + 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 new file mode 100644 index 0000000000000..d91dd1c3a8dc7 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.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.Collections.Generic; +using System.Diagnostics; +using System.Threading; + +namespace System.Runtime.InteropServices +{ + public sealed partial class PosixSignalRegistration + { + private static readonly Dictionary> s_registrations = Initialize(); + + private static unsafe Dictionary> Initialize() + { + if (!Interop.Sys.InitializeTerminalAndSignalHandling()) + { + Interop.CheckIo(-1); + } + + Interop.Sys.SetPosixSignalHandler(&OnPosixSignal); + + return new Dictionary>(); + } + + private static PosixSignalRegistration Register(PosixSignal signal, Action handler) + { + int signo = Interop.Sys.GetPlatformSignalNumber(signal); + if (signo == 0) + { + throw new PlatformNotSupportedException(); + } + + var token = new Token(signal, signo, handler); + var registration = new PosixSignalRegistration(token); + + lock (s_registrations) + { + if (!s_registrations.TryGetValue(signo, out HashSet? tokens)) + { + s_registrations[signo] = tokens = new HashSet(); + } + + if (tokens.Count == 0 && + !Interop.Sys.EnablePosixSignalHandling(signo)) + { + Interop.CheckIo(-1); + } + + tokens.Add(token); + } + + return registration; + } + + private void Unregister() + { + lock (s_registrations) + { + if (_token is Token token) + { + _token = null; + + 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) + { + Token[]? tokens = null; + + lock (s_registrations) + { + if (s_registrations.TryGetValue(signo, out HashSet? registrations)) + { + tokens = new Token[registrations.Count]; + registrations.CopyTo(tokens); + } + } + + if (tokens is null) + { + return 0; + } + + Debug.Assert(tokens.Length != 0); + + // 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) + { + 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) + { + IsBackground = true, + Name = ".NET Signal Handler" + }.UnsafeStart((signo, tokens)); + break; + + default: + ThreadPool.UnsafeQueueUserWorkItem(HandleSignal, (signo, tokens)); + break; + } + + return 1; + + static void HandleSignal(object? state) + { + (int signo, Token[] tokens) = ((int, Token[]))state!; + + 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); + } + + if (!ctx.Cancel) + { + Interop.Sys.HandleNonCanceledPosixSignal(signo); + } + } + } + } +} 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 58% 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 index d3e5405053e56..a50fca2bbee90 100644 --- 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 @@ -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.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalRegistration.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.cs similarity index 55% 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 index fb2c675acb8b1..811d75ddd5182 100644 --- a/src/libraries/System.Runtime.InteropServices/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; } + } } } 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 175c1fc58d0a2..f5e59ee8c3291 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/SByte.cs b/src/libraries/System.Private.CoreLib/src/System/SByte.cs index cec0716214ffb..ce811d0cffd93 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/SR.cs b/src/libraries/System.Private.CoreLib/src/System/SR.cs index 097aeffb0d871..6f54dc2000e70 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/Security/SecureString.cs b/src/libraries/System.Private.CoreLib/src/System/Security/SecureString.cs index 3f1cf733cb6b3..721c74149ca0a 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.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index 92dc3a9b6dfb5..59dbddd253cf2 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/String.cs b/src/libraries/System.Private.CoreLib/src/System/String.cs index 25ac8df2c582e..7ca88a4b29c79 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/DecoderExceptionFallback.cs b/src/libraries/System.Private.CoreLib/src/System/Text/DecoderExceptionFallback.cs index 94b9090114694..e3eb90b5393cb 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 3a83763ea6a2b..8fb9515001842 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 7678b5e480267..3ef7028bacbe6 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/StringBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs index 3ff675c1d40a6..8835e0ccae7ce 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. + internal 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.Private.CoreLib/src/System/Text/UnicodeDebug.cs b/src/libraries/System.Private.CoreLib/src/System/Text/UnicodeDebug.cs index 4caacbf856ccb..ecd50f3f7c55a 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 7e859d19e7afd..50faeb94ea26e 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/Threading/LowLevelSpinWaiter.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelSpinWaiter.cs index e1c0766b3f0df..8e8198de392b0 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 b45cc7d5d3803..66b73f8be0252 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) { 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 a64dc00842991..89fab991d7348 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.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs index 1b74aa3a23674..3118072c75723 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.Private.CoreLib/src/System/TimeZoneInfo.FullGlobalizationData.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.FullGlobalizationData.Unix.cs index d1f306abb0f6d..5a80d7b448649 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 1356e65758c5c..4756a93271593 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/UInt16.cs b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs index 218c092ed1927..49c08cc99fab3 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 14d06e9b0eff2..70f073b1e15bf 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 e341b0bee93a0..6b2a45f1a8ebe 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 43c549fe94e39..b413125474f55 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.Private.CoreLib/src/System/Version.cs b/src/libraries/System.Private.CoreLib/src/System/Version.cs index 455978045f3c7..bf0ce8e2b4c53 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/DataContractSerializer.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContractSerializer.cs index 71f4530f4f3d1..72229faaf8d3c 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/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 9c164eed11301..eab7d2e6c28e6 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 103013f095985..d52d1fabaf4a3 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 97e134fe4e361..40584d6d2d54d 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 48273f9792204..967e30458d3f9 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 50f78a1dd4a48..f0be0f4e66f5c 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 0fe175fc22771..50d93601ae47c 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 13098a6aa2956..6692a66ac2b15 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.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 08f018bc7149a..9d779477a880f 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.Private.Uri/src/System/IPv4AddressHelper.cs b/src/libraries/System.Private.Uri/src/System/IPv4AddressHelper.cs index ef18d5348c9be..1f751eeb57658 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 4b7e1f4ceff0f..dc687676f03f3 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 befeda705c59e..7715d7669f8f4 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 8a548c85abe0b..fa4ee9299a830 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 2ee9abc085136..547c8196d58d7 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 0e4a7033b10d5..f8773b85ffcc6 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 30e93b6242915..a2ab5b21217fe 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 cfbdc9e8a0d53..5cdccd4800811 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 ffb19d21d5208..c39f6155d3df3 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 2e2baa402875a..0a4ad3d346eed 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 86fc6cd39535d..9f2a8d5c0f7b1 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 5312db2a96c15..615c405d31bc0 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 717bc204e5827..71f43082b5e25 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/ReflectionXmlSerializationReader.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationReader.cs index 4e7b4e8e6e92d..eca311b4d31d9 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 3ed294b0ac692..c40076aa83861 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; @@ -649,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; } @@ -1356,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) @@ -1377,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!; @@ -1398,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 84a3d736550fd..1971544633bf6 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/src/System/Xml/Serialization/XmlSerializationReader.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReader.cs index 6ef46a0f68663..931735f60c6e6 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 010d537da38b6..41c5c83506d75 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 62dc75b1c994c..8308e0de3864e 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 46eda857550cb..1bf9b7069b16a 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; } } @@ -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/src/System/Xml/XmlConvert.cs b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs index b28f0d6eb8bb7..c2f54c2715c02 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 dde7f76f54a5f..7d8276f0851b5 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 cc7b1fcc8394c..9e142049756c6 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 dc3bd0d9a387c..ab2c645e9a06d 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 1c7ea589ad30f..29145c1c7371f 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 7c5a4c78db480..fb15bb3ecc6c4 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 b63284285b45c..d4f0fe916b9ec 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 07a9798589437..65c543543948f 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 83387e02926fc..f33347623eeb4 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 4a490b3128ffb..4bdaf54511125 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 a0f4551447647..e5b4d1a655ba5 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 26b902273ee77..a3d392d571c45 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 2d45c788d2f9d..c227f2cf9d350 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 31c779d6abd65..c6b516c7fdbea 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 879e8d997c7c2..d27a7d3470e3e 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 0df9d3b3a9d7a..327bd70c9f66b 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 e8e8b9be0f381..d923e086c9bec 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/TrimmingTests/System.Private.Xml.TrimmingTests.proj b/src/libraries/System.Private.Xml/tests/TrimmingTests/System.Private.Xml.TrimmingTests.proj index 89084f6e8620a..d7a0e73f68f5b 100644 --- a/src/libraries/System.Private.Xml/tests/TrimmingTests/System.Private.Xml.TrimmingTests.proj +++ b/src/libraries/System.Private.Xml/tests/TrimmingTests/System.Private.Xml.TrimmingTests.proj @@ -2,8 +2,10 @@ - + + + diff --git a/src/libraries/System.Private.Xml/tests/TrimmingTests/System.Xml.TrimmingTests.proj b/src/libraries/System.Private.Xml/tests/TrimmingTests/System.Xml.TrimmingTests.proj deleted file mode 100644 index a30ad093ba40e..0000000000000 --- a/src/libraries/System.Private.Xml/tests/TrimmingTests/System.Xml.TrimmingTests.proj +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - 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 182fb0a3d717d..167e812048c2c 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() { @@ -2810,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); @@ -2829,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.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs index 364c4f880082b..e648a51d5c6e2 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.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs index 54d1fd9a7f87f..568072314f34c 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 ea0e38e3cde1f..c2b0151b9ffca 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 ac71eddcbaa2b..5b6635cabfafc 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 4b6df845ef503..73ed9f30d3d5c 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 c80daaac7a8f3..9d3cb73564bf4 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 b04afc5149e0b..05d3aeca4b137 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 9d4ba7c85cdfd..901754ba815cb 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 488cda85c3510..da7a83aab1c2d 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 3b0986a46e414..b92685667dd50 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 fbe995267afea..44f4bf6d282eb 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.Extensions/tests/System/OperatingSystemTests.cs b/src/libraries/System.Runtime.Extensions/tests/System/OperatingSystemTests.cs index 6a6b1cdd6b6a2..405cf4373f9b8 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.InteropServices.RuntimeInformation/src/System.Runtime.InteropServices.RuntimeInformation.csproj b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System.Runtime.InteropServices.RuntimeInformation.csproj index 2ee0caf2d562f..1e04c274aa816 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 0177eec2317cc..2cd38459dc7b2 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 df0fb53273d42..6e892ce520482 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/src/Resources/Strings.resx b/src/libraries/System.Runtime.InteropServices/src/Resources/Strings.resx index d7644789f28c2..08d7e045a580f 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 7137155589b23..3bc59997f7f0c 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/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs b/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs deleted file mode 100644 index f01b2de013038..0000000000000 --- a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs +++ /dev/null @@ -1,268 +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.Threading; - -namespace System.Runtime.InteropServices -{ - public sealed partial class PosixSignalRegistration - { - private static volatile bool s_initialized; - private static readonly Dictionary?>> s_registrations = new(); - - 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) - { - if (handler == null) - { - throw new ArgumentNullException(nameof(handler)); - } - - int signo = Interop.Sys.GetPlatformSignalNumber(signal); - if (signo == 0) - { - throw new PlatformNotSupportedException(); - } - - PosixSignalRegistration registration = new PosixSignalRegistration(signal, signo, handler); - registration.Register(); - return registration; - } - - private unsafe void Register() - { - if (!s_initialized) - { - 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; - } - - lock (s_registrations) - { - if (!s_registrations.TryGetValue(_signo, out List?>? signalRegistrations)) - { - signalRegistrations = new List?>(); - s_registrations.Add(_signo, signalRegistrations); - } - - if (signalRegistrations.Count == 0) - { - if (!Interop.Sys.EnablePosixSignalHandling(_signo)) - { - // We can't use Win32Exception because that causes a cycle with - // Microsoft.Win32.Primitives. - Interop.CheckIo(-1); - } - } - - signalRegistrations.Add(new WeakReference(this)); - } - - _registered = true; - } - - private bool CallHandler(PosixSignalContext context) - { - lock (_gate) - { - if (_registered) - { - _handler(context); - return true; - } - - return false; - } - } - - [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; - - if (useDedicatedThread) - { - Thread handlerThread = new Thread(HandleSignal) - { - IsBackground = true, - Name = ".NET Signal Handler" - }; - handlerThread.UnsafeStart((signo, registrations)); - } - else - { - ThreadPool.UnsafeQueueUserWorkItem(HandleSignal, (signo, registrations)); - } - - return 1; - } - - return 0; - } - - private static PosixSignalRegistration?[]? GetRegistrations(int signo) - { - lock (s_registrations) - { - 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; - } - } - - private static void HandleSignal(object? state) - { - HandleSignal(((int, PosixSignalRegistration?[]))state!); - } - - private static void HandleSignal((int signo, PosixSignalRegistration?[]? registrations) state) - { - do - { - 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) - { - return; - } - } - - if (Interop.Sys.HandleNonCanceledPosixSignal(state.signo, handlersCalled ? 0 : 1)) - { - return; - } - - // HandleNonCanceledPosixSignal returns false when handlers got registered. - state.registrations = GetRegistrations(state.signo); - } while (true); - } - - public partial void Dispose() - { - if (_registered) - { - 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; - } - } - - if (pruneWeakReferences) - { - signalRegistrations.RemoveAll(item => item is null); - } - - if (signalRegistrations.Count == 0) - { - Interop.Sys.DisablePosixSignalHandling(_signo); - } - } - - // Synchronize with _handler invocations. - lock (_gate) - { - _registered = false; - } - } - } - } -} 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 33204d0ee7cc9..f0ffe70d47c31 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/Marshal/GetObjectForNativeVariantTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/GetObjectForNativeVariantTests.cs index a4fe32e9c0234..d8a6bc398f5e0 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.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 2202ed25744ee..0000000000000 --- 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 1716a3ba24f7f..e08d77565513a 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)] 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 599d1adc6c6d0..4f800d51dca44 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()); } } } 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 0000000000000..f1590848bea59 --- /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 0000000000000..516914d76d8e1 --- /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 0000000000000..985424ab34861 --- /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 0000000000000..3dcb95912dbe5 --- /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 87a4a28d00019..57ba4f3ec5298 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 c2f06b86b9386..6976f8a6f7772 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 eeae81e75cc68..a5f2b3b0cce7b 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/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs index 060200698b2d2..2bbc23476c509 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/DataContractSerializer.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/DataContractSerializer.cs index 2a39358aa09bb..038b9130d4dc8 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 2d414e8570248..a7cc9748babd0 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 @@ -1987,6 +1991,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 { @@ -2376,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(); @@ -2393,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.Runtime.Serialization.Xml/tests/SerializationTypes.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.cs index 12ba77621b4a6..6c8da649d56e0 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; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 1b9b1d241b854..35fe09cc66295 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; } @@ -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; } @@ -5269,9 +5271,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; } @@ -5763,6 +5765,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; } @@ -6979,9 +6983,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 +7160,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 +7337,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 +7519,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; } @@ -8667,9 +8671,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() { } @@ -8703,8 +8711,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")] @@ -8722,7 +8734,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 @@ -10873,10 +10925,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 @@ -11883,6 +11935,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 @@ -14283,6 +14361,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 +14379,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 +14428,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/ref/System.Runtime.csproj b/src/libraries/System.Runtime/ref/System.Runtime.csproj index c978be13789ea..9cbad9dddb804 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 + $(Features.Replace('nullablePublicOnly', '') + + + $(DefineConstants);FEATURE_GENERIC_MATH - @@ -89,7 +94,6 @@ - @@ -121,6 +125,7 @@ + @@ -236,6 +241,7 @@ + @@ -247,6 +253,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 0000000000000..140279d54f2d6 --- /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 0000000000000..7dd512f9871ed --- /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/DelegateTests.cs b/src/libraries/System.Runtime/tests/System/DelegateTests.cs index 269b5f7b218af..9a3658457c10d 100644 --- a/src/libraries/System.Runtime/tests/System/DelegateTests.cs +++ b/src/libraries/System.Runtime/tests/System/DelegateTests.cs @@ -1118,7 +1118,6 @@ public static void CreateDelegate9_Type_Null() Assert.NotNull(ex.Message); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/49839", TestRuntimes.Mono)] [Fact] public static void CreateDelegate10_Nullable_Method() { @@ -1130,7 +1129,6 @@ public static void CreateDelegate10_Nullable_Method() Assert.Equal(num.ToString(), s); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/49839", TestRuntimes.Mono)] [Fact] public static void CreateDelegate10_Nullable_ClosedDelegate() { 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 0000000000000..2895dd8841962 --- /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 ccd3b8a269fca..0000000000000 --- 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 0000000000000..667a5f55ac22f --- /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 c8f469d6eb3a8..0000000000000 --- 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 0000000000000..28f3c06c40393 --- /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 0000000000000..ddc380cc36e22 --- /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 0000000000000..1f5b4144f94ca --- /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/Reflection/ModuleTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/ModuleTests.cs index 29fe532e6c0eb..8c42d79063f31 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] 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 0000000000000..07f244e9172f7 --- /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; } + } +} 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 0000000000000..82877a1e2d609 --- /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/StringTests.cs b/src/libraries/System.Runtime/tests/System/StringTests.cs index aa311ff6d7d0f..b0786bb2370fb 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 0000000000000..c8de8c8f9a4df --- /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(); + } + } +} 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 0000000000000..982740495bb9b --- /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 0000000000000..ab6a6e02471ed --- /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 0000000000000..1b7ee209a9f4b --- /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 0000000000000..8e0c92c2a82c1 --- /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); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs b/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs index cbe93e981f360..4850ccfe2eca6 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs @@ -133,6 +133,7 @@ public static void AddOID(string oid, params string[] names) { } public static object? CreateFromName(string name) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] public static object? CreateFromName(string name, params object?[]? args) { throw null; } + [System.ObsoleteAttribute("EncodeOID is obsolete. Use the ASN.1 functionality provided in System.Formats.Asn1.", DiagnosticId = "SYSLIB0031", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public static byte[] EncodeOID(string str) { throw null; } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] 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 b08732a5aeaf2..dee729afd93da 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 d0b960fc4ae8a..e2fbafdbd15d1 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 33f3998499093..0221bb5775bbb 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 593287656423f..e79f466f13181 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/src/System.Security.Cryptography.Algorithms.csproj b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj index fd8d0a8b88b71..754b2e3886143 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj @@ -6,14 +6,14 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Android;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-tvOS;$(NetCoreAppCurrent)-Browser enable - - true true SR.SystemSecurityCryptographyAlgorithms_PlatformNotSupported ExcludeApiList.PNSE.Browser.txt + + @@ -130,6 +130,8 @@ Link="Common\System\Security\Cryptography\HashOneShotHelpers.cs" /> + + @@ -629,10 +633,6 @@ Link="Common\System\Security\Cryptography\ECDiffieHellmanOpenSsl.Derive.cs" /> - - 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 c7c38e7bcc4ca..3670b98ffc049 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.Algorithms/src/System/Security/Cryptography/CryptoConfig.Browser.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.Browser.cs index f57fceeba68f9..6564a0d48ca29 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.Browser.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.Browser.cs @@ -23,6 +23,7 @@ public partial class CryptoConfig public static string? MapNameToOID(string name) => 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 ff73ce605054d..e7d3919b2f3cc 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 067a50a10015d..b724370944024 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) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/DESProvider.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/DESProvider.cs index e4cb6a6302ee3..4679eb99b1678 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 6b323ccf4607a..6da56739bb854 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 1e1edf7c792b8..eae2d44dec5c1 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 bc28c3819fc23..b919b72f36266 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.Cng.csproj b/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj index b4d0c91a2d246..fb654f6698bfe 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj +++ b/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj @@ -210,6 +210,8 @@ Link="Common\System\Security\Cryptography\KeyBlobHelpers.cs" /> + 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 b03c756c1ad98..09d2b02243530 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 dc6d484263c0c..47d4f4b5dd6c4 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 9810072403fa0..9a3c728efb16d 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 d68aa596c27d8..4a913be0a2bbb 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 7181d95edc590..7d049de7e6683 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 b14a21f2daafa..bf8d40dcf4ee1 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.Encoding/src/Internal/Cryptography/OidLookup.Unix.cs b/src/libraries/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.Unix.cs index 63862ea65b4d1..cfef4e4d09695 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.OpenSsl/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.OpenSsl/src/Resources/Strings.resx index 8091178afc3a0..40e6dcd3b22d8 100644 --- a/src/libraries/System.Security.Cryptography.OpenSsl/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.OpenSsl/src/Resources/Strings.resx @@ -173,9 +173,18 @@ Specified padding mode is not valid for this algorithm. + + The specified RSA parameters are not valid. Exponent and Modulus are required. If D is present, it must have the same length as Modulus. If D is present, P, Q, DP, DQ, and InverseQ are required and must have half the length of Modulus, rounded up, otherwise they must be omitted. + + + Key is not a valid private key. + Cannot open an invalid handle. + + The provided RSAPrivateKey value has version '{0}', but version '{1}' is the maximum supported. + The TLS key derivation function requires a seed value of exactly 64 bytes. diff --git a/src/libraries/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj b/src/libraries/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj index 133c02e87c4fb..f28aa959878e3 100644 --- a/src/libraries/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj +++ b/src/libraries/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj @@ -8,6 +8,7 @@ SR.PlatformNotSupported_CryptographyOpenSSL true + @@ -54,8 +55,6 @@ Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Initialization.cs" /> - - + + + + + Common\System\Security\Cryptography\Asn1\AlgorithmIdentifierAsn.xml + + + Common\System\Security\Cryptography\Asn1\AlgorithmIdentifierAsn.xml.cs + Common\System\Security\Cryptography\Asn1\AlgorithmIdentifierAsn.xml + + + Common\System\Security\Cryptography\Asn1\AlgorithmIdentifierAsn.manual.cs + Common\System\Security\Cryptography\Asn1\AlgorithmIdentifierAsn.xml + + + Common\System\Security\Cryptography\Asn1\AttributeAsn.xml + + + Common\System\Security\Cryptography\Asn1\AttributeAsn.xml.cs + Common\System\Security\Cryptography\Asn1\AttributeAsn.xml + + + Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\RSAPrivateKeyAsn.xml + + + Common\System\Security\Cryptography\Asn1\RSAPrivateKeyAsn.xml.cs + Common\System\Security\Cryptography\Asn1\RSAPrivateKeyAsn.xml + + + Common\System\Security\Cryptography\Asn1\RSAPublicKeyAsn.xml + + + Common\System\Security\Cryptography\Asn1\RSAPublicKeyAsn.xml.cs + Common\System\Security\Cryptography\Asn1\RSAPublicKeyAsn.xml + + + Common\System\Security\Cryptography\Asn1\SubjectPublicKeyInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\SubjectPublicKeyInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\SubjectPublicKeyInfoAsn.xml + diff --git a/src/libraries/System.Security.Cryptography.OpenSsl/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/libraries/System.Security.Cryptography.OpenSsl/src/System/Security/Cryptography/RSAOpenSsl.cs index eb7d7545928e8..c6f8efb756362 100644 --- a/src/libraries/System.Security.Cryptography.OpenSsl/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography.OpenSsl/src/System/Security/Cryptography/RSAOpenSsl.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 Microsoft.Win32.SafeHandles; namespace System.Security.Cryptography @@ -29,18 +30,10 @@ public RSAOpenSsl(IntPtr handle) if (handle == IntPtr.Zero) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(handle)); - SafeEvpPKeyHandle pkey = Interop.Crypto.EvpPkeyCreate(); + SafeEvpPKeyHandle pkey = Interop.Crypto.EvpPKeyCreateRsa(handle); + Debug.Assert(!pkey.IsInvalid); - if (!Interop.Crypto.EvpPkeySetRsa(pkey, handle)) - { - pkey.Dispose(); - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere - // with the already loaded key. - ForceSetKeySize(BitsPerByte * Interop.Crypto.EvpPKeySize(pkey)); - _key = new Lazy(pkey); + SetKey(pkey); } /// @@ -60,21 +53,11 @@ public RSAOpenSsl(SafeEvpPKeyHandle pkeyHandle) if (pkeyHandle.IsInvalid) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(pkeyHandle)); - SafeEvpPKeyHandle newKey = Interop.Crypto.EvpPkeyCreate(); - - using (SafeRsaHandle rsa = Interop.Crypto.EvpPkeyGetRsa(pkeyHandle)) - { - if (rsa.IsInvalid || !Interop.Crypto.EvpPkeySetRsa(newKey, rsa)) - { - newKey.Dispose(); - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - } + SafeEvpPKeyHandle newKey = Interop.Crypto.EvpPKeyDuplicate( + pkeyHandle, + Interop.Crypto.EvpAlgorithmId.RSA); - // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere - // with the already loaded key. - ForceSetKeySize(BitsPerByte * Interop.Crypto.EvpPKeySize(newKey)); - _key = new Lazy(newKey); + SetKey(newKey); } /// @@ -84,18 +67,7 @@ public RSAOpenSsl(SafeEvpPKeyHandle pkeyHandle) /// A SafeHandle for the RSA key in OpenSSL public SafeEvpPKeyHandle DuplicateKeyHandle() { - SafeEvpPKeyHandle pkeyHandle = Interop.Crypto.EvpPkeyCreate(); - - using (SafeRsaHandle rsa = Interop.Crypto.EvpPkeyGetRsa(GetKey())) - { - if (rsa.IsInvalid || !Interop.Crypto.EvpPkeySetRsa(pkeyHandle, rsa)) - { - pkeyHandle.Dispose(); - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - } - - return pkeyHandle; + return Interop.Crypto.EvpPKeyDuplicate(GetKey(), Interop.Crypto.EvpAlgorithmId.RSA); } } } 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 50293c45860b1..5496b23d1bb97 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 a1ac99fff1249..884bf569481be 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.Pkcs/src/System.Security.Cryptography.Pkcs.csproj b/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj index 88bce55ee444b..059c3fffb7bd0 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj @@ -551,6 +551,8 @@ + Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml 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 5eee91bb97aa5..d04196601cfdc 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 f5485577c0707..5e5cfa33a07b6 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 380cd19b099bb..c03909efd391a 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); } } } 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 658c2da9e1a76..2a987506f1203 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 09ec3ce538955..6187979d905f8 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 4febd7080f082..1eaa3f8323adc 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 86bbebc594d47..e7cbefeb5b4d5 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 e5561d9279c68..942c6251f6cba 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 31a65038d598b..f73b59373233d 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 749beed10edc8..bef72007c383a 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 5d4783dbc29e8..bf6cb9ce630d0 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.Windows/Native/SafeHandles.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/SafeHandles.cs index 4c3ba2011b679..0f67d4244f6cd 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/Internal/Cryptography/Pal.iOS/AppleCertificatePal.ImportExport.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.iOS/AppleCertificatePal.ImportExport.cs index dfdb5b554f4a7..ad4baa38cc3f7 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 87e45b117e836..8ff5988f557d2 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 efb7d68349a00..47d7485c0f984 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/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.cs index 6ebaa978de95e..f528eae37b0f8 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.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj b/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj index 505d487f1d486..3b1071dd9124b 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj @@ -14,7 +14,7 @@ true - + @@ -518,6 +518,8 @@ Link="Common\System\Security\Cryptography\ECDiffieHellmanDerivation.cs" /> + + 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 37ca1cc302329..c25d3391ddb9f 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 39f736d259df8..4e219e346a9b5 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); + } + } } } 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 dc860faf1c399..e782a0c3e5045 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}"); } }); } 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 ed06c365672b2..761fc37439adf 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 fb1f9c599cfda..7d3174b078006 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 2bc2502eb9f67..de71302a1c026 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 5f7a5090097e9..e70615426c2bb 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 906e967616532..1fa91a3c912fb 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 7b20e996ad68b..eacc8ca6861f8 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 f06e87cda95a7..be087593f9b10 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.ServiceProcess.ServiceController/src/Microsoft/Win32/SafeHandles/SafeServiceHandle.cs b/src/libraries/System.ServiceProcess.ServiceController/src/Microsoft/Win32/SafeHandles/SafeServiceHandle.cs index 48f0392789609..77b1ea855baeb 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); diff --git a/src/libraries/System.Speech/src/Result/RecognizedPhrase.cs b/src/libraries/System.Speech/src/Result/RecognizedPhrase.cs index 446d744cae0a6..ebcde98e50aaf 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 01208d211838c..ab66bca82c3df 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 531f26876242a..5255ed67b97e7 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 0d9bed68345d5..e232be861680c 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 2f4042f334c56..7a27288fd4ed2 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 e630706877a36..204c45dbd3908 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.Json/Common/JsonHelpers.cs b/src/libraries/System.Text.Json/Common/JsonHelpers.cs new file mode 100644 index 0000000000000..96a2872621f40 --- /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 0000000000000..5d9ef545b8d44 --- /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 f4b27c2db1187..8020f798086e9 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 811a4ea8365e6..cbab663971eed 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 70c8a412b6e59..cefb9de2d7164 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 7e8c45ff4299c..399b893e8e8e9 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 25ae73692ecb0..5d52ae2e3553e 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 d5ed28e4800a1..a3282b6984439 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 979d32c8f7c99..42d167a462207 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 75b31db587a8f..e518212ce65b0 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 cf36ceacbeee5..66e00c0de20bb 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 1b4de7811fd74..ccfd0fb7c6e4c 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 885885325fe53..d12f3f8ed60f8 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 44eecf0b5919d..637483847e599 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 b8891251c7e62..ad9091017728f 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 294c6dd105916..bfb593f992fbd 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 1c0619ddda58e..599cae49aebd5 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 610e7fe4eef6a..3dd1e925e2f19 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 8ff487ab0c6ee..6ad76f42b7b4a 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 2ffc4725922ab..abeed3dfaaf0d 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 3e449e71aab1a..008a5b221895d 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 36a0c58a17a31..865134823f9a3 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 2d74d3535c3fc..97e39311a09e7 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); @@ -763,7 +762,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() { } @@ -833,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 { @@ -932,7 +937,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/ref/System.Text.Json.csproj b/src/libraries/System.Text.Json/ref/System.Text.Json.csproj index dc0bb21c008ca..f015d29c355e6 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 b12ccebb9c92c..0000000000000 --- 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/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index c650f6ebad8bb..e654d368051dc 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. @@ -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 9d94ae9bdd4f4..08855731abe63 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 @@ + + @@ -57,20 +59,17 @@ - - - @@ -90,6 +89,7 @@ + @@ -296,7 +296,6 @@ - @@ -313,7 +312,6 @@ - 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 8dce0c594e1fe..0bdd85880bd0f 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/Nodes/JsonNode.Dynamic.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.Dynamic.cs deleted file mode 100644 index 457af37eb0f21..0000000000000 --- 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 b343b70d3fa52..0000000000000 --- 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 8806fe3035284..0000000000000 --- 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/Attributes/JsonConverterAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonConverterAttribute.cs index 098b1e175e01c..e456a009178d9 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/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 0000000000000..8126e75690f41 --- /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/Converters/Collection/StackOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/StackOfTConverter.cs index df483b6986ec0..7e1badfb96877 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 1e1856ccdaa1a..07da2c99ebffa 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 da3be52fde065..b25f544d3c911 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/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs index 5268573219dd2..731869ec4a9bb 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 2a97240d67b97..9e34cc60a3561 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; } @@ -72,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); } @@ -81,6 +89,8 @@ private void DeterminePropertyName() { Debug.Assert(MemberInfo != null); + ClrName = MemberInfo.Name; + JsonPropertyNameAttribute? nameAttribute = GetAttribute(MemberInfo); if (nameAttribute != null) { @@ -305,6 +315,7 @@ internal virtual void Initialize( Type? runtimePropertyType, ConverterStrategy runtimeClassType, MemberInfo? memberInfo, + bool isVirtual, JsonConverter converter, JsonIgnoreCondition? ignoreCondition, JsonNumberHandling? parentTypeNumberHandling, @@ -312,12 +323,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; } @@ -361,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, @@ -479,6 +495,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 +515,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 8e268c2509940..dda0e7f12dd1f 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 7b3369a90ee67..cb2545c0cc10b 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 53ed06d9c3664..077de6d05372d 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,10 +193,12 @@ 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); + 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. @@ -208,8 +211,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,14 +229,16 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json currentType, propertyInfo.PropertyType, propertyInfo, + isVirtual, typeNumberHandling, + ref propertyOrderSpecified, ref ignoredMembers); } else { 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 +247,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,7 +264,9 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json currentType, fieldInfo.FieldType, fieldInfo, + isVirtual: false, typeNumberHandling, + ref propertyOrderSpecified, ref ignoredMembers); } } @@ -261,7 +274,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. @@ -277,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!); @@ -316,8 +334,10 @@ private void CacheMember( Type declaringType, Type memberType, MemberInfo memberInfo, + bool isVirtual, JsonNumberHandling? typeNumberHandling, - ref Dictionary? ignoredMembers) + ref bool propertyOrderSpecified, + ref Dictionary? ignoredMembers) { bool hasExtensionAttribute = memberInfo.GetCustomAttribute(typeof(JsonExtensionDataAttribute)) != null; if (hasExtensionAttribute && DataExtensionProperty != null) @@ -325,7 +345,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 +356,44 @@ private void CacheMember( } else { - string memberName = memberInfo.Name; + CacheMember(jsonPropertyInfo, PropertyCache, ref ignoredMembers); + propertyOrderSpecified |= jsonPropertyInfo.Order != 0; + } + } - // 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) { - (ignoredMembers ??= new Dictionary()).Add(memberName, memberInfo); + // 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) + { + // 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 +502,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 7beb0612a4e18..61d953ac94ce2 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] @@ -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] 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 0000000000000..eb67440c0d188 --- /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 48fe1e34c935b..eda534737b0e9 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 0dd3657e6f5c3..0c8dc6a941c38 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 0000000000000..32f19eb120c39 --- /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 0000000000000..c7198b1e30691 --- /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 0000000000000..dfb968371a224 --- /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 0000000000000..6ebb90f030c6b --- /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 a88c018a1e75d..2520aa52a8b17 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 654b413102023..d654c01bd67d9 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 afcd88a5867e8..24a86137bc175 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/JsonNode/DynamicTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/DynamicTests.cs deleted file mode 100644 index 63afb3920b03e..0000000000000 --- 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 b451ff0bc224b..e5b1c18cf57d4 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/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 0000000000000..da56c49591f20 --- /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/Serialization/JsonSerializerWrapperForString.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapperForString.cs index b01738c7a9622..6b691004df94b 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 0000000000000..aaee9f03d81cc --- /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 39899a5b91e57..c61356029789c 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 f75200bf8b550..7d3f84451000d 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 227aab7bf3abb..a583b3a121b81 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 b4db06777a311..5e02a3b5d6b1f 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/PropertyOrderTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyOrderTests.cs new file mode 100644 index 0000000000000..07b16caa00dcd --- /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/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyVisibilityTests.cs index f164592f4fb30..85a36e92b9b1e 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 3ba9831cb6db0..9e117638ddb80 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 @@ + + + + + + + + + + + + + + + + + + + + + @@ -32,7 +53,6 @@ - @@ -98,6 +118,7 @@ + @@ -118,6 +139,7 @@ + @@ -144,8 +166,7 @@ - - + @@ -160,22 +181,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 48435095e57fd..c408c57cf903b 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; } 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 dbfa9a5cdbdf5..e76e3ea160aaf 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/RegexCompiler.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs index c06b5af67c635..b9b6e791ed572 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 4cc3dc528605b..d557ec6c3aa73 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/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs index 52bd5b89ec263..4c8fe4f91902f 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/Regex.Match.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/Regex.Match.Tests.cs index d5d690b0c29c1..719f280c91726 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, diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexCharacterSetTests.cs b/src/libraries/System.Text.RegularExpressions/tests/RegexCharacterSetTests.cs index b11071d595e58..a029d679c8ba0 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.Threading.Tasks.Extensions/tests/PoolingAsyncValueTaskMethodBuilderTests.cs b/src/libraries/System.Threading.Tasks.Extensions/tests/PoolingAsyncValueTaskMethodBuilderTests.cs index c2e83881ce5b1..62a6406c309b6 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); diff --git a/src/libraries/System.Threading.Tasks/tests/MethodCoverage.cs b/src/libraries/System.Threading.Tasks/tests/MethodCoverage.cs index e696a38cf924e..923875f6e43ce 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() { 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 0be2514b34496..5da12d241967d 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 a4db2b40b5e83..758f7d8da622a 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 b6b46d1fda9a2..d991cc61d3f10 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 868418b22927c..658665d3c6661 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/libraries/shims/ApiCompatBaseline.PreviousNetCoreApp.txt b/src/libraries/shims/ApiCompatBaseline.PreviousNetCoreApp.txt index 23ae452289990..54945167821c8 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 diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index 5290a6bc8c57f..ae9cf03ab132e 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -109,7 +109,7 @@ true true true - true + false true true true diff --git a/src/mono/dlls/mscordbi/CMakeLists.txt b/src/mono/dlls/mscordbi/CMakeLists.txt index 32e61b129f34a..47924e8b9a7aa 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) diff --git a/src/mono/mono/component/debugger-agent.c b/src/mono/mono/component/debugger-agent.c index f72c95392e974..bec98edb3c2f3 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 60f4244820247..ad5a0cb1d0b1d 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 24e8a2e42c27e..292b723cc351b 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 1c5467adadddd..3628c0fbc532a 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 de50c5641137a..81ef259f3ebe1 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 718ce3e453a10..0ebf79a50c8f6 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,12 @@ 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); + static MonoComponentHotReload fn_table = { { MONO_COMPONENT_ITF_VERSION, &hot_reload_stub_available }, &hot_reload_stub_set_fastpath_data, @@ -75,6 +81,8 @@ 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, }; static bool @@ -139,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."); } @@ -172,6 +180,18 @@ 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) +{ + 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 0997e8f859c23..32820888b2638 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,12 @@ 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); + static MonoComponentHotReload fn_table = { { MONO_COMPONENT_ITF_VERSION, &hot_reload_available }, &hot_reload_set_fastpath_data, @@ -89,6 +95,8 @@ 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, }; MonoComponentHotReload * @@ -146,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]; @@ -161,6 +172,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" @@ -312,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); } @@ -419,6 +435,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 +762,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 +872,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 +890,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 +973,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 +1090,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 +1123,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])) { @@ -1103,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); @@ -1184,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); @@ -1211,6 +1327,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)) @@ -1226,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."); @@ -1261,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); @@ -1317,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; @@ -1360,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 (); @@ -1374,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 */ @@ -1389,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) { @@ -1400,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; @@ -1478,3 +1629,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 4994664103cc2..26ca0089a69ef 100644 --- a/src/mono/mono/component/hot_reload.h +++ b/src/mono/mono/component/hot_reload.h @@ -23,12 +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 4df2172841efe..5dd9d8ce1d402 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/assembly.c b/src/mono/mono/metadata/assembly.c index e98264a2f939d..8a52e63bc856d 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); diff --git a/src/mono/mono/metadata/debug-mono-ppdb.c b/src/mono/mono/metadata/debug-mono-ppdb.c index 070f9b8b0cf28..67bd4715d33bd 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 c27a97069c97e..e16317f9fc728 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 7c3f33fc79219..240479fb90a56 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/image.c b/src/mono/mono/metadata/image.c index 68b7b377266ce..3bd2a6306add3 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; } diff --git a/src/mono/mono/metadata/marshal.c b/src/mono/mono/metadata/marshal.c index 0b5cdf0640798..0a9d8f8ff1abd 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 08300a001efd5..d56f30e0f9d7b 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/metadata/metadata-internals.h b/src/mono/mono/metadata/metadata-internals.h index 545693ef12a58..9a0d030a9f171 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); } } @@ -819,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 d1d646c49ba25..71520b8aa1e67 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) { @@ -121,3 +130,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/mono/mono/metadata/metadata-update.h b/src/mono/mono/metadata/metadata-update.h index 52bfa701e57f8..15d0d51e10f4a 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 bdef02c84f020..10554e663ef4b 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,6 +1116,15 @@ 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) { + 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); + 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 diff --git a/src/mono/mono/metadata/object.c b/src/mono/mono/metadata/object.c index 5e15a0f74b9c9..4e601cca8c5e7 100644 --- a/src/mono/mono/metadata/object.c +++ b/src/mono/mono/metadata/object.c @@ -1525,10 +1525,8 @@ build_imt_slots (MonoClass *klass, MonoVTable *vt, gpointer* imt, GSList *extra_ continue; } - if (m_method_is_static (method)) { - vt_slot ++; + if (m_method_is_static (method)) continue; - } if (method->flags & METHOD_ATTRIBUTE_VIRTUAL) { add_imt_builder_entry (imt_builder, method, &imt_collisions_bitmap, vt_slot, slot_num); diff --git a/src/mono/mono/mini/aot-runtime.h b/src/mono/mono/mini/aot-runtime.h index af40e8bbb12f4..dff55a92563fa 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/interp.c b/src/mono/mono/mini/interp/interp.c index dad5f095b0026..44d1c70b396bc 100644 --- a/src/mono/mono/mini/interp/interp.c +++ b/src/mono/mono/mini/interp/interp.c @@ -1173,12 +1173,16 @@ compute_arg_offset (MonoMethodSignature *sig, int index, int prev_offset) } static guint32* -initialize_arg_offsets (InterpMethod *imethod) +initialize_arg_offsets (InterpMethod *imethod, MonoMethodSignature *csig) { if (imethod->arg_offsets) return imethod->arg_offsets; - MonoMethodSignature *sig = mono_method_signature_internal (imethod->method); + // For pinvokes, csig represents the real signature with marshalled args. If an explicit + // marshalled signature was not provided, we use the managed signature of the method. + MonoMethodSignature *sig = csig; + if (!sig) + sig = mono_method_signature_internal (imethod->method); int arg_count = sig->hasthis + sig->param_count; g_assert (arg_count); guint32 *arg_offsets = (guint32*) g_malloc ((sig->hasthis + sig->param_count) * sizeof (int)); @@ -1201,13 +1205,13 @@ initialize_arg_offsets (InterpMethod *imethod) } static guint32 -get_arg_offset_fast (InterpMethod *imethod, int index) +get_arg_offset_fast (InterpMethod *imethod, MonoMethodSignature *sig, int index) { guint32 *arg_offsets = imethod->arg_offsets; if (arg_offsets) return arg_offsets [index]; - arg_offsets = initialize_arg_offsets (imethod); + arg_offsets = initialize_arg_offsets (imethod, sig); g_assert (arg_offsets); return arg_offsets [index]; } @@ -1216,7 +1220,7 @@ static guint32 get_arg_offset (InterpMethod *imethod, MonoMethodSignature *sig, int index) { if (imethod) { - return get_arg_offset_fast (imethod, index); + return get_arg_offset_fast (imethod, sig, index); } else { g_assert (!sig->hasthis); return compute_arg_offset (sig, index, -1); @@ -2385,7 +2389,7 @@ do_jit_call (ThreadContext *context, stackval *ret_sp, stackval *sp, InterpFrame if (cinfo->ret_mt != -1) args [pindex ++] = ret_sp; for (int i = 0; i < rmethod->param_count; ++i) { - stackval *sval = STACK_ADD_BYTES (sp, get_arg_offset_fast (rmethod, stack_index + i)); + stackval *sval = STACK_ADD_BYTES (sp, get_arg_offset_fast (rmethod, NULL, stack_index + i)); if (cinfo->arginfo [i] == JIT_ARG_BYVAL) args [pindex ++] = sval->data.p; else @@ -3433,7 +3437,7 @@ interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClauseArgs del_imethod = mono_interp_get_imethod (mono_marshal_get_native_wrapper (del_imethod->method, FALSE, FALSE), error); mono_error_assert_ok (error); del->interp_invoke_impl = del_imethod; - } else if (del_imethod->method->flags & METHOD_ATTRIBUTE_VIRTUAL && !del->target) { + } else if (del_imethod->method->flags & METHOD_ATTRIBUTE_VIRTUAL && !del->target && !m_class_is_valuetype (del_imethod->method->klass)) { // 'this' is passed dynamically, we need to recompute the target method // with each call del_imethod = get_virtual_method (del_imethod, LOCAL_VAR (call_args_offset + MINT_STACK_SLOT_SIZE, MonoObject*)->vtable); @@ -7199,7 +7203,7 @@ interp_frame_get_arg (MonoInterpFrameHandle frame, int pos) g_assert (iframe->imethod); - return (char*)iframe->stack + get_arg_offset_fast (iframe->imethod, pos + iframe->imethod->hasthis); + return (char*)iframe->stack + get_arg_offset_fast (iframe->imethod, NULL, pos + iframe->imethod->hasthis); } static gpointer diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index 0eed6c7146e01..c677122e6a5f7 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); } @@ -9758,8 +9757,10 @@ 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); + 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 ? diff --git a/src/mono/mono/mini/type-checking.c b/src/mono/mono/mini/type-checking.c index 8f6f93e1f3561..b623c603741af 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); } } diff --git a/src/mono/mono/mini/unwind.c b/src/mono/mono/mini/unwind.c index bcf9bd4682efc..bdcfe78604b7c 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; 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 0f6c403f4104e..f89c42b2884dc 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 05d94220cb8b6..d7fec86e3a15d 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 c93e50175e921..26766076edc54 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) + + + + + + + + + diff --git a/src/mono/wasm/build/WasmApp.InTree.targets b/src/mono/wasm/build/WasmApp.InTree.targets index f92f2abd62bc6..1537277f087d8 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 fac5252a4ef34..b34d8f03bfa3b 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) - - + - + diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 81c419e4c5850..c1e8ce60bda6c 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/DevToolsHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs index 792fb3a52a049..de40842443c2a 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 73de23d6fcc42..58374c43565c0 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 d9ee41324c4eb..f0f56e40f8c22 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 MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId session_id, JArray object_values, ILogger logger) @@ -49,7 +51,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(); @@ -63,10 +65,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); } @@ -74,12 +76,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; } @@ -98,8 +104,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; @@ -126,8 +132,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); @@ -139,9 +145,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 75036e99a26f6..4dc7c374268c4 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, logger); + SdbHelper = new MonoSDBHelper(this, logger); } internal ExecutionContext GetContext(SessionId sessionId) @@ -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; } @@ -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); @@ -689,32 +689,82 @@ private async Task EvaluateCondition(SessionId sessionId, ExecutionContext } return false; } + + private async Task ProcessEnC(SessionId sessionId, ExecutionContext context, MonoBinaryReader retDebuggerCmdReader, CancellationToken token) + { + 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 assemblyName = await SdbHelper.GetAssemblyNameFromModule(sessionId, moduleId, token); + DebugStore store = await LoadStore(sessionId, token); + 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 retDebuggerCmdReader, CancellationToken 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); + 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(); 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) { @@ -744,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); @@ -831,31 +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, retDebuggerCmdReader, token); + await SendCommand(sessionId, "Debugger.resume", new JObject(), token); + return ret; + } + case EventKind.EnC: + { + 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}" @@ -870,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; } @@ -956,7 +1018,7 @@ private async Task OnResume(MessageId msg_id, CancellationToken token) } //discard managed frames - sdbHelper.ClearCache(); + SdbHelper.ClearCache(); GetContext(msg_id).ClearState(); } @@ -969,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; @@ -1048,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 { @@ -1056,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) @@ -1089,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; @@ -1129,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) { @@ -1196,20 +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.EnableReceiveUserBreakRequest(sessionId, 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); @@ -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(); @@ -1228,14 +1306,20 @@ private async Task RemoveBreakpoint(MessageId msg_id, JObject args, Cancellation 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; - 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 a8c426471fa0e..07e9a3a83c68b 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 { @@ -83,7 +85,9 @@ internal enum EventKind { KeepAlive = 14, UserBreak = 15, UserLog = 16, - Crash = 17 + Crash = 17, + EnC = 18, + MethodUpdate = 19 } internal enum ModifierKind { @@ -460,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") { @@ -469,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 { @@ -546,25 +631,27 @@ 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 EnableReceiveUserBreakRequest(SessionId sessionId, 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)EventKind.UserBreak); - 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; } + 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); @@ -572,9 +659,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) => @@ -610,137 +697,160 @@ 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(); + } + + public async Task GetMethodToken(SessionId sessionId, int methodId, CancellationToken token) + { + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(methodId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.Token, command_params, token); - return ret_debugger_cmd_reader.ReadInt32() & 0xffffff; //token + 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 GetAssemblyName(SessionId sessionId, int assembly_id, CancellationToken token) + 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(assembly_id); + command_params_writer.Write(moduleId); - var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdAssembly.GetLocation, command_params, token); + 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 commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(assembly_id); + + 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) @@ -750,50 +860,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; @@ -801,14 +911,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; } @@ -816,19 +926,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", ""); @@ -923,17 +1033,17 @@ public async Task GetDebuggerDisplayAttribute(SessionId sessionId, int o 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+", ""); @@ -948,47 +1058,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; @@ -1003,82 +1113,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; @@ -1100,7 +1210,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, @@ -1118,30 +1228,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++) { @@ -1214,12 +1324,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 @@ -1242,24 +1352,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); @@ -1285,22 +1395,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 @@ -1308,21 +1418,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 @@ -1333,16 +1443,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; @@ -1350,18 +1460,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; } @@ -1369,10 +1479,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: @@ -1381,108 +1491,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; } } @@ -1494,32 +1610,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) @@ -1533,16 +1649,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); } @@ -1568,21 +1684,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(); @@ -1606,16 +1722,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; @@ -1623,25 +1739,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) @@ -1672,23 +1788,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; @@ -1716,8 +1832,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)); @@ -1770,19 +1886,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()) { @@ -1834,19 +1950,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/BreakpointTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs index a4f53aac02b31..77a8cb6c12e16 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 a6af28d8e4592..b26624807150f 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/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 993e6165cc0ab..d6803ee8670fd 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 4054c6a2cea11..b45825acfcbd8 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/ApplyUpdateReferencedAssembly/ApplyUpdateReferencedAssembly.csproj b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly/ApplyUpdateReferencedAssembly.csproj new file mode 100644 index 0000000000000..550971c8b6f31 --- /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 0000000000000..b2b8f4d364c08 --- /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 0000000000000..03a4d33b0646b --- /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 0000000000000..4cd88d660a3f1 --- /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 0000000000000..8e738364bc747 --- /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-evaluate-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs index a5fbd57a300f5..1583ce1fd6f86 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); + } + + } + } 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 32177bbc0c5b0..228d9d1ff62e5 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 648dbcc64fc6d..797fa16751648 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 8fb1e86211afe..2c24917f62033 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) }, }; diff --git a/src/native/corehost/fxr/corehost_init.cpp b/src/native/corehost/fxr/corehost_init.cpp index eea9b768f5f90..2d169348fa635 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 70ea7be46fbf0..6c3148b627496 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 6a20206e2cadb..8647130c18dc1 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 6794e2a1c8254..b3a0e0004700a 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); diff --git a/src/tasks/AppleAppBuilder/Xcode.cs b/src/tasks/AppleAppBuilder/Xcode.cs index 9c0af486b6402..b79387da224f8 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 603fbcf17003c..bdd84c295ee7d 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++; } diff --git a/src/tests/Common/Coreclr.TestWrapper/MobileAppHandler.cs b/src/tests/Common/Coreclr.TestWrapper/MobileAppHandler.cs index b594455f1071c..f4d179ce4ce29 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/Common/scripts/arm32_ci_script.sh b/src/tests/Common/scripts/arm32_ci_script.sh index 8866d49536df8..33d884f4f990d 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 106137eaeaefd..f711030e9cb47 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')) diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/ApplyUpdateReferencedAssembly/ApplyUpdateReferencedAssembly.csproj b/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/ApplyUpdateReferencedAssembly/ApplyUpdateReferencedAssembly.csproj index 06190d7b86fc0..46e260f24e155 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 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 0000000000000..884dfa7ae893b --- /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 0000000000000..f0a56aa7fdf05 --- /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 + + + + + + diff --git a/src/tests/Interop/ICustomMarshaler/Primitives/ICustomMarshaler.cs b/src/tests/Interop/ICustomMarshaler/Primitives/ICustomMarshaler.cs index 271c7485d1906..78d7d541872a4 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(); 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 0000000000000..08d7e7a684f7d --- /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 0000000000000..edc51be9ca25b --- /dev/null +++ b/src/tests/JIT/Directed/ConstantFolding/folding_extends_int32_on_64_bit_hosts.csproj @@ -0,0 +1,10 @@ + + + Exe + True + None + + + + + diff --git a/src/tests/Loader/AssemblyLoadContext30Extensions/AssemblyLoadContext30Extensions.cs b/src/tests/Loader/AssemblyLoadContext30Extensions/AssemblyLoadContext30Extensions.cs index bb05cb2ec7243..cd730ea27badc 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); 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 0000000000000..e8a1139f53b91 --- /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 0000000000000..b566f02369797 --- /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 diff --git a/src/tests/profiler/eventpipe/eventpipe.cs b/src/tests/profiler/eventpipe/eventpipe.cs index 17f6d3f6ed99e..5b58b6d62fe6d 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; diff --git a/src/tests/profiler/gc/gcallocate.cs b/src/tests/profiler/gc/gcallocate.cs new file mode 100644 index 0000000000000..b3fbfd9d29cfe --- /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 0000000000000..b2fbdc913b04a --- /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 9a0561c3e6d5b..e068fdc1ea3cb 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 b41f6ba7bc9ef..fe999926de5b7 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 0000000000000..20e029704301f --- /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 0000000000000..65fb3b16240e0 --- /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 a9f28011eb02b..6d377e6d115be 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 0fa298f59f3b9..42666a2d8eac8 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) { diff --git a/src/tests/run.proj b/src/tests/run.proj index a64233555d206..3289e9c9ee645 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 } } diff --git a/src/tests/tracing/eventpipe/diagnosticport/diagnosticport.cs b/src/tests/tracing/eventpipe/diagnosticport/diagnosticport.cs index a4e6bd6418100..b1455d01e2113 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)