diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 5a423ee75b3788..0bfa9d6aaa4cc6 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "microsoft.dotnet.xharness.cli": { - "version": "1.0.0-prerelease.22411.1", + "version": "7.0.0-prerelease.23321.1", "commands": [ "xharness" ] diff --git a/.editorconfig b/.editorconfig index 15658bab8d06ef..9d0f526c0cc987 100644 --- a/.editorconfig +++ b/.editorconfig @@ -94,7 +94,8 @@ dotnet_style_object_initializer = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion dotnet_style_coalesce_expression = true:suggestion -dotnet_style_null_propagation = true:suggestion +dotnet_style_null_propagation = false:suggestion # Turning off given new warnings that came with the new analyzers and we don't want to take the risk of changing those now +dotnet_style_prefer_compound_assignment = false:suggestion dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion diff --git a/.github/PULL_REQUEST_TEMPLATE/servicing_pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/servicing_pull_request_template.md index 8932380eb8e13e..9a748a085a20f1 100644 --- a/.github/PULL_REQUEST_TEMPLATE/servicing_pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE/servicing_pull_request_template.md @@ -24,5 +24,4 @@ main PR # Package authoring signed off? - -IMPORTANT: If this change touches code that ships in a NuGet package, please make certain that you have added any necessary [package authoring](https://github.com/dotnet/runtime/blob/main/docs/project/library-servicing.md) and gotten it explicitly reviewed. +IMPORTANT: If this change touches code that ships in a NuGet package, please make certain that you have added any necessary [package authoring](../../docs/project/library-servicing.md) and gotten it explicitly reviewed. diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 4124fa04342fe4..a02fdace09ac12 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -80,4 +80,9 @@ jobs: ## Risk - IMPORTANT: If this change touches code that ships in a NuGet package, please make certain that you have added any necessary [package authoring](https://github.com/dotnet/runtime/blob/main/docs/project/library-servicing.md) and gotten it explicitly reviewed. + **IMPORTANT**: If this backport is for a servicing release, please verify that: + + - The PR target branch is `release/X.0-staging`, not `release/X.0`. + + - If the change touches code that ships in a NuGet package, you have added the necessary [package authoring](https://github.com/dotnet/runtime/blob/release/7.0/docs/project/library-servicing.md) and gotten it explicitly reviewed. + diff --git a/.github/workflows/check-service-labels.yml b/.github/workflows/check-service-labels.yml new file mode 100644 index 00000000000000..5261cc165ee128 --- /dev/null +++ b/.github/workflows/check-service-labels.yml @@ -0,0 +1,24 @@ +name: check-service-labels + +permissions: + pull-requests: read + +on: + pull_request: + types: [opened, edited, reopened, labeled, unlabeled, synchronize] + branches: + - 'release/**' + +jobs: + check-labels: + runs-on: ubuntu-latest + steps: + - name: Check 'Servicing-approved' label + run: | + echo "Merging permission is enabled for servicing PRs when the `Servicing-approved` label is applied." + if [ "${{ contains(github.event.pull_request.labels.*.name, 'Servicing-approved') }}" = "true" ]; then + exit 0 + else + echo "::error:: 'Servicing-approved' label not applied to the PR yet. More information: https://github.com/dotnet/runtime/blob/main/docs/project/library-servicing.md#approval-process" + exit 1 + fi diff --git a/Build.proj b/Build.proj index bff94b97a7a799..5f47e869fb6e42 100644 --- a/Build.proj +++ b/Build.proj @@ -10,7 +10,7 @@ - + .NET $(NetCoreAppCurrentVersion) net$(NetCoreAppCurrentVersion) net6.0 + $(NetCoreAppCurrent) 7.0 net$(NetCoreAppToolCurrentVersion) @@ -90,7 +91,7 @@ $([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'ibc')) $([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'mibc')) $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'docs')) - $([MSBuild]::NormalizeDirectory('$(NuGetPackageRoot)', 'microsoft.private.intellisense', '$(MicrosoftPrivateIntellisenseVersion)', 'IntellisenseFiles', 'net')) + $([MSBuild]::NormalizeDirectory('$(NuGetPackageRoot)', 'microsoft.private.intellisense', '$(MicrosoftPrivateIntellisenseVersion)', 'IntellisenseFiles')) $([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)', 'docs')) $([MSBuild]::NormalizeDirectory('$(DocsDir)', 'manpages')) @@ -273,7 +274,6 @@ https://dot.net microsoft,dotnetframework true - $([MSBuild]::NormalizePath('$(LibrariesProjectRoot)', 'Microsoft.NETCore.Platforms', 'src', 'runtime.json')) $(MSBuildThisFileDirectory)LICENSE.TXT MIT false diff --git a/Directory.Build.targets b/Directory.Build.targets index 541cdfb59f27f9..ef9e5f5810c7fa 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -12,6 +12,21 @@ + + + + $(PackageRID) + + + $(PackageRID) + + + + + + + + + + + + + + + + + @@ -31,8 +37,6 @@ - - @@ -61,33 +65,4 @@ - - - - - false - - - - - - - - - - - - - - - - diff --git a/eng/SourceBuild.props b/eng/SourceBuild.props index c17838918f8cec..32e6d9c0bad80a 100644 --- a/eng/SourceBuild.props +++ b/eng/SourceBuild.props @@ -11,14 +11,20 @@ true false - + $([System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier) $(__DistroRid) <_targetRidPlatformIndex>$(TargetRid.LastIndexOf('-')) - $(TargetRid.Substring(0, $(_targetRidPlatformIndex))) - $(TargetRid.Substring($(_targetRidPlatformIndex)).TrimStart('-')) + $(TargetRid.Substring($(_targetRidPlatformIndex)).TrimStart('-')) + + + $(TargetRid.Substring(0, $(_targetRidPlatformIndex))) + + + $(RuntimeOS) minimal @@ -26,19 +32,25 @@ - $(InnerBuildArgs) --arch $(TargetRidPlatform) + $(InnerBuildArgs) --arch $(TargetArch) $(InnerBuildArgs) --configuration $(Configuration) $(InnerBuildArgs) --allconfigurations $(InnerBuildArgs) --verbosity $(LogVerbosity) $(InnerBuildArgs) --nodereuse false $(InnerBuildArgs) --warnAsError false - $(InnerBuildArgs) /p:PackageRid=$(TargetRid) + $(InnerBuildArgs) --outputrid $(TargetRid) + $(InnerBuildArgs) --portablebuild $(SourceBuildPortable) $(InnerBuildArgs) /p:NoPgoOptimize=true $(InnerBuildArgs) /p:KeepNativeSymbols=true - $(InnerBuildArgs) /p:RuntimeOS=$(TargetRidWithoutPlatform) - $(InnerBuildArgs) /p:PortableBuild=$(SourceBuildPortable) + $(InnerBuildArgs) /p:RuntimeOS=$(RuntimeOS) + $(InnerBuildArgs) /p:OfficialBuildId=$(OfficialBuildId) + $(InnerBuildArgs) /p:ContinuousIntegrationBuild=$(ContinuousIntegrationBuild) $(InnerBuildArgs) /p:BuildDebPackage=false $(InnerBuildArgs) /p:EnableNgenOptimization=false + $(InnerBuildArgs) /p:EnablePackageValidation=false + $(InnerBuildArgs) /p:DisableSourceLink=false + $(InnerBuildArgs) /p:AdditionalRuntimeIdentifierParent=$(BaseOS) + $(InnerBuildArgs) /p:PrimaryRuntimeFlavor=Mono /p:RuntimeFlavor=Mono diff --git a/eng/Subsets.props b/eng/Subsets.props index 08a9d5dfaefc61..cd5f39ef63879d 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -61,7 +61,8 @@ mono.llvm+ $(DefaultMonoSubsets)mono.wasmruntime+ $(DefaultMonoSubsets)mono.aotcross+ - $(DefaultMonoSubsets)mono.runtime+mono.corelib+mono.packages+mono.tools+ + $(DefaultMonoSubsets)mono.runtime+mono.corelib+mono.packages+ + $(DefaultMonoSubsets)mono.tools+ $(DefaultMonoSubsets)host.native+ + https://github.com/dotnet/icu - c04d1340510269c5cd07a285abb097f587924d5b + 5e41bd879a83393b0cc3934576a12994ba9a7132 - + https://github.com/dotnet/msquic - dc012a715ceb9b5d5258f2fda77520586af5a36a - - - https://github.com/dotnet/emsdk - 216093204c415b6e37dfadfcbcf183881b443636 + b1a2104c42648101a2373a1278bcf4ef897ad840 https://github.com/dotnet/wcf 7f504aabb1988e9a093c1e74d8040bd52feb2f01 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + 602a8ec539e0f9febb85bd4117ee5ff69265acd6 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + 602a8ec539e0f9febb85bd4117ee5ff69265acd6 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + 602a8ec539e0f9febb85bd4117ee5ff69265acd6 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + 602a8ec539e0f9febb85bd4117ee5ff69265acd6 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + 602a8ec539e0f9febb85bd4117ee5ff69265acd6 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + 602a8ec539e0f9febb85bd4117ee5ff69265acd6 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + 602a8ec539e0f9febb85bd4117ee5ff69265acd6 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + 602a8ec539e0f9febb85bd4117ee5ff69265acd6 https://github.com/dotnet/command-line-api 5618b2d243ccdeb5c7e50a298b33b13036b4351b + + https://github.com/dotnet/emsdk + fcd8602188cfe2f73c73f5ae57e33c0654ce9858 + + + https://github.com/dotnet/emsdk + fcd8602188cfe2f73c73f5ae57e33c0654ce9858 + - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + ec5d27d08b78d68d96eeb8899f329a2fa732923a - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + ec5d27d08b78d68d96eeb8899f329a2fa732923a - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + ec5d27d08b78d68d96eeb8899f329a2fa732923a - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + ec5d27d08b78d68d96eeb8899f329a2fa732923a - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + ec5d27d08b78d68d96eeb8899f329a2fa732923a - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + ec5d27d08b78d68d96eeb8899f329a2fa732923a - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + ec5d27d08b78d68d96eeb8899f329a2fa732923a - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + ec5d27d08b78d68d96eeb8899f329a2fa732923a - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + ec5d27d08b78d68d96eeb8899f329a2fa732923a - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + ec5d27d08b78d68d96eeb8899f329a2fa732923a - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + ec5d27d08b78d68d96eeb8899f329a2fa732923a - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + ec5d27d08b78d68d96eeb8899f329a2fa732923a - + + https://github.com/dotnet/runtime-assets + ec5d27d08b78d68d96eeb8899f329a2fa732923a + + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + a2a265df3d5e8b2a84f64c50aa38497baf9970fb - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + a2a265df3d5e8b2a84f64c50aa38497baf9970fb - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + a2a265df3d5e8b2a84f64c50aa38497baf9970fb - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + a2a265df3d5e8b2a84f64c50aa38497baf9970fb - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + a2a265df3d5e8b2a84f64c50aa38497baf9970fb - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + a2a265df3d5e8b2a84f64c50aa38497baf9970fb - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + a2a265df3d5e8b2a84f64c50aa38497baf9970fb - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + a2a265df3d5e8b2a84f64c50aa38497baf9970fb https://github.com/dotnet/runtime @@ -234,57 +242,65 @@ https://github.com/dotnet/runtime e680411c22e33f45821f4ae64365a2970b2430a6 - + https://github.com/dotnet/linker - fda7b09fc005acb865deaf526c7adbb1be27a5f9 + 53b3303c57b3fe659500fb362a0eef12991c1197 - + https://github.com/dotnet/xharness - 5ebf69650b9f7b4ecab485be840b3022420f7812 + 08db853202902978dedf7562643a5b29bebf8645 - + https://github.com/dotnet/xharness - 5ebf69650b9f7b4ecab485be840b3022420f7812 + 08db853202902978dedf7562643a5b29bebf8645 - + https://github.com/dotnet/xharness - 5ebf69650b9f7b4ecab485be840b3022420f7812 + 08db853202902978dedf7562643a5b29bebf8645 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + cae11bc40b691f546d788f7ab37f7eaf0e24ded8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - e01e5b0aed54a5a8d9df74e717d1b13f0fb0e056 + 5e0b0da43f660de5798186f4fd3bc900fc90576c - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - e01e5b0aed54a5a8d9df74e717d1b13f0fb0e056 + 5e0b0da43f660de5798186f4fd3bc900fc90576c - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - e01e5b0aed54a5a8d9df74e717d1b13f0fb0e056 + 5e0b0da43f660de5798186f4fd3bc900fc90576c - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - e01e5b0aed54a5a8d9df74e717d1b13f0fb0e056 + 5e0b0da43f660de5798186f4fd3bc900fc90576c - + https://github.com/dotnet/hotreload-utils - f82b82000caf3e7a9789e1425a0baa12fdc70d09 + 75d6e441c8ee712e1bf466a51baaef4fe83b56ef - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + ec5d27d08b78d68d96eeb8899f329a2fa732923a - + https://github.com/dotnet/roslyn-analyzers - 793113a41d7f21b03470521bf48438f2abd9b12f + 31373ce8529c3d2f6b91e61585872160b0d7d7cd https://github.com/dotnet/sdk 3f2524bd65a6ab77b9160bcc23824dbc03990f3d + + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization + 5e0b0da43f660de5798186f4fd3bc900fc90576c + + + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization + 5e0b0da43f660de5798186f4fd3bc900fc90576c + diff --git a/eng/Versions.props b/eng/Versions.props index 72f17c83aa2662..3f83748d7a461b 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -1,27 +1,34 @@ - 7.0.0 + 7.0.10 7 0 - 0 + 10 7.0.100 - rc - 1 + 6.0.$([MSBuild]::Add($(PatchVersion), 11)) + servicing + + $(MajorVersion).$(MinorVersion).0.0 - false + true release true false false $(AssemblyVersion) - true + 7.0.11 + 7.0.11 + + + + 4.0.1 - - - 4.3.0-2.final + + 4.4.0 3.3.3 - 4.3.0-2.final - 4.3.0-2.final - 4.3.0-2.final - 7.0.0-preview1.22403.2 - 4.3.0-2.final - - 4.4.0-2.22412.11 + 4.4.0 + 4.4.0 + 4.4.0 + 7.0.0-preview1.22559.1 + 4.4.0 0.2.0 7.0.100-rc.1.22402.1 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 2.5.1-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 + 7.0.0-beta.23361.2 + 7.0.0-beta.23361.2 + 7.0.0-beta.23361.2 + 7.0.0-beta.23361.2 + 7.0.0-beta.23361.2 + 7.0.0-beta.23361.2 + 2.5.1-beta.23361.2 + 7.0.0-beta.23361.2 + 7.0.0-beta.23361.2 + 7.0.0-beta.23361.2 + 7.0.0-beta.23361.2 + 7.0.0-beta.23361.2 + 7.0.0-beta.23361.2 + 7.0.0-beta.23361.2 + 7.0.0-beta.23361.2 + 7.0.0-beta.23361.2 6.0.0-preview.1.102 @@ -79,14 +78,14 @@ 6.0.0 7.0.0-rc.1.22414.6 - 1.0.0-alpha.1.22411.1 - 1.0.0-alpha.1.22411.1 - 1.0.0-alpha.1.22411.1 - 1.0.0-alpha.1.22411.1 - 1.0.0-alpha.1.22411.1 - 1.0.0-alpha.1.22411.1 - 1.0.0-alpha.1.22411.1 - 1.0.0-alpha.1.22411.1 + 1.0.0-alpha.1.23061.4 + 1.0.0-alpha.1.23061.4 + 1.0.0-alpha.1.23061.4 + 1.0.0-alpha.1.23061.4 + 1.0.0-alpha.1.23061.4 + 1.0.0-alpha.1.23061.4 + 1.0.0-alpha.1.23061.4 + 1.0.0-alpha.1.23061.4 6.0.0 1.1.1 @@ -95,14 +94,14 @@ 4.5.1 6.0.0 5.0.0 - 4.8.3 + 4.8.5 4.5.0 5.0.0 5.0.0 - 4.5.4 + 4.5.5 4.5.0 - 6.0.0 - 4.7.1 + 6.0.1 + 6.0.0 4.7.0 4.7.0 4.7.0 @@ -119,45 +118,52 @@ 4.5.0 7.0.0-rc.1.22414.6 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 + 7.0.0-beta.23179.6 + 7.0.0-beta.23179.6 + 7.0.0-beta.23179.6 + 7.0.0-beta.23179.6 + 7.0.0-beta.23179.6 + 7.0.0-beta.23179.6 + 7.0.0-beta.23179.6 + 7.0.0-beta.23179.6 + 7.0.0-beta.23179.6 + 7.0.0-beta.23179.6 + 7.0.0-beta.23179.6 + 7.0.0-beta.23179.6 + 7.0.0-beta.23179.6 + 7.0.0-beta.23179.6 - 1.0.0-prerelease.22375.7 - 1.0.0-prerelease.22375.7 - 1.0.0-prerelease.22375.7 - 1.0.0-prerelease.22375.7 + 1.0.0-prerelease.22415.6 + 1.0.0-prerelease.22415.6 + 1.0.0-prerelease.22415.6 + 1.0.0-prerelease.22415.6 + 1.0.0-prerelease.22415.6 + 1.0.0-prerelease.22415.6 - 16.9.0-beta1.21055.5 + 16.11.27-beta1.23180.1 2.0.0-beta4.22355.1 3.0.3 2.1.0 2.0.3 1.0.4-preview6.19326.1 1.0.27 - 16.10.0 + 17.3.2 $(MicrosoftBuildVersion) - 6.2.1 - 6.2.1 + 6.2.2 + 6.2.2 1.1.0 17.4.0-preview-20220707-01 - 1.0.0-prerelease.22411.1 - 1.0.0-prerelease.22411.1 - 1.0.0-prerelease.22411.1 - 1.1.0-alpha.0.22408.2 - 2.4.2-pre.22 - 0.12.0-pre.20 + 7.0.0-prerelease.23309.1 + 7.0.0-prerelease.23309.1 + 7.0.0-prerelease.23309.1 + 7.0.0-alpha.0.23367.2 + 7.0.0-prerelease.23321.1 + 7.0.0-prerelease.23321.1 + 7.0.0-prerelease.23321.1 + 7.0.0-alpha.0.23226.5 + 2.4.2 + 1.0.0 2.4.5 3.1.2 13.0.1 @@ -166,30 +172,30 @@ 4.12.0 2.14.3 - + + 7.0 1.1.2-beta1.22403.2 - 7.0.0-preview-20220721.1 + 7.0.0-preview-20221010.1 - 7.0.100-1.22412.4 + 7.0.100-1.23401.1 $(MicrosoftNETILLinkTasksVersion) - 7.0.0-rc.1.22408.1 + 7.0.0-rtm.23362.1 - 2.1 - 7.0.0-alpha.1.22406.1 + 2.1.1 + 7.0.0-alpha.1.22459.1 - 11.1.0-alpha.1.22412.3 - 11.1.0-alpha.1.22412.3 - 11.1.0-alpha.1.22412.3 - 11.1.0-alpha.1.22412.3 - 11.1.0-alpha.1.22412.3 - 11.1.0-alpha.1.22412.3 - 11.1.0-alpha.1.22412.3 - 11.1.0-alpha.1.22412.3 + 11.1.0-alpha.1.23115.1 + 11.1.0-alpha.1.23115.1 + 11.1.0-alpha.1.23115.1 + 11.1.0-alpha.1.23115.1 + 11.1.0-alpha.1.23115.1 + 11.1.0-alpha.1.23115.1 + 11.1.0-alpha.1.23115.1 + 11.1.0-alpha.1.23115.1 - 7.0.0-rc.1.22411.1 - $(MicrosoftNETWorkloadEmscriptenManifest70100Version) + $(MicrosoftNETWorkloadEmscriptennet7Manifest70100Version) 1.1.87-gba258badda 1.0.0-v3.14.0.5722 diff --git a/eng/build.sh b/eng/build.sh index 84a4fd746c9177..352761a5208b91 100755 --- a/eng/build.sh +++ b/eng/build.sh @@ -31,6 +31,7 @@ usage() echo " --os Target operating system: windows, Linux, FreeBSD, OSX, MacCatalyst, tvOS," echo " tvOSSimulator, iOS, iOSSimulator, Android, Browser, NetBSD, illumos or Solaris." echo " [Default: Your machine's OS.]" + echo " --outputrid Optional argument that overrides the target rid name." echo " --projects Project or solution file(s) to build." echo " --runtimeConfiguration (-rc) Runtime build configuration: Debug, Release or Checked." echo " Checked is exclusive to the CLR runtime. It is the same as Debug, except code is" @@ -402,6 +403,15 @@ while [[ $# > 0 ]]; do shift 1 ;; + -outputrid) + if [ -z ${2+x} ]; then + echo "No value for outputrid is supplied. See help (--help) for supported values." 1>&2 + exit 1 + fi + arguments="$arguments /p:OutputRid=$(echo "$2" | tr "[:upper:]" "[:lower:]")" + shift 2 + ;; + -portablebuild) if [ -z ${2+x} ]; then echo "No value for portablebuild is supplied. See help (--help) for supported values." 1>&2 diff --git a/eng/common/build.ps1 b/eng/common/build.ps1 index 8943da242f6e92..e0420a64275504 100644 --- a/eng/common/build.ps1 +++ b/eng/common/build.ps1 @@ -26,6 +26,7 @@ Param( [string] $runtimeSourceFeed = '', [string] $runtimeSourceFeedKey = '', [switch] $excludePrereleaseVS, + [switch] $nativeToolsOnMachine, [switch] $help, [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties ) @@ -66,6 +67,7 @@ function Print-Usage() { Write-Host " -prepareMachine Prepare machine for CI run, clean up processes after build" Write-Host " -warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." + Write-Host " -nativeToolsOnMachine Sets the native tools on machine environment variable (indicating that the script should use native tools on machine)" Write-Host " -excludePrereleaseVS Set to exclude build engines in prerelease versions of Visual Studio" Write-Host "" @@ -146,6 +148,9 @@ try { $nodeReuse = $false } + if ($nativeToolsOnMachine) { + $env:NativeToolsOnMachine = $true + } if ($restore) { InitializeNativeTools } diff --git a/eng/common/cross/arm/sources.list.focal b/eng/common/cross/arm/sources.list.focal new file mode 100644 index 00000000000000..4de2600c17478c --- /dev/null +++ b/eng/common/cross/arm/sources.list.focal @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ focal main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ focal-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-security main restricted universe multiverse diff --git a/eng/common/cross/arm/sources.list.jammy b/eng/common/cross/arm/sources.list.jammy new file mode 100644 index 00000000000000..6bb0453029cc47 --- /dev/null +++ b/eng/common/cross/arm/sources.list.jammy @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse diff --git a/eng/common/cross/arm64/sources.list.focal b/eng/common/cross/arm64/sources.list.focal new file mode 100644 index 00000000000000..4de2600c17478c --- /dev/null +++ b/eng/common/cross/arm64/sources.list.focal @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ focal main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ focal-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-security main restricted universe multiverse diff --git a/eng/common/cross/arm64/sources.list.jammy b/eng/common/cross/arm64/sources.list.jammy new file mode 100644 index 00000000000000..6bb0453029cc47 --- /dev/null +++ b/eng/common/cross/arm64/sources.list.jammy @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index d3b0ac3ba7b600..5680980fa296e3 100755 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -76,10 +76,10 @@ __FreeBSDPackages+=" openssl" __FreeBSDPackages+=" krb5" __FreeBSDPackages+=" terminfo-db" -__IllumosPackages="icu-64.2nb2" -__IllumosPackages+=" mit-krb5-1.16.2nb4" -__IllumosPackages+=" openssl-1.1.1e" -__IllumosPackages+=" zlib-1.2.11" +__IllumosPackages="icu" +__IllumosPackages+=" mit-krb5" +__IllumosPackages+=" openssl" +__IllumosPackages+=" zlib" __HaikuPackages="gmp" __HaikuPackages+=" gmp_devel" @@ -186,32 +186,27 @@ while :; do __UbuntuArch=i386 __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" ;; - lldb3.6) - __LLDB_Package="lldb-3.6-dev" - ;; - lldb3.8) - __LLDB_Package="lldb-3.8-dev" - ;; - lldb3.9) - __LLDB_Package="liblldb-3.9-dev" - ;; - lldb4.0) - __LLDB_Package="liblldb-4.0-dev" - ;; - lldb5.0) - __LLDB_Package="liblldb-5.0-dev" - ;; - lldb6.0) - __LLDB_Package="liblldb-6.0-dev" + lldb*) + version="${lowerI/lldb/}" + parts=(${version//./ }) + + # for versions > 6.0, lldb has dropped the minor version + if [[ "${parts[0]}" -gt 6 ]]; then + version="${parts[0]}" + fi + + __LLDB_Package="liblldb-${version}-dev" ;; no-lldb) unset __LLDB_Package ;; llvm*) - version="$(echo "$lowerI" | tr -d '[:alpha:]-=')" + version="${lowerI/llvm/}" parts=(${version//./ }) __LLVM_MajorVersion="${parts[0]}" __LLVM_MinorVersion="${parts[1]}" + + # for versions > 6.0, llvm has dropped the minor version if [[ -z "$__LLVM_MinorVersion" && "$__LLVM_MajorVersion" -le 6 ]]; then __LLVM_MinorVersion=0; fi @@ -231,6 +226,16 @@ while :; do __CodeName=bionic fi ;; + focal) # Ubuntu 20.04 + if [[ "$__CodeName" != "jessie" ]]; then + __CodeName=focal + fi + ;; + jammy) # Ubuntu 22.04 + if [[ "$__CodeName" != "jessie" ]]; then + __CodeName=jammy + fi + ;; jessie) # Debian 8 __CodeName=jessie __UbuntuRepo="http://ftp.debian.org/debian/" @@ -390,14 +395,18 @@ elif [[ "$__CodeName" == "illumos" ]]; then if [[ "$__UseMirror" == 1 ]]; then BaseUrl=http://pkgsrc.smartos.skylime.net fi - BaseUrl="$BaseUrl/packages/SmartOS/2020Q1/${__illumosArch}/All" + BaseUrl="$BaseUrl/packages/SmartOS/trunk/${__illumosArch}/All" + echo "Downloading manifest" + wget "$BaseUrl" echo "Downloading dependencies." read -ra array <<<"$__IllumosPackages" for package in "${array[@]}"; do - echo "Installing $package..." + echo "Installing '$package'" + package="$(grep ">$package-[0-9]" All | sed -En 's/.*href="(.*)\.tgz".*/\1/p')" + echo "Resolved name '$package'" wget "$BaseUrl"/"$package".tgz ar -x "$package".tgz - tar --skip-old-files -xzf "$package".tmp.tgz -C "$__RootfsDir" 2>/dev/null + tar --skip-old-files -xzf "$package".tmp.tg* -C "$__RootfsDir" 2>/dev/null done echo "Cleaning up temporary files." popd diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake index 561576be97c262..964610524760a3 100644 --- a/eng/common/cross/toolchain.cmake +++ b/eng/common/cross/toolchain.cmake @@ -1,5 +1,12 @@ set(CROSS_ROOTFS $ENV{ROOTFS_DIR}) +# reset platform variables (e.g. cmake 3.25 sets LINUX=1) +unset(LINUX) +unset(FREEBSD) +unset(ILLUMOS) +unset(ANDROID) +unset(TIZEN) + set(TARGET_ARCH_NAME $ENV{TARGET_BUILD_ARCH}) if(EXISTS ${CROSS_ROOTFS}/bin/freebsd-version) set(CMAKE_SYSTEM_NAME FreeBSD) diff --git a/eng/common/cross/x86/sources.list.focal b/eng/common/cross/x86/sources.list.focal new file mode 100644 index 00000000000000..99d5731330e79d --- /dev/null +++ b/eng/common/cross/x86/sources.list.focal @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ focal main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ focal-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse diff --git a/eng/common/cross/x86/sources.list.jammy b/eng/common/cross/x86/sources.list.jammy new file mode 100644 index 00000000000000..af1c1feaeac1bd --- /dev/null +++ b/eng/common/cross/x86/sources.list.jammy @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ jammy main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ jammy main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse diff --git a/eng/common/generate-locproject.ps1 b/eng/common/generate-locproject.ps1 index afdd1750290923..bcb579e37a91bf 100644 --- a/eng/common/generate-locproject.ps1 +++ b/eng/common/generate-locproject.ps1 @@ -33,6 +33,27 @@ $jsonTemplateFiles | ForEach-Object { $jsonWinformsTemplateFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\\strings\.json" } # current winforms pattern +$wxlFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\\.+\.wxl" -And -Not( $_.Directory.Name -Match "\d{4}" ) } # localized files live in four digit lang ID directories; this excludes them +if (-not $wxlFiles) { + $wxlEnFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\\1033\\.+\.wxl" } # pick up en files (1033 = en) specifically so we can copy them to use as the neutral xlf files + if ($wxlEnFiles) { + $wxlFiles = @() + $wxlEnFiles | ForEach-Object { + $destinationFile = "$($_.Directory.Parent.FullName)\$($_.Name)" + $wxlFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru + } + } +} + +$macosHtmlEnFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\.lproj\\.+\.html" } # add installer HTML files +$macosHtmlFiles = @() +if ($macosHtmlEnFiles) { + $macosHtmlEnFiles | ForEach-Object { + $destinationFile = "$($_.Directory.Parent.FullName)\$($_.Name)" + $macosHtmlFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru + } +} + $xlfFiles = @() $allXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.xlf" @@ -60,7 +81,7 @@ $locJson = @{ $outputPath = "$(($_.DirectoryName | Resolve-Path -Relative) + "\")" $continue = $true foreach ($exclusion in $exclusions.Exclusions) { - if ($outputPath.Contains($exclusion)) + if ($_.FullName.Contains($exclusion)) { $continue = $false } @@ -77,8 +98,7 @@ $locJson = @{ CopyOption = "LangIDOnPath" OutputPath = "$($_.Directory.Parent.FullName | Resolve-Path -Relative)\" } - } - else { + } else { return @{ SourceFile = $sourceFile CopyOption = "LangIDOnName" @@ -88,6 +108,55 @@ $locJson = @{ } } ) + }, + @{ + LanguageSet = $LanguageSet + CloneLanguageSet = "WiX_CloneLanguages" + LssFiles = @( "wxl_loc.lss" ) + LocItems = @( + $wxlFiles | ForEach-Object { + $outputPath = "$($_.Directory.FullName | Resolve-Path -Relative)\" + $continue = $true + foreach ($exclusion in $exclusions.Exclusions) { + if ($_.FullName.Contains($exclusion)) { + $continue = $false + } + } + $sourceFile = ($_.FullName | Resolve-Path -Relative) + if ($continue) + { + return @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnPath" + OutputPath = $outputPath + } + } + } + ) + }, + @{ + LanguageSet = $LanguageSet + CloneLanguageSet = "VS_macOS_CloneLanguages" + LssFiles = @( ".\eng\common\loc\P22DotNetHtmlLocalization.lss" ) + LocItems = @( + $macosHtmlFiles | ForEach-Object { + $outputPath = "$($_.Directory.FullName | Resolve-Path -Relative)\" + $continue = $true + foreach ($exclusion in $exclusions.Exclusions) { + if ($_.FullName.Contains($exclusion)) { + $continue = $false + } + } + $sourceFile = ($_.FullName | Resolve-Path -Relative) + if ($continue) { + return @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnPath" + OutputPath = $outputPath + } + } + } + ) } ) } diff --git a/eng/common/init-tools-native.ps1 b/eng/common/init-tools-native.ps1 index 8d48ec5680fc4c..fbc67effc36311 100644 --- a/eng/common/init-tools-native.ps1 +++ b/eng/common/init-tools-native.ps1 @@ -98,11 +98,12 @@ try { Write-Error "Arcade tools directory '$ArcadeToolsDirectory' was not found; artifacts were not properly installed." exit 1 } - $ToolDirectory = (Get-ChildItem -Path "$ArcadeToolsDirectory" -Filter "$ToolName-$ToolVersion*" | Sort-Object -Descending)[0] - if ([string]::IsNullOrWhiteSpace($ToolDirectory)) { + $ToolDirectories = (Get-ChildItem -Path "$ArcadeToolsDirectory" -Filter "$ToolName-$ToolVersion*" | Sort-Object -Descending) + if ($ToolDirectories -eq $null) { Write-Error "Unable to find directory for $ToolName $ToolVersion; please make sure the tool is installed on this image." exit 1 } + $ToolDirectory = $ToolDirectories[0] $BinPathFile = "$($ToolDirectory.FullName)\binpath.txt" if (-not (Test-Path -Path "$BinPathFile")) { Write-Error "Unable to find binpath.txt in '$($ToolDirectory.FullName)' ($ToolName $ToolVersion); artifact is either installed incorrectly or is not a bootstrappable tool." @@ -112,6 +113,7 @@ try { $ToolPath = Convert-Path -Path $BinPath Write-Host "Adding $ToolName to the path ($ToolPath)..." Write-Host "##vso[task.prependpath]$ToolPath" + $env:PATH = "$ToolPath;$env:PATH" $InstalledTools += @{ $ToolName = $ToolDirectory.FullName } } } diff --git a/eng/common/loc/P22DotNetHtmlLocalization.lss b/eng/common/loc/P22DotNetHtmlLocalization.lss new file mode 100644 index 00000000000000..6661fed566e49b Binary files /dev/null and b/eng/common/loc/P22DotNetHtmlLocalization.lss differ diff --git a/eng/common/native/init-compiler.sh b/eng/common/native/init-compiler.sh index 41a26d802a93f8..f13b74080edf9b 100644 --- a/eng/common/native/init-compiler.sh +++ b/eng/common/native/init-compiler.sh @@ -71,7 +71,7 @@ if [[ -z "$CLR_CC" ]]; then # Set default versions if [[ -z "$majorVersion" ]]; then # note: gcc (all versions) and clang versions higher than 6 do not have minor version in file name, if it is zero. - if [[ "$compiler" == "clang" ]]; then versions=( 15 14 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5 ) + if [[ "$compiler" == "clang" ]]; then versions=( 16 15 14 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5 ) elif [[ "$compiler" == "gcc" ]]; then versions=( 12 11 10 9 8 7 6 5 4.9 ); fi for version in "${versions[@]}"; do diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1 index 119a6c660d1a4d..e10a5968797463 100644 --- a/eng/common/sdk-task.ps1 +++ b/eng/common/sdk-task.ps1 @@ -64,7 +64,7 @@ try { $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty } if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { - $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.1.0" -MemberType NoteProperty + $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.4.1" -MemberType NoteProperty } if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") { $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true diff --git a/eng/common/sdl/sdl.ps1 b/eng/common/sdl/sdl.ps1 new file mode 100644 index 00000000000000..ac196e164a4016 --- /dev/null +++ b/eng/common/sdl/sdl.ps1 @@ -0,0 +1,37 @@ + +function Install-Gdn { + param( + [string]$Path, + + # If omitted, install the latest version of Guardian, otherwise install that specific version. + [string]$Version + ) + + $ErrorActionPreference = 'Stop' + Set-StrictMode -Version 2.0 + $disableConfigureToolsetImport = $true + $global:LASTEXITCODE = 0 + + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + $argumentList = @("install", "Microsoft.Guardian.Cli", "-Source https://securitytools.pkgs.visualstudio.com/_packaging/Guardian/nuget/v3/index.json", "-OutputDirectory $Path", "-NonInteractive", "-NoCache") + + if ($Version) { + $argumentList += "-Version $Version" + } + + Start-Process nuget -Verbose -ArgumentList $argumentList -NoNewWindow -Wait + + $gdnCliPath = Get-ChildItem -Filter guardian.cmd -Recurse -Path $Path + + if (!$gdnCliPath) + { + Write-PipelineTelemetryError -Category 'Sdl' -Message 'Failure installing Guardian' + } + + return $gdnCliPath.FullName +} \ No newline at end of file diff --git a/eng/common/templates/job/execute-sdl.yml b/eng/common/templates/job/execute-sdl.yml index 9ff6a10a682c27..7c164ac02f4da4 100644 --- a/eng/common/templates/job/execute-sdl.yml +++ b/eng/common/templates/job/execute-sdl.yml @@ -46,6 +46,7 @@ jobs: - template: /eng/common/templates/variables/sdl-variables.yml - name: GuardianVersion value: ${{ coalesce(parameters.overrideGuardianVersion, '$(DefaultGuardianVersion)') }} + - template: /eng/common/templates/variables/pool-providers.yml pool: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: @@ -53,7 +54,7 @@ jobs: demands: Cmd # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: - name: NetCore1ESPool-Internal + name: $(DncEngInternalBuildPool) demands: ImageOverride -equals windows.vs2019.amd64 steps: - checkout: self diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml index e3ba9398016be8..ef337eac55ecf4 100644 --- a/eng/common/templates/job/job.yml +++ b/eng/common/templates/job/job.yml @@ -24,7 +24,7 @@ parameters: enablePublishBuildAssets: false enablePublishTestResults: false enablePublishUsingPipelines: false - disableComponentGovernance: false + disableComponentGovernance: '' mergeTestResults: false testRunTitle: '' testResultsFormat: '' @@ -73,6 +73,10 @@ jobs: - ${{ if eq(parameters.enableRichCodeNavigation, 'true') }}: - name: EnableRichCodeNavigation value: 'true' + # Retry signature validation up to three times, waiting 2 seconds between attempts. + # See https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu3028#retry-untrusted-root-failures + - name: NUGET_EXPERIMENTAL_CHAIN_BUILD_RETRY_POLICY + value: 3,2000 - ${{ each variable in parameters.variables }}: # handle name-value variable syntax # example: @@ -81,7 +85,7 @@ jobs: - ${{ if ne(variable.name, '') }}: - name: ${{ variable.name }} value: ${{ variable.value }} - + # handle variable groups - ${{ if ne(variable.group, '') }}: - group: ${{ variable.group }} @@ -142,14 +146,20 @@ jobs: richNavLogOutputDirectory: $(Build.SourcesDirectory)/artifacts/bin continueOnError: true - - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), ne(parameters.disableComponentGovernance, 'true')) }}: - - task: ComponentGovernanceComponentDetection@0 - continueOnError: true + - template: /eng/common/templates/steps/component-governance.yml + parameters: + ${{ if eq(parameters.disableComponentGovernance, '') }}: + ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.runAsPublic, 'false'), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/dotnet/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/microsoft/'), eq(variables['Build.SourceBranch'], 'refs/heads/main'))) }}: + disableComponentGovernance: false + ${{ else }}: + disableComponentGovernance: true + ${{ else }}: + disableComponentGovernance: ${{ parameters.disableComponentGovernance }} - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - task: MicroBuildCleanup@1 - displayName: Execute Microbuild cleanup tasks + displayName: Execute Microbuild cleanup tasks condition: and(always(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) continueOnError: ${{ parameters.continueOnError }} env: @@ -217,7 +227,7 @@ jobs: displayName: Publish XUnit Test Results inputs: testResultsFormat: 'xUnit' - testResultsFiles: '*.xml' + testResultsFiles: '*.xml' searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-xunit mergeTestResults: ${{ parameters.mergeTestResults }} @@ -228,7 +238,7 @@ jobs: displayName: Publish TRX Test Results inputs: testResultsFormat: 'VSTest' - testResultsFiles: '*.trx' + testResultsFiles: '*.trx' searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-trx mergeTestResults: ${{ parameters.mergeTestResults }} diff --git a/eng/common/templates/job/onelocbuild.yml b/eng/common/templates/job/onelocbuild.yml index 6c523b714f407a..60ab00c4de3acd 100644 --- a/eng/common/templates/job/onelocbuild.yml +++ b/eng/common/templates/job/onelocbuild.yml @@ -14,6 +14,7 @@ parameters: ReusePr: true UseLfLineEndings: true UseCheckedInLocProjectJson: false + SkipLocProjectJsonGeneration: false LanguageSet: VS_Main_Languages LclSource: lclFilesInRepo LclPackageId: '' @@ -22,13 +23,25 @@ parameters: MirrorRepo: '' MirrorBranch: main condition: '' + JobNameSuffix: '' jobs: -- job: OneLocBuild +- job: OneLocBuild${{ parameters.JobNameSuffix }} dependsOn: ${{ parameters.dependsOn }} - displayName: OneLocBuild + displayName: OneLocBuild${{ parameters.JobNameSuffix }} + + variables: + - group: OneLocBuildVariables # Contains the CeapexPat and GithubPat + - name: _GenerateLocProjectArguments + value: -SourcesDirectory ${{ parameters.SourcesDirectory }} + -LanguageSet "${{ parameters.LanguageSet }}" + -CreateNeutralXlfs + - ${{ if eq(parameters.UseCheckedInLocProjectJson, 'true') }}: + - name: _GenerateLocProjectArguments + value: ${{ variables._GenerateLocProjectArguments }} -UseCheckedInLocProjectJson + - template: /eng/common/templates/variables/pool-providers.yml ${{ if ne(parameters.pool, '') }}: pool: ${{ parameters.pool }} @@ -40,27 +53,17 @@ jobs: demands: Cmd # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: - name: NetCore1ESPool-Internal + name: $(DncEngInternalBuildPool) demands: ImageOverride -equals windows.vs2019.amd64 - variables: - - group: OneLocBuildVariables # Contains the CeapexPat and GithubPat - - name: _GenerateLocProjectArguments - value: -SourcesDirectory ${{ parameters.SourcesDirectory }} - -LanguageSet "${{ parameters.LanguageSet }}" - -CreateNeutralXlfs - - ${{ if eq(parameters.UseCheckedInLocProjectJson, 'true') }}: - - name: _GenerateLocProjectArguments - value: ${{ variables._GenerateLocProjectArguments }} -UseCheckedInLocProjectJson - - steps: - - task: Powershell@2 - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/generate-locproject.ps1 - arguments: $(_GenerateLocProjectArguments) - displayName: Generate LocProject.json - condition: ${{ parameters.condition }} + - ${{ if ne(parameters.SkipLocProjectJsonGeneration, 'true') }}: + - task: Powershell@2 + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/generate-locproject.ps1 + arguments: $(_GenerateLocProjectArguments) + displayName: Generate LocProject.json + condition: ${{ parameters.condition }} - task: OneLocBuild@2 displayName: OneLocBuild diff --git a/eng/common/templates/job/publish-build-assets.yml b/eng/common/templates/job/publish-build-assets.yml index 1cbb6a0c560038..c5fedd7f70ce6d 100644 --- a/eng/common/templates/job/publish-build-assets.yml +++ b/eng/common/templates/job/publish-build-assets.yml @@ -34,15 +34,15 @@ jobs: - job: Asset_Registry_Publish dependsOn: ${{ parameters.dependsOn }} + timeoutInMinutes: 150 ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: displayName: Publish Assets ${{ else }}: displayName: Publish to Build Asset Registry - pool: ${{ parameters.pool }} - variables: + - template: /eng/common/templates/variables/pool-providers.yml - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - group: Publish-Build-Assets - group: AzureDevOps-Artifact-Feeds-Pats @@ -51,6 +51,16 @@ jobs: - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: - template: /eng/common/templates/post-build/common-variables.yml + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + steps: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - task: DownloadBuildArtifacts@0 diff --git a/eng/common/templates/job/source-build.yml b/eng/common/templates/job/source-build.yml index 5cd5325d7b4e6b..8a3deef2b72746 100644 --- a/eng/common/templates/job/source-build.yml +++ b/eng/common/templates/job/source-build.yml @@ -44,13 +44,16 @@ jobs: ${{ if eq(parameters.platform.pool, '') }}: # The default VM host AzDO pool. This should be capable of running Docker containers: almost all # source-build builds run in Docker, including the default managed platform. + # /eng/common/templates/variables/pool-providers.yml can't be used here (some customers declare variables already), so duplicate its logic pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: - name: NetCore1ESPool-Public + name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore-Svc-Public' ), False, 'NetCore-Public')] demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open + ${{ if eq(variables['System.TeamProject'], 'internal') }}: - name: NetCore1ESPool-Internal + name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] demands: ImageOverride -equals Build.Ubuntu.1804.Amd64 + ${{ if ne(parameters.platform.pool, '') }}: pool: ${{ parameters.platform.pool }} diff --git a/eng/common/templates/job/source-index-stage1.yml b/eng/common/templates/job/source-index-stage1.yml index c85044a6849054..09c506d11855db 100644 --- a/eng/common/templates/job/source-index-stage1.yml +++ b/eng/common/templates/job/source-index-stage1.yml @@ -22,16 +22,17 @@ jobs: value: ${{ parameters.binlogPath }} - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - group: source-dot-net stage1 variables + - template: /eng/common/templates/variables/pool-providers.yml ${{ if ne(parameters.pool, '') }}: pool: ${{ parameters.pool }} ${{ if eq(parameters.pool, '') }}: pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: - name: NetCore1ESPool-Public + name: $(DncEngPublicBuildPool) demands: ImageOverride -equals windows.vs2019.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: - name: NetCore1ESPool-Internal + name: $(DncEngInternalBuildPool) demands: ImageOverride -equals windows.vs2019.amd64 steps: diff --git a/eng/common/templates/jobs/jobs.yml b/eng/common/templates/jobs/jobs.yml index 64e5929f22161b..289bb2396ce83e 100644 --- a/eng/common/templates/jobs/jobs.yml +++ b/eng/common/templates/jobs/jobs.yml @@ -88,15 +88,6 @@ jobs: - ${{ job.job }} - ${{ if eq(parameters.enableSourceBuild, true) }}: - Source_Build_Complete - pool: - # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - name: VSEngSS-MicroBuild2022-1ES - demands: Cmd - # If it's not devdiv, it's dnceng - ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: - name: NetCore1ESPool-Internal - demands: ImageOverride -equals windows.vs2019.amd64 runAsPublic: ${{ parameters.runAsPublic }} publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} diff --git a/eng/common/templates/jobs/source-build.yml b/eng/common/templates/jobs/source-build.yml index 00aa98eb3bfd38..8dd2d355f22d2f 100644 --- a/eng/common/templates/jobs/source-build.yml +++ b/eng/common/templates/jobs/source-build.yml @@ -14,7 +14,7 @@ parameters: # This is the default platform provided by Arcade, intended for use by a managed-only repo. defaultManagedPlatform: name: 'Managed' - container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-7-3e800f1-20190501005343' + container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream8-20220809204800-17a4aab' # Defines the platforms on which to run build jobs. One job is created for each platform, and the # object in this array is sent to the job template as 'platform'. If no platforms are specified, diff --git a/eng/common/templates/post-build/post-build.yml b/eng/common/templates/post-build/post-build.yml index 87fcae940cff07..c051f1b65e9cd1 100644 --- a/eng/common/templates/post-build/post-build.yml +++ b/eng/common/templates/post-build/post-build.yml @@ -95,6 +95,7 @@ stages: displayName: Validate Build Assets variables: - template: common-variables.yml + - template: /eng/common/templates/variables/pool-providers.yml jobs: - job: displayName: NuGet Validation @@ -106,7 +107,7 @@ stages: demands: Cmd # If it's not devdiv, it's dnceng ${{ else }}: - name: NetCore1ESPool-Internal + name: $(DncEngInternalBuildPool) demands: ImageOverride -equals windows.vs2019.amd64 steps: @@ -143,7 +144,7 @@ stages: demands: Cmd # If it's not devdiv, it's dnceng ${{ else }}: - name: NetCore1ESPool-Internal + name: $(DncEngInternalBuildPool) demands: ImageOverride -equals windows.vs2019.amd64 steps: - template: setup-maestro-vars.yml @@ -203,7 +204,7 @@ stages: demands: Cmd # If it's not devdiv, it's dnceng ${{ else }}: - name: NetCore1ESPool-Internal + name: $(DncEngInternalBuildPool) demands: ImageOverride -equals windows.vs2019.amd64 steps: - template: setup-maestro-vars.yml @@ -251,6 +252,7 @@ stages: displayName: Publish using Darc variables: - template: common-variables.yml + - template: /eng/common/templates/variables/pool-providers.yml jobs: - job: displayName: Publish Using Darc @@ -262,7 +264,7 @@ stages: demands: Cmd # If it's not devdiv, it's dnceng ${{ else }}: - name: NetCore1ESPool-Internal + name: $(DncEngInternalBuildPool) demands: ImageOverride -equals windows.vs2019.amd64 steps: - template: setup-maestro-vars.yml @@ -282,4 +284,4 @@ stages: -MaestroToken '$(MaestroApiAccessToken)' -WaitPublishingFinish true -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' - -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' \ No newline at end of file + -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' diff --git a/eng/common/templates/steps/component-governance.yml b/eng/common/templates/steps/component-governance.yml new file mode 100644 index 00000000000000..babc2757d8d123 --- /dev/null +++ b/eng/common/templates/steps/component-governance.yml @@ -0,0 +1,10 @@ +parameters: + disableComponentGovernance: false + +steps: +- ${{ if eq(parameters.disableComponentGovernance, 'true') }}: + - script: "echo ##vso[task.setvariable variable=skipComponentGovernanceDetection]true" + displayName: Set skipComponentGovernanceDetection variable +- ${{ if ne(parameters.disableComponentGovernance, 'true') }}: + - task: ComponentGovernanceComponentDetection@0 + continueOnError: true \ No newline at end of file diff --git a/eng/common/templates/steps/execute-sdl.yml b/eng/common/templates/steps/execute-sdl.yml index 73245593cef53d..86cf578c431443 100644 --- a/eng/common/templates/steps/execute-sdl.yml +++ b/eng/common/templates/steps/execute-sdl.yml @@ -8,29 +8,26 @@ parameters: condition: '' steps: -- ${{ if ne(parameters.overrideGuardianVersion, '') }}: - - powershell: | - $content = Get-Content $(GuardianPackagesConfigFile) - - Write-Host "packages.config content was:`n$content" - - $content = $content.Replace('$(DefaultGuardianVersion)', '$(GuardianVersion)') - $content | Set-Content $(GuardianPackagesConfigFile) - - Write-Host "packages.config content updated to:`n$content" - displayName: Use overridden Guardian version ${{ parameters.overrideGuardianVersion }} +- task: NuGetAuthenticate@1 + inputs: + nuGetServiceConnections: GuardianConnect - task: NuGetToolInstaller@1 displayName: 'Install NuGet.exe' -- task: NuGetCommand@2 - displayName: 'Install Guardian' - inputs: - restoreSolution: $(Build.SourcesDirectory)\eng\common\sdl\packages.config - feedsToUse: config - nugetConfigPath: $(Build.SourcesDirectory)\eng\common\sdl\NuGet.config - externalFeedCredentials: GuardianConnect - restoreDirectory: $(Build.SourcesDirectory)\.packages +- ${{ if ne(parameters.overrideGuardianVersion, '') }}: + - pwsh: | + . $(Build.SourcesDirectory)\eng\common\sdl\sdl.ps1 + $guardianCliLocation = Install-Gdn -Path $(Build.SourcesDirectory)\.artifacts -Version ${{ parameters.overrideGuardianVersion }} + Write-Host "##vso[task.setvariable variable=GuardianCliLocation]$guardianCliLocation" + displayName: Install Guardian (Overridden) + +- ${{ if eq(parameters.overrideGuardianVersion, '') }}: + - pwsh: | + . $(Build.SourcesDirectory)\eng\common\sdl\sdl.ps1 + $guardianCliLocation = Install-Gdn -Path $(Build.SourcesDirectory)\.artifacts + Write-Host "##vso[task.setvariable variable=GuardianCliLocation]$guardianCliLocation" + displayName: Install Guardian - ${{ if ne(parameters.overrideParameters, '') }}: - powershell: ${{ parameters.executeAllSdlToolsScript }} ${{ parameters.overrideParameters }} @@ -40,7 +37,7 @@ steps: - ${{ if eq(parameters.overrideParameters, '') }}: - powershell: ${{ parameters.executeAllSdlToolsScript }} - -GuardianPackageName Microsoft.Guardian.Cli.$(GuardianVersion) + -GuardianCliLocation $(GuardianCliLocation) -NugetPackageDirectory $(Build.SourcesDirectory)\.packages -AzureDevOpsAccessToken $(dn-bot-dotnet-build-rw-code-rw) ${{ parameters.additionalParameters }} diff --git a/eng/common/templates/steps/source-build.yml b/eng/common/templates/steps/source-build.yml index 12a8ff94d8e960..4624885e3bfebe 100644 --- a/eng/common/templates/steps/source-build.yml +++ b/eng/common/templates/steps/source-build.yml @@ -63,6 +63,11 @@ steps: targetRidArgs='/p:TargetRid=${{ parameters.platform.targetRID }}' fi + runtimeOsArgs= + if [ '${{ parameters.platform.runtimeOS }}' != '' ]; then + runtimeOsArgs='/p:RuntimeOS=${{ parameters.platform.runtimeOS }}' + fi + publishArgs= if [ '${{ parameters.platform.skipPublishValidation }}' != 'true' ]; then publishArgs='--publish' @@ -75,6 +80,7 @@ steps: $internalRuntimeDownloadArgs \ $internalRestoreArgs \ $targetRidArgs \ + $runtimeOsArgs \ /p:SourceBuildNonPortable=${{ parameters.platform.nonPortable }} \ /p:ArcadeBuildFromSource=true displayName: Build diff --git a/eng/common/templates/variables/pool-providers.yml b/eng/common/templates/variables/pool-providers.yml new file mode 100644 index 00000000000000..99c80212bac198 --- /dev/null +++ b/eng/common/templates/variables/pool-providers.yml @@ -0,0 +1,57 @@ +# Select a pool provider based off branch name. Anything with branch name containing 'release' must go into an -Svc pool, +# otherwise it should go into the "normal" pools. This separates out the queueing and billing of released branches. + +# Motivation: +# Once a given branch of a repository's output has been officially "shipped" once, it is then considered to be COGS +# (Cost of goods sold) and should be moved to a servicing pool provider. This allows both separation of queueing +# (allowing release builds and main PR builds to not intefere with each other) and billing (required for COGS) +# Additionally, the pool provider name itself may be subject to change when the .NET Core Engineering Services +# team needs to move resources around and create new and potentially differently-named pools. Using this template +# file from an Arcade-ified repo helps guard against both having to update one's release/* branches and renaming. + +# How to use: +# This yaml assumes your shipped product branches use the naming convention "release/..." (which many do). +# If we find alternate naming conventions in broad usage these can be added to the condition below. +# +# First, import the template in an arcade-ified repo to pick up the variables, e.g.: +# +# variables: +# - template: /eng/common/templates/variables/pool-providers.yml +# +# ... then anywhere specifying the pool provider use the runtime variables, +# $(DncEngInternalBuildPool) and $ (DncEngPublicBuildPool), e.g.: +# +# pool: +# name: $(DncEngInternalBuildPool) +# demands: ImageOverride -equals windows.vs2019.amd64 + +variables: + # Coalesce the target and source branches so we know when a PR targets a release branch + # If these variables are somehow missing, fall back to main (tends to have more capacity) + + # Any new -Svc alternative pools should have variables added here to allow for splitting work + - name: DncEngPublicBuildPool + value: $[ + replace( + replace( + eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), + True, + 'NetCore-Svc-Public' + ), + False, + 'NetCore-Public' + ) + ] + + - name: DncEngInternalBuildPool + value: $[ + replace( + replace( + eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), + True, + 'NetCore1ESPool-Svc-Internal' + ), + False, + 'NetCore1ESPool-Internal' + ) + ] \ No newline at end of file diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index f83a748c37e9cf..021555cf3381ad 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -365,8 +365,8 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = # If the version of msbuild is going to be xcopied, # use this version. Version matches a package here: - # https://dev.azure.com/dnceng/public/_packaging?_a=package&feed=dotnet-eng&package=RoslynTools.MSBuild&protocolType=NuGet&version=17.1.0&view=overview - $defaultXCopyMSBuildVersion = '17.1.0' + # https://dev.azure.com/dnceng/public/_packaging?_a=package&feed=dotnet-eng&package=RoslynTools.MSBuild&protocolType=NuGet&version=17.4.1&view=overview + $defaultXCopyMSBuildVersion = '17.4.1' if (!$vsRequirements) { if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { diff --git a/eng/common/tools.sh b/eng/common/tools.sh index c110d0ed410c52..e9a7ed9af6794a 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -511,7 +511,7 @@ global_json_file="${repo_root}global.json" # determine if global.json contains a "runtimes" entry global_json_has_runtimes=false if command -v jq &> /dev/null; then - if jq -er '. | select(has("runtimes"))' "$global_json_file" &> /dev/null; then + if jq -e '.tools | has("runtimes")' "$global_json_file" &> /dev/null; then global_json_has_runtimes=true fi elif [[ "$(cat "$global_json_file")" =~ \"runtimes\"[[:space:]\:]*\{ ]]; then diff --git a/eng/install-native-dependencies.sh b/eng/install-native-dependencies.sh index bc9b2036d0563f..b7d7c33a010b80 100755 --- a/eng/install-native-dependencies.sh +++ b/eng/install-native-dependencies.sh @@ -1,49 +1,51 @@ -#!/usr/bin/env bash +#!/bin/sh + +set -e # This is a simple script primarily used for CI to install necessary dependencies # -# For CI typical usage is -# -# ./install-native-dependencies.sh azDO -# -# For developer use it is not recommended to include the azDO final argument as that -# makes installation and configuration setting only required for azDO -# -# So simple developer usage would currently be +# Usage: # # ./install-native-dependencies.sh -if [ "$1" = "Linux" ]; then - sudo apt update - if [ "$?" != "0" ]; then - exit 1; - fi - sudo apt install cmake llvm-3.9 clang-3.9 lldb-3.9 liblldb-3.9-dev libunwind8 libunwind8-dev gettext libicu-dev liblttng-ust-dev libcurl4-openssl-dev libssl-dev libkrb5-dev libnuma-dev build-essential - if [ "$?" != "0" ]; then - exit 1; - fi -elif [[ "$1" == "MacCatalyst" || "$1" == "OSX" || "$1" == "tvOS" || "$1" == "iOS" ]]; then - engdir=$(dirname "${BASH_SOURCE[0]}") - - echo "Installed xcode version: `xcode-select -p`" - - if [ "$3" = "azDO" ]; then - # workaround for old osx images on hosted agents - # piped in case we get an agent without these values installed - if ! brew_output="$(brew uninstall openssl@1.0.2t 2>&1 >/dev/null)"; then - echo "didn't uninstall openssl@1.0.2t" - else - echo "successfully uninstalled openssl@1.0.2t" - fi - fi - - brew update --preinstall - brew bundle --no-upgrade --no-lock --file "${engdir}/Brewfile" - if [ "$?" != "0" ]; then - exit 1; - fi -else - echo "Must pass \"Linux\", \"tvOS\", \"iOS\" or \"OSX\" as first argument." - exit 1 +os="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + +if [ -z "$os" ]; then + . "$(dirname "$0")"/native/init-os-and-arch.sh + os="$(echo "$os" | tr "[:upper:]" "[:lower:]")" fi +case "$os" in + linux) + if [ -e /etc/os-release ]; then + . /etc/os-release + fi + + if [ "$ID" != "debian" ] && [ "$ID_LIKE" != "debian" ]; then + echo "Unsupported distro. distro: $ID" + exit 1 + fi + + apt update + + apt install -y build-essential gettext locales cmake llvm clang lldb liblldb-dev libunwind8-dev libicu-dev liblttng-ust-dev \ + libssl-dev libkrb5-dev libnuma-dev zlib1g-dev + + localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 + ;; + + osx|mac*|ios*|tvos*) + echo "Installed xcode version: $(xcode-select -p)" + + export HOMEBREW_NO_INSTALL_CLEANUP=1 + export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 + # Skip brew update for now, see https://github.com/actions/setup-python/issues/577 + # brew update --preinstall + brew bundle --no-upgrade --no-lock --file "$(dirname "$0")/Brewfile" + ;; + + *) + echo "Unsupported platform. OS: $os" + exit 1 + ;; +esac diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index 5802882a82a0b0..abda5471d74c63 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -179,7 +179,6 @@ + + - $(RuntimeIdGraphDefinitionFile) + + $([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'Microsoft.NETCore.Platforms', 'runtime.json')) + $([MSBuild]::NormalizePath('$(LibrariesProjectRoot)', 'Microsoft.NETCore.Platforms', 'src', 'runtime.json')) diff --git a/eng/native/build-commons.sh b/eng/native/build-commons.sh index 4199828f2970be..bc0083eed71852 100755 --- a/eng/native/build-commons.sh +++ b/eng/native/build-commons.sh @@ -212,6 +212,7 @@ usage() echo "-gccx.y: optional argument to build using gcc version x.y." echo "-ninja: target ninja instead of GNU make" echo "-numproc: set the number of build processes." + echo "-outputrid: optional argument that overrides the target rid name." echo "-portablebuild: pass -portablebuild=false to force a non-portable build." echo "-skipconfigure: skip build configuration." echo "-keepnativesymbols: keep native/unmanaged debug symbols." @@ -232,6 +233,7 @@ __TargetArch=$arch __TargetOS=$os __HostOS=$os __BuildOS=$os +__OutputRid='' # Get the number of processors available to the scheduler # Other techniques such as `nproc` only get the number of @@ -396,6 +398,16 @@ while :; do __TargetArch=wasm ;; + outputrid|-outputrid) + if [[ -n "$2" ]]; then + __OutputRid="$2" + shift + else + echo "ERROR: 'outputrid' requires a non-empty option argument" + exit 1 + fi + ;; + ppc64le|-ppc64le) __TargetArch=ppc64le ;; @@ -478,3 +490,7 @@ fi # init the target distro name initTargetDistroRid + +if [ -z "$__OutputRid" ]; then + __OutputRid="$(echo $__DistroRid | tr '[:upper:]' '[:lower:]')" +fi diff --git a/eng/native/configurecompiler.cmake b/eng/native/configurecompiler.cmake index 0642fccae90080..0c9138ba029050 100644 --- a/eng/native/configurecompiler.cmake +++ b/eng/native/configurecompiler.cmake @@ -69,12 +69,14 @@ if (MSVC) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /PDBCOMPRESS") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUGTYPE:CV,FIXUP") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /IGNORE:4197,4013,4254,4070,4221") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /SUBSYSTEM:WINDOWS,${WINDOWS_SUBSYSTEM_VERSION}") set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /IGNORE:4221") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUGTYPE:CV,FIXUP") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /PDBCOMPRESS") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:1572864") @@ -447,6 +449,15 @@ if (CLR_CMAKE_HOST_UNIX) add_compile_options(-Wno-incompatible-ms-struct) add_compile_options(-Wno-reserved-identifier) + + # clang 16.0 introduced buffer hardening https://discourse.llvm.org/t/rfc-c-buffer-hardening/65734 + # which we are not conforming to yet. + add_compile_options(-Wno-unsafe-buffer-usage) + + # other clang 16.0 suppressions + add_compile_options(-Wno-single-bit-bitfield-constant-conversion) + add_compile_options(-Wno-cast-function-type-strict) + add_compile_options(-Wno-incompatible-function-pointer-types-strict) else() add_compile_options(-Wno-uninitialized) add_compile_options(-Wno-strict-aliasing) @@ -788,6 +799,13 @@ if (CLR_CMAKE_HOST_WIN32) endif() elseif (NOT CLR_CMAKE_HOST_BROWSER) + # This is a workaround for upstream issue: https://gitlab.kitware.com/cmake/cmake/-/issues/22995. + # + # In Clang.cmake, the decision to use single or double hyphen for target and gcc-toolchain + # is made based on CMAKE_${LANG}_COMPILER_VERSION, but CMAKE_ASM_COMPILER_VERSION is empty + # so it picks up single hyphen options, which new clang versions don't recognize. + set (CMAKE_ASM_COMPILER_VERSION "${CMAKE_C_COMPILER_VERSION}") + enable_language(ASM) endif(CLR_CMAKE_HOST_WIN32) diff --git a/eng/native/configureplatform.cmake b/eng/native/configureplatform.cmake index c7a38c3eee8294..9f8ac48694b3c8 100644 --- a/eng/native/configureplatform.cmake +++ b/eng/native/configureplatform.cmake @@ -2,7 +2,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/functions.cmake) # If set, indicates that this is not an officially supported release. # Release branches should set this to false. -set(PRERELEASE 1) +set(PRERELEASE 0) #---------------------------------------- # Detect and set platform variable names diff --git a/eng/native/ijw/IJW.cmake b/eng/native/ijw/IJW.cmake index f606b5e98889fe..81b69f0d965d2c 100644 --- a/eng/native/ijw/IJW.cmake +++ b/eng/native/ijw/IJW.cmake @@ -31,8 +31,21 @@ if (CLR_CMAKE_HOST_WIN32) set_target_properties(${targetName} PROPERTIES COMPILE_OPTIONS "${compileOptions}") endfunction() + function(add_ijw_msbuild_project_properties targetName ijwhost_target) + # When we're building with MSBuild, we need to set some project properties + # in case CMake has decided to use the SDK support. + # We're dogfooding things, so we need to set settings in ways that the product doesn't quite support. + # We don't actually need an installed/available target framework version here + # since we are disabling implicit framework references. We just need a valid value, and net7.0 is valid. + set_target_properties(${targetName} PROPERTIES + DOTNET_TARGET_FRAMEWORK net7.0 + VS_GLOBAL_DisableImplicitFrameworkReferences true + VS_GLOBAL_GenerateRuntimeConfigurationFiles false + VS_PROJECT_IMPORT "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SetIJWProperties.props") + endfunction() + # 4365 - signed/unsigned mismatch - # 4679 - Could not import member. This is an issue with IJW and static abstract methods in interfaces. + # 4679 - Could not import member. This is an issue with IJW and static abstract methods in interfaces. add_compile_options(/wd4365 /wd4679) # IJW diff --git a/eng/native/ijw/SetIJWProperties.props b/eng/native/ijw/SetIJWProperties.props new file mode 100644 index 00000000000000..d18ba7ff054fc2 --- /dev/null +++ b/eng/native/ijw/SetIJWProperties.props @@ -0,0 +1,6 @@ + + + + false + + \ No newline at end of file diff --git a/eng/nativepgo.targets b/eng/nativepgo.targets index e6d58a33994fff..5dc312774e3078 100644 --- a/eng/nativepgo.targets +++ b/eng/nativepgo.targets @@ -1,7 +1,7 @@ - true - true + true + true false false @@ -16,14 +16,20 @@ + + - + diff --git a/eng/packaging.targets b/eng/packaging.targets index f5a82c3d36e93e..9911316896c150 100644 --- a/eng/packaging.targets +++ b/eng/packaging.targets @@ -19,13 +19,15 @@ '$(IsRIDSpecificProject)' == 'true') and '$(PreReleaseVersionLabel)' != 'servicing' and '$(GitHubRepositoryName)' != 'runtimelab'">true - false - true - $(XmlDocFileRoot)1033\$(AssemblyName).xml + $([MSBuild]::NormalizePath('$(XmlDocFileRoot)', 'net', '1033', '$(AssemblyName).xml')) + $(IntellisenseNetFile) + $([MSBuild]::NormalizePath('$(XmlDocFileRoot)', 'dotnet-plat-ext', '1033', '$(AssemblyName).xml')) + $(IntellisenseDotNetPlatExtFile) true @@ -41,7 +43,7 @@ <_IsWindowsDesktopApp Condition="$(WindowsDesktopCoreAppLibrary.Contains('$(AssemblyName);'))">true <_IsAspNetCoreApp Condition="$(AspNetCoreAppLibrary.Contains('$(AssemblyName);'))">true <_AssemblyInTargetingPack Condition="('$(IsNETCoreAppSrc)' == 'true' or '$(IsNetCoreAppRef)' == 'true' or '$(_IsAspNetCoreApp)' == 'true' or '$(_IsWindowsDesktopApp)' == 'true') and '$(TargetFrameworkIdentifier)' != '.NETFramework'">true - + $(MajorVersion).$(MinorVersion).0.$(ServicingVersion) diff --git a/eng/pipelines/common/evaluate-default-paths.yml b/eng/pipelines/common/evaluate-default-paths.yml index 7198ecd5540c1e..358027fe7152b6 100644 --- a/eng/pipelines/common/evaluate-default-paths.yml +++ b/eng/pipelines/common/evaluate-default-paths.yml @@ -113,7 +113,8 @@ jobs: - src/mono/wasm/host/* - src/mono/wasm/runtime/* - src/mono/wasm/templates/* - - src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/* + - src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/* + - src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/* - src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/* - src/mono/nuget/Microsoft.NET.Runtime.wasm.Sample.Mono/* - src/mono/nuget/Microsoft.NETCore.BrowserDebugHost.Transport/* diff --git a/eng/pipelines/common/global-build-job.yml b/eng/pipelines/common/global-build-job.yml index e1a8e45ba97bc7..7f40a13b1529d7 100644 --- a/eng/pipelines/common/global-build-job.yml +++ b/eng/pipelines/common/global-build-job.yml @@ -71,6 +71,7 @@ jobs: variables: - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - group: DotNet-HelixApi-Access + - group: AzureDevOps-Artifact-Feeds-Pats - name: _osParameter value: -os ${{ parameters.osGroup }} @@ -128,6 +129,26 @@ jobs: - ${{ if eq(parameters.isOfficialBuild, true) }}: - template: /eng/pipelines/common/restore-internal-tools.yml + # Do not set up nuget sources for source build because the source build scripts already do this. + # This will cause the working tree to be dirty, which breaks the stash command when shallow clones are used, like in CI or official builds. + - ${{ if and(ne(variables['System.TeamProject'], 'public'), ne(parameters.buildingOnSourceBuildImage, true)) }}: + - ${{ if ne(parameters.osGroup, 'windows') }}: + - task: Bash@3 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + arguments: $(Build.SourcesDirectory)/NuGet.config $Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - ${{ if eq(parameters.osGroup, 'windows') }}: + - task: PowerShell@2 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 + arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - ${{ each monoCrossAOTTargetOS in parameters.monoCrossAOTTargetOS }}: - task: DownloadPipelineArtifact@2 displayName: Download ${{monoCrossAOTTargetOS}} AOT offset files @@ -141,8 +162,11 @@ jobs: platform: buildScript: $(_sclEnableCommand) $(Build.SourcesDirectory)$(dir)build$(scriptExt) nonPortable: true + # Use a custom RID that isn't in the RID graph here to validate we don't break the usage of custom rids that aren't in the graph. + targetRID: banana.24-x64 + runtimeOS: linux - - ${{ if in(parameters.osGroup, 'OSX', 'iOS', 'tvOS', 'MacCatalyst') }}: + - ${{ if in(parameters.osGroup, 'OSX', 'MacCatalyst', 'iOS', 'iOSSimulator', 'tvOS', 'tvOSSimulator') }}: - script: $(Build.SourcesDirectory)/eng/install-native-dependencies.sh ${{ parameters.osGroup }} ${{ parameters.archType }} azDO displayName: Install Build Dependencies diff --git a/eng/pipelines/common/platform-matrix.yml b/eng/pipelines/common/platform-matrix.yml index 4ae4b32db8a9fc..2ec41d7682c7e2 100644 --- a/eng/pipelines/common/platform-matrix.yml +++ b/eng/pipelines/common/platform-matrix.yml @@ -95,7 +95,7 @@ jobs: shouldContinueOnError: ${{ parameters.shouldContinueOnError }} container: ${{ if eq(parameters.container, '') }}: - image: ubuntu-18.04-cross-arm64-20220427171722-6e40d49 + image: ubuntu-20.04-cross-arm64-20230201170357-8b7d579 ${{ if ne(parameters.container, '') }}: image: ${{ parameters.container }} registry: mcr diff --git a/eng/pipelines/common/templates/runtimes/build-test-job.yml b/eng/pipelines/common/templates/runtimes/build-test-job.yml index 764446c77e86b9..3abd56c45c9e1a 100644 --- a/eng/pipelines/common/templates/runtimes/build-test-job.yml +++ b/eng/pipelines/common/templates/runtimes/build-test-job.yml @@ -110,7 +110,7 @@ jobs: steps: # Install test build dependencies - - ${{ if eq(parameters.osGroup, 'OSX') }}: + - ${{ if in(parameters.osGroup, 'OSX', 'MacCatalyst', 'iOS', 'iOSSimulator', 'tvOS', 'tvOSSimulator') }}: - script: $(Build.SourcesDirectory)/eng/install-native-dependencies.sh $(osGroup) ${{ parameters.archType }} azDO displayName: Install native dependencies diff --git a/eng/pipelines/common/xplat-setup.yml b/eng/pipelines/common/xplat-setup.yml index 83a0efd39874f0..ef9a6bb735cd5f 100644 --- a/eng/pipelines/common/xplat-setup.yml +++ b/eng/pipelines/common/xplat-setup.yml @@ -135,12 +135,12 @@ jobs: pool: # Public Linux Build Pool ${{ if and(or(in(parameters.osGroup, 'Linux', 'FreeBSD', 'Android', 'Tizen'), eq(parameters.jobParameters.hostedOs, 'Linux')), eq(variables['System.TeamProject'], 'public')) }}: - name: NetCore1ESPool-Public + name: NetCore-Svc-Public demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open # Official Build Linux Pool ${{ if and(or(in(parameters.osGroup, 'Linux', 'FreeBSD', 'Browser', 'Android', 'Tizen'), eq(parameters.jobParameters.hostedOs, 'Linux')), ne(variables['System.TeamProject'], 'public')) }}: - name: NetCore1ESPool-Internal + name: NetCore1ESPool-Svc-Internal demands: ImageOverride -equals Build.Ubuntu.1804.Amd64 # OSX Build Pool (we don't have on-prem OSX BuildPool @@ -149,12 +149,12 @@ jobs: # Official Build Windows Pool ${{ if and(eq(parameters.osGroup, 'windows'), ne(variables['System.TeamProject'], 'public')) }}: - name: NetCore1ESPool-Internal + name: NetCore1ESPool-Svc-Internal demands: ImageOverride -equals windows.vs2022.amd64 # Public Windows Build Pool ${{ if and(or(eq(parameters.osGroup, 'windows'), eq(parameters.jobParameters.hostedOs, 'windows')), eq(variables['System.TeamProject'], 'public')) }}: - name: NetCore1ESPool-Public + name: NetCore-Svc-Public demands: ImageOverride -equals windows.vs2022.amd64.open diff --git a/eng/pipelines/coreclr/nativeaot-post-build-steps.yml b/eng/pipelines/coreclr/nativeaot-post-build-steps.yml index a44c7223039580..a920993b093cbc 100644 --- a/eng/pipelines/coreclr/nativeaot-post-build-steps.yml +++ b/eng/pipelines/coreclr/nativeaot-post-build-steps.yml @@ -29,3 +29,9 @@ steps: - ${{ if ne(parameters.osGroup, 'windows') }}: - script: $(Build.SourcesDirectory)/src/tests/run.sh --runnativeaottests $(buildConfigUpper) ${{ parameters.archType }} displayName: Run tests in single file mode + + # Publishing tooling doesn't support different configs between runtime and libs, so only run tests in Release config + # PublishAot on OSX doesn't work yet. Need an SDK with https://github.com/dotnet/installer/pull/14443. + - ${{ if and(eq(parameters.buildConfig, 'release'), ne(parameters.osGroup, 'OSX')) }}: + - script: $(Build.SourcesDirectory)$(dir)build$(scriptExt) -ci -arch ${{ parameters.archType }} $(_osParameter) -s libs.tests -c $(_BuildConfig) /p:TestAssemblies=false /p:RunNativeAotTestApps=true $(_officialBuildParameter) $(_crossBuildPropertyArg) /bl:$(Build.SourcesDirectory)/artifacts/log/$(buildConfigUpper)/NativeAotTests.binlog ${{ parameters.extraTestArgs }} + displayName: Run NativeAot Library Tests diff --git a/eng/pipelines/coreclr/perf-non-wasm-jobs.yml b/eng/pipelines/coreclr/perf-non-wasm-jobs.yml index 9cc0e60fd01222..39a8b0527b1e78 100644 --- a/eng/pipelines/coreclr/perf-non-wasm-jobs.yml +++ b/eng/pipelines/coreclr/perf-non-wasm-jobs.yml @@ -111,7 +111,7 @@ jobs: platforms: - Linux_x64 - # run mono and maui android scenarios + # run android scenarios - template: /eng/pipelines/common/platform-matrix.yml parameters: jobTemplate: /eng/pipelines/coreclr/templates/perf-job.yml @@ -119,9 +119,6 @@ jobs: runtimeFlavor: mono platforms: - Windows_x64 - variables: - - name: mauiVersion - value: $[ dependencies.Build_iOS_arm64_release_MACiOSAndroidMauiNet7.outputs['getMauiVersion.mauiVersion'] ] jobParameters: testGroup: perf runtimeType: AndroidMono @@ -129,9 +126,8 @@ jobs: runKind: android_scenarios runJobTemplate: /eng/pipelines/coreclr/templates/run-scenarios-job.yml logicalmachine: 'perfpixel4a' - additionalSetupParameters: "-MauiVersion $env:mauiVersion" - # run mono iOS scenarios and maui iOS scenarios + # run mono iOS scenarios scenarios - template: /eng/pipelines/common/platform-matrix.yml parameters: jobTemplate: /eng/pipelines/coreclr/templates/perf-job.yml @@ -139,9 +135,6 @@ jobs: runtimeFlavor: mono platforms: - OSX_x64 - variables: - - name: mauiVersion - value: $[ dependencies.Build_iOS_arm64_release_MACiOSAndroidMauiNet7.outputs['getMauiVersion.mauiVersion'] ] jobParameters: testGroup: perf runtimeType: iOSMono @@ -150,7 +143,6 @@ jobs: runJobTemplate: /eng/pipelines/coreclr/templates/run-scenarios-job.yml logicalmachine: 'perfiphone12mini' iOSLlvmBuild: False - additionalSetupParameters: "--mauiversion $(mauiVersion)" - template: /eng/pipelines/common/platform-matrix.yml parameters: @@ -159,9 +151,6 @@ jobs: runtimeFlavor: mono platforms: - OSX_x64 - variables: - - name: mauiVersion - value: $[ dependencies.Build_iOS_arm64_release_MACiOSAndroidMauiNet7.outputs['getMauiVersion.mauiVersion'] ] jobParameters: testGroup: perf runtimeType: iOSMono @@ -170,48 +159,6 @@ jobs: runJobTemplate: /eng/pipelines/coreclr/templates/run-scenarios-job.yml logicalmachine: 'perfiphone12mini' iOSLlvmBuild: True - additionalSetupParameters: "--mauiversion $(mauiVersion)" - - # run maui android scenarios for net6 - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/perf-job.yml - buildConfig: release - runtimeFlavor: mono - platforms: - - Windows_x64 - variables: - - name: mauiVersion - value: $[ dependencies.Build_iOS_arm64_release_MACiOSAndroidMauiNet6.outputs['getMauiVersion.mauiVersion'] ] - jobParameters: - testGroup: perf - runtimeType: AndroidMobileNet6 - projectFile: android_scenarios_net6.proj - runKind: android_scenarios_net6 - runJobTemplate: /eng/pipelines/coreclr/templates/run-scenarios-job.yml - logicalmachine: 'perfpixel4a' - additionalSetupParameters: "-MauiVersion $env:mauiVersion" - - # run maui iOS scenarios for net6 (Maui doesn't need Llmv true build (for net6)) - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/perf-job.yml - buildConfig: release - runtimeFlavor: mono - platforms: - - OSX_x64 - variables: - - name: mauiVersion - value: $[ dependencies.Build_iOS_arm64_release_MACiOSAndroidMauiNet6.outputs['getMauiVersion.mauiVersion'] ] - jobParameters: - testGroup: perf - runtimeType: iOSMobileNet6 - projectFile: ios_scenarios_net6.proj - runKind: ios_scenarios_net6 - runJobTemplate: /eng/pipelines/coreclr/templates/run-scenarios-job.yml - logicalmachine: 'perfiphone12mini' - iOSLlvmBuild: False - additionalSetupParameters: "--mauiversion $(mauiVersion)" # run mono microbenchmarks perf job - template: /eng/pipelines/common/platform-matrix.yml @@ -368,85 +315,3 @@ jobs: runKind: crossgen_scenarios runJobTemplate: /eng/pipelines/coreclr/templates/run-scenarios-job.yml logicalmachine: 'perftiger' - - # Uncomment to reenable package replacement - ## build maui runtime packs - #- template: /eng/pipelines/common/platform-matrix.yml - # parameters: - # jobTemplate: /eng/pipelines/common/global-build-job.yml - # buildConfig: release - # runtimeFlavor: mono - # platforms: - # - Android_x86 - # - Android_x64 - # - Android_arm - # - Android_arm64 - # - MacCatalyst_x64 - # - iOSSimulator_x64 - # - iOS_arm64 - # - iOS_arm - # jobParameters: - # buildArgs: -s mono+libs+host+packs -c $(_BuildConfig) - # nameSuffix: Maui_Packs_Mono - # isOfficialBuild: false - # extraStepsTemplate: /eng/pipelines/common/upload-intermediate-artifacts-step.yml - # extraStepsParameters: - # name: MonoRuntimePacks - - # build maui app net7.0 - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - buildConfig: release - runtimeFlavor: mono - platforms: - - iOS_arm64 - jobParameters: - # Uncomment to reenable package replacement for main - #dependsOn: - # - Build_Android_arm_release_Maui_Packs_Mono - # - Build_Android_arm64_release_Maui_Packs_Mono - # - Build_Android_x86_release_Maui_Packs_Mono - # - Build_Android_x64_release_Maui_Packs_Mono - # - Build_MacCatalyst_x64_release_Maui_Packs_Mono - # - Build_iOSSimulator_x64_release_Maui_Packs_Mono - # - Build_iOS_arm_release_Maui_Packs_Mono - # - Build_iOS_arm64_release_Maui_Packs_Mono - buildArgs: -s mono -c $(_BuildConfig) - nameSuffix: MACiOSAndroidMauiNet7 - isOfficialBuild: false - pool: - vmImage: 'macos-11' - extraStepsTemplate: /eng/pipelines/coreclr/templates/build-perf-maui-apps-net7.yml - extraStepsParameters: - rootFolder: '$(Build.SourcesDirectory)/artifacts/' - includeRootFolder: true - displayName: MAC, iOS, and Android Maui Artifacts Net7 - artifactName: MACiOSAndroidMauiArmNet7 - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - # build maui app net6.0 - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - buildConfig: release - runtimeFlavor: mono - platforms: - - iOS_arm64 - jobParameters: - buildArgs: -s mono -c $(_BuildConfig) - nameSuffix: MACiOSAndroidMauiNet6 - isOfficialBuild: false - pool: - vmImage: 'macos-11' - extraStepsTemplate: /eng/pipelines/coreclr/templates/build-perf-maui-apps-net6.yml - extraStepsParameters: - rootFolder: '$(Build.SourcesDirectory)/artifacts/' - includeRootFolder: true - displayName: MAC, iOS, and Android Maui Artifacts Net6 - artifactName: MACiOSAndroidMauiArmNet6 - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz diff --git a/eng/pipelines/coreclr/perf-wasm-prepare-artifacts-steps.yml b/eng/pipelines/coreclr/perf-wasm-prepare-artifacts-steps.yml index 54c4baf70a366e..d9bb50d068a640 100644 --- a/eng/pipelines/coreclr/perf-wasm-prepare-artifacts-steps.yml +++ b/eng/pipelines/coreclr/perf-wasm-prepare-artifacts-steps.yml @@ -12,7 +12,7 @@ steps: - script: >- mkdir -p $(Build.SourcesDirectory)/artifacts/staging && - cp -r $(Build.SourcesDirectory)/artifacts/bin/dotnet-workload $(Build.SourcesDirectory)/artifacts/staging && + cp -r $(Build.SourcesDirectory)/artifacts/bin/dotnet-net7 $(Build.SourcesDirectory)/artifacts/staging && cp -r $(Build.SourcesDirectory)/artifacts/bin/microsoft.netcore.app.runtime.browser-wasm $(Build.SourcesDirectory)/artifacts/staging && cp -r $(Build.SourcesDirectory)/artifacts/bin/microsoft.netcore.app.ref $(Build.SourcesDirectory)/artifacts/staging displayName: "Prepare artifacts staging directory" diff --git a/eng/pipelines/coreclr/templates/build-jit-job.yml b/eng/pipelines/coreclr/templates/build-jit-job.yml index bdd1a29ac8b997..96579d93c3aaf6 100644 --- a/eng/pipelines/coreclr/templates/build-jit-job.yml +++ b/eng/pipelines/coreclr/templates/build-jit-job.yml @@ -86,7 +86,7 @@ jobs: # Linux builds use docker images with dependencies preinstalled, # and FreeBSD builds use a build agent with dependencies # preinstalled, so we only need this step for OSX and Windows. - - ${{ if eq(parameters.osGroup, 'OSX') }}: + - ${{ if in(parameters.osGroup, 'OSX', 'MacCatalyst', 'iOS', 'iOSSimulator', 'tvOS', 'tvOSSimulator') }}: - script: $(Build.SourcesDirectory)/eng/install-native-dependencies.sh $(osGroup) ${{ parameters.archType }} azDO displayName: Install native dependencies (OSX) diff --git a/eng/pipelines/coreclr/templates/build-job.yml b/eng/pipelines/coreclr/templates/build-job.yml index a226e0fd35e759..07482ffc3d5f7c 100644 --- a/eng/pipelines/coreclr/templates/build-job.yml +++ b/eng/pipelines/coreclr/templates/build-job.yml @@ -110,6 +110,8 @@ jobs: # Variables used by arcade to gather asset manifests - name: _DotNetPublishToBlobFeed value: true + - ${{ if eq(variables['System.TeamProject'], 'internal') }}: + - group: AzureDevOps-Artifact-Feeds-Pats - name: officialBuildIdArg value: '' - ${{ if eq(parameters.isOfficialBuild, true) }}: @@ -160,7 +162,7 @@ jobs: # Linux builds use docker images with dependencies preinstalled, # and FreeBSD builds use a build agent with dependencies # preinstalled, so we only need this step for OSX and Windows. - - ${{ if eq(parameters.osGroup, 'OSX') }}: + - ${{ if in(parameters.osGroup, 'OSX', 'MacCatalyst', 'iOS', 'iOSSimulator', 'tvOS', 'tvOSSimulator') }}: - script: $(Build.SourcesDirectory)/eng/install-native-dependencies.sh $(osGroup) ${{ parameters.archType }} azDO displayName: Install native dependencies @@ -182,6 +184,24 @@ jobs: continueOnError: false condition: and(succeeded(), in(variables['SignType'], 'real', 'test')) + - ${{ if ne(variables['System.TeamProject'], 'public') }}: + - ${{ if ne(parameters.osGroup, 'windows') }}: + - task: Bash@3 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + arguments: $(Build.SourcesDirectory)/NuGet.config $Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - ${{ if eq(parameters.osGroup, 'windows') }}: + - task: PowerShell@2 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 + arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - ${{ if in(parameters.osGroup, 'OSX', 'iOS', 'tvOS') }}: - script: | du -sh $(Build.SourcesDirectory)/* @@ -261,25 +281,12 @@ jobs: targetFolder: $(buildProductRootFolderPath)/sharedFramework overWrite: true - # Sign diagnostic files on Windows - ${{ if and(eq(parameters.osGroup, 'windows'), eq(parameters.signBinaries, true)) }}: - - powershell: >- - eng\common\build.ps1 -ci -sign -restore -configuration:$(buildConfig) -warnaserror:0 $(officialBuildIdArg) - /p:DiagnosticsFilesRoot="$(buildProductRootFolderPath)" - /p:SignDiagnostics=true - /p:DotNetSignType=$(SignType) - -noBl - /bl:$(Build.SourcesDirectory)/artifacts/log/$(buildConfig)/SignDiagnostics.binlog - -projects $(Build.SourcesDirectory)\eng\empty.csproj - displayName: Sign Diagnostic Binaries - - - task: PublishPipelineArtifact@1 - displayName: Publish Signing Logs - inputs: - targetPath: '$(Build.SourcesDirectory)/artifacts/log/' - artifactName: ${{ format('SignLogs_{0}{1}_{2}_{3}', parameters.osGroup, parameters.osSubgroup, parameters.archType, parameters.buildConfig) }} - continueOnError: true - condition: always() + - template: /eng/pipelines/coreclr/templates/sign-diagnostic-files.yml + parameters: + basePath: $(buildProductRootFolderPath) + isOfficialBuild: ${{ parameters.signBinaries }} + timeoutInMinutes: 30 # Builds using gcc are not tested, and clrTools unitests do not publish the build artifacts - ${{ if and(ne(parameters.compilerName, 'gcc'), ne(parameters.testGroup, 'clrTools'), ne(parameters.disableClrTest, true)) }}: @@ -300,6 +307,7 @@ jobs: archType: ${{ parameters.archType }} osGroup: ${{ parameters.osGroup }} osSubgroup: ${{ parameters.osSubgroup }} + isOfficialBuild: ${{ parameters.signBinaries }} ${{ if eq(parameters.archType, 'arm') }}: hostArchType: x86 ${{ else }}: diff --git a/eng/pipelines/coreclr/templates/build-perf-maui-apps-net6.yml b/eng/pipelines/coreclr/templates/build-perf-maui-apps-net6.yml deleted file mode 100644 index 093b5b64278a25..00000000000000 --- a/eng/pipelines/coreclr/templates/build-perf-maui-apps-net6.yml +++ /dev/null @@ -1,366 +0,0 @@ -parameters: - osGroup: '' - osSubgroup: '' - archType: '' - buildConfig: '' - runtimeFlavor: '' - helixQueues: '' - targetRid: '' - nameSuffix: '' - platform: '' - shouldContinueOnError: '' - rootFolder: '' - includeRootFolder: '' - displayName: '' - artifactName: '' - archiveExtension: '' - archiveType: '' - tarCompression: '' - - -steps: - # There is a global.json in the runtime repo, but it sets the version to 7.xxx preview. This creates a local global.json to allow the running of 6.0 version. - - script: | - echo '{}' > ./global.json - displayName: Create global.json - workingDirectory: $(Build.SourcesDirectory) - - # Get the current maui nuget config so all things can be found and darc based package sources are kept up to date. - - script: | - curl -o NuGet.config 'https://raw.githubusercontent.com/dotnet/maui/main/NuGet.config' - curl -o dotnet-install.sh 'https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh' - chmod -R a+rx . - ./dotnet-install.sh --channel 6.0.4xx --quality daily --install-dir . - ./dotnet --info - ./dotnet workload install maui --from-rollback-file https://aka.ms/dotnet/maui/net6.0.json --configfile NuGet.config - displayName: Install MAUI workload - workingDirectory: $(Build.SourcesDirectory) - - - script: $(Build.SourcesDirectory)/eng/testing/performance/create-provisioning-profile.sh - displayName: Create iOS code signing and provisioning profile - - - script: | - ./dotnet new maui -n MauiTesting - cd MauiTesting - cp $(Build.SourcesDirectory)/src/tests/Common/maui/MauiScenario.props.net6 ./Directory.Build.props - cp $(Build.SourcesDirectory)/src/tests/Common/maui/MauiScenario.targets.net6 ./Directory.Build.targets - cp $(Build.SourcesDirectory)/NuGet.config ./NuGet.config - displayName: Setup MAUI Project - workingDirectory: $(Build.SourcesDirectory) - - - script: | - chmod -R a+r . - # Restore is split out because of https://github.com/dotnet/sdk/issues/21877, can be removed with --no-restore once fixed - ../dotnet restore - ../dotnet publish -bl:MauiAndroid.binlog -f net6.0-android -c Release -r android-arm64 --no-restore --self-contained - mv ./bin/Release/net6.0-android/android-arm64/com.companyname.mauitesting-Signed.apk ./MauiAndroidDefault.apk - displayName: Build MAUI Android - workingDirectory: $(Build.SourcesDirectory)/MauiTesting - - - script: | - set -x - pwd - git clone https://github.com/microsoft/dotnet-podcasts.git -b maui/perf --single-branch - cd dotnet-podcasts - cp $(Build.SourcesDirectory)/src/tests/Common/maui/MauiScenario.props.net6 ./Directory.Build.props - cp $(Build.SourcesDirectory)/src/tests/Common/maui/MauiScenario.targets.net6 ./Directory.Build.targets - displayName: Clone podcast app - workingDirectory: $(Build.SourcesDirectory) - - - script: | - set -x - pwd - chmod -R a+r . - ../../../dotnet restore Microsoft.NetConf2021.Maui.csproj - ../../../dotnet publish Microsoft.NetConf2021.Maui.csproj -bl:MauiPodcastAndroid.binlog -r android-arm64 --self-contained --no-restore -f net6.0-android -c Release - mv ./bin/Release/net6.0-android/android-arm64/com.Microsoft.NetConf2021.Maui-Signed.apk $(Build.SourcesDirectory)/MauiTesting/MauiAndroidPodcast.apk - displayName: Build MAUI Podcast Android - workingDirectory: $(Build.SourcesDirectory)/dotnet-podcasts/src/Mobile - - # This step pulls the product version from the used Microsoft.Maui.dll file properties and saves it for upload with the maui test counter. - # We pull from this file as we did not find another place to reliably get the version information pre or post build. - - powershell: | - $RetrievedMauiVersion = Get-ChildItem .\obj\Release\net6.0-android\android-arm64\linked\Microsoft.Maui.dll | Select-Object -ExpandProperty VersionInfo | Select-Object ProductVersion | Select-Object -ExpandProperty ProductVersion - $RetrievedMauiVersion - Write-Host "##vso[task.setvariable variable=mauiVersion;isOutput=true]$RetrievedMauiVersion" - name: getMauiVersion - displayName: Get and Save MAUI Version - workingDirectory: $(Build.SourcesDirectory)/MauiTesting - - - script: | - chmod -R a+r . - # remove net6.0-maccatalyst to work around https://github.com/dotnet/sdk/issues/21877 - cp MauiTesting.csproj MauiTesting.csproj.bak - sed -i'' -e 's/net6.0-ios;net6.0-maccatalyst/net6.0-ios/g' MauiTesting.csproj - - ../dotnet publish -bl:MauiiOS.binlog -f net6.0-ios --self-contained -r ios-arm64 -c Release /p:_RequireCodeSigning=false /p:ApplicationId=net.dot.mauitesting - mv ./bin/Release/net6.0-ios/ios-arm64/publish/MauiTesting.ipa ./MauiiOSDefault.ipa - - cp MauiTesting.csproj.bak MauiTesting.csproj - displayName: Build MAUI Default iOS - workingDirectory: $(Build.SourcesDirectory)/MauiTesting - - - script: | - chmod -R a+r . - # remove net6.0-maccatalyst to work around https://github.com/dotnet/sdk/issues/21877 - cp Microsoft.NetConf2021.Maui.csproj Microsoft.NetConf2021.Maui.csproj.bak - sed -i'' -e 's/net6.0-ios;net6.0-maccatalyst/net6.0-ios/g' Microsoft.NetConf2021.Maui.csproj - - ../../../dotnet build ../Web/Components/Podcast.Components.Maui.csproj - ../../../dotnet publish Microsoft.NetConf2021.Maui.csproj -bl:MauiiOSPodcast.binlog -f net6.0-ios --self-contained -r ios-arm64 -c Release /p:_RequireCodeSigning=false /p:ApplicationId=net.dot.netconf2021.maui - mv ./bin/Release/net6.0-ios/ios-arm64/publish/Microsoft.NetConf2021.Maui.ipa ./MauiiOSPodcast.ipa - - cp Microsoft.NetConf2021.Maui.csproj.bak Microsoft.NetConf2021.Maui.csproj - displayName: Build MAUI Podcast iOS - workingDirectory: $(Build.SourcesDirectory)/dotnet-podcasts/src/Mobile - - - script: | - chmod -R a+r . - ../dotnet publish -bl:MauiMacCatalyst.binlog -f net6.0-maccatalyst -c Release - mv ./bin/Release/net6.0-maccatalyst/maccatalyst-x64/MauiTesting.app ./MauiMacCatalystDefault.app - displayName: Build MAUI MacCatalyst - workingDirectory: $(Build.SourcesDirectory)/MauiTesting - - - script: | - ./dotnet new maui-blazor -n MauiBlazorTesting - cd MauiBlazorTesting - cp $(Build.SourcesDirectory)/src/tests/Common/maui/MauiScenario.props.net6 ./Directory.Build.props - cp $(Build.SourcesDirectory)/src/tests/Common/maui/MauiScenario.targets.net6 ./Directory.Build.targets - cp $(Build.SourcesDirectory)/NuGet.config ./NuGet.config - - echo -e "using Microsoft.AspNetCore.Components; - #if ANDROID - using Android.App; - #endif - - namespace MauiBlazorTesting.Pages - { - public partial class Index - { - protected override void OnAfterRender(bool firstRender) - { - if (firstRender) - { - #if ANDROID - var activity = MainActivity.Context as Activity; - activity.ReportFullyDrawn(); - #else - System.Console.WriteLine(\"__MAUI_Blazor_WebView_OnAfterRender__\"); - #endif - } - } - } - }" > Pages/Index.razor.cs - - sed -i'' -e "s/{/{\npublic static Android.Content.Context Context { get; private set; }\npublic MainActivity() { Context = this; }/g" Platforms/Android/MainActivity.cs - displayName: Setup MAUI Blazor Hybrid Project - workingDirectory: $(Build.SourcesDirectory) - - - script: | - chmod -R a+r . - # Restore is split out because of https://github.com/dotnet/sdk/issues/21877, can be removed with --no-restore once fixed - ../dotnet restore - ../dotnet publish -bl:MauiBlazorAndroid.binlog -f net6.0-android -c Release -r android-arm64 --no-restore --self-contained - mv ./bin/Release/net6.0-android/android-arm64/com.companyname.mauiblazortesting-Signed.apk ./MauiBlazorAndroidDefault.apk - displayName: Build MAUI Blazor Android - workingDirectory: $(Build.SourcesDirectory)/MauiBlazorTesting - - - script: | - chmod -R a+r . - # remove net6.0-maccatalyst to work around https://github.com/dotnet/sdk/issues/21877 - cp MauiBlazorTesting.csproj MauiBlazorTesting.csproj.bak - sed -i'' -e 's/net6.0-ios;net6.0-maccatalyst/net6.0-ios/g' MauiBlazorTesting.csproj - - # NuGet.config file cannot be in the build directory currently due to https://github.com/dotnet/aspnetcore/issues/41397 - rm NuGet.config - - ../dotnet publish -bl:MauiBlazoriOS.binlog -f net6.0-ios --self-contained -r ios-arm64 -c Release /p:_RequireCodeSigning=false /p:ApplicationId=net.dot.mauiblazortesting - mv ./bin/Release/net6.0-ios/ios-arm64/publish/MauiBlazorTesting.ipa ./MauiBlazoriOSDefault.ipa - - # Restore NuGet.config - cp $(Build.SourcesDirectory)/NuGet.config ./NuGet.config - - cp MauiBlazorTesting.csproj.bak MauiBlazorTesting.csproj - displayName: Build MAUI Blazor iOS - workingDirectory: $(Build.SourcesDirectory)/MauiBlazorTesting - - - script: | - chmod -R a+r . - - # NuGet.config file cannot be in the build directory currently due to https://github.com/dotnet/aspnetcore/issues/41397 - rm NuGet.config - - ../dotnet publish -bl:MauiBlazorMacCatalyst.binlog -f net6.0-maccatalyst -c Release - - # Restore NuGet.config - cp $(Build.SourcesDirectory)/NuGet.config ./NuGet.config - - mv ./bin/Release/net6.0-maccatalyst/maccatalyst-x64/MauiBlazorTesting.app ./MauiBlazorMacCatalystDefault.app - displayName: Build MAUI Blazor MacCatalyst - workingDirectory: $(Build.SourcesDirectory)/MauiBlazorTesting - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiAndroid binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/MauiTesting/MauiAndroid.binlog - artifactName: ${{ parameters.artifactName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiBlazorAndroid binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/MauiBlazorTesting/MauiBlazorAndroid.binlog - artifactName: ${{ parameters.artifactName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiiOS binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/MauiTesting/MauiiOS.binlog - artifactName: ${{ parameters.artifactName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiMacCatalyst binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/MauiTesting/MauiMacCatalyst.binlog - artifactName: ${{ parameters.artifactName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiBlazorAndroid binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/MauiBlazorTesting/MauiBlazorAndroid.binlog - artifactName: ${{ parameters.artifactName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiBlazoriOS binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/MauiBlazorTesting/MauiBlazoriOS.binlog - artifactName: ${{ parameters.artifactName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiBlazorMacCatalyst binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/MauiBlazorTesting/MauiBlazorMacCatalyst.binlog - artifactName: ${{ parameters.artifactName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiiOSPodcast binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/dotnet-podcasts/src/Mobile/MauiiOSPodcast.binlog - artifactName: ${{ parameters.artifactName }} - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/MauiTesting/MauiAndroidDefault.apk - includeRootFolder: true - displayName: Maui Android App - artifactName: MauiAndroidAppNet6 - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/MauiBlazorTesting/MauiBlazorAndroidDefault.apk - includeRootFolder: true - displayName: Maui Blazor Android App - artifactName: MauiBlazorAndroidAppNet6 - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/MauiTesting/MauiAndroidPodcast.apk - includeRootFolder: true - displayName: Maui Android Podcast - artifactName: MauiAndroidPodcastNet6 - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/MauiTesting/MauiiOSDefault.ipa - includeRootFolder: true - displayName: Maui iOS IPA - artifactName: MauiiOSDefaultIPANet6 - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/MauiBlazorTesting/MauiBlazoriOSDefault.ipa - includeRootFolder: true - displayName: Maui Blazor iOS IPA - artifactName: MauiBlazoriOSDefaultIPANet6 - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/dotnet-podcasts/src/Mobile/MauiiOSPodcast.ipa - includeRootFolder: true - displayName: Maui iOS Podcast IPA - artifactName: MauiiOSPodcastIPANet6 - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/MauiTesting/MauiMacCatalystDefault.app - includeRootFolder: true - displayName: Maui MacCatalyst App - artifactName: MauiMacCatalystDefaultNet6 - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/MauiBlazorTesting/MauiBlazorMacCatalystDefault.app - includeRootFolder: true - displayName: Maui Blazor MacCatalyst App - artifactName: MauiBlazorMacCatalystDefaultNet6 - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - script: rm -r -f ./bin - workingDirectory: $(Build.SourcesDirectory)/MauiTesting - displayName: Clean MauiTesting bin directory - condition: succeededOrFailed() - - - script: rm -r -f ./bin - workingDirectory: $(Build.SourcesDirectory)/MauiBlazorTesting - displayName: Clean MauiBlazorTesting bin directory - condition: succeededOrFailed() - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - osGroup: ${{ parameters.osGroup }} - osSubgroup: ${{ parameters.osSubgroup }} - archType: ${{ parameters.archType }} - buildConfig: ${{ parameters.buildConfig }} - runtimeFlavor: ${{ parameters.runtimeFlavor }} - helixQueues: ${{ parameters.helixQueues }} - targetRid: ${{ parameters.targetRid }} - nameSuffix: ${{ parameters.nameSuffix }} - platform: ${{ parameters.platform }} - shouldContinueOnError: ${{ parameters.shouldContinueOnError }} - rootFolder: ${{ parameters.rootFolder }} - includeRootFolder: ${{ parameters.includeRootFolder }} - displayName: ${{ parameters.displayName }} - artifactName: ${{ parameters.artifactName }} - archiveExtension: ${{ parameters.archiveExtension }} - archiveType: ${{ parameters.archiveType }} - tarCompression: ${{ parameters.tarCompression }} diff --git a/eng/pipelines/coreclr/templates/build-perf-maui-apps-net7.yml b/eng/pipelines/coreclr/templates/build-perf-maui-apps-net7.yml deleted file mode 100644 index 61cf711967f703..00000000000000 --- a/eng/pipelines/coreclr/templates/build-perf-maui-apps-net7.yml +++ /dev/null @@ -1,457 +0,0 @@ -parameters: - osGroup: '' - osSubgroup: '' - archType: '' - buildConfig: '' - runtimeFlavor: '' - helixQueues: '' - targetRid: '' - nameSuffix: '' - platform: '' - shouldContinueOnError: '' - rootFolder: '' - includeRootFolder: '' - displayName: '' - artifactName: '' - archiveExtension: '' - archiveType: '' - tarCompression: '' - - -steps: - # Uncomment to reenable package replacement - #- task: DownloadPipelineArtifact@2 - # displayName: Download runtime packages - # inputs: - # artifact: 'IntermediateArtifacts' - # path: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks - # patterns: | - # IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.android-!(*.symbols).nupkg - # IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.ios-!(*.symbols).nupkg - # IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.iossimulator-!(*.symbols).nupkg - # IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.maccatalyst-!(*.symbols).nupkg - - # # Other artifacts to include once they are being built - # # EX. IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.maccatalyst-*.nupkg - - #- task: CopyFiles@2 - # displayName: Flatten packages - # inputs: - # sourceFolder: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks - # contents: '*/Shipping/*.nupkg' - # cleanTargetFolder: false - # targetFolder: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks - # flattenFolders: true - - #- script: | - # for file in *.nupkg - # do - # mv -v "$file" "${file%.nupkg}.zip" - # done - # displayName: Change nupkgs to zips - # workingDirectory: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks - - - ##Unzip the nuget packages to make the actual runtimes accessible - #- task: ExtractFiles@1 - # displayName: Extract android-arm runtime - # inputs: - # archiveFilePatterns: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.android-arm.*.zip - # destinationFolder: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.android-arm - # overwriteExistingFiles: true - # cleanDestinationFolder: false - #- task: ExtractFiles@1 - # displayName: Extract android-arm64 runtime - # inputs: - # archiveFilePatterns: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.android-arm64.*.zip - # destinationFolder: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.android-arm64 - # overwriteExistingFiles: true - # cleanDestinationFolder: false - #- task: ExtractFiles@1 - # displayName: Extract android-x86 runtime - # inputs: - # archiveFilePatterns: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.android-x86.*.zip - # destinationFolder: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.android-x86 - # overwriteExistingFiles: true - # cleanDestinationFolder: false - #- task: ExtractFiles@1 - # displayName: Extract android-x64 runtime - # inputs: - # archiveFilePatterns: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.android-x64.*.zip - # destinationFolder: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.android-x64 - # overwriteExistingFiles: true - # cleanDestinationFolder: false - #- task: ExtractFiles@1 - # displayName: Extract ios-arm runtime - # inputs: - # archiveFilePatterns: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.ios-arm.*.zip - # destinationFolder: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.ios-arm - # overwriteExistingFiles: true - # cleanDestinationFolder: false - #- task: ExtractFiles@1 - # displayName: Extract ios-arm64 runtime - # inputs: - # archiveFilePatterns: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.ios-arm64.*.zip - # destinationFolder: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.ios-arm64 - # overwriteExistingFiles: true - # cleanDestinationFolder: false - #- task: ExtractFiles@1 - # displayName: Extract maccatalyst-x64 runtime - # inputs: - # archiveFilePatterns: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.maccatalyst-x64.*.zip - # destinationFolder: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.maccatalyst-x64 - # overwriteExistingFiles: true - # cleanDestinationFolder: false - #- task: ExtractFiles@1 - # displayName: Extract iossimulator-x64 runtime - # inputs: - # archiveFilePatterns: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.iossimulator-x64.*.zip - # destinationFolder: $(Build.SourcesDirectory)/MauiTesting/ArtifactPacks/Microsoft.NETCore.App.Runtime.Mono.iossimulator-x64 - # overwriteExistingFiles: true - # cleanDestinationFolder: false - - # Get the current maui nuget config so all things can be found and darc based package sources are kept up to date. - - script: | - echo '{}' > ./global.json - displayName: Create global.json - workingDirectory: $(Build.SourcesDirectory) - - - script: | - curl -o NuGet.config 'https://raw.githubusercontent.com/dotnet/maui/main/NuGet.config' - curl -o dotnet-install.sh 'https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh' - chmod -R a+rx . - cp $(Build.SourcesDirectory)/src/tests/Common/maui/rollbackfile-net7.json ./rollbackfile-net7.json - ./dotnet-install.sh --version 7.0.100-preview.7.22376.5 --install-dir . - ./dotnet --info - ./dotnet workload install maui --from-rollback-file rollbackfile-net7.json --configfile NuGet.config - displayName: Install MAUI workload - workingDirectory: $(Build.SourcesDirectory) - - - script: $(Build.SourcesDirectory)/eng/testing/performance/create-provisioning-profile.sh - displayName: Create iOS code signing and provisioning profile - - - script: | - ./dotnet new maui -n MauiTesting - cd MauiTesting - cp $(Build.SourcesDirectory)/src/tests/Common/maui/MauiScenario.props ./Directory.Build.props - cp $(Build.SourcesDirectory)/src/tests/Common/maui/MauiScenario.targets ./Directory.Build.targets - cp $(Build.SourcesDirectory)/NuGet.config ./NuGet.config - displayName: Setup MAUI Project - workingDirectory: $(Build.SourcesDirectory) - - - script: | - chmod -R a+r . - # Restore is split out because of https://github.com/dotnet/sdk/issues/21877, can be removed with --no-restore once fixed - ../dotnet restore - ../dotnet publish -bl:MauiAndroid.binlog -f net7.0-android -c Release -r android-arm64 --no-restore --self-contained - mv ./bin/Release/net7.0-android/android-arm64/com.companyname.mauitesting-Signed.apk ./MauiAndroidDefault.apk - displayName: Build MAUI Android - workingDirectory: $(Build.SourcesDirectory)/MauiTesting - - - script: | - set -x - pwd - git clone https://github.com/microsoft/dotnet-podcasts.git -b net7.0 --single-branch - cd dotnet-podcasts - cp $(Build.SourcesDirectory)/src/tests/Common/maui/MauiScenario.props ./Directory.Build.props - cp $(Build.SourcesDirectory)/src/tests/Common/maui/MauiScenario.targets ./Directory.Build.targets - displayName: Clone podcast app - workingDirectory: $(Build.SourcesDirectory) - - - script: | - set -x - pwd - chmod -R a+r . - ../../../dotnet restore Microsoft.NetConf2021.Maui.csproj - ../../../dotnet publish Microsoft.NetConf2021.Maui.csproj -bl:MauiPodcastAndroid.binlog -r android-arm64 --self-contained --no-restore -f net7.0-android -c Release - mv ./bin/Release/net7.0-android/android-arm64/com.Microsoft.NetConf2021.Maui-Signed.apk $(Build.SourcesDirectory)/MauiTesting/MauiAndroidPodcast.apk - displayName: Build MAUI Podcast Android - workingDirectory: $(Build.SourcesDirectory)/dotnet-podcasts/src/Mobile - - # This step pulls the product version from the used Microsoft.Maui.dll file properties and saves it for upload with the maui test counter. - # We pull from this file as we did not find another place to reliably get the version information pre or post build. - - powershell: | - $RetrievedMauiVersion = Get-ChildItem .\obj\Release\net7.0-android\android-arm64\linked\Microsoft.Maui.dll | Select-Object -ExpandProperty VersionInfo | Select-Object ProductVersion | Select-Object -ExpandProperty ProductVersion - $RetrievedMauiVersion - Write-Host "##vso[task.setvariable variable=mauiVersion;isOutput=true]$RetrievedMauiVersion" - name: getMauiVersion - displayName: Get and Save MAUI Version - workingDirectory: $(Build.SourcesDirectory)/MauiTesting - - - script: | - chmod -R a+r . - # remove net7.0-maccatalyst to work around https://github.com/dotnet/sdk/issues/21877 - cp MauiTesting.csproj MauiTesting.csproj.bak - sed -i'' -e 's/net7.0-ios;net7.0-maccatalyst/net7.0-ios/g' MauiTesting.csproj - - ../dotnet publish -bl:MauiiOS.binlog -f net7.0-ios --self-contained -r ios-arm64 -c Release /p:_RequireCodeSigning=false /p:ApplicationId=net.dot.mauitesting - mv ./bin/Release/net7.0-ios/ios-arm64/publish/MauiTesting.ipa ./MauiiOSDefault.ipa - - cp MauiTesting.csproj.bak MauiTesting.csproj - displayName: Build MAUI Default iOS - workingDirectory: $(Build.SourcesDirectory)/MauiTesting - - - script: | - chmod -R a+r . - # remove net7.0-maccatalyst to work around https://github.com/dotnet/sdk/issues/21877 - cp Microsoft.NetConf2021.Maui.csproj Microsoft.NetConf2021.Maui.csproj.bak - sed -i'' -e 's/net7.0-ios;net7.0-maccatalyst/net7.0-ios/g' Microsoft.NetConf2021.Maui.csproj - - ../../../dotnet build ../Web/Components/Podcast.Components.Maui.csproj - ../../../dotnet publish Microsoft.NetConf2021.Maui.csproj -bl:MauiiOSPodcast.binlog -f net7.0-ios --self-contained -r ios-arm64 -c Release /p:_RequireCodeSigning=false /p:ApplicationId=net.dot.netconf2021.maui - mv ./bin/Release/net7.0-ios/ios-arm64/publish/Microsoft.NetConf2021.Maui.ipa ./MauiiOSPodcast.ipa - - cp Microsoft.NetConf2021.Maui.csproj.bak Microsoft.NetConf2021.Maui.csproj - displayName: Build MAUI Podcast iOS - workingDirectory: $(Build.SourcesDirectory)/dotnet-podcasts/src/Mobile - - - script: | - chmod -R a+r . - ../dotnet publish -bl:MauiMacCatalyst.binlog -f net7.0-maccatalyst -c Release - mv ./bin/Release/net7.0-maccatalyst/maccatalyst-x64/MauiTesting.app ./MauiMacCatalystDefault.app - displayName: Build MAUI MacCatalyst - workingDirectory: $(Build.SourcesDirectory)/MauiTesting - - - script: | - ./dotnet new maui-blazor -n MauiBlazorTesting - cd MauiBlazorTesting - cp $(Build.SourcesDirectory)/src/tests/Common/maui/MauiScenario.props ./Directory.Build.props - cp $(Build.SourcesDirectory)/src/tests/Common/maui/MauiScenario.targets ./Directory.Build.targets - cp $(Build.SourcesDirectory)/NuGet.config ./NuGet.config - - echo -e "using Microsoft.AspNetCore.Components; - #if ANDROID - using Android.App; - #endif - - namespace MauiBlazorTesting.Pages - { - public partial class Index - { - protected override void OnAfterRender(bool firstRender) - { - if (firstRender) - { - #if ANDROID - var activity = MainActivity.Context as Activity; - activity.ReportFullyDrawn(); - #else - System.Console.WriteLine(\"__MAUI_Blazor_WebView_OnAfterRender__\"); - #endif - } - } - } - }" > Pages/Index.razor.cs - - sed -i'' -e "s/{/{\npublic static Android.Content.Context Context { get; private set; }\npublic MainActivity() { Context = this; }/g" Platforms/Android/MainActivity.cs - displayName: Setup MAUI Blazor Hybrid Project - workingDirectory: $(Build.SourcesDirectory) - - - script: | - chmod -R a+r . - # Restore is split out because of https://github.com/dotnet/sdk/issues/21877, can be removed with --no-restore once fixed - ../dotnet restore - ../dotnet publish -bl:MauiBlazorAndroid.binlog -f net7.0-android -c Release -r android-arm64 --no-restore --self-contained - mv ./bin/Release/net7.0-android/android-arm64/com.companyname.mauiblazortesting-Signed.apk ./MauiBlazorAndroidDefault.apk - displayName: Build MAUI Blazor Android - workingDirectory: $(Build.SourcesDirectory)/MauiBlazorTesting - - - script: | - chmod -R a+r . - # remove net7.0-maccatalyst to work around https://github.com/dotnet/sdk/issues/21877 - cp MauiBlazorTesting.csproj MauiBlazorTesting.csproj.bak - sed -i'' -e 's/net7.0-ios;net7.0-maccatalyst/net7.0-ios/g' MauiBlazorTesting.csproj - - # NuGet.config file cannot be in the build directory currently due to https://github.com/dotnet/aspnetcore/issues/41397 - rm NuGet.config - - ../dotnet publish -bl:MauiBlazoriOS.binlog -f net7.0-ios --self-contained -r ios-arm64 -c Release /p:_RequireCodeSigning=false /p:ApplicationId=net.dot.mauiblazortesting - mv ./bin/Release/net7.0-ios/ios-arm64/publish/MauiBlazorTesting.ipa ./MauiBlazoriOSDefault.ipa - - # Restore NuGet.config - cp $(Build.SourcesDirectory)/NuGet.config ./NuGet.config - - cp MauiBlazorTesting.csproj.bak MauiBlazorTesting.csproj - displayName: Build MAUI Blazor iOS - workingDirectory: $(Build.SourcesDirectory)/MauiBlazorTesting - - - script: | - chmod -R a+r . - - # NuGet.config file cannot be in the build directory currently due to https://github.com/dotnet/aspnetcore/issues/41397 - rm NuGet.config - - ../dotnet publish -bl:MauiBlazorMacCatalyst.binlog -f net7.0-maccatalyst -c Release - - # Restore NuGet.config - cp $(Build.SourcesDirectory)/NuGet.config ./NuGet.config - - mv ./bin/Release/net7.0-maccatalyst/maccatalyst-x64/MauiBlazorTesting.app ./MauiBlazorMacCatalystDefault.app - displayName: Build MAUI Blazor MacCatalyst - workingDirectory: $(Build.SourcesDirectory)/MauiBlazorTesting - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiAndroid binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/MauiTesting/MauiAndroid.binlog - artifactName: ${{ parameters.artifactName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiBlazorAndroid binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/MauiBlazorTesting/MauiBlazorAndroid.binlog - artifactName: ${{ parameters.artifactName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiiOS binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/MauiTesting/MauiiOS.binlog - artifactName: ${{ parameters.artifactName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiMacCatalyst binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/MauiTesting/MauiMacCatalyst.binlog - artifactName: ${{ parameters.artifactName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiBlazorAndroid binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/MauiBlazorTesting/MauiBlazorAndroid.binlog - artifactName: ${{ parameters.artifactName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiBlazoriOS binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/MauiBlazorTesting/MauiBlazoriOS.binlog - artifactName: ${{ parameters.artifactName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiBlazorMacCatalyst binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/MauiBlazorTesting/MauiBlazorMacCatalyst.binlog - artifactName: ${{ parameters.artifactName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish MauiiOSPodcast binlog' - condition: always() - inputs: - pathtoPublish: $(Build.SourcesDirectory)/dotnet-podcasts/src/Mobile/MauiiOSPodcast.binlog - artifactName: ${{ parameters.artifactName }} - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/MauiTesting/MauiAndroidDefault.apk - includeRootFolder: true - displayName: Maui Android App - artifactName: MauiAndroidApp - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/MauiBlazorTesting/MauiBlazorAndroidDefault.apk - includeRootFolder: true - displayName: Maui Blazor Android App - artifactName: MauiBlazorAndroidApp - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/MauiTesting/MauiAndroidPodcast.apk - includeRootFolder: true - displayName: Maui Android Podcast - artifactName: MauiAndroidPodcast - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/MauiTesting/MauiiOSDefault.ipa - includeRootFolder: true - displayName: Maui iOS IPA - artifactName: MauiiOSDefaultIPA - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/MauiBlazorTesting/MauiBlazoriOSDefault.ipa - includeRootFolder: true - displayName: Maui Blazor iOS IPA - artifactName: MauiBlazoriOSDefaultIPA - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/dotnet-podcasts/src/Mobile/MauiiOSPodcast.ipa - includeRootFolder: true - displayName: Maui iOS Podcast IPA - artifactName: MauiiOSPodcastIPA - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/MauiTesting/MauiMacCatalystDefault.app - includeRootFolder: true - displayName: Maui MacCatalyst App - artifactName: MauiMacCatalystDefault - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/MauiBlazorTesting/MauiBlazorMacCatalystDefault.app - includeRootFolder: true - displayName: Maui Blazor MacCatalyst App - artifactName: MauiBlazorMacCatalystDefault - archiveExtension: '.tar.gz' - archiveType: tar - tarCompression: gz - - - script: rm -r -f ./bin - workingDirectory: $(Build.SourcesDirectory)/MauiTesting - displayName: Clean MauiTesting bin directory - condition: succeededOrFailed() - - - script: rm -r -f ./bin - workingDirectory: $(Build.SourcesDirectory)/MauiBlazorTesting - displayName: Clean MauiBlazorTesting bin directory - condition: succeededOrFailed() - - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - osGroup: ${{ parameters.osGroup }} - osSubgroup: ${{ parameters.osSubgroup }} - archType: ${{ parameters.archType }} - buildConfig: ${{ parameters.buildConfig }} - runtimeFlavor: ${{ parameters.runtimeFlavor }} - helixQueues: ${{ parameters.helixQueues }} - targetRid: ${{ parameters.targetRid }} - nameSuffix: ${{ parameters.nameSuffix }} - platform: ${{ parameters.platform }} - shouldContinueOnError: ${{ parameters.shouldContinueOnError }} - rootFolder: ${{ parameters.rootFolder }} - includeRootFolder: ${{ parameters.includeRootFolder }} - displayName: ${{ parameters.displayName }} - artifactName: ${{ parameters.artifactName }} - archiveExtension: ${{ parameters.archiveExtension }} - archiveType: ${{ parameters.archiveType }} - tarCompression: ${{ parameters.tarCompression }} diff --git a/eng/pipelines/coreclr/templates/crossdac-build.yml b/eng/pipelines/coreclr/templates/crossdac-build.yml index 9bc3125f6a050b..31154ec5487986 100644 --- a/eng/pipelines/coreclr/templates/crossdac-build.yml +++ b/eng/pipelines/coreclr/templates/crossdac-build.yml @@ -1,5 +1,6 @@ parameters: archType: '' + isOfficialBuild: false osGroup: '' osSubgroup: '' hostArchType: '' @@ -52,6 +53,12 @@ steps: displayName: Gather CrossDac Artifacts + - template: /eng/pipelines/coreclr/templates/sign-diagnostic-files.yml + parameters: + basePath: $(crossDacArtifactPath) + isOfficialBuild: ${{ parameters.isOfficialBuild }} + timeoutInMinutes: 30 + - ${{ if eq(parameters.osGroup, 'Linux') }}: - task: CopyFiles@2 displayName: Gather runtime for CrossDac diff --git a/eng/pipelines/coreclr/templates/crossdac-pack.yml b/eng/pipelines/coreclr/templates/crossdac-pack.yml index 40e375bb9375c4..fc3ef404c171a9 100644 --- a/eng/pipelines/coreclr/templates/crossdac-pack.yml +++ b/eng/pipelines/coreclr/templates/crossdac-pack.yml @@ -54,19 +54,6 @@ jobs: - ${{ parameters.runtimeFlavor }}_${{ parameters.runtimeVariant }}_product_build_${{ platform }}_${{ parameters.buildConfig }} steps: - # Install MicroBuild for signing the package - - ${{ if eq(parameters.isOfficialBuild, true) }}: - - template: /eng/pipelines/common/restore-internal-tools.yml - - - task: MicroBuildSigningPlugin@2 - displayName: Install MicroBuild plugin for Signing - inputs: - signType: $(SignType) - zipSources: false - feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json - continueOnError: false - condition: and(succeeded(), in(variables['SignType'], 'real', 'test')) - - task: DownloadBuildArtifacts@0 displayName: Download CrossDac artifacts inputs: @@ -77,16 +64,6 @@ jobs: - script: $(Build.SourcesDirectory)$(dir)build$(scriptExt) -subset crossdacpack -arch $(archType) $(osArg) -c $(buildConfig) $(officialBuildIdArg) $(crossDacArgs) -ci displayName: Build crossdac packaging - # Sign diagnostic files - - ${{ if eq(parameters.isOfficialBuild, true) }}: - - powershell: >- - eng\common\build.ps1 -ci -sign -restore -configuration:$(buildConfig) -warnaserror:0 $(officialBuildIdArg) - /p:PackagesFolder="$(Build.SourcesDirectory)/artifacts/packages/$(buildConfig)" - /p:SignDiagnosticsPackages=true - /p:DotNetSignType=$(SignType) - -projects $(Build.SourcesDirectory)\eng\empty.csproj - displayName: Sign CrossDac package and contents - # Save packages using the prepare-signed-artifacts format. - template: /eng/pipelines/common/upload-intermediate-artifacts-step.yml parameters: diff --git a/eng/pipelines/coreclr/templates/helix-queues-setup.yml b/eng/pipelines/coreclr/templates/helix-queues-setup.yml index 6e9a1ed26516ef..f0825c4b33009f 100644 --- a/eng/pipelines/coreclr/templates/helix-queues-setup.yml +++ b/eng/pipelines/coreclr/templates/helix-queues-setup.yml @@ -44,61 +44,59 @@ jobs: # Linux arm - ${{ if eq(parameters.platform, 'Linux_arm') }}: - ${{ if eq(variables['System.TeamProject'], 'public') }}: - - (Ubuntu.1804.Arm32.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm32v7-bfcd90a-20200121150440 + - (Ubuntu.1804.Arm32.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm32v7 - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - (Debian.10.Arm32)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-arm32v7-20210304164340-6616c63 - - (Debian.11.Arm32)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-arm32v7-20210304164347-5a7c380 - - (Ubuntu.1804.Arm32)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm32v7-bfcd90a-20200121150440 + - (Debian.10.Arm32)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-arm32v7 + - (Debian.11.Arm32)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-arm32v7 + - (Ubuntu.1804.Arm32)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm32v7 # Linux arm64 - ${{ if eq(parameters.platform, 'Linux_arm64') }}: - ${{ if eq(variables['System.TeamProject'], 'public') }}: - - (Ubuntu.1804.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm64v8-20210531091519-97d8652 + - (Ubuntu.1804.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm64v8 - ${{ if and(eq(variables['System.TeamProject'], 'public'), notIn(parameters.jobParameters.helixQueueGroup, 'pr', 'ci', 'libraries')) }}: - - (Debian.10.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-arm64v8-20210304164340-56c6673 - - (Debian.11.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-arm64v8-20210304164340-5a7c380 + - (Debian.10.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-arm64v8 + - (Debian.11.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-arm64v8 - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - (Debian.10.Arm64)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-arm64v8-20210304164340-56c6673 - - (Debian.11.Arm64)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-arm64v8-20210304164340-5a7c380 - - (Ubuntu.1804.Arm64)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm64v8-20210531091519-97d8652 + - (Debian.10.Arm64)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-arm64v8 + - (Debian.11.Arm64)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-arm64v8 + - (Ubuntu.1804.Arm64)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm64v8 # Linux musl x64 - ${{ if eq(parameters.platform, 'Linux_musl_x64') }}: - ${{ if eq(variables['System.TeamProject'], 'public') }}: - - (Alpine.314.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-amd64-20210910135833-1848e19 + - (Alpine.314.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-amd64 - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - (Alpine.314.Amd64)ubuntu.1604.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-amd64-20210910135833-1848e19 + - (Alpine.314.Amd64)ubuntu.1604.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-amd64 # Linux musl arm32 - ${{ if eq(parameters.platform, 'Linux_musl_arm') }}: - ${{ if eq(variables['System.TeamProject'], 'public') }}: - - (Alpine.314.Arm32.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-arm32v7-20210910135806-8a6f4f3 + - (Alpine.314.Arm32.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-arm32v7 - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - (Alpine.314.Arm32)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-arm32v7-20210910135806-8a6f4f3 + - (Alpine.314.Arm32)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-arm32v7 # Linux musl arm64 - ${{ if eq(parameters.platform, 'Linux_musl_arm64') }}: - ${{ if eq(variables['System.TeamProject'], 'public') }}: - - (Alpine.314.Arm64.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-arm64v8-20210910135810-8a6f4f3 + - (Alpine.314.Arm64.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-arm64v8 - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - (Alpine.314.Arm64)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-arm64v8-20210910135810-8a6f4f3 + - (Alpine.314.Arm64)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-arm64v8 # Linux x64 - ${{ if eq(parameters.platform, 'Linux_x64') }}: - ${{ if and(eq(variables['System.TeamProject'], 'public'), in(parameters.jobParameters.helixQueueGroup, 'pr', 'ci', 'libraries')) }}: - Ubuntu.1804.Amd64.Open - ${{ if and(eq(variables['System.TeamProject'], 'public'), notIn(parameters.jobParameters.helixQueueGroup, 'pr', 'ci', 'libraries')) }}: - - (Debian.10.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-20220810215022-f344011 - - (Debian.11.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64-20220810215032-f344011 + - (Debian.10.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64 + - (Debian.11.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64 - Ubuntu.1804.Amd64.Open - - (Centos.8.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-8-helix-20201229003624-c1bf759 - RedHat.7.Amd64.Open - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - (Debian.10.Amd64)Ubuntu.1804.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-20220810215022-f344011 - - (Debian.11.Amd64)Ubuntu.1804.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64-20220810215032-f344011 + - (Debian.10.Amd64)Ubuntu.1804.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64 + - (Debian.11.Amd64)Ubuntu.1804.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64 - Ubuntu.1804.Amd64 - - (Centos.8.Amd64)Ubuntu.1604.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-8-helix-20201229003624-c1bf759 - - (Fedora.34.Amd64)Ubuntu.1804.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix-20220809205730-e7e8d1c + - (Fedora.34.Amd64)Ubuntu.1804.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix - RedHat.7.Amd64 # OSX arm64 @@ -123,41 +121,33 @@ jobs: - ${{ if eq(parameters.jobParameters.helixQueueGroup, 'cet') }}: - Windows.11.Amd64.Cet.Open - ${{ if ne(parameters.jobParameters.helixQueueGroup, 'cet') }}: - - (Windows.Nano.1809.Amd64.Open)windows.10.amd64.serverrs5.open@mcr.microsoft.com/dotnet-buildtools/prereqs:nanoserver-1809-helix-amd64-08e8e40-20200107182504 - - Windows.7.Amd64.Open + - (Windows.Nano.1809.Amd64.Open)windows.10.amd64.serverrs5.open@mcr.microsoft.com/dotnet-buildtools/prereqs:nanoserver-1809-helix-amd64 - Windows.10.Amd64.Open - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - Windows.7.Amd64 - - Windows.81.Amd64 - Windows.10.Amd64 - - Windows.10.Amd64.Core - - (Windows.Nano.1809.Amd64)windows.10.amd64.serverrs5@mcr.microsoft.com/dotnet-buildtools/prereqs:nanoserver-1809-helix-amd64-08e8e40-20200107182504 + - (Windows.Nano.1809.Amd64)windows.10.amd64.serverrs5@mcr.microsoft.com/dotnet-buildtools/prereqs:nanoserver-1809-helix-amd64 # windows x86 - ${{ if eq(parameters.platform, 'windows_x86') }}: - ${{ if and(eq(variables['System.TeamProject'], 'public'), in(parameters.jobParameters.helixQueueGroup, 'pr', 'ci', 'libraries')) }}: - Windows.10.Amd64.Open - ${{ if and(eq(variables['System.TeamProject'], 'public'), notIn(parameters.jobParameters.helixQueueGroup, 'pr', 'ci', 'libraries')) }}: - - Windows.7.Amd64.Open - Windows.10.Amd64.Open - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - Windows.7.Amd64 - - Windows.81.Amd64 - Windows.10.Amd64 - - Windows.10.Amd64.Core # windows arm - ${{ if eq(parameters.platform, 'windows_arm') }}: - ${{ if and(eq(variables['System.TeamProject'], 'public'), in(parameters.jobParameters.helixQueueGroup, 'pr', 'ci', 'libraries')) }}: - - Windows.10.Arm64v8.Open + - Windows.11.Arm64.Open - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - Windows.10.Arm64 + - Windows.11.Arm64 # windows arm64 - ${{ if eq(parameters.platform, 'windows_arm64') }}: - ${{ if and(eq(variables['System.TeamProject'], 'public'), in(parameters.jobParameters.helixQueueGroup, 'pr', 'ci', 'libraries')) }}: - - Windows.10.Arm64v8.Open + - Windows.11.Arm64.Open - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - Windows.10.Arm64 + - Windows.11.Arm64 ${{ insert }}: ${{ parameters.jobParameters }} diff --git a/eng/pipelines/coreclr/templates/perf-job.yml b/eng/pipelines/coreclr/templates/perf-job.yml index 60f603de7962b3..0c8fce9101541a 100644 --- a/eng/pipelines/coreclr/templates/perf-job.yml +++ b/eng/pipelines/coreclr/templates/perf-job.yml @@ -67,7 +67,7 @@ jobs: # Test job depends on the corresponding build job ${{ if eq(parameters.downloadSpecificBuild.buildId, '') }}: dependsOn: - - ${{ if not(in(parameters.runtimeType, 'AndroidMono', 'iOSMono', 'AndroidMobileNet6', 'iOSMobileNet6', 'wasm')) }}: + - ${{ if not(in(parameters.runtimeType, 'AndroidMono', 'iOSMono', 'wasm')) }}: - ${{ format('coreclr_{0}_product_build_{1}{2}_{3}_{4}', parameters.runtimeVariant, parameters.osGroup, parameters.osSubgroup, parameters.archType, parameters.buildConfig) }} - ${{ if and(ne(parameters.liveLibrariesBuildConfig, ''), eq(parameters.skipLiveLibrariesDownload, 'false')) }}: - ${{ format('libraries_build_{0}{1}_{2}_{3}', parameters.osGroup, parameters.osSubgroup, parameters.archType, parameters.liveLibrariesBuildConfig) }} @@ -79,21 +79,15 @@ jobs: - ${{ format('build_{0}{1}_{2}_{3}_{4}', parameters.osGroup, parameters.osSubgroup, parameters.archType, parameters.buildConfig, parameters.codeGenType) }} - ${{ if eq(parameters.runtimeType, 'AndroidMono')}}: - ${{ 'build_Android_arm64_release_AndroidMono' }} - - ${{ 'Build_iOS_arm64_release_MACiOSAndroidMauiNet7' }} - - ${{ if eq(parameters.runtimeType, 'AndroidMobileNet6')}}: - - ${{ 'Build_iOS_arm64_release_MACiOSAndroidMauiNet6' }} - ${{ if eq(parameters.runtimeType, 'iOSMono')}}: - ${{ 'build_iOS_arm64_release_iOSMono' }} - - ${{ 'Build_iOS_arm64_release_MACiOSAndroidMauiNet7' }} - - ${{ if eq(parameters.runtimeType, 'iOSMobileNet6')}}: - - ${{ 'Build_iOS_arm64_release_MACiOSAndroidMauiNet6' }} - ${{ if and(eq(parameters.osGroup, 'windows'), not(in(parameters.runtimeType, 'AndroidMono', 'iOSMono', 'AndroidMobileNet6', 'iOSMobileNet6'))) }}: + ${{ if and(eq(parameters.osGroup, 'windows'), not(in(parameters.runtimeType, 'AndroidMono', 'iOSMono'))) }}: ${{ if eq(parameters.runtimeType, 'mono') }}: extraSetupParameters: -Architecture ${{ parameters.archType }} -MonoDotnet $(Build.SourcesDirectory)\.dotnet-mono ${{ if eq(parameters.runtimeType, 'coreclr') }}: extraSetupParameters: -CoreRootDirectory $(Build.SourcesDirectory)\artifacts\tests\coreclr\${{ parameters.osGroup }}.${{ parameters.archType }}.Release\Tests\Core_Root -Architecture ${{ parameters.archType }} - ${{ if and(ne(parameters.osGroup, 'windows'), not(in(parameters.runtimeType, 'AndroidMono', 'iOSMono', 'AndroidMobileNet6', 'iOSMobileNet6'))) }}: + ${{ if and(ne(parameters.osGroup, 'windows'), not(in(parameters.runtimeType, 'AndroidMono', 'iOSMono'))) }}: ${{ if and(eq(parameters.runtimeType, 'mono'), ne(parameters.codeGenType, 'AOT')) }}: extraSetupParameters: --architecture ${{ parameters.archType }} --monodotnet $(Build.SourcesDirectory)/.dotnet-mono ${{ if and(eq(parameters.runtimeType, 'wasm'), ne(parameters.codeGenType, 'AOT')) }}: @@ -106,9 +100,9 @@ jobs: extraSetupParameters: --corerootdirectory $(Build.SourcesDirectory)/artifacts/tests/coreclr/${{ parameters.osGroup }}.${{ parameters.archType }}.Release/Tests/Core_Root --architecture ${{ parameters.archType }} ${{ if and(eq(parameters.runtimeType, 'coreclr'), eq(parameters.osSubGroup, '_musl')) }}: extraSetupParameters: --corerootdirectory $(Build.SourcesDirectory)/artifacts/tests/coreclr/${{ parameters.osGroup }}.${{ parameters.archType }}.Release/Tests/Core_Root --architecture ${{ parameters.archType }} --alpine - ${{ if in(parameters.runtimeType, 'AndroidMono', 'AndroidMobileNet6') }}: + ${{ if in(parameters.runtimeType, 'AndroidMono') }}: extraSetupParameters: -Architecture ${{ parameters.archType }} -AndroidMono - ${{ if in(parameters.runtimeType, 'iosMono', 'iOSMobileNet6') }}: + ${{ if in(parameters.runtimeType, 'iosMono') }}: extraSetupParameters: --architecture ${{ parameters.archType }} --iosmono --iosllvmbuild ${{ parameters.iOSLlvmBuild }} variables: @@ -133,6 +127,9 @@ jobs: - ${{ parameters.framework }} steps: # Extra steps that will be passed to the performance template and run before sending the job to helix (all of which is done in the template) + - script: | + $(Build.SourcesDirectory)/eng/common/msbuild.sh $(Build.SourcesDirectory)/eng/testing/performance/add_properties_to_pipeline.proj /t:SetVariables + displayName: Add Properties To Pipeline Env # Optionally download live-built libraries - ${{ if and(ne(parameters.liveLibrariesBuildConfig, ''), eq(parameters.skipLiveLibrariesDownload, 'false')) }}: @@ -145,7 +142,7 @@ jobs: displayName: 'live-built libraries' # Download coreclr - - ${{ if not(in(parameters.runtimeType, 'AndroidMono', 'iOSMono', 'AndroidMobileNet6', 'iOSMobileNet6', 'wasm')) }}: + - ${{ if not(in(parameters.runtimeType, 'AndroidMono', 'iOSMono', 'wasm')) }}: - template: /eng/pipelines/common/download-artifact-step.yml parameters: unpackFolder: $(buildProductRootFolderPath) @@ -184,7 +181,7 @@ jobs: - script: >- mkdir -p $(librariesDownloadDir)/bin/wasm/wasm-data && mkdir -p $(librariesDownloadDir)/bin/wasm/dotnet && - cp -r $(librariesDownloadDir)/BrowserWasm/staging/dotnet-workload/* $(librariesDownloadDir)/bin/wasm/dotnet && + cp -r $(librariesDownloadDir)/BrowserWasm/staging/dotnet-net7/* $(librariesDownloadDir)/bin/wasm/dotnet && cp src/mono/wasm/test-main.js $(librariesDownloadDir)/bin/wasm/wasm-data/test-main.js && find $(librariesDownloadDir)/bin/wasm -type d && find $(librariesDownloadDir)/bin/wasm -type f -exec chmod 664 {} \; @@ -202,7 +199,7 @@ jobs: - script: "mkdir -p $(librariesDownloadDir)/bin/aot/sgen;mkdir -p $(librariesDownloadDir)/bin/aot/pack;cp -r $(librariesDownloadDir)/LinuxMonoAOT/artifacts/obj/mono/Linux.${{ parameters.archType }}.Release/mono/* $(librariesDownloadDir)/bin/aot/sgen;cp -r $(librariesDownloadDir)/LinuxMonoAOT/artifacts/bin/microsoft.netcore.app.runtime.linux-${{ parameters.archType }}/Release/* $(librariesDownloadDir)/bin/aot/pack" displayName: "Create aot directory (Linux)" - # Download AndroidMono and MauiAndroid + # Download artifacts for Android Testing - ${{ if eq(parameters.runtimeType, 'AndroidMono')}}: - template: /eng/pipelines/common/download-artifact-step.yml parameters: @@ -211,53 +208,8 @@ jobs: artifactFileName: 'AndroidMonoarm64.tar.gz' artifactName: 'AndroidMonoarm64' displayName: 'Mono Android HelloWorld' - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory) - cleanUnpackFolder: false - artifactFileName: 'MauiAndroidApp.tar.gz' - artifactName: 'MauiAndroidApp' - displayName: 'Maui Android App' - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory) - cleanUnpackFolder: false - artifactFileName: 'MauiAndroidPodcast.tar.gz' - artifactName: 'MauiAndroidPodcast' - displayName: 'Maui Android Podcast' - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory) - cleanUnpackFolder: false - artifactFileName: 'MauiBlazorAndroidApp.tar.gz' - artifactName: 'MauiBlazorAndroidApp' - displayName: 'Maui Blazor Android App' - - # Download Maui Android net6 stuff - - ${{ if eq(parameters.runtimeType, 'AndroidMobileNet6')}}: - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory) - cleanUnpackFolder: false - artifactFileName: 'MauiAndroidAppNet6.tar.gz' - artifactName: 'MauiAndroidAppNet6' - displayName: 'Maui Android App Net6' - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory) - cleanUnpackFolder: false - artifactFileName: 'MauiAndroidPodcastNet6.tar.gz' - artifactName: 'MauiAndroidPodcastNet6' - displayName: 'Maui Android Podcast Net6' - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory) - cleanUnpackFolder: false - artifactFileName: 'MauiBlazorAndroidAppNet6.tar.gz' - artifactName: 'MauiBlazorAndroidAppNet6' - displayName: 'Maui Blazor Android App Net6' - - # Download iOSMono tests and MauiiOS/MacCatalyst + + # Download iOSMono tests - ${{ if eq(parameters.runtimeType, 'iOSMono') }}: - template: /eng/pipelines/common/download-artifact-step.yml parameters: @@ -291,80 +243,6 @@ jobs: downloadPath: '$(Build.SourcesDirectory)/iosHelloWorldZip/llvmzip' artifactName: 'iOSSampleAppLLVM' checkDownloadedFiles: true - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory)/MauiiOSDefaultIPA - cleanUnpackFolder: false - artifactFileName: 'MauiiOSDefaultIPA.tar.gz' - artifactName: 'MauiiOSDefaultIPA' - displayName: 'Maui iOS IPA' - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory)/MauiMacCatalystDefault - cleanUnpackFolder: false - artifactFileName: 'MauiMacCatalystDefault.tar.gz' - artifactName: 'MauiMacCatalystDefault' - displayName: 'Maui MacCatalyst App' - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory)/MauiiOSPodcastIPA - cleanUnpackFolder: false - artifactFileName: 'MauiiOSPodcastIPA.tar.gz' - artifactName: 'MauiiOSPodcastIPA' - displayName: 'Maui iOS Podcast IPA' - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory)/MauiBlazoriOSDefaultIPA - cleanUnpackFolder: false - artifactFileName: 'MauiBlazoriOSDefaultIPA.tar.gz' - artifactName: 'MauiBlazoriOSDefaultIPA' - displayName: 'Maui Blazor iOS IPA' - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory)/MauiBlazorMacCatalystDefault - cleanUnpackFolder: false - artifactFileName: 'MauiBlazorMacCatalystDefault.tar.gz' - artifactName: 'MauiBlazorMacCatalystDefault' - displayName: 'Maui Blazor MacCatalyst App' - - - # Download Maui iOS net6 stuff - - ${{ if eq(parameters.runtimeType, 'iOSMobileNet6') }}: - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory)/MauiiOSDefaultIPA - cleanUnpackFolder: false - artifactFileName: 'MauiiOSDefaultIPANet6.tar.gz' - artifactName: 'MauiiOSDefaultIPANet6' - displayName: 'Maui iOS IPA Net6' - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory)/MauiMacCatalystDefault - cleanUnpackFolder: false - artifactFileName: 'MauiMacCatalystDefaultNet6.tar.gz' - artifactName: 'MauiMacCatalystDefaultNet6' - displayName: 'Maui MacCatalyst App Net6' - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory)/MauiiOSPodcastIPA - cleanUnpackFolder: false - artifactFileName: 'MauiiOSPodcastIPANet6.tar.gz' - artifactName: 'MauiiOSPodcastIPANet6' - displayName: 'Maui iOS Podcast IPA Net6' - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory)/MauiBlazoriOSDefaultIPA - cleanUnpackFolder: false - artifactFileName: 'MauiBlazoriOSDefaultIPANet6.tar.gz' - artifactName: 'MauiBlazoriOSDefaultIPANet6' - displayName: 'Maui Blazor iOS IPA Net6' - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - unpackFolder: $(Build.SourcesDirectory)/MauiBlazorMacCatalystDefault - cleanUnpackFolder: false - artifactFileName: 'MauiBlazorMacCatalystDefaultNet6.tar.gz' - artifactName: 'MauiBlazorMacCatalystDefaultNet6' - displayName: 'Maui Blazor MacCatalyst App Net6' # Create Core_Root - script: $(Build.SourcesDirectory)/src/tests/build$(scriptExt) $(buildConfig) $(archType) generatelayoutonly $(librariesOverrideArg) @@ -372,10 +250,10 @@ jobs: condition: and(succeeded(), ne(variables.runtimeFlavorName, 'Mono'), ne('${{ parameters.runtimeType }}', 'wasm')) # Copy the runtime directory into the testhost folder to include OOBs. - - script: "build.cmd -subset libs.pretest -configuration release -ci -arch $(archType) -testscope innerloop /p:RuntimeArtifactsPath=$(librariesDownloadDir)\\bin\\mono\\$(osGroup).$(archType).$(buildConfigUpper) /p:RuntimeFlavor=mono;xcopy $(Build.SourcesDirectory)\\artifacts\\bin\\runtime\\$(_Framework)-$(osGroup)-$(buildConfigUpper)-$(archType)\\* $(Build.SourcesDirectory)\\artifacts\\bin\\testhost\\$(_Framework)-$(osGroup)-$(buildConfigUpper)-$(archType)\\shared\\Microsoft.NETCore.App\\7.0.0 /E /I /Y;xcopy $(Build.SourcesDirectory)\\artifacts\\bin\\testhost\\$(_Framework)-$(osGroup)-$(buildConfigUpper)-$(archType)\\* $(Build.SourcesDirectory)\\.dotnet-mono /E /I /Y;copy $(Build.SourcesDirectory)\\artifacts\\bin\\coreclr\\$(osGroup).$(archType).$(buildConfigUpper)\\corerun.exe $(Build.SourcesDirectory)\\.dotnet-mono\\shared\\Microsoft.NETCore.App\\7.0.0\\corerun.exe" + - script: "build.cmd -subset libs.pretest -configuration release -ci -arch $(archType) -testscope innerloop /p:RuntimeArtifactsPath=$(librariesDownloadDir)\\bin\\mono\\$(osGroup).$(archType).$(buildConfigUpper) /p:RuntimeFlavor=mono;xcopy $(Build.SourcesDirectory)\\artifacts\\bin\\runtime\\$(_Framework)-$(osGroup)-$(buildConfigUpper)-$(archType)\\* $(Build.SourcesDirectory)\\artifacts\\bin\\testhost\\$(_Framework)-$(osGroup)-$(buildConfigUpper)-$(archType)\\shared\\Microsoft.NETCore.App\\$(productVersion) /E /I /Y;xcopy $(Build.SourcesDirectory)\\artifacts\\bin\\testhost\\$(_Framework)-$(osGroup)-$(buildConfigUpper)-$(archType)\\* $(Build.SourcesDirectory)\\.dotnet-mono /E /I /Y;copy $(Build.SourcesDirectory)\\artifacts\\bin\\coreclr\\$(osGroup).$(archType).$(buildConfigUpper)\\corerun.exe $(Build.SourcesDirectory)\\.dotnet-mono\\shared\\Microsoft.NETCore.App\\$(productVersion)\\corerun.exe" displayName: "Create mono dotnet (Windows)" - condition: and(and(succeeded(), eq(variables.runtimeFlavorName, 'Mono')), eq(variables.osGroup, 'windows'), not(in('${{ parameters.runtimeType }}', 'AndroidMono', 'iOSMono', 'AndroidMobileNet6', 'iOSMobileNet6'))) + condition: and(and(succeeded(), eq(variables.runtimeFlavorName, 'Mono')), eq(variables.osGroup, 'windows'), not(in('${{ parameters.runtimeType }}', 'AndroidMono', 'iOSMono'))) - - script: "mkdir $(Build.SourcesDirectory)/.dotnet-mono;./build.sh -subset libs.pretest -configuration release -ci -arch $(archType) -testscope innerloop /p:RuntimeArtifactsPath=$(librariesDownloadDir)/bin/mono/$(osGroup).$(archType).$(buildConfigUpper) /p:RuntimeFlavor=mono;cp $(Build.SourcesDirectory)/artifacts/bin/runtime/$(_Framework)-$(osGroup)-$(buildConfigUpper)-$(archType)/* $(Build.SourcesDirectory)/artifacts/bin/testhost/$(_Framework)-$(osGroup)-$(buildConfigUpper)-$(archType)/shared/Microsoft.NETCore.App/7.0.0 -rf;cp $(Build.SourcesDirectory)/artifacts/bin/testhost/$(_Framework)-$(osGroup)-$(buildConfigUpper)-$(archType)/* $(Build.SourcesDirectory)/.dotnet-mono -r;cp $(Build.SourcesDirectory)/artifacts/bin/coreclr/$(osGroup).$(archType).$(buildConfigUpper)/corerun $(Build.SourcesDirectory)/.dotnet-mono/shared/Microsoft.NETCore.App/7.0.0/corerun" + - script: "mkdir $(Build.SourcesDirectory)/.dotnet-mono;./build.sh -subset libs.pretest -configuration release -ci -arch $(archType) -testscope innerloop /p:RuntimeArtifactsPath=$(librariesDownloadDir)/bin/mono/$(osGroup).$(archType).$(buildConfigUpper) /p:RuntimeFlavor=mono;cp $(Build.SourcesDirectory)/artifacts/bin/runtime/$(_Framework)-$(osGroup)-$(buildConfigUpper)-$(archType)/* $(Build.SourcesDirectory)/artifacts/bin/testhost/$(_Framework)-$(osGroup)-$(buildConfigUpper)-$(archType)/shared/Microsoft.NETCore.App/$(productVersion) -rf;cp $(Build.SourcesDirectory)/artifacts/bin/testhost/$(_Framework)-$(osGroup)-$(buildConfigUpper)-$(archType)/* $(Build.SourcesDirectory)/.dotnet-mono -r;cp $(Build.SourcesDirectory)/artifacts/bin/coreclr/$(osGroup).$(archType).$(buildConfigUpper)/corerun $(Build.SourcesDirectory)/.dotnet-mono/shared/Microsoft.NETCore.App/$(productVersion)/corerun" displayName: "Create mono dotnet (Linux)" - condition: and(and(succeeded(), eq(variables.runtimeFlavorName, 'Mono')), ne(variables.osGroup, 'windows'), not(in('${{ parameters.runtimeType }}', 'AndroidMono', 'iOSMono', 'AndroidMobileNet6', 'iOSMobileNet6'))) + condition: and(and(succeeded(), eq(variables.runtimeFlavorName, 'Mono')), ne(variables.osGroup, 'windows'), not(in('${{ parameters.runtimeType }}', 'AndroidMono', 'iOSMono'))) diff --git a/eng/pipelines/coreclr/templates/run-performance-job.yml b/eng/pipelines/coreclr/templates/run-performance-job.yml index d4e25aff3f4985..344bfe78bdcf4a 100644 --- a/eng/pipelines/coreclr/templates/run-performance-job.yml +++ b/eng/pipelines/coreclr/templates/run-performance-job.yml @@ -64,29 +64,37 @@ jobs: - HelixPerfUploadTokenValue: '$(PerfCommandUploadTokenLinux)' - ${{ if and(notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.osGroup, 'windows')) }}: - HelixPerfUploadTokenValue: '$(PerfCommandUploadToken)' - - HelixPreCommandStemWindows: 'set ORIGPYPATH=%PYTHONPATH%;py -m pip install -U pip;py -3 -m venv %HELIX_WORKITEM_PAYLOAD%\.venv;call %HELIX_WORKITEM_PAYLOAD%\.venv\Scripts\activate.bat;set PYTHONPATH=;py -3 -m pip install -U pip;py -3 -m pip install azure.storage.blob==12.0.0;py -3 -m pip install azure.storage.queue==12.0.0;set "PERFLAB_UPLOAD_TOKEN=$(HelixPerfUploadTokenValue)"' + - ${{ if eq(parameters.runtimeType, 'wasm') }}: + - HelixPreCommandsWasmOnLinux: >- + sudo apt-get -y remove nodejs && + curl -fsSL https://deb.nodesource.com/setup_14.x | sudo -E bash - && + sudo apt-get -y install nodejs && + npm install --prefix $HELIX_WORKITEM_PAYLOAD jsvu -g && + $HELIX_WORKITEM_PAYLOAD/bin/jsvu --os=linux64 --engines=v8,javascriptcore + - ${{ if ne(parameters.runtimeType, 'wasm') }}: + - HelixPreCommandsWasmOnLinux: echo + - HelixPreCommandStemWindows: 'set ORIGPYPATH=%PYTHONPATH%;py -m pip install -U pip;py -3 -m venv %HELIX_WORKITEM_PAYLOAD%\.venv;call %HELIX_WORKITEM_PAYLOAD%\.venv\Scripts\activate.bat;set PYTHONPATH=;py -3 -m pip install -U pip;py -3 -m pip install urllib3==1.26.15;py -3 -m pip install azure.storage.blob==12.0.0;py -3 -m pip install azure.storage.queue==12.0.0;set "PERFLAB_UPLOAD_TOKEN=$(HelixPerfUploadTokenValue)"' - HelixPreCommandStemLinux: >- export ORIGPYPATH=$PYTHONPATH export CRYPTOGRAPHY_ALLOW_OPENSSL_102=true; echo "** Installing prerequistes **"; - python3 -m pip install -U pip && + python3 -m pip install --user -U pip && sudo apt-get -y install python3-venv && python3 -m venv $HELIX_WORKITEM_PAYLOAD/.venv && ls -l $HELIX_WORKITEM_PAYLOAD/.venv/bin/activate && export PYTHONPATH= && - python3 -m pip install -U pip && - pip3 install azure.storage.blob==12.0.0 && - pip3 install azure.storage.queue==12.0.0 && + python3 -m pip install --user -U pip && + pip3 install urllib3==1.26.15 && + pip3 install --user azure.storage.blob==12.0.0 && + pip3 install --user azure.storage.queue==12.0.0 && sudo apt-get update && sudo apt -y install curl dirmngr apt-transport-https lsb-release ca-certificates && - curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - && - sudo apt-get -y install nodejs && - npm install --prefix $HELIX_WORKITEM_PAYLOAD jsvu -g && - $HELIX_WORKITEM_PAYLOAD/bin/jsvu --os=linux64 --engines=v8,javascriptcore && - export PERFLAB_UPLOAD_TOKEN="$(HelixPerfUploadTokenValue)" && - export PERF_PREREQS_INSTALLED=1; - test "x$PERF_PREREQS_INSTALLED" = "x1" || echo "** Error: Failed to install prerequites **" - - HelixPreCommandStemMusl: 'export ORIGPYPATH=$PYTHONPATH;sudo apk add icu-libs krb5-libs libgcc libintl libssl1.1 libstdc++ zlib cargo;sudo apk add libgdiplus --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing; python3 -m venv $HELIX_WORKITEM_PAYLOAD/.venv;source $HELIX_WORKITEM_PAYLOAD/.venv/bin/activate;export PYTHONPATH=;python3 -m pip install -U pip;pip3 install azure.storage.blob==12.7.1;pip3 install azure.storage.queue==12.1.5;export PERFLAB_UPLOAD_TOKEN="$(HelixPerfUploadTokenValue)"' + $(HelixPreCommandsWasmOnLinux) && + export DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER=1 && + export PERFLAB_UPLOAD_TOKEN="$(HelixPerfUploadTokenValue)" + || export PERF_PREREQS_INSTALL_FAILED=1; + test "x$PERF_PREREQS_INSTALL_FAILED" = "x1" && echo "** Error: Failed to install prerequites **" + - HelixPreCommandStemMusl: 'export ORIGPYPATH=$PYTHONPATH;sudo apk add icu-libs krb5-libs libgcc libintl libssl1.1 libstdc++ zlib cargo;sudo apk add libgdiplus --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing; python3 -m venv $HELIX_WORKITEM_PAYLOAD/.venv;source $HELIX_WORKITEM_PAYLOAD/.venv/bin/activate;export PYTHONPATH=;python3 -m pip install -U pip;pip3 install urllib3==1.26.15;pip3 install azure.storage.blob==12.7.1;pip3 install azure.storage.queue==12.1.5;export PERFLAB_UPLOAD_TOKEN="$(HelixPerfUploadTokenValue)"' - ExtraMSBuildLogsWindows: 'set MSBUILDDEBUGCOMM=1;set "MSBUILDDEBUGPATH=%HELIX_WORKITEM_UPLOAD_ROOT%"' - ExtraMSBuildLogsLinux: 'export MSBUILDDEBUGCOMM=1;export "MSBUILDDEBUGPATH=$HELIX_WORKITEM_UPLOAD_ROOT"' - HelixPreCommand: '' @@ -148,11 +156,11 @@ jobs: _Framework: ${{ framework }} steps: - ${{ parameters.steps }} - - powershell: $(Build.SourcesDirectory)\eng\testing\performance\performance-setup.ps1 $(IsInternal)$(Interpreter) -Framework $(_Framework) -Kind ${{ parameters.runKind }} -LogicalMachine ${{ parameters.logicalMachine }} ${{ parameters.pgoRunType }} ${{ parameters.extraSetupParameters }} + - powershell: $(Build.SourcesDirectory)\eng\testing\performance\performance-setup.ps1 $(IsInternal)$(Interpreter) -Framework $(_Framework) -Kind ${{ parameters.runKind }} -LogicalMachine ${{ parameters.logicalMachine }} ${{ parameters.pgoRunType }} -UseLocalCommitTime ${{ parameters.extraSetupParameters }} displayName: Performance Setup (Windows) condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT')) continueOnError: ${{ parameters.continueOnError }} - - script: $(Build.SourcesDirectory)/eng/testing/performance/performance-setup.sh $(IsInternal)$(Interpreter) --framework $(_Framework) --kind ${{ parameters.runKind }} --logicalmachine ${{ parameters.logicalMachine }} ${{ parameters.extraSetupParameters }} + - script: $(Build.SourcesDirectory)/eng/testing/performance/performance-setup.sh $(IsInternal)$(Interpreter) --framework $(_Framework) --kind ${{ parameters.runKind }} --logicalmachine ${{ parameters.logicalMachine }} --uselocalcommittime ${{ parameters.extraSetupParameters }} displayName: Performance Setup (Unix) condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/pipelines/coreclr/templates/run-scenarios-job.yml b/eng/pipelines/coreclr/templates/run-scenarios-job.yml index f22aa119d0d81c..f68bdedd85e61f 100644 --- a/eng/pipelines/coreclr/templates/run-scenarios-job.yml +++ b/eng/pipelines/coreclr/templates/run-scenarios-job.yml @@ -63,16 +63,16 @@ jobs: - SharedHelixPreCommands: 'chmod +x $HELIX_WORKITEM_PAYLOAD/machine-setup.sh;. $HELIX_WORKITEM_PAYLOAD/machine-setup.sh;export PYTHONPATH=$HELIX_WORKITEM_PAYLOAD/scripts:$HELIX_WORKITEM_PAYLOAD' - ${{ if eq(parameters.osGroup, 'windows') }}: - - HelixPreCommandWindows: 'set ORIGPYPATH=%PYTHONPATH%;py -3 -m venv %HELIX_WORKITEM_PAYLOAD%\.venv;call %HELIX_WORKITEM_PAYLOAD%\.venv\Scripts\activate.bat;set PYTHONPATH=;py -3 -m pip install -U pip;py -3 -m pip install --user azure.storage.blob==12.0.0 --force-reinstall;py -3 -m pip install --user azure.storage.queue==12.0.0 --force-reinstall;set "PERFLAB_UPLOAD_TOKEN=$(PerfCommandUploadToken)"' + - HelixPreCommandWindows: 'set ORIGPYPATH=%PYTHONPATH%;py -3 -m venv %HELIX_WORKITEM_PAYLOAD%\.venv;call %HELIX_WORKITEM_PAYLOAD%\.venv\Scripts\activate.bat;set PYTHONPATH=;py -3 -m pip install -U pip;py -3 -m pip install --user azure.storage.blob==12.0.0;py -3 -m pip install --user azure.storage.queue==12.0.0;py -3 -m pip install --user urllib3==1.26.15;set "PERFLAB_UPLOAD_TOKEN=$(PerfCommandUploadToken)"' - HelixPostCommandsWindows: 'set PYTHONPATH=%ORIGPYPATH%' - ${{ if and(ne(parameters.osGroup, 'windows'), ne(parameters.osGroup, 'OSX'), ne(parameters.osSubGroup, '_musl')) }}: - - HelixPreCommandLinux: 'export ORIGPYPATH=$PYTHONPATH;export CRYPTOGRAPHY_ALLOW_OPENSSL_102=true;sudo apt-get -y install python3-venv;python3 -m venv $HELIX_WORKITEM_PAYLOAD/.venv;source $HELIX_WORKITEM_PAYLOAD/.venv/bin/activate;export PYTHONPATH=;python3 -m pip install -U pip;pip3 install --user azure.storage.blob==12.0.0 --force-reinstall;pip3 install --user azure.storage.queue==12.0.0 --force-reinstall;export PERFLAB_UPLOAD_TOKEN="$(PerfCommandUploadTokenLinux)"' + - HelixPreCommandLinux: 'export ORIGPYPATH=$PYTHONPATH;export CRYPTOGRAPHY_ALLOW_OPENSSL_102=true;sudo apt-get -y install python3-venv;python3 -m venv $HELIX_WORKITEM_PAYLOAD/.venv;source $HELIX_WORKITEM_PAYLOAD/.venv/bin/activate;export PYTHONPATH=;python3 -m pip install -U pip;pip3 install --user azure.storage.blob==12.0.0;pip3 install --user azure.storage.queue==12.0.0;pip3 install --user urllib3==1.26.15;export PERFLAB_UPLOAD_TOKEN="$(PerfCommandUploadTokenLinux)"' - HelixPostCommandsLinux: 'export PYTHONPATH=$ORIGPYPATH' - ${{ if and(ne(parameters.osGroup, 'windows'), ne(parameters.osGroup, 'OSX'), eq(parameters.osSubGroup, '_musl')) }}: - - HelixPreCommandMusl: 'export ORIGPYPATH=$PYTHONPATH;sudo apk add py3-virtualenv;python3 -m venv $HELIX_WORKITEM_PAYLOAD/.venv;source $HELIX_WORKITEM_PAYLOAD/.venv/bin/activate;export PYTHONPATH=;python3 -m pip install -U pip;pip3 install --user azure.storage.blob==12.0.0 --force-reinstall;pip3 install --user azure.storage.queue==12.0.0 --force-reinstall;export PERFLAB_UPLOAD_TOKEN="$(PerfCommandUploadTokenLinux)"' + - HelixPreCommandMusl: 'export ORIGPYPATH=$PYTHONPATH;sudo apk add py3-virtualenv;python3 -m venv $HELIX_WORKITEM_PAYLOAD/.venv;source $HELIX_WORKITEM_PAYLOAD/.venv/bin/activate;export PYTHONPATH=;python3 -m pip install -U pip;pip3 install --user azure.storage.blob==12.0.0;pip3 install --user azure.storage.queue==12.0.0;pip3 install --user urllib3==1.26.15;export PERFLAB_UPLOAD_TOKEN="$(PerfCommandUploadTokenLinux)"' - HelixPostCommandsMusl: 'export PYTHONPATH=$ORIGPYPATH' - ${{ if eq(parameters.osGroup, 'OSX') }}: - - HelixPreCommandOSX: 'export ORIGPYPATH=$PYTHONPATH;export CRYPTOGRAPHY_ALLOW_OPENSSL_102=true;python3 -m venv $HELIX_WORKITEM_PAYLOAD/.venv;source $HELIX_WORKITEM_PAYLOAD/.venv/bin/activate;export PYTHONPATH=;python3 -m pip install -U pip;pip3 install azure.storage.blob==12.0.0 --force-reinstall;pip3 install azure.storage.queue==12.0.0 --force-reinstall;export PERFLAB_UPLOAD_TOKEN="$(PerfCommandUploadTokenLinux)"' + - HelixPreCommandOSX: 'export ORIGPYPATH=$PYTHONPATH;export CRYPTOGRAPHY_ALLOW_OPENSSL_102=true;python3 -m venv $HELIX_WORKITEM_PAYLOAD/.venv;source $HELIX_WORKITEM_PAYLOAD/.venv/bin/activate;export PYTHONPATH=;python3 -m pip install -U pip;pip3 install azure.storage.blob==12.0.0;pip3 install azure.storage.queue==12.0.0;pip3 install urllib3==1.26.15;export PERFLAB_UPLOAD_TOKEN="$(PerfCommandUploadTokenLinux)"' - HelixPostCommandOSX: 'export PYTHONPATH=$ORIGPYPATH' # extra private job settings @@ -107,6 +107,7 @@ jobs: - AdditionalHelixPreCommands: $(HelixPreCommandOSX) - AdditionalHelixPostCommands: $(HelixPostCommandOSX) + - ExtraSetupArguments: '' - ${{ if ne(parameters.runtimeType, 'wasm') }}: - ExtraSetupArguments: --install-dir $(PayloadDirectory)/dotnet - ${{ if and(eq(parameters.runtimeType, 'wasm'), in(variables['Build.Reason'], 'PullRequest')) }}: @@ -126,11 +127,11 @@ jobs: steps: - ${{ parameters.steps }} # run performance-setup - - powershell: $(Build.SourcesDirectory)\eng\testing\performance\performance-setup.ps1 $(IsInternal) -Framework $(_Framework) -Kind ${{ parameters.runKind }} -LogicalMachine ${{ parameters.logicalMachine }} ${{ parameters.extraSetupParameters }} ${{ parameters.additionalSetupParameters }} + - powershell: $(Build.SourcesDirectory)\eng\testing\performance\performance-setup.ps1 $(IsInternal) -Framework $(_Framework) -Kind ${{ parameters.runKind }} -LogicalMachine ${{ parameters.logicalMachine }} -UseLocalCommitTime ${{ parameters.extraSetupParameters }} ${{ parameters.additionalSetupParameters }} displayName: Performance Setup (Windows) condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT')) continueOnError: ${{ parameters.continueOnError }} - - script: $(Build.SourcesDirectory)/eng/testing/performance/performance-setup.sh $(IsInternal) --framework $(_Framework) --kind ${{ parameters.runKind }} --logicalmachine ${{ parameters.logicalMachine }} ${{ parameters.extraSetupParameters }} ${{ parameters.additionalSetupParameters }} + - script: $(Build.SourcesDirectory)/eng/testing/performance/performance-setup.sh $(IsInternal) --framework $(_Framework) --kind ${{ parameters.runKind }} --logicalmachine ${{ parameters.logicalMachine }} --uselocalcommittime ${{ parameters.extraSetupParameters }} ${{ parameters.additionalSetupParameters }} displayName: Performance Setup (Linux/MAC) condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) continueOnError: ${{ parameters.continueOnError }} @@ -144,7 +145,7 @@ jobs: # copy wasm packs if running on wasm - script: >- mkdir -p $(librariesDownloadDir)/bin/wasm/data && - cp -r $(librariesDownloadDir)/BrowserWasm/staging/dotnet-workload $(librariesDownloadDir)/bin/wasm && + cp -r $(librariesDownloadDir)/BrowserWasm/staging/dotnet-net7 $(librariesDownloadDir)/bin/wasm && cp src/mono/wasm/test-main.js $(librariesDownloadDir)/bin/wasm/data/test-main.js && find $(librariesDownloadDir)/bin/wasm -type f -exec chmod 664 {} \; displayName: "Create wasm directory (Linux)" @@ -157,36 +158,36 @@ jobs: displayName: Copy scenario support files (Linux/MAC) condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) # build Startup - - script: $(PayloadDirectory)\dotnet\dotnet.exe publish -c Release -o $(WorkItemDirectory)\Startup -f net6.0 -r win-$(Architecture) $(PerformanceDirectory)\src\tools\ScenarioMeasurement\Startup\Startup.csproj + - script: $(PayloadDirectory)\dotnet\dotnet.exe publish -c Release -o $(WorkItemDirectory)\Startup -f net7.0 -r win-$(Architecture) $(PerformanceDirectory)\src\tools\ScenarioMeasurement\Startup\Startup.csproj -p:DisableTransitiveFrameworkReferenceDownloads=true displayName: Build Startup tool (Windows) env: - PERFLAB_TARGET_FRAMEWORKS: net6.0 + PERFLAB_TARGET_FRAMEWORKS: net7.0 condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT')) - - script: $(PayloadDirectory)/dotnet/dotnet publish -c Release -o $(WorkItemDirectory)/startup -f net6.0 -r linux-$(Architecture) $(PerformanceDirectory)/src/tools/ScenarioMeasurement/Startup/Startup.csproj + - script: $(PayloadDirectory)/dotnet/dotnet publish -c Release -o $(WorkItemDirectory)/startup -f net7.0 -r linux-$(Architecture) $(PerformanceDirectory)/src/tools/ScenarioMeasurement/Startup/Startup.csproj -p:DisableTransitiveFrameworkReferenceDownloads=true displayName: Build Startup tool (Linux) env: - PERFLAB_TARGET_FRAMEWORKS: net6.0 + PERFLAB_TARGET_FRAMEWORKS: net7.0 condition: and(succeeded(), eq(variables['Agent.Os'], 'Linux')) - - script: $(PayloadDirectory)/dotnet/dotnet publish -c Release -o $(WorkItemDirectory)/startup -f net6.0 -r osx-$(Architecture) $(PerformanceDirectory)/src/tools/ScenarioMeasurement/Startup/Startup.csproj + - script: $(PayloadDirectory)/dotnet/dotnet publish -c Release -o $(WorkItemDirectory)/startup -f net7.0 -r osx-$(Architecture) $(PerformanceDirectory)/src/tools/ScenarioMeasurement/Startup/Startup.csproj -p:DisableTransitiveFrameworkReferenceDownloads=true displayName: Build Startup tool (MAC) env: - PERFLAB_TARGET_FRAMEWORKS: net6.0 + PERFLAB_TARGET_FRAMEWORKS: net7.0 condition: and(succeeded(), eq(variables['Agent.Os'], 'Darwin')) # build SizeOnDisk - - script: $(PayloadDirectory)\dotnet\dotnet.exe publish -c Release -o $(WorkItemDirectory)\SOD -f net6.0 -r win-$(Architecture) $(PerformanceDirectory)\src\tools\ScenarioMeasurement\SizeOnDisk\SizeOnDisk.csproj + - script: $(PayloadDirectory)\dotnet\dotnet.exe publish -c Release -o $(WorkItemDirectory)\SOD -f net7.0 -r win-$(Architecture) $(PerformanceDirectory)\src\tools\ScenarioMeasurement\SizeOnDisk\SizeOnDisk.csproj -p:DisableTransitiveFrameworkReferenceDownloads=true displayName: Build SizeOnDisk tool (Windows) env: - PERFLAB_TARGET_FRAMEWORKS: net6.0 + PERFLAB_TARGET_FRAMEWORKS: net7.0 condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT')) - - script: $(PayloadDirectory)/dotnet/dotnet publish -c Release -o $(WorkItemDirectory)/SOD -f net6.0 -r linux-$(Architecture) $(PerformanceDirectory)/src/tools/ScenarioMeasurement/SizeOnDisk/SizeOnDisk.csproj + - script: $(PayloadDirectory)/dotnet/dotnet publish -c Release -o $(WorkItemDirectory)/SOD -f net7.0 -r linux-$(Architecture) $(PerformanceDirectory)/src/tools/ScenarioMeasurement/SizeOnDisk/SizeOnDisk.csproj -p:DisableTransitiveFrameworkReferenceDownloads=true displayName: Build SizeOnDisk tool (Linux) env: - PERFLAB_TARGET_FRAMEWORKS: net6.0 + PERFLAB_TARGET_FRAMEWORKS: net7.0 condition: and(succeeded(), eq(variables['Agent.Os'], 'Linux')) - - script: $(PayloadDirectory)/dotnet/dotnet publish -c Release -o $(WorkItemDirectory)/SOD -f net6.0 -r osx-$(Architecture) $(PerformanceDirectory)/src/tools/ScenarioMeasurement/SizeOnDisk/SizeOnDisk.csproj + - script: $(PayloadDirectory)/dotnet/dotnet publish -c Release -o $(WorkItemDirectory)/SOD -f net7.0 -r osx-$(Architecture) $(PerformanceDirectory)/src/tools/ScenarioMeasurement/SizeOnDisk/SizeOnDisk.csproj -p:DisableTransitiveFrameworkReferenceDownloads=true displayName: Build SizeOnDisk tool (MAC) env: - PERFLAB_TARGET_FRAMEWORKS: net6.0 + PERFLAB_TARGET_FRAMEWORKS: net7.0 condition: and(succeeded(), eq(variables['Agent.Os'], 'Darwin')) # Zip the workitem directory (for xharness based workitems) diff --git a/eng/pipelines/coreclr/templates/sign-diagnostic-files.yml b/eng/pipelines/coreclr/templates/sign-diagnostic-files.yml new file mode 100644 index 00000000000000..578a4f9999d5a7 --- /dev/null +++ b/eng/pipelines/coreclr/templates/sign-diagnostic-files.yml @@ -0,0 +1,80 @@ +parameters: + basePath: '' + isOfficialBuild: '' + timeoutInMinutes: '' + +steps: +- ${{ if and(eq(parameters.isOfficialBuild, true), ne(variables['Build.Reason'], 'PullRequest'), or(startswith(variables['Build.SourceBranch'], 'refs/heads/release/'), startswith(variables['Build.SourceBranch'], 'refs/heads/internal/release/'), startswith(variables['Build.SourceBranch'], 'refs/heads/main'))) }}: + - task: UseDotNet@2 + displayName: Install .NET 6 SDK for signing. + inputs: + packageType: 'sdk' + version: '6.0.x' + installationPath: '$(Agent.TempDirectory)/dotnet' + + - task: EsrpCodeSigning@1 + displayName: Sign Diagnostic Binaries + inputs: + ConnectedServiceName: 'dotnetesrp-diagnostics-dnceng' + FolderPath: ${{ parameters.basePath }} + Pattern: | + **/mscordaccore*.dll + **/mscordbi*.dll + UseMinimatch: true + signConfigType: 'inlineSignParams' + inlineOperation: >- + [ + { + "keyCode": "CP-471322", + "operationCode": "SigntoolSign", + "parameters": { + "OpusName": "Microsoft", + "OpusInfo": "http://www.microsoft.com", + "PageHash": "/NPH", + "FileDigest": "/fd sha256", + "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + }, + "toolName": "sign", + "toolVersion": "1.0" + }, + { + "KeyCode": "CP-471322", + "OperationCode": "SigntoolVerify", + "Parameters": {}, + "ToolName": "sign", + "ToolVersion": "1.0" + } + ] + SessionTimeout: ${{ parameters.timeoutInMinutes }} + MaxConcurrency: '50' + MaxRetryAttempts: '5' + env: + DOTNET_MULTILEVEL_LOOKUP: 0 + DOTNET_ROOT: '$(Agent.TempDirectory)/dotnet' + DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR: '$(Agent.TempDirectory)/dotnet' + + - powershell: | + $filesToSign = $(Get-ChildItem -Recurse ${{ parameters.basePath }} -Include mscordaccore*.dll, mscordbi*.dll) + foreach ($file in $filesToSign) { + $signingCert = $(Get-AuthenticodeSignature $file).SignerCertificate + if ($signingCert -eq $null) + { + throw "File $file does not contain a signature." + } + + if ($signingCert.Subject -ne "CN=.NET DAC, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" ` + -or $signingCert.Issuer -ne "CN=Microsoft Code Signing PCA 2010, O=Microsoft Corporation, L=Redmond, S=Washington, C=US") + { + throw "File $file not in expected trust chain." + } + + $certEKU = $signingCert.Extensions.Where({ $_.Oid.FriendlyName -eq "Enhanced Key Usage" }) | Select -First 1 + + if ($certEKU.EnhancedKeyUsages.Where({ $_.Value -eq "1.3.6.1.4.1.311.84.4.1" }).Count -ne 1) + { + throw "Signature for $file does not contain expected EKU." + } + + Write-Host "$file is correctly signed." + } + displayName: Validate diagnostic signatures diff --git a/eng/pipelines/installer/jobs/base-job.yml b/eng/pipelines/installer/jobs/base-job.yml index d5b83707d77257..0155c095669c53 100644 --- a/eng/pipelines/installer/jobs/base-job.yml +++ b/eng/pipelines/installer/jobs/base-job.yml @@ -98,6 +98,9 @@ jobs: ${{ if in(parameters.osGroup, 'Linux', 'FreeBSD') }}: value: '/root/runtime/' + - ${{ if eq(variables['System.TeamProject'], 'internal') }}: + - group: AzureDevOps-Artifact-Feeds-Pats + ### ### Platform-specific variable setup ### @@ -380,7 +383,7 @@ jobs: displayName: 'Libraries artifacts' cleanUnpackFolder: false - - ${{ if in(parameters.osGroup, 'OSX', 'iOS', 'tvOS') }}: + - ${{ if in(parameters.osGroup, 'OSX', 'MacCatalyst', 'iOS', 'iOSSimulator', 'tvOS', 'tvOSSimulator') }}: - script: $(Build.SourcesDirectory)/eng/install-native-dependencies.sh ${{ parameters.osGroup }} ${{ parameters.archType }} azDO displayName: Install Build Dependencies @@ -389,6 +392,24 @@ jobs: df -h displayName: Disk Usage before Build + - ${{ if ne(variables['System.TeamProject'], 'public') }}: + - ${{ if ne(parameters.osGroup, 'windows') }}: + - task: Bash@3 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + arguments: $(Build.SourcesDirectory)/NuGet.config $Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - ${{ if eq(parameters.osGroup, 'windows') }}: + - task: PowerShell@2 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 + arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + # Build the default subset non-MacOS platforms - ${{ if ne(parameters.osGroup, 'OSX') }}: - script: $(BaseJobBuildCommand) @@ -453,20 +474,6 @@ jobs: continueOnError: true condition: succeededOrFailed() - - ${{ if and(eq(parameters.osGroup, 'windows'), eq(parameters.isOfficialBuild, true)) }}: - - task: NuGetCommand@2 - displayName: Push Visual Studio NuPkgs - inputs: - command: push - packagesToPush: '$(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/*/VS.Redist.Common.*.nupkg' - nuGetFeedType: external - publishFeedCredentials: 'DevDiv - VS package feed' - condition: and( - succeeded(), - eq(variables['_BuildConfig'], 'Release'), - ne(variables['DisableVSPublish'], 'true'), - ne(variables['PostBuildSign'], 'true')) - - template: steps/upload-job-artifacts.yml parameters: name: ${{ coalesce(parameters.name, parameters.platform) }} diff --git a/eng/pipelines/libraries/base-job.yml b/eng/pipelines/libraries/base-job.yml index 3e5119b3e8a83f..85ba1f4733de5b 100644 --- a/eng/pipelines/libraries/base-job.yml +++ b/eng/pipelines/libraries/base-job.yml @@ -48,6 +48,7 @@ jobs: variables: - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - group: DotNet-HelixApi-Access + - group: AzureDevOps-Artifact-Feeds-Pats - _buildScriptFileName: build @@ -148,4 +149,22 @@ jobs: artifactName: '$(_runtimeArtifactName)' displayName: '$(runtimeFlavorName) build drop' + - ${{ if ne(variables['System.TeamProject'], 'public') }}: + - ${{ if ne(parameters.osGroup, 'windows') }}: + - task: Bash@3 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + arguments: $(Build.SourcesDirectory)/NuGet.config $Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - ${{ if eq(parameters.osGroup, 'windows') }}: + - task: PowerShell@2 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 + arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - ${{ parameters.steps }} diff --git a/eng/pipelines/libraries/build-job.yml b/eng/pipelines/libraries/build-job.yml index 5e84ed9b06b7c8..72611feef3bb48 100644 --- a/eng/pipelines/libraries/build-job.yml +++ b/eng/pipelines/libraries/build-job.yml @@ -87,7 +87,7 @@ jobs: - ${{ if eq(parameters.isOfficialBuild, true) }}: - template: /eng/pipelines/common/restore-internal-tools.yml - - ${{ if in(parameters.osGroup, 'OSX', 'iOS', 'tvOS') }}: + - ${{ if in(parameters.osGroup, 'OSX', 'MacCatalyst', 'iOS', 'iOSSimulator', 'tvOS', 'tvOSSimulator') }}: - script: $(Build.SourcesDirectory)/eng/install-native-dependencies.sh ${{ parameters.osGroup }} ${{ parameters.archType }} azDO displayName: Install Build Dependencies diff --git a/eng/pipelines/libraries/enterprise/linux.yml b/eng/pipelines/libraries/enterprise/linux.yml index ca2ec8777146d6..2a6e5ea99e880d 100644 --- a/eng/pipelines/libraries/enterprise/linux.yml +++ b/eng/pipelines/libraries/enterprise/linux.yml @@ -33,7 +33,7 @@ jobs: - job: EnterpriseLinuxTests timeoutInMinutes: 120 pool: - name: NetCore1ESPool-Public + name: NetCore-Svc-Public demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open steps: - bash: | diff --git a/eng/pipelines/libraries/helix-queues-setup.yml b/eng/pipelines/libraries/helix-queues-setup.yml index cac632e79177b1..54a2d7280986f4 100644 --- a/eng/pipelines/libraries/helix-queues-setup.yml +++ b/eng/pipelines/libraries/helix-queues-setup.yml @@ -28,60 +28,59 @@ jobs: # Linux arm - ${{ if eq(parameters.platform, 'Linux_arm') }}: - ${{ if or(eq(parameters.jobParameters.isExtraPlatforms, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: - - (Debian.10.Arm32.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-arm32v7-20210304164340-6616c63 - - (Debian.11.Arm32.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-arm32v7-20210304164347-5a7c380 + - (Debian.10.Arm32.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-arm32v7 + - (Debian.11.Arm32.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-arm32v7 # Linux armv6 - ${{ if eq(parameters.platform, 'Linux_armv6') }}: # - ${{ if eq(parameters.jobParameters.isFullMatrix, true) }}: - - (Raspbian.10.Armv6.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:raspbian-10-helix-arm32v6-20211215185610-60748cc + - (Raspbian.10.Armv6.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:raspbian-10-helix-arm32v6 # Linux arm64 - ${{ if eq(parameters.platform, 'Linux_arm64') }}: - ${{ if or(eq(parameters.jobParameters.isExtraPlatforms, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: - - (Ubuntu.2204.Arm64.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-22.04-helix-arm64v8-20220504035342-1b9461f + - (Ubuntu.2204.Arm64.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-22.04-helix-arm64v8 - ${{ if or(ne(parameters.jobParameters.isExtraPlatforms, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: - - (Ubuntu.1804.ArmArch.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm64v8-20220427172132-97d8652 + - (Ubuntu.1804.Arm64.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm64v8 # Linux musl x64 - ${{ if eq(parameters.platform, 'Linux_musl_x64') }}: - ${{ if or(ne(parameters.jobParameters.isExtraPlatforms, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: - - (Alpine.314.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-amd64-20220803180115-99b3286 + - (Alpine.314.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-amd64 - ${{ if or(eq(parameters.jobParameters.isExtraPlatforms, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: - - (Alpine.313.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.13-helix-amd64-20210910135845-8a6f4f3 + - (Alpine.313.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.13-helix-amd64 # Linux musl arm64 - ${{ if and(eq(parameters.platform, 'Linux_musl_arm64'), or(eq(parameters.jobParameters.isExtraPlatforms, true), eq(parameters.jobParameters.includeAllPlatforms, true))) }}: - - (Alpine.313.Arm64.Open)ubuntu.1804.armarch.open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.13-helix-arm64v8-20210910135808-8a6f4f3 - - (Alpine.314.Arm64.Open)ubuntu.1804.armarch.open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-arm64v8-20210910135810-8a6f4f3 + - (Alpine.313.Arm64.Open)ubuntu.1804.armarch.open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.13-helix-arm64v8 + - (Alpine.314.Arm64.Open)ubuntu.1804.armarch.open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.14-helix-arm64v8 # Linux x64 - ${{ if eq(parameters.platform, 'Linux_x64') }}: - ${{ if and(eq(parameters.jobParameters.interpreter, ''), ne(parameters.jobParameters.isSingleFile, true)) }}: - ${{ if and(eq(parameters.jobParameters.testScope, 'outerloop'), eq(parameters.jobParameters.runtimeFlavor, 'mono')) }}: - - (Centos.8.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-8-helix-20201229003624-c1bf759 + - (Centos.8.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-8-helix - RedHat.7.Amd64.Open - SLES.15.Amd64.Open - - (Fedora.34.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix-20220809205730-e7e8d1c - - (Ubuntu.2204.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-22.04-helix-amd64-20220504035722-1b9461f - - (Debian.10.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-20220810215022-f344011 + - (Fedora.34.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix + - (Ubuntu.2204.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-22.04-helix-amd64 + - (Debian.10.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64 - ${{ if or(ne(parameters.jobParameters.testScope, 'outerloop'), ne(parameters.jobParameters.runtimeFlavor, 'mono')) }}: - ${{ if or(eq(parameters.jobParameters.isExtraPlatforms, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: - - (Centos.8.Amd64.Open)Ubuntu.1604.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-8-helix-20201229003624-c1bf759 - SLES.15.Amd64.Open - - (Fedora.34.Amd64.Open)ubuntu.1804.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix-20220809205730-e7e8d1c + - (Fedora.34.Amd64.Open)ubuntu.1804.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix - Ubuntu.2204.Amd64.Open - - (Debian.11.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64-20220810215032-f344011 - - (Mariner.1.0.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:cbl-mariner-1.0-helix-20210528192219-92bf620 - - (openSUSE.15.2.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:opensuse-15.2-helix-amd64-20211018152525-9cc02fe + - (Debian.11.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64 + - (Mariner.1.0.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:cbl-mariner-1.0-helix + - (openSUSE.15.2.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:opensuse-15.2-helix-amd64 - ${{ if or(ne(parameters.jobParameters.isExtraPlatforms, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: - - (Centos.7.Amd64.Open)Ubuntu.1604.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-7-mlnet-helix-20220601183719-dde38af + - (Centos.7.Amd64.Open)Ubuntu.1604.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-7-mlnet-helix - RedHat.7.Amd64.Open - - (Debian.10.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-20220810215022-f344011 + - (Debian.10.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64 - Ubuntu.1804.Amd64.Open - ${{ if or(eq(parameters.jobParameters.interpreter, 'true'), eq(parameters.jobParameters.isSingleFile, true)) }}: # Limiting interp runs as we don't need as much coverage. - - (Debian.10.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-20210304164434-56c6673 + - (Debian.10.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64 # Linux s390x - ${{ if eq(parameters.platform, 'Linux_s390x') }}: @@ -115,19 +114,19 @@ jobs: # iOS devices - ${{ if in(parameters.platform, 'iOS_arm64') }}: - # split traffic for runtime-extra-platforms (which mostly runs on rolling builds) + # if necessary, you can split traffic between queues this way for PR's and rolling builds - ${{ if ne(parameters.jobParameters.isExtraPlatforms, true) }}: - - OSX.1015.Amd64.Iphone.Open + - OSX.13.Amd64.Iphone.Open - ${{ if eq(parameters.jobParameters.isExtraPlatforms, true) }}: - - OSX.1200.Amd64.Iphone.Open + - OSX.13.Amd64.Iphone.Open # tvOS devices - ${{ if in(parameters.platform, 'tvOS_arm64') }}: - # split traffic for runtime-extra-platforms (which mostly runs on rolling builds) + # if necessary, you can split traffic between queues this way for PR's and rolling builds - ${{ if ne(parameters.jobParameters.isExtraPlatforms, true) }}: - - OSX.1015.Amd64.AppleTV.Open + - OSX.13.Amd64.AppleTV.Open - ${{ if eq(parameters.jobParameters.isExtraPlatforms, true) }}: - - OSX.1100.Amd64.AppleTV.Open + - OSX.13.Amd64.AppleTV.Open # windows x64 - ${{ if eq(parameters.platform, 'windows_x64') }}: @@ -141,15 +140,15 @@ jobs: - ${{ if or(eq(parameters.jobParameters.isExtraPlatforms, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: - Windows.Amd64.Server2022.Open - ${{ if ne(parameters.jobParameters.testScope, 'outerloop') }}: - - (Windows.10.Amd64.ServerRS5.Open)windows.10.amd64.serverrs5.open@mcr.microsoft.com/dotnet-buildtools/prereqs:windowsservercore-ltsc2019-helix-amd64-20220502135740-56c6673 + - (Windows.10.Amd64.ServerRS5.Open)windows.10.amd64.serverrs5.open@mcr.microsoft.com/dotnet-buildtools/prereqs:windowsservercore-ltsc2019-helix-amd64 - ${{ if or(ne(parameters.jobParameters.isExtraPlatforms, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: - Windows.81.Amd64.Open - - Windows.10.Amd64.Server2022.ES.Open + - Windows.Amd64.Server2022.Open - Windows.11.Amd64.Client.Open - ${{ if eq(parameters.jobParameters.testScope, 'outerloop') }}: - - (Windows.10.Amd64.ServerRS5.Open)windows.10.amd64.serverrs5.open@mcr.microsoft.com/dotnet-buildtools/prereqs:windowsservercore-ltsc2019-helix-amd64-20220502135740-56c6673 + - (Windows.10.Amd64.ServerRS5.Open)windows.10.amd64.serverrs5.open@mcr.microsoft.com/dotnet-buildtools/prereqs:windowsservercore-ltsc2019-helix-amd64 - ${{ if ne(parameters.jobParameters.runtimeFlavor, 'mono') }}: - - (Windows.Nano.1809.Amd64.Open)windows.10.amd64.serverrs5.open@mcr.microsoft.com/dotnet-buildtools/prereqs:nanoserver-1809-helix-amd64-08e8e40-20200107182504 + - (Windows.Nano.1809.Amd64.Open)windows.10.amd64.serverrs5.open@mcr.microsoft.com/dotnet-buildtools/prereqs:nanoserver-1809-helix-amd64 # .NETFramework - ${{ if eq(parameters.jobParameters.framework, 'net48') }}: @@ -170,22 +169,20 @@ jobs: - Windows.11.Amd64.Client.Open - Windows.Amd64.Server2022.Open - ${{ if or(ne(parameters.jobParameters.isExtraPlatforms, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: - - Windows.10.Amd64.Server2022.ES.Open + - Windows.Amd64.Server2022.Open - Windows.7.Amd64.Open # .NETFramework - ${{ if eq(parameters.jobParameters.framework, 'net48') }}: - - Windows.10.Amd64.Client21H1.Open + - windows.10.amd64.client.open # windows arm - ${{ if eq(parameters.platform, 'windows_arm') }}: - - Windows.10.Arm64v8.Open + - Windows.11.Arm64.Open # windows arm64 - ${{ if eq(parameters.platform, 'windows_arm64') }}: - - Windows.10.Arm64.Open - # TODO: Uncomment once there is HW deployed to service Win11 ARM64 queue - # - Windows.11.Arm64.Open + - Windows.11.Arm64.Open # WebAssembly - ${{ if eq(parameters.platform, 'Browser_wasm') }}: @@ -193,10 +190,10 @@ jobs: # WebAssembly Firefox - ${{ if eq(parameters.platform, 'Browser_wasm_firefox') }}: - - (Ubuntu.1804.Amd64)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-webassembly-20220504035734-67908a0 + - (Ubuntu.1804.Amd64)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-webassembly # WebAssembly windows - ${{ if eq(parameters.platform, 'Browser_wasm_win') }}: - - (Windows.Amd64.Server2022.Open)windows.amd64.server2022.open@mcr.microsoft.com/dotnet-buildtools/prereqs:windowsservercore-ltsc2022-helix-webassembly-20220620175048-bf70060 + - (Windows.Amd64.Server2022.Open)windows.amd64.server2022.open@mcr.microsoft.com/dotnet-buildtools/prereqs:windowsservercore-ltsc2022-helix-webassembly ${{ insert }}: ${{ parameters.jobParameters }} diff --git a/eng/pipelines/libraries/run-test-job.yml b/eng/pipelines/libraries/run-test-job.yml index 02113a43903e89..b202278e52103e 100644 --- a/eng/pipelines/libraries/run-test-job.yml +++ b/eng/pipelines/libraries/run-test-job.yml @@ -88,6 +88,10 @@ jobs: artifactFileName: $(librariesTestsArtifactName)$(archiveExtension) unpackFolder: $(Build.SourcesDirectory)/artifacts + - ${{ if in(parameters.osGroup, 'OSX', 'MacCatalyst', 'iOS', 'iOSSimulator', 'tvOS', 'tvOSSimulator') }}: + - script: $(Build.SourcesDirectory)/eng/install-native-dependencies.sh ${{ parameters.osGroup }} + displayName: Install Build Dependencies + - ${{ if ne(parameters.liveRuntimeBuildConfig, '') }}: - script: $(_buildScript) -subset host.native+libs.pretest diff --git a/eng/pipelines/libraries/stress/http.yml b/eng/pipelines/libraries/stress/http.yml index 6a270c0a60a7af..6100d90c010f96 100644 --- a/eng/pipelines/libraries/stress/http.yml +++ b/eng/pipelines/libraries/stress/http.yml @@ -12,6 +12,7 @@ schedules: include: - main - release/6.0 + - release/7.0 variables: - template: ../variables.yml @@ -30,7 +31,7 @@ jobs: variables: DUMPS_SHARE_MOUNT_ROOT: "/dumps-share" pool: - name: NetCore1ESPool-Public + name: NetCore-Svc-Public demands: ImageOverride -equals 1es-ubuntu-1804-open steps: @@ -96,7 +97,7 @@ jobs: variables: DUMPS_SHARE_MOUNT_ROOT: "C:/dumps-share" pool: - name: NetCore1ESPool-Public + name: NetCore-Svc-Public demands: ImageOverride -equals 1es-windows-2022-open steps: diff --git a/eng/pipelines/libraries/stress/ssl.yml b/eng/pipelines/libraries/stress/ssl.yml index 856627aac2ba10..163b73c028d5b2 100644 --- a/eng/pipelines/libraries/stress/ssl.yml +++ b/eng/pipelines/libraries/stress/ssl.yml @@ -12,6 +12,7 @@ schedules: include: - main - release/6.0 + - release/7.0 variables: - template: ../variables.yml @@ -29,7 +30,7 @@ jobs: displayName: Docker Linux timeoutInMinutes: 120 pool: - name: NetCore1ESPool-Public + name: NetCore-Svc-Public demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open steps: @@ -54,7 +55,7 @@ jobs: displayName: Docker NanoServer timeoutInMinutes: 120 pool: - name: NetCore1ESPool-Public + name: NetCore-Svc-Public demands: ImageOverride -equals 1es-windows-2022-open steps: diff --git a/eng/pipelines/mono/templates/build-job.yml b/eng/pipelines/mono/templates/build-job.yml index ac1e23915b478a..b8bedb3a1488f3 100644 --- a/eng/pipelines/mono/templates/build-job.yml +++ b/eng/pipelines/mono/templates/build-job.yml @@ -123,7 +123,7 @@ jobs: # Linux builds use docker images with dependencies preinstalled, # and FreeBSD builds use a build agent with dependencies # preinstalled, so we only need this step for OSX and Windows. - - ${{ if in(parameters.osGroup, 'OSX', 'iOS', 'tvOS') }}: + - ${{ if in(parameters.osGroup, 'OSX', 'MacCatalyst', 'iOS', 'iOSSimulator', 'tvOS', 'tvOSSimulator') }}: - script: $(Build.SourcesDirectory)/eng/install-native-dependencies.sh $(osGroup) ${{ parameters.archType }} azDO displayName: Install native dependencies diff --git a/eng/pipelines/mono/templates/generate-offsets.yml b/eng/pipelines/mono/templates/generate-offsets.yml index 6c84674b2becf1..6a9b125271586c 100644 --- a/eng/pipelines/mono/templates/generate-offsets.yml +++ b/eng/pipelines/mono/templates/generate-offsets.yml @@ -56,7 +56,7 @@ jobs: # Linux builds use docker images with dependencies preinstalled, # and FreeBSD builds use a build agent with dependencies # preinstalled, so we only need this step for OSX and Windows. - - ${{ if in(parameters.osGroup, 'OSX', 'iOS', 'tvOS') }}: + - ${{ if in(parameters.osGroup, 'OSX', 'MacCatalyst', 'iOS', 'iOSSimulator', 'tvOS', 'tvOSSimulator') }}: - script: $(Build.SourcesDirectory)/eng/install-native-dependencies.sh $(osGroup) ${{ parameters.archType }} azDO displayName: Install native dependencies diff --git a/eng/pipelines/mono/templates/workloads-build.yml b/eng/pipelines/mono/templates/workloads-build.yml index 28fb20114c89a3..55b43c406d22bb 100644 --- a/eng/pipelines/mono/templates/workloads-build.yml +++ b/eng/pipelines/mono/templates/workloads-build.yml @@ -56,12 +56,15 @@ jobs: IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.browser-wasm*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.android-*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.browser-wasm*.nupkg + IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.multithread.browser-wasm*.nupkg + IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.perftrace.browser-wasm*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.ios-*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.iossimulator-*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.maccatalyst-*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.tvos-*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.tvossimulator-*.nupkg - IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NET.Workload.Mono.ToolChain.Manifest*.nupkg + IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NET.Workload.Mono.ToolChain.net6.Manifest*.nupkg + IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NET.Workload.Mono.ToolChain.net7.Manifest*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NET.Runtime.MonoTargets.Sdk*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NET.Runtime.MonoAOTCompiler.Task*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NET.Runtime.WebAssembly.Sdk*.nupkg @@ -87,6 +90,15 @@ jobs: - template: /eng/pipelines/common/upload-intermediate-artifacts-step.yml parameters: name: workloads + + # Publish Logs + - task: PublishPipelineArtifact@1 + displayName: Publish Logs + inputs: + targetPath: $(Build.SourcesDirectory)/artifacts/log + artifactName: 'WorkloadLogs' + continueOnError: true + condition: always() # Delete wixpdb files before they are uploaded to artifacts - task: DeleteFiles@1 diff --git a/eng/pipelines/official/jobs/prepare-signed-artifacts.yml b/eng/pipelines/official/jobs/prepare-signed-artifacts.yml index 8b250869d444ed..016b799e0099fc 100644 --- a/eng/pipelines/official/jobs/prepare-signed-artifacts.yml +++ b/eng/pipelines/official/jobs/prepare-signed-artifacts.yml @@ -9,7 +9,7 @@ jobs: displayName: Prepare Signed Artifacts dependsOn: ${{ parameters.dependsOn }} pool: - name: NetCore1ESPool-Internal + name: NetCore1ESPool-Svc-Internal demands: ImageOverride -equals 1es-windows-2022 # Double the default timeout. timeoutInMinutes: 240 diff --git a/eng/pipelines/official/stages/publish.yml b/eng/pipelines/official/stages/publish.yml index 1061c783f2ab57..86aaa49a8bae31 100644 --- a/eng/pipelines/official/stages/publish.yml +++ b/eng/pipelines/official/stages/publish.yml @@ -19,7 +19,7 @@ stages: publishUsingPipelines: true dependsOn: PrepareSignedArtifacts pool: - name: NetCore1ESPool-Internal + name: NetCore1ESPool-Svc-Internal demands: ImageOverride -equals 1es-windows-2022 # Stages-based publishing entry point diff --git a/eng/pipelines/runtime-community.yml b/eng/pipelines/runtime-community.yml index 2ceed817ca6989..50ba34100d3ea6 100644 --- a/eng/pipelines/runtime-community.yml +++ b/eng/pipelines/runtime-community.yml @@ -1,4 +1,23 @@ -trigger: none +trigger: + batch: true + branches: + include: + - release/*.* + exclude: + - release/6.0* + paths: + include: + - '*' + - docs/manpages/* + exclude: + - '**.md' + - eng/Version.Details.xml + - .devcontainer/* + - .github/* + - docs/* + - LICENSE.TXT + - PATENTS.TXT + - THIRD-PARTY-NOTICES.TXT schedules: - cron: "0 7,19 * * *" # run at 7:00 and 19:00 (UTC) which is 23:00 and 11:00 (PST). diff --git a/eng/pipelines/runtime-extra-platforms-other.yml b/eng/pipelines/runtime-extra-platforms-other.yml index 28e2e1b6a83300..3f77cd342fc374 100644 --- a/eng/pipelines/runtime-extra-platforms-other.yml +++ b/eng/pipelines/runtime-extra-platforms-other.yml @@ -143,7 +143,7 @@ jobs: jobParameters: testScope: innerloop nameSuffix: AllSubsets_Mono - buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true + buildArgs: -s mono+mono.mscordbi+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true timeoutInMinutes: 120 condition: >- or( @@ -412,6 +412,47 @@ jobs: eq(variables['monoContainsChange'], true), eq(variables['isRollingBuild'], true)) +# +# Build the whole product using Mono for Android Linux with Bionic libc +# +- template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + helixQueuesTemplate: /eng/pipelines/libraries/helix-queues-setup.yml + buildConfig: Release + runtimeFlavor: mono + platforms: + - Linux_bionic_arm64 + - Linux_bionic_x64 + variables: + - ${{ if and(eq(variables['System.TeamProject'], 'public'), eq(variables['Build.Reason'], 'PullRequest')) }}: + - name: _HelixSource + value: pr/dotnet/runtime/$(Build.SourceBranch) + - ${{ if and(eq(variables['System.TeamProject'], 'public'), ne(variables['Build.Reason'], 'PullRequest')) }}: + - name: _HelixSource + value: ci/dotnet/runtime/$(Build.SourceBranch) + - name: timeoutPerTestInMinutes + value: 60 + - name: timeoutPerTestCollectionInMinutes + value: 180 + jobParameters: + testGroup: innerloop + targetRid: Linux-bionic-x64 + nameSuffix: AllSubsets_Mono + buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true + timeoutInMinutes: 240 + condition: >- + or( + eq(dependencies.evaluate_paths.outputs['SetPathVars_libraries.containsChange'], true), + eq(dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'], true), + eq(dependencies.evaluate_paths.outputs['SetPathVars_installer.containsChange'], true), + eq(variables['isRollingBuild'], true)) + # extra steps, run tests + extraStepsTemplate: /eng/pipelines/libraries/helix.yml + extraStepsParameters: + creator: dotnet-bot + testRunNamePrefixSuffix: Mono_$(_BuildConfig)_LinuxBionic + # # Build the whole product using Mono and run runtime tests # Build Mono release diff --git a/eng/pipelines/runtime-extra-platforms-wasm.yml b/eng/pipelines/runtime-extra-platforms-wasm.yml index 70e862197fe351..b41a679e45c9a0 100644 --- a/eng/pipelines/runtime-extra-platforms-wasm.yml +++ b/eng/pipelines/runtime-extra-platforms-wasm.yml @@ -168,14 +168,15 @@ jobs: - Browser_wasm alwaysRun: ${{ parameters.isWasmOnlyBuild }} - - template: /eng/pipelines/common/templates/wasm-debugger-tests.yml - parameters: - platforms: - - Browser_wasm_firefox - browser: firefox - alwaysRun: ${{ parameters.isWasmOnlyBuild }} - # ff tests are unstable currently - shouldContinueOnError: true + # Known to be unstable so disabled for 7.0 on PRs + #- template: /eng/pipelines/common/templates/wasm-debugger-tests.yml + #parameters: + #platforms: + #- Browser_wasm_firefox + #browser: firefox + #alwaysRun: ${{ parameters.isWasmOnlyBuild }} + ## ff tests are unstable currently + #shouldContinueOnError: true # Disable for now #- template: /eng/pipelines/coreclr/perf-wasm-jobs.yml diff --git a/eng/pipelines/runtime-extra-platforms.yml b/eng/pipelines/runtime-extra-platforms.yml index 02591d91b6b0e3..1ce933286fc59b 100644 --- a/eng/pipelines/runtime-extra-platforms.yml +++ b/eng/pipelines/runtime-extra-platforms.yml @@ -7,7 +7,24 @@ # if there is a push while a build in progress, it will wait, # until the running build finishes, and produce a build with all the changes # that happened during the last build. -trigger: none +trigger: + batch: true + branches: + include: + - release/*.* + paths: + include: + - '*' + - docs/manpages/* + exclude: + - '**.md' + - eng/Version.Details.xml + - .devcontainer/* + - .github/* + - docs/* + - LICENSE.TXT + - PATENTS.TXT + - THIRD-PARTY-NOTICES.TXT schedules: - cron: "0 9,21 * * *" # run at 9:00 and 21:00 (UTC) which is 1:00 and 13:00 (PST). @@ -16,12 +33,6 @@ schedules: include: - main always: false # run only if there were changes since the last successful scheduled run. - - cron: "0 6,18 * * *" # run at 6:00 and 18:00 (UTC) which is 22:00 and 10:00 (PST). - displayName: Runtime extra release schedule - branches: - include: - - release/* - always: false # run only if there were changes since the last successful scheduled run. variables: - template: /eng/pipelines/common/variables.yml diff --git a/eng/pipelines/runtime-official.yml b/eng/pipelines/runtime-official.yml index d011ddb32f28f7..569e0052ee1f28 100644 --- a/eng/pipelines/runtime-official.yml +++ b/eng/pipelines/runtime-official.yml @@ -32,8 +32,12 @@ variables: value: .NETCore - name: _DotNetValidationArtifactsCategory value: .NETCoreValidation -- name: PostBuildSign - value: true +- ${{ if or(startswith(variables['Build.SourceBranch'], 'refs/heads/release/'), startswith(variables['Build.SourceBranch'], 'refs/heads/internal/release/')) }}: + - name: PostBuildSign + value: false +- ${{ else }}: + - name: PostBuildSign + value: true stages: - stage: Build @@ -140,7 +144,7 @@ stages: # - windows_arm # - windows_arm64 jobParameters: - buildArgs: -s mono+libs+host+packs+mono.mscordbi -c $(_BuildConfig) + buildArgs: -s mono+libs+host+packs -c $(_BuildConfig) nameSuffix: AllSubsets_Mono isOfficialBuild: ${{ variables.isOfficialBuild }} extraStepsTemplate: /eng/pipelines/common/upload-intermediate-artifacts-step.yml @@ -155,7 +159,7 @@ stages: platforms: - Browser_wasm jobParameters: - buildArgs: -s mono+libs+host+packs+mono.mscordbi -c $(_BuildConfig) /p:MonoWasmBuildVariant=perftrace + buildArgs: -s mono+libs+host+packs -c $(_BuildConfig) /p:MonoWasmBuildVariant=perftrace nameSuffix: AllSubsets_Mono_perftrace isOfficialBuild: ${{ variables.isOfficialBuild }} runtimeVariant: perftrace @@ -171,7 +175,7 @@ stages: platforms: - Browser_wasm jobParameters: - buildArgs: -s mono+libs+host+packs+mono.mscordbi -c $(_BuildConfig) /p:MonoWasmBuildVariant=multithread + buildArgs: -s mono+libs+host+packs -c $(_BuildConfig) /p:MonoWasmBuildVariant=multithread nameSuffix: AllSubsets_Mono_multithread isOfficialBuild: ${{ variables.isOfficialBuild }} runtimeVariant: multithread @@ -364,19 +368,19 @@ stages: # # Build Sourcebuild leg # - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - buildConfig: Release - helixQueueGroup: ci - platforms: - - SourceBuild_Linux_x64 - jobParameters: - nameSuffix: SourceBuild - extraStepsTemplate: /eng/pipelines/common/upload-intermediate-artifacts-step.yml - extraStepsParameters: - name: SourceBuildPackages - timeoutInMinutes: 95 + # - template: /eng/pipelines/common/platform-matrix.yml + # parameters: + # jobTemplate: /eng/pipelines/common/global-build-job.yml + # buildConfig: Release + # helixQueueGroup: ci + # platforms: + # - SourceBuild_Linux_x64 + # jobParameters: + # nameSuffix: SourceBuild + # extraStepsTemplate: /eng/pipelines/common/upload-intermediate-artifacts-step.yml + # extraStepsParameters: + # name: SourceBuildPackages + # timeoutInMinutes: 95 # diff --git a/eng/pipelines/runtime-staging.yml b/eng/pipelines/runtime-staging.yml index b5ce3e8bf43144..2d7576c6fbe88e 100644 --- a/eng/pipelines/runtime-staging.yml +++ b/eng/pipelines/runtime-staging.yml @@ -201,47 +201,6 @@ jobs: creator: dotnet-bot testRunNamePrefixSuffix: Mono_$(_BuildConfig) -# -# Build the whole product using Mono for Android Linux with Bionic libc -# -- template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - helixQueuesTemplate: /eng/pipelines/libraries/helix-queues-setup.yml - buildConfig: Release - runtimeFlavor: mono - platforms: - - Linux_bionic_arm64 - - Linux_bionic_x64 - variables: - - ${{ if and(eq(variables['System.TeamProject'], 'public'), eq(variables['Build.Reason'], 'PullRequest')) }}: - - name: _HelixSource - value: pr/dotnet/runtime/$(Build.SourceBranch) - - ${{ if and(eq(variables['System.TeamProject'], 'public'), ne(variables['Build.Reason'], 'PullRequest')) }}: - - name: _HelixSource - value: ci/dotnet/runtime/$(Build.SourceBranch) - - name: timeoutPerTestInMinutes - value: 60 - - name: timeoutPerTestCollectionInMinutes - value: 180 - jobParameters: - testGroup: innerloop - targetRid: Linux-bionic-x64 - nameSuffix: AllSubsets_Mono - buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true - timeoutInMinutes: 240 - condition: >- - or( - eq(dependencies.evaluate_paths.outputs['SetPathVars_libraries.containsChange'], true), - eq(dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'], true), - eq(dependencies.evaluate_paths.outputs['SetPathVars_installer.containsChange'], true), - eq(variables['isRollingBuild'], true)) - # extra steps, run tests - extraStepsTemplate: /eng/pipelines/libraries/helix.yml - extraStepsParameters: - creator: dotnet-bot - testRunNamePrefixSuffix: Mono_$(_BuildConfig)_LinuxBionic - # # Build the whole product using Mono for Android and run runtime tests with Android devices # diff --git a/eng/pipelines/runtime-wasm-features.yml b/eng/pipelines/runtime-wasm-features.yml new file mode 100644 index 00000000000000..940b59c800ab25 --- /dev/null +++ b/eng/pipelines/runtime-wasm-features.yml @@ -0,0 +1,27 @@ +# This pipeline is meant to be run manually. It contains +# jobs that exercise extra/optional features for wasm, eg. SIMD + +trigger: none + +variables: + - template: /eng/pipelines/common/variables.yml + +jobs: + +# Evaluate paths +- template: /eng/pipelines/common/evaluate-default-paths.yml + +# Run AOT tests with SIMD enabled +- template: /eng/pipelines/common/templates/wasm-library-tests.yml + parameters: + platforms: + - Browser_wasm + nameSuffix: _SIMD_AOT + isExtraPlatformsBuild: false + isWasmOnlyBuild: true + extraBuildArgs: /p:EnableAggressiveTrimming=true /p:BuildAOTTestsOnHelix=true /p:RunAOTCompilation=true + extraHelixArgs: /p:NeedsToBuildWasmAppsOnHelix=true /p:WasmEnableSIMD=true + runSmokeOnlyArg: '' + alwaysRun: true + scenarios: + - WasmTestOnNodeJS diff --git a/eng/pipelines/runtime-wasm.yml b/eng/pipelines/runtime-wasm.yml index 62de930be9d1f9..04eac67a9399cf 100644 --- a/eng/pipelines/runtime-wasm.yml +++ b/eng/pipelines/runtime-wasm.yml @@ -19,3 +19,22 @@ jobs: parameters: isWasmOnlyBuild: ${{ variables.isWasmOnlyBuild }} isRollingBuild: ${{ variables.isRollingBuild }} + +# Run AOT tests with SIMD enabled +- template: /eng/pipelines/common/templates/wasm-library-tests.yml + parameters: + platforms: + - Browser_wasm + nameSuffix: _SIMD_AOT + isExtraPlatformsBuild: false + isWasmOnlyBuild: true + extraBuildArgs: /p:EnableAggressiveTrimming=true /p:BuildAOTTestsOnHelix=true /p:RunAOTCompilation=true + extraHelixArgs: /p:NeedsToBuildWasmAppsOnHelix=true /p:WasmEnableSIMD=true + runSmokeOnlyArg: '' + alwaysRun: true + # failures due to + # https://github.com/dotnet/runtime/issues/75044 + # and https://github.com/dotnet/runtime/issues/75098 + shouldContinueOnError: true + scenarios: + - WasmTestOnNodeJS diff --git a/eng/regenerate-third-party-notices.proj b/eng/regenerate-third-party-notices.proj index 994e54daa6d10e..011a6832c3903b 100644 --- a/eng/regenerate-third-party-notices.proj +++ b/eng/regenerate-third-party-notices.proj @@ -20,6 +20,7 @@ + diff --git a/eng/resolveContract.targets b/eng/resolveContract.targets index 4a2b0a5adfcbd6..179813179a9b26 100644 --- a/eng/resolveContract.targets +++ b/eng/resolveContract.targets @@ -20,6 +20,17 @@ true false + + true @@ -36,7 +47,7 @@ @@ -51,14 +62,16 @@ AfterTargets="ResolveProjectReferences" BeforeTargets="FindReferenceAssembliesForReferences" Condition="'@(ProjectReference)' != '' and '@(_ResolvedProjectReferencePaths)' != ''"> - + false + None <_resolvedP2PFiltered Include="@(ProjectReference)" - ProjectReferenceItemSpec="$([System.IO.Path]::GetFullPath('%(ProjectReference.Identity)'))" + ProjectReferenceItemSpec="$([System.IO.Path]::GetFullPath('%(ProjectReference.Identity)'))" SkipUseReferenceAssembly="%(ProjectReference.SkipUseReferenceAssembly)" /> <_ResolvedProjectReferencePaths Condition="'%(_resolvedP2PFiltered.ProjectReferenceItemSpec)' == '%(_resolvedP2PFiltered.MSBuildSourceProjectFile)' and '%(_resolvedP2PFiltered.SkipUseReferenceAssembly)' == 'true'" diff --git a/eng/restore/docs.targets b/eng/restore/docs.targets index 764bc5bbefb64b..2ce95b3bd5e644 100644 --- a/eng/restore/docs.targets +++ b/eng/restore/docs.targets @@ -9,7 +9,7 @@ AfterTargets="Restore"> - + $([System.String]::new('%(RecursiveDir)').TrimEnd('\/')) @@ -35,7 +35,7 @@ - + + + diff --git a/eng/testing/linker/SupportFiles/Directory.Build.props b/eng/testing/linker/SupportFiles/Directory.Build.props index da3533e49c939f..1e08117fd3171c 100644 --- a/eng/testing/linker/SupportFiles/Directory.Build.props +++ b/eng/testing/linker/SupportFiles/Directory.Build.props @@ -2,7 +2,6 @@ true true - true full false true diff --git a/eng/testing/linker/SupportFiles/Directory.Build.targets b/eng/testing/linker/SupportFiles/Directory.Build.targets index 491c45e4a43fa9..e735d71fa0f723 100644 --- a/eng/testing/linker/SupportFiles/Directory.Build.targets +++ b/eng/testing/linker/SupportFiles/Directory.Build.targets @@ -12,6 +12,10 @@ DependsOnTargets="BundleTestWasmApp" Condition="'$(TargetArchitecture)' == 'wasm' And '$(TargetOS)' == 'browser'" /> + + $(CoreCLRBuildIntegrationDir)Microsoft.DotNet.ILCompiler.SingleEntry.targets + + diff --git a/eng/testing/linker/project.csproj.template b/eng/testing/linker/project.csproj.template index 80180798fd30e8..9b1c14e36b814d 100644 --- a/eng/testing/linker/project.csproj.template +++ b/eng/testing/linker/project.csproj.template @@ -6,6 +6,7 @@ {NetCoreAppMaximumVersion} {UseMonoRuntime} {RuntimeIdentifier} + {PublishAot} {MonoAOTCompilerDir} @@ -25,6 +26,15 @@ {RepositoryEngineeringDir} <_ExtraTrimmerArgs>{ExtraTrimmerArgs} $(_ExtraTrimmerArgs) + {AdditionalProperties} + + + {IlcToolsPath} + {IlcBuildTasksPath} + {IlcSdkPath} + {IlcFrameworkPath} + {IlcFrameworkNativePath} + {CoreCLRBuildIntegrationDir} diff --git a/eng/testing/linker/trimmingTests.targets b/eng/testing/linker/trimmingTests.targets index ac688f61b6b2d7..9f821e99c2a3a5 100644 --- a/eng/testing/linker/trimmingTests.targets +++ b/eng/testing/linker/trimmingTests.targets @@ -72,10 +72,14 @@ <_switchesAsItems Include="%(TestConsoleApps.DisabledFeatureSwitches)" Value="false" /> <_switchesAsItems Include="%(TestConsoleApps.EnabledFeatureSwitches)" Value="true" /> + + <_propertiesAsItems Include="%(TestConsoleApps.DisabledProperties)" Value="false" /> + <_propertiesAsItems Include="%(TestConsoleApps.EnabledProperties)" Value="true" /> <_runtimeHostConfigurationOptionsString>@(_switchesAsItems->'<RuntimeHostConfigurationOption Include="%(Identity)" Value="%(Value)" Trim="true" />', '%0a ') + <_additionalPropertiesString>@(_propertiesAsItems->'<%(Identity)>%(Value)</%(Identity)>', '%0a ') @@ -85,8 +89,16 @@ .Replace('{NetCoreAppMaximumVersion}', '$(NetCoreAppMaximumVersion)') .Replace('{UseMonoRuntime}','$(UseMonoRuntime)') .Replace('{RuntimeIdentifier}','%(TestConsoleApps.TestRuntimeIdentifier)') + .Replace('{PublishAot}','$(IsNativeAotTestProject)') .Replace('{MicrosoftNETILLinkTasksVersion}', '$(MicrosoftNETILLinkTasksVersion)') .Replace('{ExtraTrimmerArgs}', '%(TestConsoleApps.ExtraTrimmerArgs)') + .Replace('{AdditionalProperties}', '$(_additionalPropertiesString)') + .Replace('{IlcToolsPath}', '$(CoreCLRILCompilerDir)') + .Replace('{IlcBuildTasksPath}', '$(CoreCLRILCompilerDir)netstandard/ILCompiler.Build.Tasks.dll') + .Replace('{IlcSdkPath}', '$(CoreCLRAotSdkDir)') + .Replace('{IlcFrameworkPath}', '$(MicrosoftNetCoreAppRuntimePackRidLibTfmDir)') + .Replace('{IlcFrameworkNativePath}', '$(MicrosoftNetCoreAppRuntimePackNativeDir)') + .Replace('{CoreCLRBuildIntegrationDir}', '$(CoreCLRBuildIntegrationDir)') .Replace('{RuntimeHostConfigurationOptions}', '$(_runtimeHostConfigurationOptionsString)') .Replace('{AdditionalProjectReferences}', '$(_additionalProjectReferencesString)') .Replace('{RepositoryEngineeringDir}', '$(RepositoryEngineeringDir)') diff --git a/eng/testing/performance/add_properties_to_pipeline.proj b/eng/testing/performance/add_properties_to_pipeline.proj new file mode 100644 index 00000000000000..1558c86e1cb5c8 --- /dev/null +++ b/eng/testing/performance/add_properties_to_pipeline.proj @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/eng/testing/performance/android_scenarios.proj b/eng/testing/performance/android_scenarios.proj index f01d7c17c26438..5d22af65c6cc67 100644 --- a/eng/testing/performance/android_scenarios.proj +++ b/eng/testing/performance/android_scenarios.proj @@ -35,42 +35,6 @@ $(Python) test.py sod --scenario-name "%(Identity)" $(Python) post.py - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidDefault.apk .; $(Python) pre.py --apk-name MauiAndroidDefault.apk - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiblazorandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiBlazorAndroidDefault.apk .; $(Python) pre.py --apk-name MauiBlazorAndroidDefault.apk - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidPodcast.apk .; $(Python) pre.py --apk-name MauiAndroidPodcast.apk - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidDefault.apk .; $(Python) pre.py --unzip --apk-name MauiAndroidDefault.apk - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiblazorandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiBlazorAndroidDefault.apk .; $(Python) pre.py --unzip --apk-name MauiBlazorAndroidDefault.apk - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidPodcast.apk .; $(Python) pre.py --unzip --apk-name MauiAndroidPodcast.apk - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - $(WorkItemDirectory) echo on;set XHARNESSPATH=$(XharnessPath);cd $(ScenarioDirectory)helloandroid;copy %HELIX_CORRELATION_PAYLOAD%\HelloAndroid.apk .;$(Python) pre.py --apk-name HelloAndroid.apk @@ -83,41 +47,5 @@ $(Python) test.py devicestartup --device-type android --package-path pub\HelloAndroid.apk --package-name net.dot.HelloAndroid --scenario-name "%(Identity)" --disable-animations $(Python) post.py - - $(WorkItemDirectory) - echo on;set XHARNESSPATH=$(XharnessPath);cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidDefault.apk .;$(Python) pre.py --apk-name MauiAndroidDefault.apk - $(Python) test.py devicestartup --device-type android --package-path pub\MauiAndroidDefault.apk --package-name com.companyname.mauitesting --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - echo on;set XHARNESSPATH=$(XharnessPath);cd $(ScenarioDirectory)mauiblazorandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiBlazorAndroidDefault.apk .;$(Python) pre.py --apk-name MauiBlazorAndroidDefault.apk - $(Python) test.py devicestartup --device-type android --package-path pub\MauiBlazorAndroidDefault.apk --package-name com.companyname.mauiblazortesting --scenario-name "%(Identity)" --use-fully-drawn-time --fully-drawn-extra-delay 6 - $(Python) post.py - - - $(WorkItemDirectory) - echo on;set XHARNESSPATH=$(XharnessPath);cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidPodcast.apk .;$(Python) pre.py --apk-name MauiAndroidPodcast.apk - $(Python) test.py devicestartup --device-type android --package-path pub\MauiAndroidPodcast.apk --package-name com.Microsoft.NetConf2021.Maui --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - echo on;set XHARNESSPATH=$(XharnessPath);cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidDefault.apk .;$(Python) pre.py --apk-name MauiAndroidDefault.apk - $(Python) test.py devicestartup --device-type android --package-path pub\MauiAndroidDefault.apk --package-name com.companyname.mauitesting --scenario-name "%(Identity)" --disable-animations - $(Python) post.py - - - $(WorkItemDirectory) - echo on;set XHARNESSPATH=$(XharnessPath);cd $(ScenarioDirectory)mauiblazorandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiBlazorAndroidDefault.apk .;$(Python) pre.py --apk-name MauiBlazorAndroidDefault.apk - $(Python) test.py devicestartup --device-type android --package-path pub\MauiBlazorAndroidDefault.apk --package-name com.companyname.mauiblazortesting --scenario-name "%(Identity)" --use-fully-drawn-time --fully-drawn-extra-delay 6 --disable-animations - $(Python) post.py - - - $(WorkItemDirectory) - echo on;set XHARNESSPATH=$(XharnessPath);cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidPodcast.apk .;$(Python) pre.py --apk-name MauiAndroidPodcast.apk - $(Python) test.py devicestartup --device-type android --package-path pub\MauiAndroidPodcast.apk --package-name com.Microsoft.NetConf2021.Maui --scenario-name "%(Identity)" --disable-animations - $(Python) post.py - diff --git a/eng/testing/performance/android_scenarios_net6.proj b/eng/testing/performance/android_scenarios_net6.proj deleted file mode 100644 index edd493db6481a8..00000000000000 --- a/eng/testing/performance/android_scenarios_net6.proj +++ /dev/null @@ -1,99 +0,0 @@ - - - true - 1.0.0-prerelease.21566.2 - %HELIX_CORRELATION_PAYLOAD%\microsoft.dotnet.xharness.cli\$(MicrosoftDotNetXHarnessCLIVersion)\tools\net6.0\any\Microsoft.DotNet.XHarness.CLI.dll - - - python3 - $(HelixPreCommands);chmod +x $HELIX_WORKITEM_PAYLOAD/SOD/SizeOnDisk - - - - - %(Identity) - - - - - %HELIX_CORRELATION_PAYLOAD%\performance\src\scenarios\ - - - $HELIX_CORRELATION_PAYLOAD/performance/src/scenarios/ - - - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidDefault.apk .; $(Python) pre.py --apk-name MauiAndroidDefault.apk - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiblazorandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiBlazorAndroidDefault.apk .; $(Python) pre.py --apk-name MauiBlazorAndroidDefault.apk - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidPodcast.apk .; $(Python) pre.py --apk-name MauiAndroidPodcast.apk - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidDefault.apk .; $(Python) pre.py --unzip --apk-name MauiAndroidDefault.apk - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiblazorandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiBlazorAndroidDefault.apk .; $(Python) pre.py --unzip --apk-name MauiBlazorAndroidDefault.apk - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidPodcast.apk .; $(Python) pre.py --unzip --apk-name MauiAndroidPodcast.apk - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - echo on;set XHARNESSPATH=$(XharnessPath);cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidDefault.apk .;$(Python) pre.py --apk-name MauiAndroidDefault.apk - $(Python) test.py devicestartup --device-type android --package-path pub\MauiAndroidDefault.apk --package-name com.companyname.mauitesting --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - echo on;set XHARNESSPATH=$(XharnessPath);cd $(ScenarioDirectory)mauiblazorandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiBlazorAndroidDefault.apk .;$(Python) pre.py --apk-name MauiBlazorAndroidDefault.apk - $(Python) test.py devicestartup --device-type android --package-path pub\MauiBlazorAndroidDefault.apk --package-name com.companyname.mauiblazortesting --scenario-name "%(Identity)" --use-fully-drawn-time --fully-drawn-extra-delay 6 - $(Python) post.py - - - $(WorkItemDirectory) - echo on;set XHARNESSPATH=$(XharnessPath);cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidPodcast.apk .;$(Python) pre.py --apk-name MauiAndroidPodcast.apk - $(Python) test.py devicestartup --device-type android --package-path pub\MauiAndroidPodcast.apk --package-name com.Microsoft.NetConf2021.Maui --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - echo on;set XHARNESSPATH=$(XharnessPath);cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidDefault.apk .;$(Python) pre.py --apk-name MauiAndroidDefault.apk - $(Python) test.py devicestartup --device-type android --package-path pub\MauiAndroidDefault.apk --package-name com.companyname.mauitesting --scenario-name "%(Identity)" --disable-animations - $(Python) post.py - - - $(WorkItemDirectory) - echo on;set XHARNESSPATH=$(XharnessPath);cd $(ScenarioDirectory)mauiblazorandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiBlazorAndroidDefault.apk .;$(Python) pre.py --apk-name MauiBlazorAndroidDefault.apk - $(Python) test.py devicestartup --device-type android --package-path pub\MauiBlazorAndroidDefault.apk --package-name com.companyname.mauiblazortesting --scenario-name "%(Identity)" --use-fully-drawn-time --fully-drawn-extra-delay 6 --disable-animations - $(Python) post.py - - - $(WorkItemDirectory) - echo on;set XHARNESSPATH=$(XharnessPath);cd $(ScenarioDirectory)mauiandroid;copy %HELIX_CORRELATION_PAYLOAD%\MauiAndroidPodcast.apk .;$(Python) pre.py --apk-name MauiAndroidPodcast.apk - $(Python) test.py devicestartup --device-type android --package-path pub\MauiAndroidPodcast.apk --package-name com.Microsoft.NetConf2021.Maui --scenario-name "%(Identity)" --disable-animations - $(Python) post.py - - - diff --git a/eng/testing/performance/ios_scenarios.proj b/eng/testing/performance/ios_scenarios.proj index 40dee2324c31d5..6b30417fdff479 100644 --- a/eng/testing/performance/ios_scenarios.proj +++ b/eng/testing/performance/ios_scenarios.proj @@ -60,132 +60,6 @@ $(Python) test.py devicestartup --device-type ios --package-path HelloiOS.app --package-name net.dot.HelloiOS --scenario-name "%(Identity)" ((result=$?)) - # Post commands - $(Python) post.py - exit $result - ]]> - - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiios;cp -rf $HELIX_CORRELATION_PAYLOAD/MauiMacCatalystDefault ./app;$(Python) pre.py --name app - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiios;cp $HELIX_CORRELATION_PAYLOAD/MauiiOSDefault.ipa .;$(Python) pre.py --name MauiiOSDefault.ipa - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiios;cp $HELIX_CORRELATION_PAYLOAD/MauiiOSDefault.ipa .;$(Python) pre.py --unzip --name MauiiOSDefault.ipa - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiblazorios;cp $HELIX_CORRELATION_PAYLOAD/MauiBlazoriOSDefault.ipa .;$(Python) pre.py --name MauiBlazoriOSDefault.ipa - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiblazorios;cp $HELIX_CORRELATION_PAYLOAD/MauiBlazoriOSDefault.ipa .;$(Python) pre.py --unzip --name MauiBlazoriOSDefault.ipa - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiblazorios;cp -rf $HELIX_CORRELATION_PAYLOAD/MauiBlazorMacCatalystDefault ./app;$(Python) pre.py --name app - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiios;cp $HELIX_CORRELATION_PAYLOAD/MauiiOSPodcast.ipa .;$(Python) pre.py --name MauiiOSPodcast.ipa - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiios;cp $HELIX_CORRELATION_PAYLOAD/MauiiOSPodcast.ipa .;$(Python) pre.py --unzip --name MauiiOSPodcast.ipa - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory).zip - 00:15:00 - ios-device - - - - - - $(WorkItemDirectory).zip - 00:15:00 - ios-device - - - - - - $(WorkItemDirectory).zip - 00:15:00 - ios-device - - - - - - export PYTHONPATH=$ORIGPYPATH;$(HelixPostCommands) - - diff --git a/eng/testing/performance/ios_scenarios_net6.proj b/eng/testing/performance/ios_scenarios_net6.proj deleted file mode 100644 index 36c8cabc5560d1..00000000000000 --- a/eng/testing/performance/ios_scenarios_net6.proj +++ /dev/null @@ -1,168 +0,0 @@ - - - true - - - python3 - $(HelixPreCommands);chmod +x $HELIX_WORKITEM_PAYLOAD/SOD/SizeOnDisk - $(HelixPreCommands);chmod +x $HELIX_WORKITEM_PAYLOAD/startup/Startup - - - - - %(Identity) - - - - - nollvm - llvm - - - - %HELIX_CORRELATION_PAYLOAD%\performance\src\scenarios\ - - - - $HELIX_CORRELATION_PAYLOAD/performance/src/scenarios/ - - - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiios;cp -rf $HELIX_CORRELATION_PAYLOAD/MauiMacCatalystDefault ./app;$(Python) pre.py --name app - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiios;cp $HELIX_CORRELATION_PAYLOAD/MauiiOSDefault.ipa .;$(Python) pre.py --name MauiiOSDefault.ipa - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiios;cp $HELIX_CORRELATION_PAYLOAD/MauiiOSDefault.ipa .;$(Python) pre.py --unzip --name MauiiOSDefault.ipa - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiblazorios;cp $HELIX_CORRELATION_PAYLOAD/MauiBlazoriOSDefault.ipa .;$(Python) pre.py --name MauiBlazoriOSDefault.ipa - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiblazorios;cp $HELIX_CORRELATION_PAYLOAD/MauiBlazoriOSDefault.ipa .;$(Python) pre.py --unzip --name MauiBlazoriOSDefault.ipa - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiblazorios;cp -rf $HELIX_CORRELATION_PAYLOAD/MauiBlazorMacCatalystDefault ./app;$(Python) pre.py --name app - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiios;cp $HELIX_CORRELATION_PAYLOAD/MauiiOSPodcast.ipa .;$(Python) pre.py --name MauiiOSPodcast.ipa - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory) - cd $(ScenarioDirectory)mauiios;cp $HELIX_CORRELATION_PAYLOAD/MauiiOSPodcast.ipa .;$(Python) pre.py --unzip --name MauiiOSPodcast.ipa - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - $(WorkItemDirectory).zip - 00:15:00 - ios-device - - - - - - $(WorkItemDirectory).zip - 00:15:00 - ios-device - - - - - - $(WorkItemDirectory).zip - 00:15:00 - ios-device - - - - - - - - - export PYTHONPATH=$ORIGPYPATH;$(HelixPostCommands) - - - diff --git a/eng/testing/performance/microbenchmarks.proj b/eng/testing/performance/microbenchmarks.proj index 1331e1e7a811c0..35b6d316242990 100644 --- a/eng/testing/performance/microbenchmarks.proj +++ b/eng/testing/performance/microbenchmarks.proj @@ -34,7 +34,8 @@ python3 $(BaseDirectory)/Core_Root/corerun $(BaseDirectory)/Baseline_Core_Root/corerun - $(HelixPreCommands);chmod +x $(PerformanceDirectory)/tools/machine-setup.sh;. $(PerformanceDirectory)/tools/machine-setup.sh + + $(HelixPreCommands);chmod +x $(PerformanceDirectory)/tools/machine-setup.sh;. $(PerformanceDirectory)/tools/machine-setup.sh;export DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER=1 $HELIX_WORKITEM_ROOT/artifacts/BenchmarkDotNet.Artifacts $HELIX_WORKITEM_ROOT/artifacts/BenchmarkDotNet.Artifacts_Baseline $(PerformanceDirectory)/src/tools/ResultsComparer/ResultsComparer.csproj @@ -48,10 +49,10 @@ - --corerun %HELIX_CORRELATION_PAYLOAD%\dotnet-mono\shared\Microsoft.NETCore.App\7.0.0\corerun.exe + --corerun %HELIX_CORRELATION_PAYLOAD%\dotnet-mono\shared\Microsoft.NETCore.App\$(ProductVersion)\corerun.exe - --corerun $(BaseDirectory)/dotnet-mono/shared/Microsoft.NETCore.App/7.0.0/corerun + --corerun $(BaseDirectory)/dotnet-mono/shared/Microsoft.NETCore.App/$(ProductVersion)/corerun @@ -132,10 +133,10 @@ $(WorkItemDirectory) $(WorkItemCommand) --bdn-artifacts $(BaselineArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(BaselineCoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)" - if [ "x$PERF_PREREQS_INSTALLED" = "x1" ]; then - $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)"; + if [ "x$PERF_PREREQS_INSTALL_FAILED" = "x1" ]; then + echo "\n\n** Error: Failed to install prerequisites **\n\n"; (exit 1); else - echo "\n\n** Error: Failed to install prerequisites **\n\n"; export _commandExitCode=1; + $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)"; fi $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)" $(DotnetExe) run -f $(PERFLAB_Framework) -p $(ResultsComparer) --base $(BaselineArtifactsDirectory) --diff $(ArtifactsDirectory) --threshold 2$(Percent) --xml $(XMLResults);$(FinalCommand) @@ -148,10 +149,10 @@ $(WorkItemDirectory) $(WorkItemCommand) --bdn-artifacts $(BaselineArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(BaselineCoreRunArgument)" - if [ "x$PERF_PREREQS_INSTALLED" = "x1" ]; then - $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument)"; + if [ "x$PERF_PREREQS_INSTALL_FAILED" = "x1" ]; then + echo "\n\n** Error: Failed to install prerequisites **\n\n"; (exit 1); else - echo "\n\n** Error: Failed to install prerequisites **\n\n"; export _commandExitCode=1; + $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument)"; fi $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument)" $(DotnetExe) run -f $(PERFLAB_Framework) -p $(ResultsComparer) --base $(BaselineArtifactsDirectory) --diff $(ArtifactsDirectory) --threshold 2$(Percent) --xml $(XMLResults) diff --git a/eng/testing/performance/performance-setup.ps1 b/eng/testing/performance/performance-setup.ps1 index 99cbf657b87286..1590b5f1e36d09 100644 --- a/eng/testing/performance/performance-setup.ps1 +++ b/eng/testing/performance/performance-setup.ps1 @@ -26,7 +26,8 @@ Param( [switch] $DynamicPGO, [switch] $FullPGO, [switch] $iOSLlvmBuild, - [string] $MauiVersion + [string] $MauiVersion, + [switch] $UseLocalCommitTime ) $RunFromPerformanceRepo = ($Repository -eq "dotnet/performance") -or ($Repository -eq "dotnet-performance") @@ -120,13 +121,19 @@ elseif($FullPGO) $SetupArguments = "$SetupArguments --full-pgo" } +if($UseLocalCommitTime) +{ + $LocalCommitTime = (git show -s --format=%ci $CommitSha) + $SetupArguments = "$SetupArguments --commit-time `"$LocalCommitTime`"" +} + if ($RunFromPerformanceRepo) { $SetupArguments = "--perf-hash $CommitSha $CommonSetupArguments" robocopy $SourceDirectory $PerformanceDirectory /E /XD $PayloadDirectory $SourceDirectory\artifacts $SourceDirectory\.git } else { - git clone --branch main --depth 1 --quiet https://github.com/dotnet/performance $PerformanceDirectory + git clone --branch release/7.0 --depth 1 --quiet https://github.com/dotnet/performance $PerformanceDirectory } if($MonoDotnet -ne "") @@ -155,14 +162,7 @@ if ($AndroidMono) { { mkdir $WorkItemDirectory } - if($Kind -ne "android_scenarios_net6") - { - Copy-Item -path "$SourceDirectory\androidHelloWorld\HelloAndroid.apk" $PayloadDirectory -Verbose - } - Copy-Item -path "$SourceDirectory\MauiAndroidDefault.apk" $PayloadDirectory -Verbose - Copy-Item -path "$SourceDirectory\MauiBlazorAndroidDefault.apk" $PayloadDirectory -Verbose - Copy-Item -path "$SourceDirectory\MauiAndroidPodcast.apk" $PayloadDirectory -Verbose - + Copy-Item -path "$SourceDirectory\androidHelloWorld\HelloAndroid.apk" $PayloadDirectory -Verbose $SetupArguments = $SetupArguments -replace $Architecture, 'arm64' } diff --git a/eng/testing/performance/performance-setup.sh b/eng/testing/performance/performance-setup.sh index f830a0ac641f75..5af80ba888d62c 100755 --- a/eng/testing/performance/performance-setup.sh +++ b/eng/testing/performance/performance-setup.sh @@ -37,6 +37,7 @@ javascript_engine="v8" iosmono=false iosllvmbuild="" maui_version="" +use_local_commit_time=false only_sanity=false while (($# > 0)); do @@ -160,6 +161,10 @@ while (($# > 0)); do maui_version=$2 shift 2 ;; + --uselocalcommittime) + use_local_commit_time=true + shift 1 + ;; --perffork) perf_fork=$2 shift 2 @@ -200,6 +205,7 @@ while (($# > 0)); do echo " --iosmono Set for ios Mono/Maui runs" echo " --iosllvmbuild Set LLVM for iOS Mono/Maui runs" echo " --mauiversion Set the maui version for Mono/Maui runs" + echo " --uselocalcommittime Pass local runtime commit time to the setup script" echo "" exit 0 ;; @@ -314,6 +320,11 @@ if [[ "$internal" != true ]]; then setup_arguments="$setup_arguments --not-in-lab" fi +if [[ "$use_local_commit_time" == true ]]; then + local_commit_time=$(git show -s --format=%ci $commit_sha) + setup_arguments="$setup_arguments --commit-time \"$local_commit_time\"" +fi + if [[ "$run_from_perf_repo" == true ]]; then payload_directory= workitem_directory=$source_directory @@ -323,7 +334,7 @@ else if [[ -n "$perf_fork" ]]; then git clone --branch $perf_fork_branch --depth 1 --quiet $perf_fork $performance_directory else - git clone --branch main --depth 1 --quiet https://github.com/dotnet/performance.git $performance_directory + git clone --branch release/7.0 --depth 1 --quiet https://github.com/dotnet/performance.git $performance_directory fi # uncomment to use BenchmarkDotNet sources instead of nuget packages # git clone https://github.com/dotnet/BenchmarkDotNet.git $benchmark_directory @@ -374,37 +385,13 @@ fi if [[ "$iosmono" == "true" ]]; then if [[ "$iosllvmbuild" == "True" ]]; then - if [[ "$kind" != "ios_scenarios_net6" ]]; then - # LLVM Mono .app - mkdir -p $payload_directory/iosHelloWorld && cp -rv $source_directory/iosHelloWorld/llvm $payload_directory/iosHelloWorld - mkdir -p $payload_directory/iosHelloWorldZip/llvmzip && cp -rv $source_directory/iosHelloWorldZip/llvmzip $payload_directory/iosHelloWorldZip - fi + # LLVM Mono .app + mkdir -p $payload_directory/iosHelloWorld && cp -rv $source_directory/iosHelloWorld/llvm $payload_directory/iosHelloWorld + mkdir -p $payload_directory/iosHelloWorldZip/llvmzip && cp -rv $source_directory/iosHelloWorldZip/llvmzip $payload_directory/iosHelloWorldZip else - # NoLLVM Mono .app, Maui iOS IPA, Maui Maccatalyst, Maui iOS Podcast IPA - if [[ "$kind" != "ios_scenarios_net6" ]]; then - mkdir -p $payload_directory/iosHelloWorld && cp -rv $source_directory/iosHelloWorld/nollvm $payload_directory/iosHelloWorld - mkdir -p $payload_directory/iosHelloWorldZip/nollvmzip && cp -rv $source_directory/iosHelloWorldZip/nollvmzip $payload_directory/iosHelloWorldZip - fi - mkdir -p $payload_directory/MauiMacCatalystDefault && cp -rv $source_directory/MauiMacCatalystDefault/MauiMacCatalystDefault.app $payload_directory/MauiMacCatalystDefault - mkdir -p $payload_directory/MauiBlazorMacCatalystDefault && cp -rv $source_directory/MauiBlazorMacCatalystDefault/MauiBlazorMacCatalystDefault.app $payload_directory/MauiBlazorMacCatalystDefault - cp -v $source_directory/MauiiOSDefaultIPA/MauiiOSDefault.ipa $payload_directory/MauiiOSDefault.ipa - cp -v $source_directory/MauiBlazoriOSDefaultIPA/MauiBlazoriOSDefault.ipa $payload_directory/MauiBlazoriOSDefault.ipa - cp -v $source_directory/MauiiOSPodcastIPA/MauiiOSPodcast.ipa $payload_directory/MauiiOSPodcast.ipa - - # Get the .app so we can resign in the xharness item - cp -v $source_directory/MauiiOSDefaultIPA/MauiiOSDefault.ipa $source_directory/MauiiOSDefaultIPA/MauiiOSDefault.zip - unzip -d $source_directory/MauiiOSDefaultIPA $source_directory/MauiiOSDefaultIPA/MauiiOSDefault.zip - mv $source_directory/MauiiOSDefaultIPA/Payload/MauiTesting.app $payload_directory/ - - # Get the .app so we can resign in the xharness item for Maui Blazor - cp -v $source_directory/MauiBlazoriOSDefaultIPA/MauiBlazoriOSDefault.ipa $source_directory/MauiBlazoriOSDefaultIPA/MauiBlazoriOSDefault.zip - unzip -d $source_directory/MauiBlazoriOSDefaultIPA $source_directory/MauiBlazoriOSDefaultIPA/MauiBlazoriOSDefault.zip - mv $source_directory/MauiBlazoriOSDefaultIPA/Payload/MauiBlazorTesting.app $payload_directory/ - - # Get the .app so we can resign in the xharness item for podcast - cp -v $source_directory/MauiiOSPodcastIPA/MauiiOSPodcast.ipa $source_directory/MauiiOSPodcastIPA/MauiiOSPodcast.zip - unzip -d $source_directory/MauiiOSPodcastIPA $source_directory/MauiiOSPodcastIPA/MauiiOSPodcast.zip - mv $source_directory/MauiiOSPodcastIPA/Payload/Microsoft.NetConf2021.Maui.app $payload_directory/ + # NoLLVM Mono .app + mkdir -p $payload_directory/iosHelloWorld && cp -rv $source_directory/iosHelloWorld/nollvm $payload_directory/iosHelloWorld + mkdir -p $payload_directory/iosHelloWorldZip/nollvmzip && cp -rv $source_directory/iosHelloWorldZip/nollvmzip $payload_directory/iosHelloWorldZip fi fi diff --git a/eng/testing/scenarios/BuildWasmAppsJobsList.txt b/eng/testing/scenarios/BuildWasmAppsJobsList.txt index bbbca109e575c1..7a92f4e164eb3d 100644 --- a/eng/testing/scenarios/BuildWasmAppsJobsList.txt +++ b/eng/testing/scenarios/BuildWasmAppsJobsList.txt @@ -17,6 +17,8 @@ Wasm.Build.Tests.RebuildTests Wasm.Build.Tests.SatelliteAssembliesTests Wasm.Build.Tests.WasmBuildAppTest Wasm.Build.Tests.WasmNativeDefaultsTests -Wasm.Build.Tests.WorkloadTests Wasm.Build.Tests.WasmRunOutOfAppBundleTests +Wasm.Build.Tests.WasmSIMDTests Wasm.Build.Tests.WasmTemplateTests +Wasm.Build.Tests.WorkloadTests + diff --git a/eng/testing/tests.android.targets b/eng/testing/tests.android.targets index a8b279bf853178..6539bed6e1c9de 100644 --- a/eng/testing/tests.android.targets +++ b/eng/testing/tests.android.targets @@ -69,6 +69,8 @@ DestinationFolder="$(TestArchiveTestsDir)" SkipUnchangedFiles="true" Condition="'$(ArchiveTests)' == 'true' and '$(IgnoreForCI)' != 'true'" /> + + \ No newline at end of file diff --git a/eng/testing/tests.singlefile.targets b/eng/testing/tests.singlefile.targets index 86a8b98d545f84..d307e837e6235a 100644 --- a/eng/testing/tests.singlefile.targets +++ b/eng/testing/tests.singlefile.targets @@ -26,48 +26,13 @@ $(CoreCLRILCompilerDir)netstandard/ILCompiler.Build.Tasks.dll $(CoreCLRAotSdkDir) $(NetCoreAppCurrentTestHostSharedFrameworkPath) - $(NoWarn);IL3050;IL3051;IL3052;IL3054;IL3055;IL1005;IL3002 + $(NoWarn);IL1005;IL3002 partial - false true + true true - - - - $(NoWarn);IL2026;IL2116 - - $(NoWarn);IL2041;IL2042;IL2043;IL2056 - - $(NoWarn);IL2045 - - $(NoWarn);IL2046 - - $(NoWarn);IL2050 - - $(NoWarn);IL2032;IL2055;IL2057;IL2058;IL2059;IL2060;IL2061;IL2096 - - $(NoWarn);IL2062;IL2063;IL2064;IL2065;IL2066 - - $(NoWarn);IL2067;IL2068;IL2069;IL2070;IL2071;IL2072;IL2073;IL2074;IL2075;IL2076;IL2077;IL2078;IL2079;IL2080;IL2081;IL2082;IL2083;IL2084;IL2085;IL2086;IL2087;IL2088;IL2089;IL2090;IL2091 - - $(NoWarn);IL2092;IL2093;IL2094;IL2095 - - $(NoWarn);IL2097;IL2098;IL2099;IL2106 - - $(NoWarn);IL2103 - - $(NoWarn);IL2107;IL2117 - - $(NoWarn);IL2109 - - $(NoWarn);IL2110;IL2111;IL2114;IL2115 - - $(NoWarn);IL2112;IL2113 - - $(NoWarn);IL2118;IL2119;IL2120 - diff --git a/eng/testing/tests.wasm.targets b/eng/testing/tests.wasm.targets index 02af8e04189c71..dd2e34364522b9 100644 --- a/eng/testing/tests.wasm.targets +++ b/eng/testing/tests.wasm.targets @@ -27,6 +27,9 @@ <_WasmStrictVersionMatch Condition="'$(ContinuousIntegrationBuild)' == 'true'">true true <_UseWasmSymbolicator Condition="'$(TestTrimming)' != 'true'">true + true + false + _GetWorkloadsToInstall;$(InstallWorkloadUsingArtifactsDependsOn) @@ -58,12 +61,16 @@ + + + + @@ -103,6 +110,8 @@ <_AOTBuildCommand Condition="'$(ContinuousIntegrationBuild)' != 'true'">$(_AOTBuildCommand) /p:RuntimeSrcDir=$(RepoRoot) /p:RuntimeConfig=$(Configuration) <_AOTBuildCommand>$(_AOTBuildCommand) /p:RunAOTCompilation=$(RunAOTCompilation) + <_AOTBuildCommand Condition="'$(BrowserHost)' == 'windows'">$(_AOTBuildCommand) %AOT_BUILD_ARGS% + <_AOTBuildCommand Condition="'$(BrowserHost)' != 'windows'">$(_AOTBuildCommand) %24AOT_BUILD_ARGS <_AOTBuildCommand>$(_AOTBuildCommand) $(_ShellCommandSeparator) cd wasm_build/AppBundle $(_AOTBuildCommand) @@ -138,15 +147,6 @@ - - - @@ -315,4 +315,30 @@ + + + + + + + + + + + + + diff --git a/eng/testing/workloads-testing.targets b/eng/testing/workloads-testing.targets index 5da047758f8185..3e04032c750708 100644 --- a/eng/testing/workloads-testing.targets +++ b/eng/testing/workloads-testing.targets @@ -5,6 +5,21 @@ true + + <_SdkForWorkloadTestingBasePath>$(ArtifactsBinDir) + <_SdkWithNoWorkloadPath>$([MSBuild]::NormalizeDirectory($(_SdkForWorkloadTestingBasePath), 'dotnet-none')) + <_SdkWithNoWorkloadStampPath>$([MSBuild]::NormalizePath($(_SdkWithNoWorkloadPath), '.version-$(SdkVersionForWorkloadTesting).stamp')) + + $(InstallWorkloadUsingArtifactsDependsOn); + _SetPackageVersionForWorkloadsTesting; + _GetNuGetsToBuild; + _PreparePackagesForWorkloadInstall; + GetWorkloadInputs; + _ProvisionDotNetForWorkloadTesting; + _InstallWorkloads + + + <_DefaultPropsForNuGetBuild Include="Configuration=$(Configuration)" /> <_DefaultPropsForNuGetBuild Include="TargetOS=Browser" /> @@ -12,43 +27,21 @@ <_DefaultPropsForNuGetBuild Include="ContinuousIntegrationBuild=$(ContinuousIntegrationBuild)" /> - - - + + - - - - - - <_SourceFiles Include="$(SdkWithNoWorkloadForTestingPath)\**" /> + <_SdkWithNoWorkloadTarget Include="none" InstallPath="$(_SdkWithNoWorkloadPath)" /> - - - - - - - - - - - - - - + + <_DotNetInstallScriptName Condition="!$([MSBuild]::IsOSPlatform('windows'))">dotnet-install.sh @@ -59,16 +52,16 @@ <_DotNetInstallCommand Condition="!$([MSBuild]::IsOSPlatform('windows'))" - >$(_DotNetInstallScriptPath) -i $(SdkWithNoWorkloadForTestingPath) -v $(SdkVersionForWorkloadTesting) + >$(_DotNetInstallScriptPath) -i $(_SdkWithNoWorkloadPath) -v $(SdkVersionForWorkloadTesting) <_DotNetInstallCommand Condition="$([MSBuild]::IsOSPlatform('windows'))" - >$(_DotNetInstallScriptPath) -InstallDir $(SdkWithNoWorkloadForTestingPath) -Version $(SdkVersionForWorkloadTesting) + >$(_DotNetInstallScriptPath) -InstallDir $(_SdkWithNoWorkloadPath) -Version $(SdkVersionForWorkloadTesting) <_DotNetInstallCommand Condition="!$([MSBuild]::IsOSPlatform('windows'))" - >$(_DotNetInstallScriptPath) -i $(SdkWithNoWorkloadForTestingPath) -v latest -q daily --channel 7.0 + >$(_DotNetInstallScriptPath) -i $(_SdkWithNoWorkloadPath) -v latest -q daily --channel $(DotNetChannelToUseForWorkloadTesting) <_DotNetInstallCommand Condition="$([MSBuild]::IsOSPlatform('windows'))" - >$(_DotNetInstallScriptPath) -InstallDir $(SdkWithNoWorkloadForTestingPath) -Quality daily -Channel 7.0 + >$(_DotNetInstallScriptPath) -InstallDir $(_SdkWithNoWorkloadPath) -Quality daily -Channel $(DotNetChannelToUseForWorkloadTesting) + + + + + + $(PackageVersion) + $(ProductVersion) + - + + + + <_SdkWithWorkloadToInstall Include="@(WorkloadCombinationsToInstall)" /> + <_SdkWithWorkloadToInstall InstallPath="$(_SdkForWorkloadTestingBasePath)\dotnet-%(Identity)" /> + + <_SdkWithWorkloadToInstall StampPath="%(InstallPath)\version.stamp" /> @@ -104,19 +112,17 @@ - <_PackageVersion>$(PackageVersion) - <_PackageVersion Condition="'$(StabilizePackageVersion)' == 'true'">$(ProductVersion) - <_AOTCrossNuGetPath>$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.AOT.$(NETCoreSdkRuntimeIdentifier).Cross.$(RuntimeIdentifier).$(_PackageVersion).nupkg + <_AOTCrossNuGetPath>$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.AOT.$(NETCoreSdkRuntimeIdentifier).Cross.$(RuntimeIdentifier).$(PackageVersionForWorkloadManifests).nupkg - <_NuGetsToBuild Include="$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Ref.$(_PackageVersion).nupkg" + <_NuGetsToBuild Include="$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Ref.$(PackageVersionForWorkloadManifests).nupkg" Project="$(InstallerProjectRoot)pkg/sfx/Microsoft.NETCore.App\Microsoft.NETCore.App.Ref.sfxproj" Properties="@(_DefaultPropsForNuGetBuild, ';')" Descriptor="Ref pack"/> @@ -153,37 +159,35 @@ --> - <_PackageVersion>$(PackageVersion) - <_PackageVersion Condition="'$(StabilizePackageVersion)' == 'true'">$(ProductVersion) - <_DefaultBuildVariant Condition="'$(WasmEnableThreads)' == 'true' or '$(MonoWasmBuildVariant)' == 'multithread'">.multithread. <_DefaultBuildVariant Condition="'$(WasmEnablePerfTracing)' == 'true' or '$(MonoWasmBuildVariant)' == 'perftrace'">.perftrace. <_DefaultBuildVariant Condition="'$(_DefaultBuildVariant)' == ''">. - <_DefaultRuntimePackNuGetPath>$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.Mono$(_DefaultBuildVariant)browser-wasm.$(_PackageVersion).nupkg + <_DefaultRuntimePackNuGetPath>$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.Mono$(_DefaultBuildVariant)browser-wasm.$(PackageVersionForWorkloadManifests).nupkg - <_RuntimePackNugetAvailable Include="$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.Mono*$(_PackageVersion).nupkg" /> + <_RuntimePackNugetAvailable Include="$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.Mono*$(PackageVersionForWorkloadManifests).nupkg" /> + <_RuntimePackNugetAvailable Remove="@(_RuntimePackNugetAvailable)" Condition="$([System.String]::new('%(_RuntimePackNugetAvailable.FileName)').EndsWith('.symbols'))" /> + Text="Expected to find either one or three in $(LibrariesShippingPackagesDir): @(_RuntimePackNugetAvailable->'%(FileName)%(Extension)')" /> <_BuildVariants Include="multithread" Condition="'$(_DefaultBuildVariant)' != '.multithread.'" /> <_BuildVariants Include="perftrace" Condition="'$(_DefaultBuildVariant)' != '.perftrace.'" /> - <_NuGetsToBuild Include="$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.Mono.%(_BuildVariants.Identity).browser-wasm.$(_PackageVersion).nupkg" + <_NuGetsToBuild Include="$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.Mono.%(_BuildVariants.Identity).browser-wasm.$(PackageVersionForWorkloadManifests).nupkg" Project="$(InstallerProjectRoot)pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.sfxproj" Dependencies="$(_DefaultRuntimePackNuGetPath)" Properties="@(_DefaultPropsForNuGetBuild, ';');MonoWasmBuildVariant=%(_BuildVariants.Identity)" Descriptor="runtime pack for %(_BuildVariants.Identity)" /> - <_NuGetsToBuild Include="$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.Mono.browser-wasm.$(_PackageVersion).nupkg" + <_NuGetsToBuild Include="$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.Mono.browser-wasm.$(PackageVersionForWorkloadManifests).nupkg" Project="$(InstallerProjectRoot)pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.sfxproj" - Properties="@(_DefaultPropsForNuGetBuild, ';')" + Properties="@(_DefaultPropsForNuGetBuild, ';');MonoWasmBuildVariant=" Dependencies="$(_DefaultRuntimePackNuGetPath)" Descriptor="single threaded runtime pack" Condition="'$(_DefaultBuildVariant)' != '.'" /> @@ -202,42 +206,24 @@ *******************" /> - + Outputs="@(_SdkWithWorkloadToInstall->'%(StampPath)');$(_SdkWithNoWorkloadStampPath)"> <_BuiltNuGets Include="$(LibrariesShippingPackagesDir)\*.nupkg" /> - - - - - - - - - - - + SdkWithNoWorkloadInstalledPath="$(_SdkWithNoWorkloadPath)" + /> - + diff --git a/eng/testing/xunit/xunit.targets b/eng/testing/xunit/xunit.targets index 6b048e6f6a9a4e..e72ebd444ad835 100644 --- a/eng/testing/xunit/xunit.targets +++ b/eng/testing/xunit/xunit.targets @@ -6,6 +6,11 @@ Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'" /> + + true + true + + $(OutDir) diff --git a/global.json b/global.json index 9f0baa92e79cf3..df5c16088b84a3 100644 --- a/global.json +++ b/global.json @@ -1,16 +1,16 @@ { "sdk": { - "version": "7.0.100-preview.7.22377.5", + "version": "7.0.109", "allowPrerelease": true, "rollForward": "major" }, "tools": { - "dotnet": "7.0.100-preview.7.22377.5" + "dotnet": "7.0.109" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22411.2", - "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22411.2", - "Microsoft.DotNet.SharedFramework.Sdk": "7.0.0-beta.22411.2", + "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.23361.2", + "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.23361.2", + "Microsoft.DotNet.SharedFramework.Sdk": "7.0.0-beta.23361.2", "Microsoft.Build.NoTargets": "3.5.0", "Microsoft.Build.Traversal": "3.1.6", "Microsoft.NET.Sdk.IL": "7.0.0-rc.1.22414.6" diff --git a/src/coreclr/.nuget/Directory.Build.props b/src/coreclr/.nuget/Directory.Build.props index 0346d99435a90e..dc79f51339f654 100644 --- a/src/coreclr/.nuget/Directory.Build.props +++ b/src/coreclr/.nuget/Directory.Build.props @@ -17,6 +17,11 @@ true + + + + false + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.cs index a2d74336bee2b7..84bc2f8ebd3c47 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.cs @@ -504,7 +504,9 @@ Signature LazyCreateSignature() Span shouldCopyBackParameters = new(ref argStorage._copyBack0, argCount); StackAllocatedByRefs byrefStorage = default; +#pragma warning disable CS8500 IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; +#pragma warning restore CS8500 CheckArguments( copyOfParameters, diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MdFieldInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MdFieldInfo.cs index 8f241f4d8b6b3e..eba04c404f4790 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MdFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MdFieldInfo.cs @@ -46,7 +46,11 @@ o is MdFieldInfo m && public override bool Equals(object? obj) => ReferenceEquals(this, obj) || - (MetadataUpdater.IsSupported && CacheEquals(obj)); + (MetadataUpdater.IsSupported && + obj is MdFieldInfo fi && + fi.m_tkField == m_tkField && + ReferenceEquals(fi.m_declaringType, m_declaringType) && + ReferenceEquals(fi.m_reflectedTypeCache.GetRuntimeType(), m_reflectedTypeCache.GetRuntimeType())); public override int GetHashCode() => HashCode.Combine(m_tkField.GetHashCode(), m_declaringType.GetUnderlyingNativeHandle().GetHashCode()); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs index ac322bdf20c002..58de30a1976c32 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -18,23 +18,12 @@ internal sealed unsafe class RtFieldInfo : RuntimeFieldInfo, IRuntimeFieldInfo // lazy caching private string? m_name; private RuntimeType? m_fieldType; - internal FieldAccessor? m_invoker; - + private InvocationFlags m_invocationFlags; internal InvocationFlags InvocationFlags { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (Invoker._invocationFlags & InvocationFlags.Initialized) != 0 ? - Invoker._invocationFlags : InitializeInvocationFlags(); - } - - private FieldAccessor Invoker - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - m_invoker ??= new FieldAccessor(this); - return m_invoker; - } + get => (m_invocationFlags & InvocationFlags.Initialized) != 0 ? + m_invocationFlags : InitializeInvocationFlags(); } [MethodImpl(MethodImplOptions.NoInlining)] @@ -67,7 +56,7 @@ private InvocationFlags InitializeInvocationFlags() } // must be last to avoid threading problems - return Invoker._invocationFlags = invocationFlags | InvocationFlags.Initialized; + return m_invocationFlags = invocationFlags | InvocationFlags.Initialized; } #endregion @@ -113,63 +102,6 @@ internal override bool CacheEquals(object? o) return o is RtFieldInfo m && m.m_fieldHandle == m_fieldHandle; } - [DebuggerStepThrough] - [DebuggerHidden] - internal object? GetValueNonEmit(object? obj) - { - RuntimeType? declaringType = DeclaringType as RuntimeType; - RuntimeType fieldType = (RuntimeType)FieldType; - bool domainInitialized = false; - - if (declaringType == null) - { - return RuntimeFieldHandle.GetValue(this, obj, fieldType, null, ref domainInitialized); - } - else - { - domainInitialized = declaringType.DomainInitialized; - object? retVal = RuntimeFieldHandle.GetValue(this, obj, fieldType, declaringType, ref domainInitialized); - declaringType.DomainInitialized = domainInitialized; - return retVal; - } - } - - [DebuggerStepThrough] - [DebuggerHidden] - internal void SetValueNonEmit(object? obj, object? value) - { - RuntimeType? declaringType = DeclaringType as RuntimeType; - RuntimeType fieldType = (RuntimeType)FieldType; - bool domainInitialized = false; - - if (declaringType == null) - { - RuntimeFieldHandle.SetValue( - this, - obj, - value, - fieldType, - Attributes, - declaringType: null, - ref domainInitialized); - } - else - { - domainInitialized = declaringType.DomainInitialized; - - RuntimeFieldHandle.SetValue( - this, - obj, - value, - fieldType, - Attributes, - declaringType, - ref domainInitialized); - - declaringType.DomainInitialized = domainInitialized; - } - } - #endregion #region MemberInfo Overrides @@ -186,7 +118,10 @@ internal override RuntimeModule GetRuntimeModule() public override bool Equals(object? obj) => ReferenceEquals(this, obj) || - (MetadataUpdater.IsSupported && CacheEquals(obj)); + (MetadataUpdater.IsSupported && + obj is RtFieldInfo fi && + fi.m_fieldHandle == m_fieldHandle && + ReferenceEquals(fi.m_reflectedTypeCache.GetRuntimeType(), m_reflectedTypeCache.GetRuntimeType())); public override int GetHashCode() => HashCode.Combine(m_fieldHandle.GetHashCode(), m_declaringType.GetUnderlyingNativeHandle().GetHashCode()); @@ -211,7 +146,20 @@ public override int GetHashCode() => CheckConsistency(obj); - return Invoker.GetValue(obj); + RuntimeType fieldType = (RuntimeType)FieldType; + + bool domainInitialized = false; + if (declaringType == null) + { + return RuntimeFieldHandle.GetValue(this, obj, fieldType, null, ref domainInitialized); + } + else + { + domainInitialized = declaringType.DomainInitialized; + object? retVal = RuntimeFieldHandle.GetValue(this, obj, fieldType, declaringType, ref domainInitialized); + declaringType.DomainInitialized = domainInitialized; + return retVal; + } } public override object GetRawConstantValue() { throw new InvalidOperationException(); } @@ -258,7 +206,17 @@ public override void SetValue(object? obj, object? value, BindingFlags invokeAtt fieldType.CheckValue(ref value, copyBack: ref _ref, binder, culture, invokeAttr); } - Invoker.SetValue(obj, value); + bool domainInitialized = false; + if (declaringType is null) + { + RuntimeFieldHandle.SetValue(this, obj, value, fieldType, m_fieldAttributes, null, ref domainInitialized); + } + else + { + domainInitialized = declaringType.DomainInitialized; + RuntimeFieldHandle.SetValue(this, obj, value, fieldType, m_fieldAttributes, declaringType, ref domainInitialized); + declaringType.DomainInitialized = domainInitialized; + } } [DebuggerStepThrough] diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeEventInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeEventInfo.cs index 09adb05fc289c3..943c4d6ccd050b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeEventInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeEventInfo.cs @@ -71,7 +71,11 @@ public override string ToString() public override bool Equals(object? obj) => ReferenceEquals(this, obj) || - (MetadataUpdater.IsSupported && CacheEquals(obj)); + (MetadataUpdater.IsSupported && + obj is RuntimeEventInfo ei && + ei.m_token == m_token && + ReferenceEquals(ei.m_declaringType, m_declaringType) && + ReferenceEquals(ei.m_reflectedTypeCache.GetRuntimeType(), m_reflectedTypeCache.GetRuntimeType())); public override int GetHashCode() => HashCode.Combine(m_token.GetHashCode(), m_declaringType.GetUnderlyingNativeHandle().GetHashCode()); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs index c55ee607da7738..4e1246b481b102 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs @@ -316,7 +316,9 @@ public override MethodImplAttributes GetMethodImplementationFlags() Span shouldCopyBackParameters = new(ref argStorage._copyBack0, 1); StackAllocatedByRefs byrefStorage = default; +#pragma warning disable 8500 IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; +#pragma warning restore 8500 CheckArguments( copyOfParameters, diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs index b844b6eaa0d752..a6b97f36353b85 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs @@ -182,7 +182,10 @@ public override IList GetCustomAttributesData() public override bool Equals(object? obj) => ReferenceEquals(this, obj) || - (MetadataUpdater.IsSupported && CacheEquals(obj)); + (MetadataUpdater.IsSupported && obj is RuntimePropertyInfo rpi && + rpi.m_token == m_token && + ReferenceEquals(rpi.m_declaringType, m_declaringType) && + ReferenceEquals(rpi.m_reflectedTypeCache.GetRuntimeType(), m_reflectedTypeCache.GetRuntimeType())); public override int GetHashCode() => HashCode.Combine(m_token.GetHashCode(), m_declaringType.GetUnderlyingNativeHandle().GetHashCode()); diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 6b7d2425135cc0..4b8846c4ad34f5 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -1185,7 +1185,6 @@ public RuntimeFieldInfoStub(RuntimeFieldHandleInternal fieldHandle, object keepa private object? m_d; private int m_b; private object? m_e; - private object? m_f; private RuntimeFieldHandleInternal m_fieldHandle; #pragma warning restore 414, 169 diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 595f5482fca5b9..bc731b674d2e97 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -176,7 +176,7 @@ internal static unsafe void ConvertFixedToNative(int flags, string strManaged, I internal static unsafe string ConvertFixedToManaged(IntPtr cstr, int length) { - int end = SpanHelpers.IndexOf(ref *(byte*)cstr, 0, length); + int end = new ReadOnlySpan((byte*)cstr, length).IndexOf((byte)0); if (end >= 0) { length = end; @@ -450,7 +450,7 @@ internal static unsafe void ConvertToNative(string? strManaged, IntPtr nativeHom internal static unsafe string ConvertToManaged(IntPtr nativeHome, int length) { - int end = SpanHelpers.IndexOf(ref *(char*)nativeHome, '\0', length); + int end = new ReadOnlySpan((char*)nativeHome, length).IndexOf('\0'); if (end >= 0) { length = end; diff --git a/src/coreclr/binder/inc/assembly.hpp b/src/coreclr/binder/inc/assembly.hpp index 8c24ebb6d30e01..908e9387932650 100644 --- a/src/coreclr/binder/inc/assembly.hpp +++ b/src/coreclr/binder/inc/assembly.hpp @@ -26,6 +26,7 @@ #endif // !defined(DACCESS_COMPILE) #include "bundle.h" +#include class DomainAssembly; @@ -50,7 +51,7 @@ namespace BINDER_SPACE PEImage* GetPEImage(); BOOL GetIsInTPA(); - inline AssemblyBinder* GetBinder() + PTR_AssemblyBinder GetBinder() { return m_pBinder; } @@ -70,21 +71,21 @@ namespace BINDER_SPACE LONG m_cRef; PEImage *m_pPEImage; AssemblyName *m_pAssemblyName; - AssemblyBinder *m_pBinder; + PTR_AssemblyBinder m_pBinder; bool m_isInTPA; DomainAssembly *m_domainAssembly; +#if !defined(DACCESS_COMPILE) inline void SetBinder(AssemblyBinder *pBinder) { _ASSERTE(m_pBinder == NULL || m_pBinder == pBinder); m_pBinder = pBinder; } - friend class ::DefaultAssemblyBinder; - -#if !defined(DACCESS_COMPILE) friend class ::CustomAssemblyBinder; #endif // !defined(DACCESS_COMPILE) + + friend class ::DefaultAssemblyBinder; }; #include "assembly.inl" diff --git a/src/coreclr/binder/utils.cpp b/src/coreclr/binder/utils.cpp index 44d44dc9c0a7be..edd1e0026b74d8 100644 --- a/src/coreclr/binder/utils.cpp +++ b/src/coreclr/binder/utils.cpp @@ -150,56 +150,68 @@ namespace BINDER_SPACE isNativeImage = false; HRESULT pathResult = S_OK; - IF_FAIL_GO(pathResult = GetNextPath(paths, startPos, outPath)); - if (pathResult == S_FALSE) + while(true) { - return S_FALSE; - } - - if (Path::IsRelative(outPath)) - { - GO_WITH_HRESULT(E_INVALIDARG); - } - - { - // Find the beginning of the simple name - SString::CIterator iSimpleNameStart = outPath.End(); - - if (!outPath.FindBack(iSimpleNameStart, DIRECTORY_SEPARATOR_CHAR_W)) - { - iSimpleNameStart = outPath.Begin(); - } - else + IF_FAIL_GO(pathResult = GetNextPath(paths, startPos, outPath)); + if (pathResult == S_FALSE) { - // Advance past the directory separator to the first character of the file name - iSimpleNameStart++; + return S_FALSE; } - if (iSimpleNameStart == outPath.End()) + if (Path::IsRelative(outPath)) { GO_WITH_HRESULT(E_INVALIDARG); } - const SString sNiDll(SString::Literal, W(".ni.dll")); - const SString sNiExe(SString::Literal, W(".ni.exe")); - const SString sDll(SString::Literal, W(".dll")); - const SString sExe(SString::Literal, W(".exe")); - - if (!dllOnly && (outPath.EndsWithCaseInsensitive(sNiDll) || - outPath.EndsWithCaseInsensitive(sNiExe))) - { - simpleName.Set(outPath, iSimpleNameStart, outPath.End() - 7); - isNativeImage = true; - } - else if (outPath.EndsWithCaseInsensitive(sDll) || - (!dllOnly && outPath.EndsWithCaseInsensitive(sExe))) - { - simpleName.Set(outPath, iSimpleNameStart, outPath.End() - 4); - } - else { - // Invalid filename - GO_WITH_HRESULT(E_INVALIDARG); + // Find the beginning of the simple name + SString::CIterator iSimpleNameStart = outPath.End(); + + if (!outPath.FindBack(iSimpleNameStart, DIRECTORY_SEPARATOR_CHAR_W)) + { + iSimpleNameStart = outPath.Begin(); + } + else + { + // Advance past the directory separator to the first character of the file name + iSimpleNameStart++; + } + + if (iSimpleNameStart == outPath.End()) + { + GO_WITH_HRESULT(E_INVALIDARG); + } + + const SString sNiDll(SString::Literal, W(".ni.dll")); + const SString sNiExe(SString::Literal, W(".ni.exe")); + const SString sDll(SString::Literal, W(".dll")); + const SString sExe(SString::Literal, W(".exe")); + + if (dllOnly && (outPath.EndsWithCaseInsensitive(sExe) || + outPath.EndsWithCaseInsensitive(sNiExe))) + { + // Skip exe files when the caller requested only dlls + continue; + } + + if (outPath.EndsWithCaseInsensitive(sNiDll) || + outPath.EndsWithCaseInsensitive(sNiExe)) + { + simpleName.Set(outPath, iSimpleNameStart, outPath.End() - 7); + isNativeImage = true; + } + else if (outPath.EndsWithCaseInsensitive(sDll) || + outPath.EndsWithCaseInsensitive(sExe)) + { + simpleName.Set(outPath, iSimpleNameStart, outPath.End() - 4); + } + else + { + // Invalid filename + GO_WITH_HRESULT(E_INVALIDARG); + } + + break; } } diff --git a/src/coreclr/debug/createdump/crashinfo.cpp b/src/coreclr/debug/createdump/crashinfo.cpp index 23601cfde1b5d9..25478df208905c 100644 --- a/src/coreclr/debug/createdump/crashinfo.cpp +++ b/src/coreclr/debug/createdump/crashinfo.cpp @@ -2,22 +2,23 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "createdump.h" +#include // 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) : +CrashInfo::CrashInfo(const CreateDumpOptions& options) : m_ref(1), - m_pid(pid), + m_pid(options.Pid), m_ppid(-1), m_hdac(nullptr), m_pClrDataEnumRegions(nullptr), m_pClrDataProcess(nullptr), - m_gatherFrames(gatherFrames), - m_crashThread(crashThread), - m_signal(signal), + m_gatherFrames(options.CrashReport), + m_crashThread(options.CrashThread), + m_signal(options.Signal), m_moduleInfos(&ModuleInfoCompare), m_mainModule(nullptr), m_cbModuleMappings(0), @@ -30,6 +31,11 @@ CrashInfo::CrashInfo(pid_t pid, bool gatherFrames, pid_t crashThread, uint32_t s #else m_auxvValues.fill(0); m_fd = -1; + memset(&m_siginfo, 0, sizeof(m_siginfo)); + m_siginfo.si_signo = options.Signal; + m_siginfo.si_code = options.SignalCode; + m_siginfo.si_errno = options.SignalErrno; + m_siginfo.si_addr = options.SignalAddress; #endif } @@ -328,24 +334,34 @@ CrashInfo::EnumerateMemoryRegionsWithDAC(MINIDUMP_TYPE minidumpType) { TRACE("EnumerateMemoryRegionsWithDAC: Memory enumeration STARTED (%d %d)\n", m_enumMemoryPagesAdded, m_dataTargetPagesAdded); - // Since on both Linux and MacOS all the RW regions will be added for heap - // dumps by createdump, the only thing differentiating a MiniDumpNormal and - // a MiniDumpWithPrivateReadWriteMemory is that the later uses the EnumMemory - // APIs. This is kind of expensive on larger applications (4 minutes, or even - // more), and this should already be in RW pages. Change the dump type to the - // faster normal one. This one already ensures necessary DAC globals, etc. - // without the costly assembly, module, class, type runtime data structures - // enumeration. + // Since on MacOS all the RW regions will be added for heap dumps by createdump, the + // only thing differentiating a MiniDumpNormal and a MiniDumpWithPrivateReadWriteMemory + // is that the later uses the EnumMemoryRegions APIs. This is kind of expensive on larger + // applications (4 minutes, or even more), and this should already be in RW pages. Change + // the dump type to the faster normal one. This one already ensures necessary DAC globals, + // etc. without the costly assembly, module, class, type runtime data structures enumeration. + CLRDataEnumMemoryFlags flags = CLRDATA_ENUM_MEM_DEFAULT; if (minidumpType & MiniDumpWithPrivateReadWriteMemory) { - char* fastHeapDumps = getenv("COMPlus_DbgEnableFastHeapDumps"); - if (fastHeapDumps != nullptr && strcmp(fastHeapDumps, "1") == 0) + // This is the old fast heap env var for backwards compatibility for VS4Mac. + CLRConfigNoCache fastHeapDumps = CLRConfigNoCache::Get("DbgEnableFastHeapDumps", /*noprefix*/ false, &getenv); + DWORD val = 0; + if (fastHeapDumps.IsSet() && fastHeapDumps.TryAsInteger(10, val) && val == 1) { minidumpType = MiniDumpNormal; } + // This the new variable that also skips the expensive (in both time and memory usage) + // enumeration of the low level data structures and adds all the loader allocator heaps + // instead. The above original env var didn't generate a complete enough heap dump on + // Linux and this new one does. + fastHeapDumps = CLRConfigNoCache::Get("EnableFastHeapDumps", /*noprefix*/ false, &getenv); + if (fastHeapDumps.IsSet() && fastHeapDumps.TryAsInteger(10, val) && val == 1) + { + flags = CLRDATA_ENUM_MEM_HEAP2; + } } // Calls CrashInfo::EnumMemoryRegion for each memory region found by the DAC - HRESULT hr = m_pClrDataEnumRegions->EnumMemoryRegions(this, minidumpType, CLRDATA_ENUM_MEM_DEFAULT); + HRESULT hr = m_pClrDataEnumRegions->EnumMemoryRegions(this, minidumpType, flags); if (FAILED(hr)) { printf_error("EnumMemoryRegions FAILED %s (%08x)\n", GetHResultString(hr), hr); diff --git a/src/coreclr/debug/createdump/crashinfo.h b/src/coreclr/debug/createdump/crashinfo.h index 50c0cca3eec723..3d835a791e842f 100644 --- a/src/coreclr/debug/createdump/crashinfo.h +++ b/src/coreclr/debug/createdump/crashinfo.h @@ -57,6 +57,7 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi #ifdef __APPLE__ vm_map_t m_task; // the mach task for the process #else + siginfo_t m_siginfo; // signal info (if any) bool m_canUseProcVmReadSyscall; int m_fd; // /proc//mem handle #endif @@ -81,7 +82,7 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi void operator=(const CrashInfo&) = delete; public: - CrashInfo(pid_t pid, bool gatherFrames, pid_t crashThread, uint32_t signal); + CrashInfo(const CreateDumpOptions& options); virtual ~CrashInfo(); // Memory usage stats @@ -125,6 +126,7 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi #ifndef __APPLE__ inline const std::vector& AuxvEntries() const { return m_auxvEntries; } inline size_t GetAuxvSize() const { return m_auxvEntries.size() * sizeof(elf_aux_entry); } + inline const siginfo_t* SigInfo() const { return &m_siginfo; } #endif // IUnknown @@ -142,7 +144,6 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi #ifdef __APPLE__ bool EnumerateMemoryRegions(); void InitializeOtherMappings(); - bool TryFindDyLinker(mach_vm_address_t address, mach_vm_size_t size, bool* found); void VisitModule(MachOModule& module); void VisitSegment(MachOModule& module, const segment_command_64& segment); void VisitSection(MachOModule& module, const section_64& section); diff --git a/src/coreclr/debug/createdump/crashinfomac.cpp b/src/coreclr/debug/createdump/crashinfomac.cpp index 02534a7a9e7eb0..21ec72ff234170 100644 --- a/src/coreclr/debug/createdump/crashinfomac.cpp +++ b/src/coreclr/debug/createdump/crashinfomac.cpp @@ -148,18 +148,23 @@ CrashInfo::EnumerateMemoryRegions() } } - // Now find all the modules and add them to the module list - for (const MemoryRegion& region : m_allMemoryRegions) + // Get the dylinker info and enumerate all the modules + struct task_dyld_info dyld_info; + mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; + kern_return_t result = ::task_info(Task(), TASK_DYLD_INFO, (task_info_t)&dyld_info, &count); + if (result != KERN_SUCCESS) { - bool found; - if (!TryFindDyLinker(region.StartAddress(), region.Size(), &found)) { - return false; - } - if (found) { - break; - } + TRACE("EnumerateMemoryRegions: task_info(TASK_DYLD_INFO) FAILED %x %s\n", result, mach_error_string(result)); + return false; + } + + // Enumerate all the modules in dyld's image cache. VisitModule is called for every module found. + if (!EnumerateModules(dyld_info.all_image_info_addr)) + { + return false; } - TRACE("AllMemoryRegions %06llx native ModuleMappings %06llx\n", cbAllMemoryRegions / PAGE_SIZE, m_cbModuleMappings / PAGE_SIZE); + + TRACE("EnumerateMemoryRegions: cbAllMemoryRegions %06llx native cbModuleMappings %06llx\n", cbAllMemoryRegions / PAGE_SIZE, m_cbModuleMappings / PAGE_SIZE); return true; } @@ -216,46 +221,6 @@ CrashInfo::InitializeOtherMappings() TRACE("OtherMappings: %06llx\n", cbOtherMappings / PAGE_SIZE); } -bool -CrashInfo::TryFindDyLinker(mach_vm_address_t address, mach_vm_size_t size, bool* found) -{ - bool result = true; - *found = false; - - if (size > sizeof(mach_header_64)) - { - mach_header_64 header; - size_t read = 0; - if (ReadProcessMemory((void*)address, &header, sizeof(mach_header_64), &read)) - { - if (header.magic == MH_MAGIC_64) - { - TRACE("TryFindDyLinker: found module header at %016llx %08llx ncmds %d sizeofcmds %08x type %02x\n", - address, - size, - header.ncmds, - header.sizeofcmds, - header.filetype); - - if (header.filetype == MH_DYLINKER) - { - TRACE("TryFindDyLinker: found dylinker\n"); - *found = true; - - // Enumerate all the modules in dyld's image cache. VisitModule is called for every module found. - result = EnumerateModules(address, &header); - } - } - } - else - { - TRACE("TryFindDyLinker: ReadProcessMemory header at %p %d FAILED\n", address, read); - } - } - - return result; -} - void CrashInfo::VisitModule(MachOModule& module) { AddModuleInfo(false, module.BaseAddress(), nullptr, module.Name()); @@ -487,7 +452,7 @@ ModuleInfo::LoadModule() } else { - TRACE("LoadModule: dlopen(%s) FAILED %d %s\n", m_moduleName.c_str(), errno, strerror(errno)); + TRACE("LoadModule: dlopen(%s) FAILED %s\n", m_moduleName.c_str(), dlerror()); } } } diff --git a/src/coreclr/debug/createdump/crashreportwriter.cpp b/src/coreclr/debug/createdump/crashreportwriter.cpp index 9be54c7ca6a0cd..3fb91ba2bd622e 100644 --- a/src/coreclr/debug/createdump/crashreportwriter.cpp +++ b/src/coreclr/debug/createdump/crashreportwriter.cpp @@ -175,17 +175,17 @@ CrashReportWriter::WriteCrashReport() } CloseArray(); // threads CloseObject(); // payload -#ifdef __APPLE__ OpenObject("parameters"); if (exceptionType != nullptr) { WriteValue("ExceptionType", exceptionType); } +#ifdef __APPLE__ WriteSysctl("kern.osproductversion", "OSVersion"); WriteSysctl("hw.model", "SystemModel"); WriteValue("SystemManufacturer", "apple"); - CloseObject(); // parameters #endif // __APPLE__ + CloseObject(); // parameters } #ifdef __APPLE__ diff --git a/src/coreclr/debug/createdump/createdump.h b/src/coreclr/debug/createdump/createdump.h index 0894b7640e60a1..51ffca9c520269 100644 --- a/src/coreclr/debug/createdump/createdump.h +++ b/src/coreclr/debug/createdump/createdump.h @@ -20,6 +20,9 @@ extern void trace_verbose_printf(const char* format, ...); #define TRACE_VERBOSE(args, ...) #endif +// Keep in sync with the definitions in dbgutil.cpp and daccess.h +#define DACCESS_TABLE_SYMBOL "g_dacTable" + #ifdef HOST_64BIT #define PRIA "016" #else @@ -86,6 +89,24 @@ typedef int T_CONTEXT; #include #include #include + +typedef struct +{ + const char* DumpPathTemplate; + const char* DumpType; + MINIDUMP_TYPE MinidumpType; + bool CreateDump; + bool CrashReport; + int Pid; + int CrashThread; + int Signal; +#if defined(HOST_UNIX) + int SignalCode; + int SignalErrno; + void* SignalAddress; +#endif +} CreateDumpOptions; + #ifdef HOST_UNIX #ifdef __APPLE__ #include @@ -106,12 +127,9 @@ typedef int T_CONTEXT; #define MAX_LONGPATH 1024 #endif +extern bool CreateDump(const CreateDumpOptions& options); extern bool FormatDumpName(std::string& name, const char* pattern, const char* exename, int pid); -extern bool CreateDump(const char* dumpPathTemplate, int pid, const char* dumpType, MINIDUMP_TYPE minidumpType, bool createDump, bool crashReport, int crashThread, int signal); extern std::string GetLastErrorString(); extern void printf_status(const char* format, ...); extern void printf_error(const char* format, ...); - -// Keep in sync with the definitions in dbgutil.cpp and daccess.h -#define DACCESS_TABLE_SYMBOL "g_dacTable" diff --git a/src/coreclr/debug/createdump/createdumpunix.cpp b/src/coreclr/debug/createdump/createdumpunix.cpp index f86a283546a356..b5db55688e43a9 100644 --- a/src/coreclr/debug/createdump/createdumpunix.cpp +++ b/src/coreclr/debug/createdump/createdumpunix.cpp @@ -11,9 +11,9 @@ long g_pageSize = 0; // The Linux/MacOS create dump code // bool -CreateDump(const char* dumpPathTemplate, int pid, const char* dumpType, MINIDUMP_TYPE minidumpType, bool createDump, bool crashReport, int crashThread, int signal) +CreateDump(const CreateDumpOptions& options) { - ReleaseHolder crashInfo = new CrashInfo(pid, crashReport, crashThread, signal); + ReleaseHolder crashInfo = new CrashInfo(options); DumpWriter dumpWriter(*crashInfo); std::string dumpPath; bool result = false; @@ -29,11 +29,11 @@ CreateDump(const char* dumpPathTemplate, int pid, const char* dumpType, MINIDUMP { goto exit; } - printf_status("Gathering state for process %d %s\n", pid, crashInfo->Name().c_str()); + printf_status("Gathering state for process %d %s\n", options.Pid, crashInfo->Name().c_str()); - if (signal != 0 || crashThread != 0) + if (options.Signal != 0 || options.CrashThread != 0) { - printf_status("Crashing thread %08x signal %08x\n", crashThread, signal); + printf_status("Crashing thread %04x signal %d (%04x)\n", options.CrashThread, options.Signal, options.Signal); } // Suspend all the threads in the target process and build the list of threads @@ -42,32 +42,32 @@ CreateDump(const char* dumpPathTemplate, int pid, const char* dumpType, MINIDUMP goto exit; } // Gather all the info about the process, threads (registers, etc.) and memory regions - if (!crashInfo->GatherCrashInfo(minidumpType)) + if (!crashInfo->GatherCrashInfo(options.MinidumpType)) { goto exit; } // Format the dump pattern template now that the process name on MacOS has been obtained - if (!FormatDumpName(dumpPath, dumpPathTemplate, crashInfo->Name().c_str(), pid)) + if (!FormatDumpName(dumpPath, options.DumpPathTemplate, crashInfo->Name().c_str(), options.Pid)) { goto exit; } // Write the crash report json file if enabled - if (crashReport) + if (options.CrashReport) { CrashReportWriter crashReportWriter(*crashInfo); crashReportWriter.WriteCrashReport(dumpPath); } - if (createDump) + if (options.CreateDump) { // Gather all the useful memory regions from the DAC - if (!crashInfo->EnumerateMemoryRegionsWithDAC(minidumpType)) + if (!crashInfo->EnumerateMemoryRegionsWithDAC(options.MinidumpType)) { goto exit; } // Join all adjacent memory regions crashInfo->CombineMemoryRegions(); - printf_status("Writing %s to file %s\n", dumpType, dumpPath.c_str()); + printf_status("Writing %s to file %s\n", options.DumpType, dumpPath.c_str()); // Write the actual dump file if (!dumpWriter.OpenDump(dumpPath.c_str())) @@ -85,7 +85,7 @@ CreateDump(const char* dumpPathTemplate, int pid, const char* dumpType, MINIDUMP } result = true; exit: - if (kill(pid, 0) == 0) + if (kill(options.Pid, 0) == 0) { printf_status("Target process is alive\n"); } @@ -98,7 +98,7 @@ CreateDump(const char* dumpPathTemplate, int pid, const char* dumpType, MINIDUMP } else { - printf_error("kill(%d, 0) FAILED %s (%d)\n", pid, strerror(err), err); + printf_error("kill(%d, 0) FAILED %s (%d)\n", options.Pid, strerror(err), err); } } crashInfo->CleanupAndResumeProcess(); diff --git a/src/coreclr/debug/createdump/createdumpwindows.cpp b/src/coreclr/debug/createdump/createdumpwindows.cpp index 52cf16e2d0b99b..4e8429255a3431 100644 --- a/src/coreclr/debug/createdump/createdumpwindows.cpp +++ b/src/coreclr/debug/createdump/createdumpwindows.cpp @@ -18,14 +18,14 @@ typedef struct _PROCESS_BASIC_INFORMATION_ { // The Windows create dump code // bool -CreateDump(const char* dumpPathTemplate, int pid, const char* dumpType, MINIDUMP_TYPE minidumpType, bool createDump, bool crashReport, int crashThread, int signal) +CreateDump(const CreateDumpOptions& options) { HANDLE hFile = INVALID_HANDLE_VALUE; HANDLE hProcess = NULL; bool result = false; - _ASSERTE(createDump); - _ASSERTE(!crashReport); + _ASSERTE(options.CreateDump); + _ASSERTE(!options.CrashReport); ArrayHolder pszName = new char[MAX_LONGPATH + 1]; std::string dumpPath; @@ -38,7 +38,7 @@ CreateDump(const char* dumpPathTemplate, int pid, const char* dumpType, MINIDUMP printf_error("Failed to get parent process id status %d\n", status); goto exit; } - pid = (int)processInformation.InheritedFromUniqueProcessId; + int pid = (int)processInformation.InheritedFromUniqueProcessId; hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); if (hProcess == NULL) @@ -51,11 +51,11 @@ CreateDump(const char* dumpPathTemplate, int pid, const char* dumpType, MINIDUMP printf_error("Get process name FAILED - %s\n", GetLastErrorString().c_str()); goto exit; } - if (!FormatDumpName(dumpPath, dumpPathTemplate, pszName, pid)) + if (!FormatDumpName(dumpPath, options.DumpPathTemplate, pszName, pid)) { goto exit; } - printf_status("Writing %s for process %d to file %s\n", dumpType, pid, dumpPath.c_str()); + printf_status("Writing %s for process %d to file %s\n", options.DumpType, pid, dumpPath.c_str()); hFile = CreateFileA(dumpPath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) @@ -67,7 +67,7 @@ CreateDump(const char* dumpPathTemplate, int pid, const char* dumpType, MINIDUMP // Retry the write dump on ERROR_PARTIAL_COPY for (int i = 0; i < 5; i++) { - if (MiniDumpWriteDump(hProcess, pid, hFile, minidumpType, NULL, NULL, NULL)) + if (MiniDumpWriteDump(hProcess, pid, hFile, options.MinidumpType, NULL, NULL, NULL)) { result = true; break; diff --git a/src/coreclr/debug/createdump/dumpwriterelf.cpp b/src/coreclr/debug/createdump/dumpwriterelf.cpp index 0cdc0feb6a4f1a..be3072d347bbdc 100644 --- a/src/coreclr/debug/createdump/dumpwriterelf.cpp +++ b/src/coreclr/debug/createdump/dumpwriterelf.cpp @@ -148,7 +148,7 @@ DumpWriter::WriteDump() // Write all the thread's state and registers for (const ThreadInfo* thread : m_crashInfo.Threads()) { - if (!WriteThread(*thread, SIGABRT)) { + if (!WriteThread(*thread)) { return false; } } @@ -358,13 +358,20 @@ DumpWriter::WriteNTFileInfo() } bool -DumpWriter::WriteThread(const ThreadInfo& thread, int fatal_signal) +DumpWriter::WriteThread(const ThreadInfo& thread) { prstatus_t pr; memset(&pr, 0, sizeof(pr)); + const siginfo_t* siginfo = nullptr; - pr.pr_info.si_signo = fatal_signal; - pr.pr_cursig = fatal_signal; + if (m_crashInfo.Signal() != 0 && thread.IsCrashThread()) + { + siginfo = m_crashInfo.SigInfo(); + pr.pr_info.si_signo = siginfo->si_signo; + pr.pr_info.si_code = siginfo->si_code; + pr.pr_info.si_errno = siginfo->si_errno; + pr.pr_cursig = siginfo->si_signo; + } pr.pr_pid = thread.Tid(); pr.pr_ppid = thread.Ppid(); pr.pr_pgrp = thread.Tgid(); @@ -395,9 +402,8 @@ DumpWriter::WriteThread(const ThreadInfo& thread, int fatal_signal) return false; } - nhdr.n_namesz = 6; - #if defined(__i386__) + nhdr.n_namesz = 6; nhdr.n_descsz = sizeof(user_fpxregs_struct); nhdr.n_type = NT_PRXFPREG; if (!WriteData(&nhdr, sizeof(nhdr)) || @@ -408,6 +414,7 @@ DumpWriter::WriteThread(const ThreadInfo& thread, int fatal_signal) #endif #if defined(__arm__) && defined(__VFP_FP__) && !defined(__SOFTFP__) + nhdr.n_namesz = 6; nhdr.n_descsz = sizeof(user_vfpregs_struct); nhdr.n_type = NT_ARM_VFP; if (!WriteData(&nhdr, sizeof(nhdr)) || @@ -417,5 +424,19 @@ DumpWriter::WriteThread(const ThreadInfo& thread, int fatal_signal) } #endif + if (siginfo != nullptr) + { + TRACE("Writing NT_SIGINFO tid %04x signo %d (%04x) code %04x errno %04x addr %p\n", + thread.Tid(), siginfo->si_signo, siginfo->si_signo, siginfo->si_code, siginfo->si_errno, siginfo->si_addr); + + nhdr.n_namesz = 5; + nhdr.n_descsz = sizeof(siginfo_t); + nhdr.n_type = NT_SIGINFO; + if (!WriteData(&nhdr, sizeof(nhdr)) || + !WriteData("CORE\0SIG", 8) || + !WriteData(siginfo, sizeof(siginfo_t))) { + return false; + } + } return true; } diff --git a/src/coreclr/debug/createdump/dumpwriterelf.h b/src/coreclr/debug/createdump/dumpwriterelf.h index cb8731871de211..425d0b9214c3e1 100644 --- a/src/coreclr/debug/createdump/dumpwriterelf.h +++ b/src/coreclr/debug/createdump/dumpwriterelf.h @@ -31,6 +31,10 @@ #define NT_FILE 0x46494c45 #endif +#ifndef NT_SIGINFO +#define NT_SIGINFO 0x53494749 +#endif + class DumpWriter { private: @@ -54,21 +58,22 @@ class DumpWriter bool WriteAuxv(); size_t GetNTFileInfoSize(size_t* alignmentBytes = nullptr); bool WriteNTFileInfo(); - bool WriteThread(const ThreadInfo& thread, int fatal_signal); + bool WriteThread(const ThreadInfo& thread); bool WriteData(const void* buffer, size_t length) { return WriteData(m_fd, buffer, length); } size_t GetProcessInfoSize() const { return sizeof(Nhdr) + 8 + sizeof(prpsinfo_t); } size_t GetAuxvInfoSize() const { return sizeof(Nhdr) + 8 + m_crashInfo.GetAuxvSize(); } size_t GetThreadInfoSize() const { - return m_crashInfo.Threads().size() * ((sizeof(Nhdr) + 8 + sizeof(prstatus_t)) - + sizeof(Nhdr) + 8 + sizeof(user_fpregs_struct) + return (m_crashInfo.Signal() != 0 ? (sizeof(Nhdr) + 8 + sizeof(siginfo_t)) : 0) + + (m_crashInfo.Threads().size() * ((sizeof(Nhdr) + 8 + sizeof(prstatus_t)) + + (sizeof(Nhdr) + 8 + sizeof(user_fpregs_struct)) #if defined(__i386__) - + sizeof(Nhdr) + 8 + sizeof(user_fpxregs_struct) + + (sizeof(Nhdr) + 8 + sizeof(user_fpxregs_struct)) #endif #if defined(__arm__) && defined(__VFP_FP__) && !defined(__SOFTFP__) - + sizeof(Nhdr) + 8 + sizeof(user_vfpregs_struct) + + (sizeof(Nhdr) + 8 + sizeof(user_vfpregs_struct)) #endif - ); + )); } }; diff --git a/src/coreclr/debug/createdump/main.cpp b/src/coreclr/debug/createdump/main.cpp index 96dc36343879e1..75a616dc2e47b4 100644 --- a/src/coreclr/debug/createdump/main.cpp +++ b/src/coreclr/debug/createdump/main.cpp @@ -55,22 +55,28 @@ bool g_checkForSingleFile = false; // int __cdecl main(const int argc, const char* argv[]) { - MINIDUMP_TYPE minidumpType = (MINIDUMP_TYPE)(MiniDumpWithPrivateReadWriteMemory | - MiniDumpWithDataSegs | - MiniDumpWithHandleData | - MiniDumpWithUnloadedModules | - MiniDumpWithFullMemoryInfo | - MiniDumpWithThreadInfo | - MiniDumpWithTokenInformation); - const char* dumpType = "minidump with heap"; - const char* dumpPathTemplate = nullptr; - bool crashReport = false; - bool createDump = true; + CreateDumpOptions options; + options.MinidumpType = (MINIDUMP_TYPE)(MiniDumpWithPrivateReadWriteMemory | + MiniDumpWithDataSegs | + MiniDumpWithHandleData | + MiniDumpWithUnloadedModules | + MiniDumpWithFullMemoryInfo | + MiniDumpWithThreadInfo | + MiniDumpWithTokenInformation); + options.DumpType = "minidump with heap"; + options.DumpPathTemplate = nullptr; + options.CrashReport = false; + options.CreateDump = true; + options.Signal = 0; + options.CrashThread = 0; + options.Pid = 0; +#if defined(HOST_UNIX) + options.SignalCode = 0; + options.SignalErrno = 0; + options.SignalAddress = nullptr; +#endif bool help = false; - int signal = 0; - int crashThread = 0; int exitCode = 0; - int pid = 0; #ifdef HOST_UNIX exitCode = PAL_InitializeDLL(); @@ -89,72 +95,84 @@ int __cdecl main(const int argc, const char* argv[]) { if ((strcmp(*argv, "-f") == 0) || (strcmp(*argv, "--name") == 0)) { - dumpPathTemplate = *++argv; + options.DumpPathTemplate = *++argv; } else if ((strcmp(*argv, "-n") == 0) || (strcmp(*argv, "--normal") == 0)) { - dumpType = "minidump"; - minidumpType = (MINIDUMP_TYPE)(MiniDumpNormal | - MiniDumpWithDataSegs | - MiniDumpWithHandleData | - MiniDumpWithThreadInfo); + options.DumpType = "minidump"; + options.MinidumpType = (MINIDUMP_TYPE)(MiniDumpNormal | + MiniDumpWithDataSegs | + MiniDumpWithHandleData | + MiniDumpWithThreadInfo); } else if ((strcmp(*argv, "-h") == 0) || (strcmp(*argv, "--withheap") == 0)) { - dumpType = "minidump with heap"; - minidumpType = (MINIDUMP_TYPE)(MiniDumpWithPrivateReadWriteMemory | - MiniDumpWithDataSegs | - MiniDumpWithHandleData | - MiniDumpWithUnloadedModules | - MiniDumpWithFullMemoryInfo | - MiniDumpWithThreadInfo | - MiniDumpWithTokenInformation); + options.DumpType = "minidump with heap"; + options.MinidumpType = (MINIDUMP_TYPE)(MiniDumpWithPrivateReadWriteMemory | + MiniDumpWithDataSegs | + MiniDumpWithHandleData | + MiniDumpWithUnloadedModules | + MiniDumpWithFullMemoryInfo | + MiniDumpWithThreadInfo | + MiniDumpWithTokenInformation); } else if ((strcmp(*argv, "-t") == 0) || (strcmp(*argv, "--triage") == 0)) { - dumpType = "triage minidump"; - minidumpType = (MINIDUMP_TYPE)(MiniDumpFilterTriage | - MiniDumpIgnoreInaccessibleMemory | - MiniDumpWithoutOptionalData | - MiniDumpWithProcessThreadData | - MiniDumpFilterModulePaths | - MiniDumpWithUnloadedModules | - MiniDumpFilterMemory | - MiniDumpWithHandleData); + options.DumpType = "triage minidump"; + options.MinidumpType = (MINIDUMP_TYPE)(MiniDumpFilterTriage | + MiniDumpIgnoreInaccessibleMemory | + MiniDumpWithoutOptionalData | + MiniDumpWithProcessThreadData | + MiniDumpFilterModulePaths | + MiniDumpWithUnloadedModules | + MiniDumpFilterMemory | + MiniDumpWithHandleData); } else if ((strcmp(*argv, "-u") == 0) || (strcmp(*argv, "--full") == 0)) { - dumpType = "full dump"; - minidumpType = (MINIDUMP_TYPE)(MiniDumpWithFullMemory | - MiniDumpWithDataSegs | - MiniDumpWithHandleData | - MiniDumpWithUnloadedModules | - MiniDumpWithFullMemoryInfo | - MiniDumpWithThreadInfo | - MiniDumpWithTokenInformation); + options.DumpType = "full dump"; + options.MinidumpType = (MINIDUMP_TYPE)(MiniDumpWithFullMemory | + MiniDumpWithDataSegs | + MiniDumpWithHandleData | + MiniDumpWithUnloadedModules | + MiniDumpWithFullMemoryInfo | + MiniDumpWithThreadInfo | + MiniDumpWithTokenInformation); } #ifdef HOST_UNIX else if (strcmp(*argv, "--crashreport") == 0) { - crashReport = true; + options.CrashReport = true; } else if (strcmp(*argv, "--crashreportonly") == 0) { - crashReport = true; - createDump = false; + options.CrashReport = true; + options.CreateDump = false; } else if (strcmp(*argv, "--crashthread") == 0) { - crashThread = atoi(*++argv); + options.CrashThread = atoi(*++argv); } else if (strcmp(*argv, "--signal") == 0) { - signal = atoi(*++argv); + options.Signal = atoi(*++argv); } else if (strcmp(*argv, "--singlefile") == 0) { g_checkForSingleFile = true; } + else if (strcmp(*argv, "--code") == 0) + { + options.SignalCode = atoi(*++argv); + } + else if (strcmp(*argv, "--errno") == 0) + { + options.SignalErrno = atoi(*++argv); + } + else if (strcmp(*argv, "--address") == 0) + { + options.SignalAddress = (void*)atoll(*++argv); + } #endif else if ((strcmp(*argv, "-d") == 0) || (strcmp(*argv, "--diag") == 0)) { @@ -183,7 +201,7 @@ int __cdecl main(const int argc, const char* argv[]) else { #ifdef HOST_UNIX - pid = atoi(*argv); + options.Pid = atoi(*argv); #else printf_error("The pid argument is no longer supported\n"); return -1; @@ -194,7 +212,7 @@ int __cdecl main(const int argc, const char* argv[]) } #ifdef HOST_UNIX - if (pid == 0) + if (options.Pid == 0) { help = true; } @@ -212,7 +230,7 @@ int __cdecl main(const int argc, const char* argv[]) TRACE("TickFrequency: %d ticks per ms\n", g_ticksPerMS); ArrayHolder tmpPath = new char[MAX_LONGPATH]; - if (dumpPathTemplate == nullptr) + if (options.DumpPathTemplate == nullptr) { if (::GetTempPathA(MAX_LONGPATH, tmpPath) == 0) { @@ -225,10 +243,10 @@ int __cdecl main(const int argc, const char* argv[]) printf_error("strcat_s failed (%d)", exitCode); return exitCode; } - dumpPathTemplate = tmpPath; + options.DumpPathTemplate = tmpPath; } - if (CreateDump(dumpPathTemplate, pid, dumpType, minidumpType, createDump, crashReport, crashThread, signal)) + if (CreateDump(options)) { printf_status("Dump successfully written in %llums\n", GetTimeStamp() - g_startTime); } diff --git a/src/coreclr/debug/createdump/threadinfo.cpp b/src/coreclr/debug/createdump/threadinfo.cpp index 7304fc506c51e3..5b779b9260738e 100644 --- a/src/coreclr/debug/createdump/threadinfo.cpp +++ b/src/coreclr/debug/createdump/threadinfo.cpp @@ -387,3 +387,9 @@ ThreadInfo::GetThreadStack() TRACE("Thread %04x null stack pointer\n", m_tid); } } + +bool +ThreadInfo::IsCrashThread() const +{ + return m_tid == m_crashInfo.CrashThread(); +} diff --git a/src/coreclr/debug/createdump/threadinfo.h b/src/coreclr/debug/createdump/threadinfo.h index ed82c1ec51a652..4600dccb50911e 100644 --- a/src/coreclr/debug/createdump/threadinfo.h +++ b/src/coreclr/debug/createdump/threadinfo.h @@ -156,6 +156,7 @@ class ThreadInfo inline const uint64_t GetFramePointer() const { return m_gpRegisters.ARM_fp; } #endif #endif // __APPLE__ + bool IsCrashThread() const; private: void UnwindNativeFrames(CONTEXT* pContext); diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index 71bfcd7f898f47..bb476fa8145aaf 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -6246,24 +6246,15 @@ bool ClrDataAccess::ReportMem(TADDR addr, TSIZE_T size, bool fExpectSuccess /*= { if (!IsFullyReadable(addr, size)) { - if (!fExpectSuccess) + if (fExpectSuccess) { - // We know the read might fail (eg. we're trying to find mapped pages in - // a module image), so just skip this block silently. - // Note that the EnumMemoryRegion callback won't necessarily do anything if any part of - // the region is unreadable, and so there is no point in calling it. For cases where we expect - // the read might fail, but we want to report any partial blocks, we have to break up the region - // into pages and try reporting each page anyway - return true; + // We're reporting bogus memory, so the target must be corrupt (or there is a issue). We should abort + // reporting and continue with the next data structure (where the exception is caught), + // just like we would for a DAC read error (otherwise we might do something stupid + // like get into an infinite loop, or otherwise waste time with corrupt data). + TARGET_CONSISTENCY_CHECK(false, "Found unreadable memory while reporting memory regions for dump gathering"); + return false; } - - // We're reporting bogus memory, so the target must be corrupt (or there is a issue). We should abort - // reporting and continue with the next data structure (where the exception is caught), - // just like we would for a DAC read error (otherwise we might do something stupid - // like get into an infinite loop, or otherwise waste time with corrupt data). - - TARGET_CONSISTENCY_CHECK(false, "Found unreadable memory while reporting memory regions for dump gathering"); - return false; } } @@ -6275,9 +6266,7 @@ bool ClrDataAccess::ReportMem(TADDR addr, TSIZE_T size, bool fExpectSuccess /*= // data structure at all. Hopefully experience will help guide this going forward. // @dbgtodo : Extend dump-gathering API to allow a dump-log to be included. const TSIZE_T kMaxMiniDumpRegion = 4*1024*1024 - 3; // 4MB-3 - if( size > kMaxMiniDumpRegion - && (m_enumMemFlags == CLRDATA_ENUM_MEM_MINI - || m_enumMemFlags == CLRDATA_ENUM_MEM_TRIAGE)) + if (size > kMaxMiniDumpRegion && (m_enumMemFlags == CLRDATA_ENUM_MEM_MINI || m_enumMemFlags == CLRDATA_ENUM_MEM_TRIAGE)) { TARGET_CONSISTENCY_CHECK( false, "Dump target consistency failure - truncating minidump data structure"); size = kMaxMiniDumpRegion; diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 91a6e4ecc7e261..acf458a39d2bea 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -3618,7 +3618,7 @@ void DacDbiInterfaceImpl::EnumerateMemRangesForLoaderAllocator(PTR_LoaderAllocat // GetVirtualCallStubManager returns VirtualCallStubManager*, but it's really an address to target as // pLoaderAllocator is DACized. Cast it so we don't try to to a Host to Target translation. - VirtualCallStubManager *pVcsMgr = PTR_VirtualCallStubManager(TO_TADDR(pLoaderAllocator->GetVirtualCallStubManager())); + VirtualCallStubManager *pVcsMgr = pLoaderAllocator->GetVirtualCallStubManager(); LOG((LF_CORDB, LL_INFO10000, "DDBII::EMRFLA: VirtualCallStubManager 0x%x\n", PTR_HOST_TO_TADDR(pVcsMgr))); if (pVcsMgr) { diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index c6a01bd2d48ff9..33a2149e9a56e3 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1328,6 +1328,7 @@ class ClrDataAccess HRESULT EnumMemCollectImages(); HRESULT EnumMemCLRStatic(CLRDataEnumMemoryFlags flags); + HRESULT EnumMemDumpJitManagerInfo(IN CLRDataEnumMemoryFlags flags); HRESULT EnumMemCLRHeapCrticalStatic(CLRDataEnumMemoryFlags flags); HRESULT EnumMemDumpModuleList(CLRDataEnumMemoryFlags flags); HRESULT EnumMemDumpAppDomainInfo(CLRDataEnumMemoryFlags flags); diff --git a/src/coreclr/debug/daccess/enummem.cpp b/src/coreclr/debug/daccess/enummem.cpp index 7db97915d132d1..ee30bf593bb5f3 100644 --- a/src/coreclr/debug/daccess/enummem.cpp +++ b/src/coreclr/debug/daccess/enummem.cpp @@ -283,6 +283,21 @@ HRESULT ClrDataAccess::EnumMemCLRStatic(IN CLRDataEnumMemoryFlags flags) return S_OK; } +HRESULT ClrDataAccess::EnumMemDumpJitManagerInfo(IN CLRDataEnumMemoryFlags flags) +{ + SUPPORTS_DAC; + + HRESULT status = S_OK; + + if (flags == CLRDATA_ENUM_MEM_HEAP2) + { + EEJitManager* managerPtr = ExecutionManager::GetEEJitManager(); + managerPtr->EnumMemoryRegions(flags); + } + + return status; +} + //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // This function reports memory that a heap dump need to debug CLR @@ -325,6 +340,9 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWorkerHeap(IN CLRDataEnumMemoryFlags fla // Dump AppDomain-specific info CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAppDomainInfo(flags); ) + // Dump jit manager info + CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpJitManagerInfo(flags); ) + // Dump the Debugger object data needed CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDebugger->EnumMemoryRegions(flags); ) @@ -680,6 +698,11 @@ HRESULT ClrDataAccess::EnumMemDumpAppDomainInfo(CLRDataEnumMemoryFlags flags) { SUPPORTS_DAC; + if (flags == CLRDATA_ENUM_MEM_HEAP2) + { + SystemDomain::System()->GetLoaderAllocator()->EnumMemoryRegions(flags); + } + AppDomainIterator adIter(FALSE); EX_TRY { @@ -1853,7 +1876,7 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWrapper(IN CLRDataEnumMemoryFlags flags) // triage micro-dump status = EnumMemoryRegionsWorkerMicroTriage(flags); } - else if (flags == CLRDATA_ENUM_MEM_HEAP) + else if (flags == CLRDATA_ENUM_MEM_HEAP || flags == CLRDATA_ENUM_MEM_HEAP2) { status = EnumMemoryRegionsWorkerHeap(flags); } @@ -1946,7 +1969,15 @@ ClrDataAccess::EnumMemoryRegions(IN ICLRDataEnumMemoryRegionsCallback* callback, if (miniDumpFlags & MiniDumpWithPrivateReadWriteMemory) { // heap dump - status = EnumMemoryRegionsWrapper(CLRDATA_ENUM_MEM_HEAP); + if (flags == CLRDATA_ENUM_MEM_HEAP2) + { + DacLogMessage("EnumMemoryRegions(CLRDATA_ENUM_MEM_HEAP2)\n"); + } + else + { + flags = CLRDATA_ENUM_MEM_HEAP; + } + status = EnumMemoryRegionsWrapper(flags); } else if (miniDumpFlags & MiniDumpWithFullAuxiliaryState) { diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 7e2906cfcae443..0972249284582d 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -195,7 +195,12 @@ BOOL DacValidateMD(PTR_MethodDesc pMD) PTR_MethodTable pMethodTable = pMD->GetMethodTable(); // Standard fast check - if (!pMethodTable->ValidateWithPossibleAV()) + if ((pMethodTable == NULL) || dac_cast(pMethodTable) == (TADDR)-1) + { + retval = FALSE; + } + + if (retval && !pMethodTable->ValidateWithPossibleAV()) { retval = FALSE; } @@ -3497,7 +3502,7 @@ ClrDataAccess::TraverseVirtCallStubHeap(CLRDATA_ADDRESS pAppDomain, VCSHeapType SOSDacEnter(); BaseDomain* pBaseDomain = PTR_BaseDomain(TO_TADDR(pAppDomain)); - VirtualCallStubManager *pVcsMgr = PTR_VirtualCallStubManager((TADDR)pBaseDomain->GetLoaderAllocator()->GetVirtualCallStubManager()); + VirtualCallStubManager *pVcsMgr = pBaseDomain->GetLoaderAllocator()->GetVirtualCallStubManager(); if (!pVcsMgr) { hr = E_POINTER; diff --git a/src/coreclr/debug/dbgutil/machoreader.cpp b/src/coreclr/debug/dbgutil/machoreader.cpp index 7fef34e1afb16d..07528e1d176cbf 100644 --- a/src/coreclr/debug/dbgutil/machoreader.cpp +++ b/src/coreclr/debug/dbgutil/machoreader.cpp @@ -75,7 +75,7 @@ TryGetSymbol(ICorDebugDataTarget* dataTarget, uint64_t baseAddress, const char* // MachO module //-------------------------------------------------------------------- -MachOModule::MachOModule(MachOReader& reader, mach_vm_address_t baseAddress, mach_header_64* header, std::string* name) : +MachOModule::MachOModule(MachOReader& reader, mach_vm_address_t baseAddress, std::string* name) : m_reader(reader), m_baseAddress(baseAddress), m_loadBias(0), @@ -84,9 +84,6 @@ MachOModule::MachOModule(MachOReader& reader, mach_vm_address_t baseAddress, mac m_nlists(nullptr), m_strtabAddress(0) { - if (header != nullptr) { - m_header = *header; - } if (name != nullptr) { m_name = *name; } @@ -363,43 +360,25 @@ MachOReader::MachOReader() } bool -MachOReader::EnumerateModules(mach_vm_address_t address, mach_header_64* header) +MachOReader::EnumerateModules(mach_vm_address_t dyldInfoAddress) { - _ASSERTE(header->magic == MH_MAGIC_64); - _ASSERTE(header->filetype == MH_DYLINKER); - - MachOModule dylinker(*this, address, header); - - // Search for symbol for the dyld image info cache - uint64_t dyldInfoAddress = 0; - if (!dylinker.TryLookupSymbol("dyld_all_image_infos", &dyldInfoAddress)) - { - Trace("ERROR: Can not find the _dyld_all_image_infos symbol\n"); - return false; - } - // Read the all image info from the dylinker image dyld_all_image_infos dyldInfo; - if (!ReadMemory((void*)dyldInfoAddress, &dyldInfo, sizeof(dyld_all_image_infos))) { Trace("ERROR: Failed to read dyld_all_image_infos at %p\n", (void*)dyldInfoAddress); return false; } - std::string dylinkerPath; - if (!ReadString(dyldInfo.dyldPath, dylinkerPath)) + Trace("MOD: infoArray %p infoArrayCount %d\n", dyldInfo.infoArray, dyldInfo.infoArrayCount); + + // Create the dyld module info + if (!TryRegisterModule(dyldInfo.dyldImageLoadAddress, dyldInfo.dyldPath, true)) { - Trace("ERROR: Failed to read name at %p\n", dyldInfo.dyldPath); + Trace("ERROR: Failed to read dyld header at %p\n", dyldInfo.dyldImageLoadAddress); return false; } - dylinker.SetName(dylinkerPath); - Trace("MOD: %016llx %08x %s\n", dylinker.BaseAddress(), dylinker.Header().flags, dylinker.Name().c_str()); - VisitModule(dylinker); - void* imageInfosAddress = (void*)dyldInfo.infoArray; size_t imageInfosSize = dyldInfo.infoArrayCount * sizeof(dyld_image_info); - Trace("MOD: infoArray %p infoArrayCount %d\n", dyldInfo.infoArray, dyldInfo.infoArrayCount); - ArrayHolder imageInfos = new (std::nothrow) dyld_image_info[dyldInfo.infoArrayCount]; if (imageInfos == nullptr) { @@ -413,22 +392,38 @@ MachOReader::EnumerateModules(mach_vm_address_t address, mach_header_64* header) } for (int i = 0; i < dyldInfo.infoArrayCount; i++) { - mach_vm_address_t imageAddress = (mach_vm_address_t)imageInfos[i].imageLoadAddress; - const char* imageFilePathAddress = imageInfos[i].imageFilePath; + // Ignore any errors and continue to next module + TryRegisterModule(imageInfos[i].imageLoadAddress, imageInfos[i].imageFilePath, false); + } + return true; +} - std::string imagePath; - if (!ReadString(imageFilePathAddress, imagePath)) - { - Trace("ERROR: Failed to read image name at %p\n", imageFilePathAddress); - continue; - } - MachOModule module(*this, imageAddress, nullptr, &imagePath); - if (!module.ReadHeader()) +bool +MachOReader::TryRegisterModule(const struct mach_header* imageAddress, const char* imageFilePathAddress, bool dylinker) +{ + std::string imagePath; + if (!ReadString(imageFilePathAddress, imagePath)) + { + return false; + } + MachOModule module(*this, (mach_vm_address_t)imageAddress, &imagePath); + if (!module.ReadHeader()) + { + return false; + } + Trace("MOD: %016llx %08x %s\n", imageAddress, module.Header().flags, imagePath.c_str()); + VisitModule(module); + if (dylinker) + { + // Make sure the memory for the symbol and string tables are in the core dump for our + // dump readers which still use this symbol to enumerate modules. + uint64_t dyldInfoAddress = 0; + if (!module.TryLookupSymbol("dyld_all_image_infos", &dyldInfoAddress)) { - continue; + Trace("ERROR: Can not find the _dyld_all_image_infos symbol\n"); + return false; } - Trace("MOD: %016llx %08x %s\n", imageAddress, module.Header().flags, imagePath.c_str()); - VisitModule(module); + Trace("MOD: dyldInfoAddress %016llx\n", dyldInfoAddress); } return true; } diff --git a/src/coreclr/debug/dbgutil/machoreader.h b/src/coreclr/debug/dbgutil/machoreader.h index a5f458b17ff2d5..19630e624f61c6 100644 --- a/src/coreclr/debug/dbgutil/machoreader.h +++ b/src/coreclr/debug/dbgutil/machoreader.h @@ -27,7 +27,7 @@ class MachOModule uint64_t m_strtabAddress; public: - MachOModule(MachOReader& reader, mach_vm_address_t baseAddress, mach_header_64* header = nullptr, std::string* name = nullptr); + MachOModule(MachOReader& reader, mach_vm_address_t baseAddress, std::string* name = nullptr); ~MachOModule(); inline mach_vm_address_t BaseAddress() const { return m_baseAddress; } @@ -41,8 +41,6 @@ class MachOModule bool EnumerateSegments(); private: - inline void SetName(std::string& name) { m_name = name; } - bool ReadLoadCommands(); bool ReadSymbolTable(); uint64_t GetAddressFromFileOffset(uint32_t offset); @@ -54,9 +52,10 @@ class MachOReader friend MachOModule; public: MachOReader(); - bool EnumerateModules(mach_vm_address_t address, mach_header_64* header); + bool EnumerateModules(mach_vm_address_t dyldInfoAddress); private: + bool TryRegisterModule(const struct mach_header* imageAddress, const char* imageFilePathAddress, bool dylinker); bool ReadString(const char* address, std::string& str); virtual void VisitModule(MachOModule& module) { }; virtual void VisitSegment(MachOModule& module, const segment_command_64& segment) { }; diff --git a/src/coreclr/debug/di/divalue.cpp b/src/coreclr/debug/di/divalue.cpp index 34937c1a58ea57..9645ee76526b37 100644 --- a/src/coreclr/debug/di/divalue.cpp +++ b/src/coreclr/debug/di/divalue.cpp @@ -3707,17 +3707,17 @@ HRESULT CordbArrayValue::GetDimensions(ULONG32 cdim, ULONG32 dims[]) // // indicates whether the array has base indices // Arguments: -// output: pbHasBaseIndices - true iff the array has more than one dimension and pbHasBaseIndices is not null -// Return Value: S_OK on success or E_INVALIDARG if pbHasBaseIndices is null -HRESULT CordbArrayValue::HasBaseIndices(BOOL *pbHasBaseIndices) +// output: pbHasBaseIndicies - true iff the array has more than one dimension and pbHasBaseIndicies is not null +// Return Value: S_OK on success or E_INVALIDARG if pbHasBaseIndicies is null +HRESULT CordbArrayValue::HasBaseIndicies(BOOL *pbHasBaseIndicies) { PUBLIC_REENTRANT_API_ENTRY(this); FAIL_IF_NEUTERED(this); - VALIDATE_POINTER_TO_OBJECT(pbHasBaseIndices, BOOL *); + VALIDATE_POINTER_TO_OBJECT(pbHasBaseIndicies, BOOL *); - *pbHasBaseIndices = m_info.arrayInfo.offsetToLowerBounds != 0; + *pbHasBaseIndicies = m_info.arrayInfo.offsetToLowerBounds != 0; return S_OK; -} // CordbArrayValue::HasBaseIndices +} // CordbArrayValue::HasBaseIndicies // gets the base indices for a multidimensional array // Arguments: @@ -3725,7 +3725,7 @@ HRESULT CordbArrayValue::HasBaseIndices(BOOL *pbHasBaseIndices) // indices - an array to hold the base indices for the array dimensions (allocated and managed // by the caller, it must have space for cdim elements) // Return Value: S_OK on success or E_INVALIDARG if cdim is not equal to the array rank or indices is null -HRESULT CordbArrayValue::GetBaseIndices(ULONG32 cdim, ULONG32 indices[]) +HRESULT CordbArrayValue::GetBaseIndicies(ULONG32 cdim, ULONG32 indices[]) { PUBLIC_REENTRANT_API_ENTRY(this); FAIL_IF_NEUTERED(this); @@ -3743,7 +3743,7 @@ HRESULT CordbArrayValue::GetBaseIndices(ULONG32 cdim, ULONG32 indices[]) indices[i] = m_arrayLowerBase[i]; return S_OK; -} // CordbArrayValue::GetBaseIndices +} // CordbArrayValue::GetBaseIndicies // Get an element at the position indicated by the values in indices (one index for each dimension) // Arguments: diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index bcad293e339bf5..45169c8470cee6 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -9722,8 +9722,8 @@ class CordbArrayValue : public CordbValue, COM_METHOD GetRank(ULONG32 * pnRank); COM_METHOD GetCount(ULONG32 * pnCount); COM_METHOD GetDimensions(ULONG32 cdim, ULONG32 dims[]); - COM_METHOD HasBaseIndices(BOOL * pbHasBaseIndices); - COM_METHOD GetBaseIndices(ULONG32 cdim, ULONG32 indices[]); + COM_METHOD HasBaseIndicies(BOOL * pbHasBaseIndicies); + COM_METHOD GetBaseIndicies(ULONG32 cdim, ULONG32 indices[]); COM_METHOD GetElement(ULONG32 cdim, ULONG32 indices[], ICorDebugValue ** ppValue); COM_METHOD GetElementAtPosition(ULONG32 nIndex, ICorDebugValue ** ppValue); diff --git a/src/coreclr/debug/ee/debugger.cpp b/src/coreclr/debug/ee/debugger.cpp index df05ee034a0d96..f8a3adc971a460 100644 --- a/src/coreclr/debug/ee/debugger.cpp +++ b/src/coreclr/debug/ee/debugger.cpp @@ -13191,19 +13191,14 @@ void STDCALL ExceptionHijackWorker( // See code:ExceptionHijackPersonalityRoutine for more information. // // Arguments: -// * pExceptionRecord - not used -// * MemoryStackFp - not used -// * BackingStoreFp - not used -// * pContextRecord - not used -// * pDispatcherContext - not used -// * GlobalPointer - not used +// Standard personality routine signature. // // Return Value: // Always return ExceptionContinueSearch. // EXCEPTION_DISPOSITION EmptyPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord, - IN ULONG64 MemoryStackFp, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext) { @@ -13216,7 +13211,7 @@ EXCEPTION_DISPOSITION EmptyPersonalityRoutine(IN PEXCEPTION_RECORD pExcept // Personality routine for unwinder the assembly hijack stub on 64-bit. // // Arguments: -// standard Personality routine signature. +// Standard personality routine signature. // // Assumptions: // This is caleld by the OS exception logic during exception handling. @@ -13243,9 +13238,8 @@ EXCEPTION_DISPOSITION EmptyPersonalityRoutine(IN PEXCEPTION_RECORD pExcept // On AMD64, we work around this by using an empty personality routine. EXTERN_C EXCEPTION_DISPOSITION -ExceptionHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG32 MemoryStackFp), +ExceptionHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -13268,7 +13262,7 @@ ExceptionHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord // This copies pHijackContext into pDispatcherContext, which the OS can then // use to walk the stack. - FixupDispatcherContext(pDispatcherContext, pHijackContext, pContextRecord, (PEXCEPTION_ROUTINE)EmptyPersonalityRoutine); + FixupDispatcherContext(pDispatcherContext, pHijackContext, (PEXCEPTION_ROUTINE)EmptyPersonalityRoutine); #else _ASSERTE(!"NYI - ExceptionHijackPersonalityRoutine()"); #endif @@ -16596,22 +16590,23 @@ Debugger::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) { DAC_ENUM_VTHIS(); SUPPORTS_DAC; - _ASSERTE(m_rgHijackFunction != NULL); - if ( flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags != CLRDATA_ENUM_MEM_TRIAGE) { if (m_pMethodInfos.IsValid()) { m_pMethodInfos->EnumMemoryRegions(flags); } - DacEnumMemoryRegion(dac_cast(m_pLazyData), - sizeof(DebuggerLazyInit)); + DacEnumMemoryRegion(dac_cast(m_pLazyData), sizeof(DebuggerLazyInit)); } // Needed for stack walking from an initial native context. If the debugger can find the // on-disk image of clr.dll, then this is not necessary. - DacEnumMemoryRegion(dac_cast(m_rgHijackFunction), sizeof(MemoryRange)*kMaxHijackFunctions); + if (m_rgHijackFunction.IsValid()) + { + DacEnumMemoryRegion(dac_cast(m_rgHijackFunction), sizeof(MemoryRange)*kMaxHijackFunctions); + } } diff --git a/src/coreclr/debug/ee/debugger.h b/src/coreclr/debug/ee/debugger.h index 695b88aaef1ba4..f73f9c83810dc3 100644 --- a/src/coreclr/debug/ee/debugger.h +++ b/src/coreclr/debug/ee/debugger.h @@ -3872,7 +3872,7 @@ HANDLE OpenWin32EventOrThrow( bool DbgIsSpecialILOffset(DWORD offset); #if !defined(TARGET_X86) -void FixupDispatcherContext(T_DISPATCHER_CONTEXT* pDispatcherContext, T_CONTEXT* pContext, T_CONTEXT* pOriginalContext, PEXCEPTION_ROUTINE pUnwindPersonalityRoutine = NULL); +void FixupDispatcherContext(T_DISPATCHER_CONTEXT* pDispatcherContext, T_CONTEXT* pContext, PEXCEPTION_ROUTINE pUnwindPersonalityRoutine = NULL); #endif #endif /* DEBUGGER_H_ */ diff --git a/src/coreclr/debug/ee/funceval.cpp b/src/coreclr/debug/ee/funceval.cpp index 1d09d9df25355f..82fe538d9f3fb7 100644 --- a/src/coreclr/debug/ee/funceval.cpp +++ b/src/coreclr/debug/ee/funceval.cpp @@ -3579,7 +3579,7 @@ static void GCProtectArgsAndDoNormalFuncEval(DebuggerEval *pDE, GCX_FORBID(); RecordFuncEvalException( pDE, ppException); } - // Note: we need to catch all exceptioins here because they all get reported as the result of + // Note: we need to catch all exceptions here because they all get reported as the result of // the funceval. If a ThreadAbort occurred other than for a funcEval abort, we'll re-throw it manually. EX_END_CATCH(SwallowAllExceptions); @@ -3994,32 +3994,34 @@ void * STDCALL FuncEvalHijackWorker(DebuggerEval *pDE) #if defined(FEATURE_EH_FUNCLETS) && !defined(TARGET_UNIX) EXTERN_C EXCEPTION_DISPOSITION -FuncEvalHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG32 MemoryStackFp), +FuncEvalHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) { - DebuggerEval* pDE = NULL; + // The offset of the DebuggerEval pointer relative to the establisher frame. + SIZE_T debuggerEvalPtrOffset = 0; #if defined(TARGET_AMD64) - pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame); + // On AMD64 the establisher frame is the SP of FuncEvalHijack itself. + // In FuncEvalHijack we store RCX at the current SP. + debuggerEvalPtrOffset = 0; #elif defined(TARGET_ARM) - // on ARM the establisher frame is the SP of the caller of FuncEvalHijack, on other platforms it's FuncEvalHijack's SP. - // in FuncEvalHijack we allocate 8 bytes of stack space and then store R0 at the current SP, so if we subtract 8 from + // On ARM the establisher frame is the SP of the FuncEvalHijack's caller. + // In FuncEvalHijack we allocate 8 bytes of stack space and then store R0 at the current SP, so if we subtract 8 from // the establisher frame we can get the stack location where R0 was stored. - pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame - 8); - + debuggerEvalPtrOffset = 8; #elif defined(TARGET_ARM64) - // on ARM64 the establisher frame is the SP of the caller of FuncEvalHijack. - // in FuncEvalHijack we allocate 32 bytes of stack space and then store R0 at the current SP + 16, so if we subtract 16 from - // the establisher frame we can get the stack location where R0 was stored. - pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame - 16); + // On ARM64 the establisher frame is the SP of the FuncEvalHijack's caller. + // In FuncEvalHijack we allocate 32 bytes of stack space and then store X0 at the current SP + 16, so if we subtract 16 from + // the establisher frame we can get the stack location where X0 was stored. + debuggerEvalPtrOffset = 16; #else _ASSERTE(!"NYI - FuncEvalHijackPersonalityRoutine()"); #endif - FixupDispatcherContext(pDispatcherContext, &(pDE->m_context), pContextRecord); + DebuggerEval* pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame - debuggerEvalPtrOffset); + FixupDispatcherContext(pDispatcherContext, &(pDE->m_context)); // Returning ExceptionCollidedUnwind will cause the OS to take our new context record and // dispatcher context and restart the exception dispatching on this call frame, which is diff --git a/src/coreclr/debug/ee/functioninfo.cpp b/src/coreclr/debug/ee/functioninfo.cpp index 4cef128986074b..54c31f50f17127 100644 --- a/src/coreclr/debug/ee/functioninfo.cpp +++ b/src/coreclr/debug/ee/functioninfo.cpp @@ -2456,9 +2456,7 @@ DebuggerMethodInfoEntry::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) // For a MiniDumpNormal, what is needed for modules is already enumerated elsewhere. // Don't waste time doing it here an extra time. Also, this will add many MB extra into the dump. - if ((key.pModule.IsValid()) && - CLRDATA_ENUM_MEM_MINI != flags - && CLRDATA_ENUM_MEM_TRIAGE != flags) + if ((key.pModule.IsValid()) && CLRDATA_ENUM_MEM_MINI != flags && CLRDATA_ENUM_MEM_TRIAGE != flags && CLRDATA_ENUM_MEM_HEAP2 != flags) { key.pModule->EnumMemoryRegions(flags, true); } @@ -2476,7 +2474,7 @@ DebuggerMethodInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) DAC_ENUM_DTHIS(); SUPPORTS_DAC; - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2) { // Modules are enumerated already for minidumps, save the empty calls. if (m_module.IsValid()) @@ -2505,7 +2503,7 @@ DebuggerJitInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) m_methodInfo->EnumMemoryRegions(flags); } - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2) { if (m_nativeCodeVersion.GetMethodDesc().IsValid()) { diff --git a/src/coreclr/dlls/mscordbi/CMakeLists.txt b/src/coreclr/dlls/mscordbi/CMakeLists.txt index c24a90cf70409c..c577651141e5c5 100644 --- a/src/coreclr/dlls/mscordbi/CMakeLists.txt +++ b/src/coreclr/dlls/mscordbi/CMakeLists.txt @@ -100,6 +100,17 @@ elseif(CLR_CMAKE_HOST_UNIX) mscordaccore ) + # Before llvm 16, lld was setting `--undefined-version` by default. The default was + # flipped to `--no-undefined-version` in lld 16, so we will explicitly set it to + # `--undefined-version` for our use-case. + include(CheckLinkerFlag OPTIONAL) + if(COMMAND check_linker_flag) + check_linker_flag(CXX -Wl,--undefined-version LINKER_SUPPORTS_UNDEFINED_VERSION) + if (LINKER_SUPPORTS_UNDEFINED_VERSION) + add_linker_flag(-Wl,--undefined-version) + endif(LINKER_SUPPORTS_UNDEFINED_VERSION) + endif(COMMAND check_linker_flag) + # COREDBI_LIBRARIES is mentioned twice because ld is one pass linker and will not find symbols # if they are defined after they are used. Having all libs twice makes sure that ld will actually # find all symbols. diff --git a/src/coreclr/dlls/mscoree/CMakeLists.txt b/src/coreclr/dlls/mscoree/CMakeLists.txt index dc22c27a0957e9..785655763d5d91 100644 --- a/src/coreclr/dlls/mscoree/CMakeLists.txt +++ b/src/coreclr/dlls/mscoree/CMakeLists.txt @@ -11,7 +11,7 @@ set(CLR_SOURCES if(CLR_CMAKE_TARGET_WIN32) list(APPEND CLR_SOURCES - delayloadhook.cpp + ${CLR_SRC_NATIVE_DIR}/libs/Common/delayloadhook_windows.cpp Native.rc ) diff --git a/src/coreclr/dlls/mscoree/exports.cpp b/src/coreclr/dlls/mscoree/exports.cpp index e8ee88275df8eb..3da6254b0dfb2f 100644 --- a/src/coreclr/dlls/mscoree/exports.cpp +++ b/src/coreclr/dlls/mscoree/exports.cpp @@ -14,6 +14,7 @@ #include #include #include +#include "../../vm/ceemain.h" #ifdef FEATURE_GDBJIT #include "../../vm/gdbjithelpers.h" #endif // FEATURE_GDBJIT @@ -22,14 +23,27 @@ #define ASSERTE_ALL_BUILDS(expr) _ASSERTE_ALL_BUILDS((expr)) -// Holder for const wide strings -typedef NewArrayHolder ConstWStringHolder; +#ifdef TARGET_UNIX +#define NO_HOSTING_API_RETURN_ADDRESS ((void*)ULONG_PTR_MAX) +void* g_hostingApiReturnAddress = NO_HOSTING_API_RETURN_ADDRESS; -// Specifies whether coreclr is embedded or standalone -extern bool g_coreclr_embedded; +class HostingApiFrameHolder +{ +public: + HostingApiFrameHolder(void* returnAddress) + { + g_hostingApiReturnAddress = returnAddress; + } + + ~HostingApiFrameHolder() + { + g_hostingApiReturnAddress = NO_HOSTING_API_RETURN_ADDRESS; + } +}; +#endif // TARGET_UNIX -// Specifies whether hostpolicy is embedded in executable or standalone -extern bool g_hostpolicy_embedded; +// Holder for const wide strings +typedef NewArrayHolder ConstWStringHolder; // Holder for array of wide strings class ConstWStringArrayHolder : public NewArrayHolder @@ -158,6 +172,26 @@ static void ConvertConfigPropertiesToUnicode( *propertyValuesWRef = propertyValuesW; } +coreclr_error_writer_callback_fn g_errorWriter = nullptr; + +// +// Set callback for writing error logging +// +// Parameters: +// errorWriter - callback that will be called for each line of the error info +// - passing in NULL removes a callback that was previously set +// +// Returns: +// S_OK +// +extern "C" +DLLEXPORT +int coreclr_set_error_writer(coreclr_error_writer_callback_fn error_writer) +{ + g_errorWriter = error_writer; + return S_OK; +} + #ifdef FEATURE_GDBJIT GetInfoForMethodDelegate getInfoForMethodDelegate = NULL; extern "C" int coreclr_create_delegate(void*, unsigned int, const char*, const char*, const char*, void**); @@ -179,6 +213,7 @@ extern "C" int coreclr_create_delegate(void*, unsigned int, const char*, const c // HRESULT indicating status of the operation. S_OK if the assembly was successfully executed // extern "C" +NOINLINE DLLEXPORT int coreclr_initialize( const char* exePath, @@ -197,6 +232,10 @@ int coreclr_initialize( bool hostPolicyEmbedded = false; PInvokeOverrideFn* pinvokeOverride = nullptr; +#ifdef TARGET_UNIX + HostingApiFrameHolder apiFrameHolder(_ReturnAddress()); +#endif + ConvertConfigPropertiesToUnicode( propertyKeys, propertyValues, @@ -405,6 +444,7 @@ int coreclr_create_delegate( // HRESULT indicating status of the operation. S_OK if the assembly was successfully executed // extern "C" +NOINLINE DLLEXPORT int coreclr_execute_assembly( void* hostHandle, @@ -420,6 +460,10 @@ int coreclr_execute_assembly( } *exitCode = -1; +#ifdef TARGET_UNIX + HostingApiFrameHolder apiFrameHolder(_ReturnAddress()); +#endif + ICLRRuntimeHost4* host = reinterpret_cast(hostHandle); ConstWStringArrayHolder argvW; @@ -432,3 +476,16 @@ int coreclr_execute_assembly( return hr; } + +void LogErrorToHost(const char* format, ...) +{ + if (g_errorWriter != NULL) + { + char messageBuffer[1024]; + va_list args; + va_start(args, format); + _vsnprintf_s(messageBuffer, ARRAY_SIZE(messageBuffer), _TRUNCATE, format, args); + g_errorWriter(messageBuffer); + va_end(args); + } +} diff --git a/src/coreclr/dlls/mscoree/mscorwks_ntdef.src b/src/coreclr/dlls/mscoree/mscorwks_ntdef.src index 987f67bc36aff4..0ac421b63e0718 100644 --- a/src/coreclr/dlls/mscoree/mscorwks_ntdef.src +++ b/src/coreclr/dlls/mscoree/mscorwks_ntdef.src @@ -22,6 +22,7 @@ EXPORTS coreclr_create_delegate coreclr_execute_assembly coreclr_initialize + coreclr_set_error_writer coreclr_shutdown coreclr_shutdown_2 diff --git a/src/coreclr/dlls/mscoree/mscorwks_unixexports.src b/src/coreclr/dlls/mscoree/mscorwks_unixexports.src index ebf0556e7a870d..a35a59c095604a 100644 --- a/src/coreclr/dlls/mscoree/mscorwks_unixexports.src +++ b/src/coreclr/dlls/mscoree/mscorwks_unixexports.src @@ -2,6 +2,7 @@ coreclr_create_delegate coreclr_execute_assembly coreclr_initialize +coreclr_set_error_writer coreclr_shutdown coreclr_shutdown_2 diff --git a/src/coreclr/dlls/mscorrc/mscorrc.rc b/src/coreclr/dlls/mscorrc/mscorrc.rc index 892c1f1c27bfcb..d1489320f29b51 100644 --- a/src/coreclr/dlls/mscorrc/mscorrc.rc +++ b/src/coreclr/dlls/mscorrc/mscorrc.rc @@ -271,6 +271,7 @@ BEGIN IDS_EE_BADMARSHAL_ABSTRACTRETCRITICALHANDLE "Returned CriticalHandles cannot be abstract." IDS_EE_BADMARSHAL_CUSTOMMARSHALER "Custom marshalers are only allowed on classes, strings, arrays, and boxed value types." IDS_EE_BADMARSHAL_GENERICS_RESTRICTION "Non-blittable generic types cannot be marshaled." + IDS_EE_BADMARSHAL_INT128_RESTRICTION "System.Int128 and System.UInt128 cannot be passed by value to unmanaged." IDS_EE_BADMARSHAL_AUTOLAYOUT "Structures marked with [StructLayout(LayoutKind.Auto)] cannot be marshaled." IDS_EE_BADMARSHAL_STRING_OUT "Cannot marshal a string by-value with the [Out] attribute." IDS_EE_BADMARSHAL_MARSHAL_DISABLED "Cannot marshal managed types when the runtime marshalling system is disabled." @@ -548,7 +549,7 @@ BEGIN IDS_ER_STACK_OVERFLOW "Description: The process was terminated due to stack overflow." IDS_ER_STACK "Stack:" IDS_ER_WORDAT "at" - IDS_ER_UNMANAGEDFAILFASTMSG "at IP 0x%x (0x%x) with exit code 0x%x." + IDS_ER_UNMANAGEDFAILFASTMSG "at IP 0x%1 (0x%2) with exit code 0x%3." IDS_ER_UNHANDLEDEXCEPTIONINFO "exception code %1, exception address %2" IDS_ER_MESSAGE_TRUNCATE "The remainder of the message was truncated." IDS_ER_CODECONTRACT_FAILED "Description: The application encountered a bug. A managed code contract (precondition, postcondition, object invariant, or assert) failed." diff --git a/src/coreclr/dlls/mscorrc/resource.h b/src/coreclr/dlls/mscorrc/resource.h index d2177bd56db2fd..3245329339c91f 100644 --- a/src/coreclr/dlls/mscorrc/resource.h +++ b/src/coreclr/dlls/mscorrc/resource.h @@ -283,6 +283,7 @@ #define IDS_EE_BADMARSHAL_ABSTRACTOUTCRITICALHANDLE 0x1a63 #define IDS_EE_BADMARSHAL_RETURNCHCOMTONATIVE 0x1a64 #define IDS_EE_BADMARSHAL_CRITICALHANDLE 0x1a65 +#define IDS_EE_BADMARSHAL_INT128_RESTRICTION 0x1a66 #define IDS_EE_BADMARSHAL_ABSTRACTRETCRITICALHANDLE 0x1a6a #define IDS_EE_CH_IN_VARIANT_NOT_SUPPORTED 0x1a6b diff --git a/src/coreclr/gc/env/gcenv.base.h b/src/coreclr/gc/env/gcenv.base.h index 56257dcb90965c..c6a73eb6afea5b 100644 --- a/src/coreclr/gc/env/gcenv.base.h +++ b/src/coreclr/gc/env/gcenv.base.h @@ -86,6 +86,7 @@ inline HRESULT HRESULT_FROM_WIN32(unsigned long x) #define CLR_E_GC_BAD_AFFINITY_CONFIG_FORMAT 0x8013200B #define CLR_E_GC_BAD_HARD_LIMIT 0x8013200D #define CLR_E_GC_LARGE_PAGE_MISSING_HARD_LIMIT 0x8013200E +#define CLR_E_GC_BAD_REGION_SIZE 0x8013200F #define NOERROR 0x0 #define ERROR_TIMEOUT 1460 diff --git a/src/coreclr/gc/env/gcenv.ee.h b/src/coreclr/gc/env/gcenv.ee.h index 3a7c049d4af880..2f4eecc3501fae 100644 --- a/src/coreclr/gc/env/gcenv.ee.h +++ b/src/coreclr/gc/env/gcenv.ee.h @@ -94,6 +94,8 @@ class GCToEEInterface static uint32_t GetCurrentProcessCpuCount(); static void DiagAddNewRegion(int generation, uint8_t* rangeStart, uint8_t* rangeEnd, uint8_t* rangeEndReserved); + + static void LogErrorToHost(const char *message); }; #endif // __GCENV_EE_H__ diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 993719b1536cf8..7653205e837802 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -91,6 +91,8 @@ BOOL bgc_heap_walk_for_etw_p = FALSE; #define UOH_ALLOCATION_RETRY_MAX_COUNT 2 +#define MAX_YP_SPIN_COUNT_UNIT 32768 + uint32_t yp_spin_count_unit = 0; uint32_t original_spin_count_unit = 0; size_t loh_size_threshold = LARGE_OBJECT_SIZE; @@ -2287,6 +2289,7 @@ double gc_heap::short_plugs_pad_ratio = 0; int gc_heap::generation_skip_ratio_threshold = 0; int gc_heap::conserve_mem_setting = 0; +bool gc_heap::spin_count_unit_config_p = false; uint64_t gc_heap::suspended_start_time = 0; uint64_t gc_heap::end_gc_time = 0; @@ -2331,7 +2334,9 @@ size_t gc_heap::heap_hard_limit = 0; size_t gc_heap::heap_hard_limit_oh[total_oh_count]; #ifdef USE_REGIONS + size_t gc_heap::regions_range = 0; + #endif //USE_REGIONS bool affinity_config_specified_p = false; @@ -2516,6 +2521,8 @@ int gc_heap::num_sip_regions = 0; size_t gc_heap::end_gen0_region_space = 0; +size_t gc_heap::end_gen0_region_committed_space = 0; + size_t gc_heap::gen0_pinned_free_space = 0; bool gc_heap::gen0_large_chunk_found = false; @@ -2741,7 +2748,6 @@ size_t gc_heap::compact_or_sweep_gcs[2]; #ifdef FEATURE_LOH_COMPACTION BOOL gc_heap::loh_compaction_always_p = FALSE; gc_loh_compaction_mode gc_heap::loh_compaction_mode = loh_compaction_default; -int gc_heap::loh_pinned_queue_decay = LOH_PIN_DECAY; #endif //FEATURE_LOH_COMPACTION GCEvent gc_heap::full_gc_approach_event; @@ -2756,8 +2762,6 @@ BOOL gc_heap::fgn_last_gc_was_concurrent = FALSE; VOLATILE(bool) gc_heap::full_gc_approach_event_set; -bool gc_heap::special_sweep_p = false; - size_t gc_heap::full_gc_counts[gc_type_max]; bool gc_heap::maxgen_size_inc_p = false; @@ -2856,10 +2860,20 @@ size_t gc_heap::interesting_mechanism_bits_per_heap[max_gc_mechanism_bits_co mark_queue_t gc_heap::mark_queue; +#ifdef USE_REGIONS +bool gc_heap::special_sweep_p = false; +#endif //USE_REGIONS + +int gc_heap::loh_pinned_queue_decay = LOH_PIN_DECAY; + #endif // MULTIPLE_HEAPS /* end of per heap static initialization */ +#ifdef USE_REGIONS +const size_t uninitialized_end_gen0_region_space = (size_t)(-1); +#endif //USE_REGIONS + // budget smoothing size_t gc_heap::smoothed_desired_per_heap[total_generation_count]; /* end of static initialization */ @@ -3171,7 +3185,8 @@ gc_heap::dt_high_frag_p (gc_tuning_point tp, #ifndef MULTIPLE_HEAPS if (gen_number == max_generation) { - float frag_ratio = (float)(dd_fragmentation (dynamic_data_of (max_generation))) / (float)generation_size (max_generation); + size_t maxgen_size = generation_size (max_generation); + float frag_ratio = (maxgen_size ? ((float)dd_fragmentation (dynamic_data_of (max_generation)) / (float)maxgen_size) : 0.0f); if (frag_ratio > 0.65) { dprintf (GTC_LOG, ("g2 FR: %d%%", (int)(frag_ratio*100))); @@ -3183,7 +3198,8 @@ gc_heap::dt_high_frag_p (gc_tuning_point tp, ret = (fr > dd_fragmentation_limit(dd)); if (ret) { - fragmentation_burden = (float)fr / generation_size (gen_number); + size_t gen_size = generation_size (gen_number); + fragmentation_burden = (gen_size ? ((float)fr / (float)gen_size) : 0.0f); ret = (fragmentation_burden > dd_v_fragmentation_burden_limit (dd)); } dprintf (GTC_LOG, ("h%d: gen%d, frag is %Id, alloc effi: %d%%, unusable frag is %Id, ratio is %d", @@ -3955,6 +3971,8 @@ bool region_allocator::allocate_large_region (uint8_t** start, uint8_t** end, al return allocate_region (size, start, end, direction, fn); } +// Whenever a region is deleted, it is expected that the memory and the mark array +// of the region is decommitted already. void region_allocator::delete_region (uint8_t* region_start) { enter_spin_lock(); @@ -4473,8 +4491,12 @@ class CObjectHeader : public Object return ((ArrayBase *)this)->GetNumComponents(); } - void Validate(BOOL bDeep=TRUE) + void Validate(BOOL bDeep=TRUE, BOOL bVerifyNextHeader = FALSE, BOOL bVerifySyncBlock = FALSE) { + // declaration of extra parameters just so the call site would need no #ifdefs + UNREFERENCED_PARAMETER(bVerifyNextHeader); + UNREFERENCED_PARAMETER(bVerifySyncBlock); + MethodTable * pMT = GetMethodTable(); _ASSERTE(pMT->SanityCheck()); @@ -6085,20 +6107,22 @@ class heap_select uint16_t proc_no[MAX_SUPPORTED_CPUS]; uint16_t node_no[MAX_SUPPORTED_CPUS]; uint16_t max_node_no = 0; - for (uint16_t i = 0; i < n_heaps; i++) + uint16_t heap_num; + for (heap_num = 0; heap_num < n_heaps; heap_num++) { - if (!GCToOSInterface::GetProcessorForHeap (i, &proc_no[i], &node_no[i])) + if (!GCToOSInterface::GetProcessorForHeap (heap_num, &proc_no[heap_num], &node_no[heap_num])) break; - if (!do_numa || node_no[i] == NUMA_NODE_UNDEFINED) - node_no[i] = 0; - max_node_no = max(max_node_no, node_no[i]); + assert(proc_no[heap_num] < MAX_SUPPORTED_CPUS); + if (!do_numa || node_no[heap_num] == NUMA_NODE_UNDEFINED) + node_no[heap_num] = 0; + max_node_no = max(max_node_no, node_no[heap_num]); } // Pass 2: assign heap numbers by numa node int cur_heap_no = 0; for (uint16_t cur_node_no = 0; cur_node_no <= max_node_no; cur_node_no++) { - for (int i = 0; i < n_heaps; i++) + for (int i = 0; i < heap_num; i++) { if (node_no[i] != cur_node_no) continue; @@ -6754,7 +6778,7 @@ void gc_heap::gc_thread_function () uint32_t wait_result = gc_heap::ee_suspend_event.Wait(gradual_decommit_in_progress_p ? DECOMMIT_TIME_STEP_MILLISECONDS : INFINITE, FALSE); if (wait_result == WAIT_TIMEOUT) { - gradual_decommit_in_progress_p = decommit_step (); + gradual_decommit_in_progress_p = decommit_step (DECOMMIT_TIME_STEP_MILLISECONDS); continue; } @@ -6847,7 +6871,7 @@ void gc_heap::gc_thread_function () // check if we should do some decommitting if (gradual_decommit_in_progress_p) { - gradual_decommit_in_progress_p = decommit_step (); + gradual_decommit_in_progress_p = decommit_step (DECOMMIT_TIME_STEP_MILLISECONDS); } } else @@ -6895,6 +6919,14 @@ bool gc_heap::virtual_alloc_commit_for_heap (void* addr, size_t size, int h_numb bool gc_heap::virtual_commit (void* address, size_t size, int bucket, int h_number, bool* hard_limit_exceeded_p) { + /** + * Here are all the possible cases for the commits: + * + * Case 1: This is for a particular generation - the bucket will be one of the gc_oh_num != unknown, and the h_number will be the right heap + * Case 2: This is for bookkeeping - the bucket will be recorded_committed_bookkeeping_bucket, and the h_number will be -1 + * + * Note : We never commit into free directly, so bucket != recorded_committed_free_bucket + */ #ifndef HOST_64BIT assert (heap_hard_limit == 0); #endif //!HOST_64BIT @@ -6910,21 +6942,23 @@ bool gc_heap::virtual_commit (void* address, size_t size, int bucket, int h_numb check_commit_cs.Enter(); bool exceeded_p = false; - if (heap_hard_limit_oh[bucket] != 0) + if (heap_hard_limit_oh[soh] != 0) { if ((bucket < total_oh_count) && (committed_by_oh[bucket] + size) > heap_hard_limit_oh[bucket]) { exceeded_p = true; } } - else if ((current_total_committed + size) > heap_hard_limit) + else { - dprintf (1, ("%Id + %Id = %Id > limit %Id ", - current_total_committed, size, - (current_total_committed + size), - heap_hard_limit)); + size_t base = current_total_committed; + size_t limit = heap_hard_limit; - exceeded_p = true; + if ((base + size) > limit) + { + dprintf (1, ("%Id + %Id = %Id > limit %Id ", base, size, (base + size), limit)); + exceeded_p = true; + } } if (!exceeded_p) @@ -6974,7 +7008,10 @@ bool gc_heap::virtual_commit (void* address, size_t size, int bucket, int h_numb current_total_committed, (current_total_committed - size))); current_total_committed -= size; if (h_number < 0) + { + assert (current_total_committed_bookkeeping >= size); current_total_committed_bookkeeping -= size; + } check_commit_cs.Leave(); } @@ -6983,6 +7020,13 @@ bool gc_heap::virtual_commit (void* address, size_t size, int bucket, int h_numb bool gc_heap::virtual_decommit (void* address, size_t size, int bucket, int h_number) { + /** + * Here are all possible cases for the decommits: + * + * Case 1: This is for a particular generation - the bucket will be one of the gc_oh_num != unknown, and the h_number will be the right heap + * Case 2: This is for bookkeeping - the bucket will be recorded_committed_bookkeeping_bucket, and the h_number will be -1 + * Case 3: This is for free - the bucket will be recorded_committed_free_bucket, and the h_number will be -1 + */ #ifndef HOST_64BIT assert (heap_hard_limit == 0); #endif //!HOST_64BIT @@ -7006,9 +7050,13 @@ bool gc_heap::virtual_decommit (void* address, size_t size, int bucket, int h_nu g_heaps[h_number]->committed_by_oh_per_heap[bucket] -= size; } #endif // _DEBUG && MULTIPLE_HEAPS + assert (current_total_committed >= size); current_total_committed -= size; - if (h_number < 0) + if (bucket == recorded_committed_bookkeeping_bucket) + { + assert (current_total_committed_bookkeeping >= size); current_total_committed_bookkeeping -= size; + } check_commit_cs.Leave(); } @@ -8723,10 +8771,10 @@ bool gc_heap::on_used_changed (uint8_t* new_used) { if (new_bookkeeping_covered_committed == new_used) { - dprintf (REGIONS_LOG, ("The minimal commit for the GC bookkeepping data structure failed, giving up")); + dprintf (REGIONS_LOG, ("The minimal commit for the GC bookkeeping data structure failed, giving up")); return false; } - dprintf (REGIONS_LOG, ("The speculative commit for the GC bookkeepping data structure failed, retry for minimal commit")); + dprintf (REGIONS_LOG, ("The speculative commit for the GC bookkeeping data structure failed, retry for minimal commit")); speculative_commit_tried = true; } } @@ -10232,7 +10280,7 @@ static int __cdecl cmp_mark_list_item (const void* vkey, const void* vdatum) #endif // _DEBUG #ifdef USE_REGIONS -uint8_t** gc_heap::get_region_mark_list (uint8_t* start, uint8_t* end, uint8_t*** mark_list_end_ptr) +uint8_t** gc_heap::get_region_mark_list (BOOL& use_mark_list, uint8_t* start, uint8_t* end, uint8_t*** mark_list_end_ptr) { size_t region_number = get_basic_region_index_for_address (start); size_t source_number = region_number; @@ -10362,6 +10410,13 @@ void gc_heap::merge_mark_lists (size_t total_mark_list_size) // blast this piece to the mark list append_to_mark_list(source[lowest_source], x); +#ifdef USE_REGIONS + if (mark_list_index > mark_list_end) + { + use_mark_list = false; + return nullptr; + } +#endif //USE_REGIONS piece_count++; source[lowest_source] = x; @@ -10381,6 +10436,13 @@ void gc_heap::merge_mark_lists (size_t total_mark_list_size) } // we're left with just one source that we copy append_to_mark_list(source[0], source_end[0]); +#ifdef USE_REGIONS + if (mark_list_index > mark_list_end) + { + use_mark_list = false; + return nullptr; + } +#endif //USE_REGIONS piece_count++; } @@ -10437,7 +10499,7 @@ static uint8_t** binary_search (uint8_t** left, uint8_t** right, uint8_t* e) return a + l; } -uint8_t** gc_heap::get_region_mark_list (uint8_t* start, uint8_t* end, uint8_t*** mark_list_end_ptr) +uint8_t** gc_heap::get_region_mark_list (BOOL& use_mark_list, uint8_t* start, uint8_t* end, uint8_t*** mark_list_end_ptr) { // do a binary search over the sorted marked list to find start and end of the // mark list for this region @@ -11216,11 +11278,6 @@ void gc_heap::clear_region_info (heap_segment* region) seg_deleted); bgc_verify_mark_array_cleared (region); - - if (dt_high_memory_load_p()) - { - decommit_mark_array_by_seg (region); - } #endif //BACKGROUND_GC } @@ -11311,7 +11368,7 @@ heap_segment* gc_heap::get_free_region (int gen_number, size_t size) uint8_t* region_end = heap_segment_reserved (region); init_heap_segment (region, __this, region_start, (region_end - region_start), - gen_number); + gen_number, true); gc_oh_num oh = gen_to_oh (gen_number); dprintf(3, ("commit-accounting: from free to %d [%Ix, %Ix) for heap %d", oh, get_region_start (region), heap_segment_committed (region), heap_number)); @@ -11335,6 +11392,9 @@ heap_segment* gc_heap::get_free_region (int gen_number, size_t size) heap_number, (size_t)region, region_start, region_end, gen_number)); + + // Something is wrong if a free region is already filled + assert (heap_segment_allocated(region) == heap_segment_mem (region)); } else { @@ -11662,7 +11722,7 @@ void gc_heap::clear_gen1_cards() heap_segment* gc_heap::make_heap_segment (uint8_t* new_pages, size_t size, gc_heap* hp, int gen_num) { gc_oh_num oh = gen_to_oh (gen_num); - size_t initial_commit = SEGMENT_INITIAL_COMMIT; + size_t initial_commit = use_large_pages_p ? size : SEGMENT_INITIAL_COMMIT; int h_number = #ifdef MULTIPLE_HEAPS hp->heap_number; @@ -11687,8 +11747,7 @@ heap_segment* gc_heap::make_heap_segment (uint8_t* new_pages, size_t size, gc_he heap_segment_mem (new_segment) = start; heap_segment_used (new_segment) = start; heap_segment_reserved (new_segment) = new_pages + size; - heap_segment_committed (new_segment) = (use_large_pages_p ? - heap_segment_reserved(new_segment) : (new_pages + initial_commit)); + heap_segment_committed (new_segment) = new_pages + initial_commit; init_heap_segment (new_segment, hp #ifdef USE_REGIONS @@ -11702,11 +11761,14 @@ heap_segment* gc_heap::make_heap_segment (uint8_t* new_pages, size_t size, gc_he void gc_heap::init_heap_segment (heap_segment* seg, gc_heap* hp #ifdef USE_REGIONS - , uint8_t* start, size_t size, int gen_num + , uint8_t* start, size_t size, int gen_num, bool existing_region_p #endif //USE_REGIONS ) { - seg->flags = 0; +#ifndef USE_REGIONS + bool existing_region_p = false; +#endif //!USE_REGIONS + seg->flags = existing_region_p ? (seg->flags & heap_segment_flags_ma_committed) : 0; heap_segment_next (seg) = 0; heap_segment_plan_allocated (seg) = heap_segment_mem (seg); heap_segment_allocated (seg) = heap_segment_mem (seg); @@ -12580,6 +12642,20 @@ void gc_heap::distribute_free_regions() { #ifdef USE_REGIONS const int kind_count = large_free_region + 1; + +#ifdef MULTIPLE_HEAPS + BOOL joined_last_gc_before_oom = FALSE; + for (int i = 0; i < n_heaps; i++) + { + if (g_heaps[i]->last_gc_before_oom) + { + joined_last_gc_before_oom = TRUE; + break; + } + } +#else + BOOL joined_last_gc_before_oom = last_gc_before_oom; +#endif //MULTIPLE_HEAPS if (settings.reason == reason_induced_aggressive) { #ifdef MULTIPLE_HEAPS @@ -12595,7 +12671,7 @@ void gc_heap::distribute_free_regions() global_regions_to_decommit[kind].transfer_regions (&hp->free_regions[kind]); } } - while (decommit_step()) + while (decommit_step(DECOMMIT_TIME_STEP_MILLISECONDS)) { } #ifdef MULTIPLE_HEAPS @@ -12639,6 +12715,7 @@ void gc_heap::distribute_free_regions() size_t num_decommit_regions_by_time = 0; size_t size_decommit_regions_by_time = 0; size_t heap_budget_in_region_units[MAX_SUPPORTED_CPUS][kind_count]; + size_t min_heap_budget_in_region_units[MAX_SUPPORTED_CPUS]; size_t region_size[kind_count] = { global_region_allocator.get_region_alignment(), global_region_allocator.get_large_region_alignment() }; region_free_list surplus_regions[kind_count]; for (int kind = basic_free_region; kind < kind_count; kind++) @@ -12656,6 +12733,7 @@ void gc_heap::distribute_free_regions() gc_heap* hp = pGenGCHeap; // just to reduce the number of #ifdefs in the code below const int i = 0; + const int n_heaps = 1; #endif //MULTIPLE_HEAPS for (int kind = basic_free_region; kind < kind_count; kind++) @@ -12666,7 +12744,10 @@ void gc_heap::distribute_free_regions() for (heap_segment* region = region_list.get_first_free_region(); region != nullptr; region = next_region) { next_region = heap_segment_next (region); - if (heap_segment_age_in_free (region) >= AGE_IN_FREE_TO_DECOMMIT) + int age_in_free_to_decommit = min (max (AGE_IN_FREE_TO_DECOMMIT, n_heaps), MAX_AGE_IN_FREE); + // when we are about to get OOM, we'd like to discount the free regions that just have the initial page commit as they are not useful + if ((heap_segment_age_in_free (region) >= age_in_free_to_decommit) || + ((get_region_committed_size (region) == GC_PAGE_SIZE) && joined_last_gc_before_oom)) { num_decommit_regions_by_time++; size_decommit_regions_by_time += get_region_committed_size (region); @@ -12683,13 +12764,43 @@ void gc_heap::distribute_free_regions() global_free_huge_regions.transfer_regions (&hp->free_regions[huge_free_region]); heap_budget_in_region_units[i][basic_free_region] = 0; + min_heap_budget_in_region_units[i] = 0; heap_budget_in_region_units[i][large_free_region] = 0; - for (int gen = soh_gen0; gen < total_generation_count; gen++) + } + + for (int gen = soh_gen0; gen < total_generation_count; gen++) + { + if ((gen <= soh_gen2) && + total_budget_in_region_units[basic_free_region] >= (total_num_free_regions[basic_free_region] + + surplus_regions[basic_free_region].get_num_free_regions())) + { + // don't accumulate budget from higher soh generations if we cannot cover lower ones + dprintf (REGIONS_LOG, ("out of free regions - skipping gen %d budget = %Id >= avail %Id", + gen, + total_budget_in_region_units[basic_free_region], + total_num_free_regions[basic_free_region] + surplus_regions[basic_free_region].get_num_free_regions())); + continue; + } +#ifdef MULTIPLE_HEAPS + for (int i = 0; i < n_heaps; i++) + { + gc_heap* hp = g_heaps[i]; +#else //MULTIPLE_HEAPS { + gc_heap* hp = pGenGCHeap; + // just to reduce the number of #ifdefs in the code below + const int i = 0; + const int n_heaps = 1; +#endif //MULTIPLE_HEAPS ptrdiff_t budget_gen = max (hp->estimate_gen_growth (gen), 0); int kind = gen >= loh_generation; size_t budget_gen_in_region_units = (budget_gen + (region_size[kind] - 1)) / region_size[kind]; dprintf (REGIONS_LOG, ("h%2d gen %d has an estimated growth of %Id bytes (%Id regions)", i, gen, budget_gen, budget_gen_in_region_units)); + if (gen <= soh_gen2) + { + // preserve the budget for the previous generation - we should not go below that + min_heap_budget_in_region_units[i] = heap_budget_in_region_units[i][kind]; + } heap_budget_in_region_units[i][kind] += budget_gen_in_region_units; total_budget_in_region_units[kind] += budget_gen_in_region_units; } @@ -12739,15 +12850,54 @@ void gc_heap::distribute_free_regions() { dprintf (REGIONS_LOG, ("distributing the %Id %s regions deficit", -balance, kind_name[kind])); +#ifdef MULTIPLE_HEAPS // we may have a deficit or - if background GC is going on - a surplus. // adjust the budget per heap accordingly - ptrdiff_t adjustment_per_heap = (balance + (n_heaps - 1)) / n_heaps; - -#ifdef MULTIPLE_HEAPS - for (int i = 0; i < n_heaps; i++) + if (balance != 0) { - ptrdiff_t new_budget = (ptrdiff_t)heap_budget_in_region_units[i][kind] + adjustment_per_heap; - heap_budget_in_region_units[i][kind] = max (0, new_budget); + ptrdiff_t curr_balance = 0; + ptrdiff_t rem_balance = 0; + for (int i = 0; i < n_heaps; i++) + { + curr_balance += balance; + ptrdiff_t adjustment_per_heap = curr_balance / n_heaps; + curr_balance -= adjustment_per_heap * n_heaps; + ptrdiff_t new_budget = (ptrdiff_t)heap_budget_in_region_units[i][kind] + adjustment_per_heap; + ptrdiff_t min_budget = (kind == basic_free_region) ? (ptrdiff_t)min_heap_budget_in_region_units[i] : 0; + dprintf (REGIONS_LOG, ("adjusting the budget for heap %d from %Id %s regions by %Id to %Id", + i, + heap_budget_in_region_units[i][kind], + kind_name[kind], + adjustment_per_heap, + max (min_budget, new_budget))); + heap_budget_in_region_units[i][kind] = max (min_budget, new_budget); + rem_balance += new_budget - heap_budget_in_region_units[i][kind]; + } + assert (rem_balance <= 0); + dprintf (REGIONS_LOG, ("remaining balance: %Id %s regions", rem_balance, kind_name[kind])); + + // if we have a left over deficit, distribute that to the heaps that still have more than the minimum + while (rem_balance < 0) + { + for (int i = 0; i < n_heaps; i++) + { + size_t min_budget = (kind == basic_free_region) ? min_heap_budget_in_region_units[i] : 0; + if (heap_budget_in_region_units[i][kind] > min_budget) + { + dprintf (REGIONS_LOG, ("adjusting the budget for heap %d from %Id %s regions by %Id to %Id", + i, + heap_budget_in_region_units[i][kind], + kind_name[kind], + -1, + heap_budget_in_region_units[i][kind] - 1)); + + heap_budget_in_region_units[i][kind] -= 1; + rem_balance += 1; + if (rem_balance == 0) + break; + } + } + } } #endif //MULTIPLE_HEAPS } @@ -12799,11 +12949,12 @@ void gc_heap::distribute_free_regions() if (hp->free_regions[kind].get_num_free_regions() > heap_budget_in_region_units[i][kind]) { - dprintf (REGIONS_LOG, ("removing %Id %s regions from heap %d with %Id regions", + dprintf (REGIONS_LOG, ("removing %Id %s regions from heap %d with %Id regions, budget is %Id", hp->free_regions[kind].get_num_free_regions() - heap_budget_in_region_units[i][kind], kind_name[kind], i, - hp->free_regions[kind].get_num_free_regions())); + hp->free_regions[kind].get_num_free_regions(), + heap_budget_in_region_units[i][kind])); remove_surplus_regions (&hp->free_regions[kind], &surplus_regions[kind], heap_budget_in_region_units[i][kind]); } @@ -12818,14 +12969,16 @@ void gc_heap::distribute_free_regions() const int i = 0; #endif //MULTIPLE_HEAPS + // second pass: fill all the regions having less than budget if (hp->free_regions[kind].get_num_free_regions() < heap_budget_in_region_units[i][kind]) { int64_t num_added_regions = add_regions (&hp->free_regions[kind], &surplus_regions[kind], heap_budget_in_region_units[i][kind]); - dprintf (REGIONS_LOG, ("added %Id %s regions to heap %d - now has %Id", + dprintf (REGIONS_LOG, ("added %Id %s regions to heap %d - now has %Id, budget is %Id", num_added_regions, kind_name[kind], i, - hp->free_regions[kind].get_num_free_regions())); + hp->free_regions[kind].get_num_free_regions(), + heap_budget_in_region_units[i][kind])); } hp->free_regions[kind].sort_by_committed_and_age(); } @@ -12847,8 +13000,29 @@ void gc_heap::distribute_free_regions() } } #else //MULTIPLE_HEAPS - while (decommit_step()) + // we want to limit the amount of decommit we do per time to indirectly + // limit the amount of time spent in recommit and page faults + // we use the elapsed time since the last GC to arrive at the desired + // decommit size + // we limit the elapsed time to 10 seconds to avoid spending too much time decommitting + // if less than DECOMMIT_TIME_STEP_MILLISECONDS elapsed, we don't decommit - + // we don't want to decommit fractions of regions here + dynamic_data* dd0 = dynamic_data_of (0); + size_t ephemeral_elapsed = (size_t)((dd_time_clock (dd0) - gc_last_ephemeral_decommit_time) / 1000); + if (ephemeral_elapsed >= DECOMMIT_TIME_STEP_MILLISECONDS) { + gc_last_ephemeral_decommit_time = dd_time_clock (dd0); + size_t decommit_step_milliseconds = min (ephemeral_elapsed, (10*1000)); + + decommit_step (decommit_step_milliseconds); + } + // transfer any remaining regions on the decommit list back to the free list + for (int kind = basic_free_region; kind < count_free_region_kinds; kind++) + { + if (global_regions_to_decommit[kind].get_num_free_regions() != 0) + { + free_regions[kind].transfer_regions (&global_regions_to_decommit[kind]); + } } #endif //MULTIPLE_HEAPS #endif //USE_REGIONS @@ -13327,13 +13501,17 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size, gc_log = CreateLogFile(GCConfig::GetLogFile(), false); if (gc_log == NULL) + { + GCToEEInterface::LogErrorToHost("Cannot create log file"); return E_FAIL; + } // GCLogFileSize in MBs. gc_log_file_size = static_cast(GCConfig::GetLogFileSize()); if (gc_log_file_size <= 0 || gc_log_file_size > 500) { + GCToEEInterface::LogErrorToHost("Invalid log file size (valid size needs to be larger than 0 and smaller than 500)"); fclose (gc_log); return E_FAIL; } @@ -13343,7 +13521,7 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size, if (!gc_log_buffer) { fclose(gc_log); - return E_FAIL; + return E_OUTOFMEMORY; } memset (gc_log_buffer, '*', gc_log_buffer_size); @@ -13358,13 +13536,16 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size, gc_config_log = CreateLogFile(GCConfig::GetConfigLogFile(), true); if (gc_config_log == NULL) + { + GCToEEInterface::LogErrorToHost("Cannot create log file"); return E_FAIL; + } gc_config_log_buffer = new (nothrow) uint8_t [gc_config_log_buffer_size]; if (!gc_config_log_buffer) { fclose(gc_config_log); - return E_FAIL; + return E_OUTOFMEMORY; } compact_ratio = static_cast(GCConfig::GetCompactRatio()); @@ -13471,6 +13652,7 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size, else { assert (!"cannot use regions without specifying the range!!!"); + GCToEEInterface::LogErrorToHost("Cannot use regions without specifying the range (using DOTNET_GCRegionRange)"); return E_FAIL; } #else //USE_REGIONS @@ -13481,10 +13663,20 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size, if (!reserve_initial_memory (soh_segment_size, loh_segment_size, poh_segment_size, number_of_heaps, use_large_pages_p, separated_poh_p, heap_no_to_numa_node)) return E_OUTOFMEMORY; - if (separated_poh_p) + if (use_large_pages_p) { - heap_hard_limit_oh[poh] = min_segment_size_hard_limit * number_of_heaps; - heap_hard_limit += heap_hard_limit_oh[poh]; + if (heap_hard_limit_oh[soh]) + { + heap_hard_limit_oh[soh] = soh_segment_size * number_of_heaps; + heap_hard_limit_oh[loh] = loh_segment_size * number_of_heaps; + heap_hard_limit_oh[poh] = poh_segment_size * number_of_heaps; + heap_hard_limit = heap_hard_limit_oh[soh] + heap_hard_limit_oh[loh] + heap_hard_limit_oh[poh]; + } + else + { + assert (heap_hard_limit); + heap_hard_limit = (soh_segment_size + loh_segment_size + poh_segment_size) * number_of_heaps; + } } #endif //USE_REGIONS @@ -13569,6 +13761,14 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size, yp_spin_count_unit = 32 * g_num_processors; #endif //MULTIPLE_HEAPS + // Check if the values are valid for the spin count if provided by the user + // and if they are, set them as the yp_spin_count_unit and then ignore any updates made in SetYieldProcessorScalingFactor. + uint32_t spin_count_unit_from_config = (uint32_t)GCConfig::GetGCSpinCountUnit(); + gc_heap::spin_count_unit_config_p = (spin_count_unit_from_config > 0) && (spin_count_unit_from_config <= MAX_YP_SPIN_COUNT_UNIT); + if (gc_heap::spin_count_unit_config_p) + { + yp_spin_count_unit = spin_count_unit_from_config; + } original_spin_count_unit = yp_spin_count_unit; #if defined(__linux__) @@ -13584,6 +13784,7 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size, if (!init_semi_shared()) { + GCToEEInterface::LogErrorToHost("PER_HEAP_ISOLATED data members initialization failed"); hres = E_FAIL; } @@ -13619,7 +13820,7 @@ gc_heap::init_semi_shared() } #else //MULTIPLE_HEAPS - mark_list_size = max (8192, soh_segment_size/(64*32)); + mark_list_size = min(100*1024, max (8192, soh_segment_size/(64*32))); g_mark_list = make_mark_list (mark_list_size); #endif //MULTIPLE_HEAPS @@ -14118,6 +14319,11 @@ gc_heap::init_gc_heap (int h_number) #ifdef RECORD_LOH_STATE loh_state_index = 0; #endif //RECORD_LOH_STATE + +#ifdef USE_REGIONS + special_sweep_p = false; +#endif //USE_REGIONS + #endif //MULTIPLE_HEAPS #ifdef MULTIPLE_HEAPS @@ -14162,6 +14368,8 @@ gc_heap::init_gc_heap (int h_number) #endif //CARD_BUNDLE #ifdef BACKGROUND_GC + background_saved_highest_address = nullptr; + background_saved_lowest_address = nullptr; if (gc_can_use_concurrent) mark_array = translate_mark_array (card_table_mark_array (&g_gc_card_table[card_word (card_of (g_gc_lowest_address))])); else @@ -14187,6 +14395,7 @@ gc_heap::init_gc_heap (int h_number) num_condemned_regions = 0; #endif //STRESS_REGIONS end_gen0_region_space = 0; + end_gen0_region_committed_space = 0; gen0_pinned_free_space = 0; gen0_large_chunk_found = false; // REGIONS PERF TODO: we should really allocate the POH regions together just so that @@ -16180,7 +16389,8 @@ BOOL gc_heap::short_on_end_of_seg (heap_segment* seg) uint8_t* allocated = heap_segment_allocated (seg); #ifdef USE_REGIONS - BOOL sufficient_p = sufficient_space_regions (end_gen0_region_space, end_space_after_gc()); + assert (end_gen0_region_space != uninitialized_end_gen0_region_space); + BOOL sufficient_p = sufficient_space_regions_for_allocation (end_gen0_region_space, end_space_after_gc()); #else BOOL sufficient_p = sufficient_space_end_seg (allocated, heap_segment_committed (seg), @@ -20080,20 +20290,22 @@ bool gc_heap::try_get_new_free_region() bool gc_heap::init_table_for_region (int gen_number, heap_segment* region) { #ifdef BACKGROUND_GC - if (is_bgc_in_progress()) - { - dprintf (GC_TABLE_LOG, ("new seg %Ix, mark_array is %Ix", - heap_segment_mem (region), mark_array)); - if (!commit_mark_array_new_seg (__this, region)) - { - dprintf (GC_TABLE_LOG, ("failed to commit mark array for the new region %Ix-%Ix", - get_region_start (region), heap_segment_reserved (region))); + dprintf (GC_TABLE_LOG, ("new seg %Ix, mark_array is %Ix", + heap_segment_mem (region), mark_array)); + if (((region->flags & heap_segment_flags_ma_committed) == 0) && + !commit_mark_array_new_seg (__this, region)) + { + dprintf (GC_TABLE_LOG, ("failed to commit mark array for the new region %Ix-%Ix", + get_region_start (region), heap_segment_reserved (region))); - // We don't have memory to commit the mark array so we cannot use the new region. - global_region_allocator.delete_region (get_region_start (region)); - return false; - } - } + // We don't have memory to commit the mark array so we cannot use the new region. + decommit_region (region, gen_to_oh (gen_number), heap_number); + return false; + } + if ((region->flags & heap_segment_flags_ma_committed) != 0) + { + bgc_verify_mark_array_cleared (region, true); + } #endif //BACKGROUND_GC if (gen_number <= max_generation) @@ -21138,6 +21350,12 @@ void gc_heap::gc1() dprintf (3, ("New allocation quantum: %d(0x%Ix)", allocation_quantum, allocation_quantum)); } } +#ifdef USE_REGIONS + if (end_gen0_region_space == uninitialized_end_gen0_region_space) + { + end_gen0_region_space = get_gen0_end_space (memory_type_reserved); + } +#endif //USE_REGIONS descr_generations ("END"); @@ -21358,7 +21576,14 @@ void gc_heap::gc1() dynamic_data* dd = hp->dynamic_data_of (gen); dd_desired_allocation (dd) = desired_per_heap; dd_gc_new_allocation (dd) = desired_per_heap; +#ifdef USE_REGIONS + // we may have had some incoming objects during this GC - + // adjust the consumed budget for these dd_new_allocation (dd) = desired_per_heap - already_consumed_per_heap; +#else //USE_REGIONS + // for segments, we want to keep the .NET 6.0 behavior where we did not adjust + dd_new_allocation (dd) = desired_per_heap; +#endif //USE_REGIONS if (gen == 0) { @@ -21483,6 +21708,12 @@ void gc_heap::gc1() recover_bgc_settings(); #endif //BACKGROUND_GC #endif //MULTIPLE_HEAPS +#ifdef USE_REGIONS + if (!(settings.concurrent) && (settings.condemned_generation == max_generation)) + { + last_gc_before_oom = FALSE; + } +#endif //USE_REGIONS } void gc_heap::save_data_for_no_gc() @@ -22275,7 +22506,8 @@ void gc_heap::init_records() } #ifdef USE_REGIONS - end_gen0_region_space = 0; + end_gen0_region_space = uninitialized_end_gen0_region_space; + end_gen0_region_committed_space = 0; gen0_pinned_free_space = 0; gen0_large_chunk_found = false; num_regions_freed_in_sweep = 0; @@ -22339,6 +22571,8 @@ void gc_heap::garbage_collect_pm_full_gc() void gc_heap::garbage_collect (int n) { + gc_pause_mode saved_settings_pause_mode = settings.pause_mode; + //reset the number of alloc contexts alloc_contexts_used = 0; @@ -22744,7 +22978,7 @@ void gc_heap::garbage_collect (int n) #endif //MULTIPLE_HEAPS done: - if (settings.pause_mode == pause_no_gc) + if (saved_settings_pause_mode == pause_no_gc) allocate_for_no_gc_after_gc(); } @@ -22837,15 +23071,15 @@ void gc_heap::sync_promoted_bytes() total_old_card_surv += g_heaps[hp_idx]->old_card_survived_per_region[region_index]; } - heap_segment_survived (current_region) = (int)total_surv; + heap_segment_survived (current_region) = total_surv; heap_segment_old_card_survived (current_region) = (int)total_old_card_surv; #else - heap_segment_survived (current_region) = (int)(survived_per_region[region_index]); + heap_segment_survived (current_region) = survived_per_region[region_index]; heap_segment_old_card_survived (current_region) = (int)(old_card_survived_per_region[region_index]); #endif //MULTIPLE_HEAPS - dprintf (REGIONS_LOG, ("region #%d %Ix surv %Id, old card surv %Id", + dprintf (REGIONS_LOG, ("region #%d %Ix surv %zd, old card surv %Id", region_index, heap_segment_mem (current_region), heap_segment_survived (current_region), @@ -23039,8 +23273,8 @@ void gc_heap::equalize_promoted_bytes() { break; } - assert (surv_per_heap[i] >= (size_t)heap_segment_survived (region)); - dprintf (REGIONS_LOG, ("heap: %d surv: %Id - %Id = %Id", + assert (surv_per_heap[i] >= heap_segment_survived (region)); + dprintf (REGIONS_LOG, ("heap: %d surv: %Id - %zd = %Id", i, surv_per_heap[i], heap_segment_survived (region), @@ -23051,7 +23285,7 @@ void gc_heap::equalize_promoted_bytes() heap_segment_next (region) = surplus_regions; surplus_regions = region; - max_survived = max (max_survived, (size_t)heap_segment_survived (region)); + max_survived = max (max_survived, heap_segment_survived (region)); } if (surv_per_heap[i] < avg_surv_per_heap) { @@ -23069,7 +23303,7 @@ void gc_heap::equalize_promoted_bytes() heap_segment* next_region; for (heap_segment* region = surplus_regions; region != nullptr; region = next_region) { - int size_class = (int)(heap_segment_survived (region)*survived_scale_factor); + size_t size_class = (size_t)(heap_segment_survived (region)*survived_scale_factor); assert ((0 <= size_class) && (size_class < NUM_SIZE_CLASSES)); next_region = heap_segment_next (region); heap_segment_next (region) = surplus_regions_by_size_class[size_class]; @@ -23131,7 +23365,7 @@ void gc_heap::equalize_promoted_bytes() g_heaps[heap_num]->thread_rw_region_front (gen_idx, region); // adjust survival for this heap - dprintf (REGIONS_LOG, ("heap: %d surv: %Id + %Id = %Id", + dprintf (REGIONS_LOG, ("heap: %d surv: %Id + %zd = %Id", heap_num, surv_per_heap[heap_num], heap_segment_survived (region), @@ -23861,12 +24095,17 @@ BOOL ref_p (uint8_t* r) return (straight_ref_p (r) || partial_object_p (r)); } -mark_queue_t::mark_queue_t() : curr_slot_index(0) +mark_queue_t::mark_queue_t() +#ifdef MARK_PHASE_PREFETCH + : curr_slot_index(0) +#endif //MARK_PHASE_PREFETCH { +#ifdef MARK_PHASE_PREFETCH for (size_t i = 0; i < slot_count; i++) { slot_table[i] = nullptr; } +#endif //MARK_PHASE_PREFETCH } // place an object in the mark queue @@ -23876,6 +24115,7 @@ mark_queue_t::mark_queue_t() : curr_slot_index(0) FORCEINLINE uint8_t *mark_queue_t::queue_mark(uint8_t *o) { +#ifdef MARK_PHASE_PREFETCH Prefetch (o); // while the prefetch is taking effect, park our object in the queue @@ -23888,6 +24128,9 @@ uint8_t *mark_queue_t::queue_mark(uint8_t *o) curr_slot_index = (slot_index + 1) % slot_count; if (old_o == nullptr) return nullptr; +#else //MARK_PHASE_PREFETCH + uint8_t* old_o = o; +#endif //MARK_PHASE_PREFETCH // this causes us to access the method table pointer of the old object BOOL already_marked = marked (old_o); @@ -23939,6 +24182,7 @@ uint8_t *mark_queue_t::queue_mark(uint8_t *o, int condemned_gen) // returns nullptr if there is no such object uint8_t* mark_queue_t::get_next_marked() { +#ifdef MARK_PHASE_PREFETCH size_t slot_index = curr_slot_index; size_t empty_slot_count = 0; while (empty_slot_count < slot_count) @@ -23958,15 +24202,18 @@ uint8_t* mark_queue_t::get_next_marked() } empty_slot_count++; } +#endif //MARK_PHASE_PREFETCH return nullptr; } void mark_queue_t::verify_empty() { +#ifdef MARK_PHASE_PREFETCH for (size_t slot_index = 0; slot_index < slot_count; slot_index++) { assert(slot_table[slot_index] == nullptr); } +#endif //MARK_PHASE_PREFETCH } void gc_heap::mark_object_simple1 (uint8_t* oo, uint8_t* start THREAD_NUMBER_DCL) @@ -24659,12 +24906,7 @@ void gc_heap::set_background_overflow_p (uint8_t* oo) heap_segment* overflow_region = get_region_info_for_address (oo); overflow_region->flags |= heap_segment_flags_overflow; dprintf (3,("setting overflow flag for region %p", heap_segment_mem (overflow_region))); -#ifdef MULTIPLE_HEAPS - gc_heap* overflow_heap = heap_segment_heap (overflow_region); -#else - gc_heap* overflow_heap = nullptr; -#endif - overflow_heap->background_overflow_p = TRUE; + background_overflow_p = TRUE; } #endif //USE_REGIONS @@ -25311,13 +25553,13 @@ void gc_heap::background_process_mark_overflow_internal (uint8_t* min_add, uint8 dprintf (2, ("h%d: SOH: ov-mo: %Id", heap_number, total_marked_objects)); fire_overflow_event (min_add, max_add, total_marked_objects, i); - if (small_object_segments) + if (i >= soh_gen2) { concurrent_print_time_delta (concurrent_p ? "Cov SOH" : "Nov SOH"); + small_object_segments = FALSE; } total_marked_objects = 0; - small_object_segments = FALSE; } } } @@ -25666,6 +25908,7 @@ BOOL gc_heap::process_mark_overflow(int condemned_gen_number) BOOL overflow_p = FALSE; recheck: + drain_mark_queue(); if ((! (max_overflow_address == 0) || ! (min_overflow_address == MAX_PTR))) { @@ -25930,7 +26173,8 @@ void gc_heap::scan_dependent_handles (int condemned_gen_number, ScanContext *sc, if (process_mark_overflow(condemned_gen_number)) fUnscannedPromotions = true; - drain_mark_queue(); + // mark queue must be empty after process_mark_overflow + mark_queue.verify_empty(); // Perform the scan and set the flag if any promotions resulted. if (GCScan::GcDhReScan(sc)) @@ -26258,6 +26502,10 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) static uint64_t last_mark_time = 0; #endif //FEATURE_EVENT_TRACE +#ifdef USE_REGIONS + special_sweep_p = false; +#endif //USE_REGIONS + #ifdef MULTIPLE_HEAPS gc_t_join.join(this, gc_join_begin_mark_phase); if (gc_t_join.joined()) @@ -26266,7 +26514,6 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) maxgen_size_inc_p = false; #ifdef USE_REGIONS - special_sweep_p = false; region_count = global_region_allocator.get_used_region_count(); grow_mark_list_piece(); verify_region_to_generation_map(); @@ -26545,7 +26792,9 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) // handle table has been fully promoted. GCScan::GcDhInitialScan(GCHeap::Promote, condemned_gen_number, max_generation, &sc); scan_dependent_handles(condemned_gen_number, &sc, true); - drain_mark_queue(); + + // mark queue must be empty after scan_dependent_handles + mark_queue.verify_empty(); fire_mark_event (ETW::GC_ROOT_DH_HANDLES, current_promoted_bytes, last_promoted_bytes); #ifdef MULTIPLE_HEAPS @@ -26635,7 +26884,9 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) // Scan dependent handles again to promote any secondaries associated with primaries that were promoted // for finalization. As before scan_dependent_handles will also process any mark stack overflow. scan_dependent_handles(condemned_gen_number, &sc, false); - drain_mark_queue(); + + // mark queue must be empty after scan_dependent_handles + mark_queue.verify_empty(); fire_mark_event (ETW::GC_ROOT_DH_HANDLES, current_promoted_bytes, last_promoted_bytes); #endif //FEATURE_PREMORTEM_FINALIZATION @@ -27728,8 +27979,7 @@ BOOL gc_heap::plan_loh() loh_pinned_queue_length = LOH_PIN_QUEUE_LENGTH; } - if (heap_number == 0) - loh_pinned_queue_decay = LOH_PIN_DECAY; + loh_pinned_queue_decay = LOH_PIN_DECAY; loh_pinned_queue_tos = 0; loh_pinned_queue_bos = 0; @@ -28521,7 +28771,8 @@ void gc_heap::process_remaining_regions (int current_plan_gen_num, generation* c return; } - set_region_plan_gen_num_sip (current_region, current_plan_gen_num); + decide_on_demotion_pin_surv (current_region); + if (!heap_segment_swept_in_plan (current_region)) { heap_segment_plan_allocated (current_region) = generation_allocation_pointer (consing_gen); @@ -28637,6 +28888,7 @@ void gc_heap::update_planned_gen0_free_space (size_t free_size, uint8_t* plug) // the regions again and do this update in plan phase. void gc_heap::get_gen0_end_plan_space() { + end_gen0_region_space = 0; for (int gen_idx = settings.condemned_generation; gen_idx >= 0; gen_idx--) { generation* gen = generation_of (gen_idx); @@ -28668,7 +28920,7 @@ void gc_heap::get_gen0_end_plan_space() } } -size_t gc_heap::get_gen0_end_space() +size_t gc_heap::get_gen0_end_space(memory_type type) { size_t end_space = 0; heap_segment* seg = generation_start_segment (generation_of (0)); @@ -28682,12 +28934,13 @@ size_t gc_heap::get_gen0_end_space() //uint8_t* allocated = ((seg == ephemeral_heap_segment) ? // alloc_allocated : heap_segment_allocated (seg)); uint8_t* allocated = heap_segment_allocated (seg); - end_space += heap_segment_reserved (seg) - allocated; + uint8_t* end = (type == memory_type_reserved) ? heap_segment_reserved (seg) : heap_segment_committed (seg); + end_space += end - allocated; dprintf (REGIONS_LOG, ("h%d gen0 seg %Ix, end %Ix-%Ix=%Ix, end_space->%Id", heap_number, heap_segment_mem (seg), - heap_segment_reserved (seg), allocated, - (heap_segment_reserved (seg) - allocated), + end, allocated, + (end - allocated), end_space)); seg = heap_segment_next (seg); @@ -28996,7 +29249,7 @@ void gc_heap::plan_phase (int condemned_gen_number) uint8_t** mark_list_index = nullptr; uint8_t** mark_list_next = nullptr; if (use_mark_list) - mark_list_next = get_region_mark_list (x, end, &mark_list_index); + mark_list_next = get_region_mark_list (use_mark_list, x, end, &mark_list_index); #else // USE_REGIONS assert (!marked (x)); uint8_t** mark_list_next = &mark_list[0]; @@ -29284,7 +29537,7 @@ void gc_heap::plan_phase (int condemned_gen_number) current_brick = brick_of (x); #ifdef USE_REGIONS if (use_mark_list) - mark_list_next = get_region_mark_list (x, end, &mark_list_index); + mark_list_next = get_region_mark_list (use_mark_list, x, end, &mark_list_index); if (should_sweep_in_plan (seg1)) { @@ -29354,7 +29607,7 @@ void gc_heap::plan_phase (int condemned_gen_number) current_brick = brick_of (x); if (use_mark_list) - mark_list_next = get_region_mark_list (x, end, &mark_list_index); + mark_list_next = get_region_mark_list (use_mark_list, x, end, &mark_list_index); if (should_sweep_in_plan (seg1)) { @@ -30005,37 +30258,15 @@ void gc_heap::plan_phase (int condemned_gen_number) should_compact = decide_on_compacting (condemned_gen_number, fragmentation, should_expand); } -#ifdef FEATURE_LOH_COMPACTION - loh_compacted_p = FALSE; -#endif //FEATURE_LOH_COMPACTION - if (condemned_gen_number == max_generation) { #ifdef FEATURE_LOH_COMPACTION if (settings.loh_compaction) { - if (plan_loh()) - { - should_compact = TRUE; - get_gc_data_per_heap()->set_mechanism (gc_heap_compact, compact_loh_forced); - loh_compacted_p = TRUE; - } + should_compact = TRUE; + get_gc_data_per_heap()->set_mechanism (gc_heap_compact, compact_loh_forced); } else - { - if ((heap_number == 0) && (loh_pinned_queue)) - { - loh_pinned_queue_decay--; - - if (!loh_pinned_queue_decay) - { - delete loh_pinned_queue; - loh_pinned_queue = 0; - } - } - } - - if (!loh_compacted_p) #endif //FEATURE_LOH_COMPACTION { GCToEEInterface::DiagWalkUOHSurvivors(__this, loh_generation); @@ -30066,6 +30297,7 @@ void gc_heap::plan_phase (int condemned_gen_number) gc_t_join.join(this, gc_join_decide_on_compaction); if (gc_t_join.joined()) { +#ifndef USE_REGIONS //safe place to delete large heap segments if (condemned_gen_number == max_generation) { @@ -30074,7 +30306,7 @@ void gc_heap::plan_phase (int condemned_gen_number) g_heaps [i]->rearrange_uoh_segments (); } } - +#endif //!USE_REGIONS if (maxgen_size_inc_p && provisional_mode_triggered #ifdef BACKGROUND_GC && !is_bgc_in_progress() @@ -30086,9 +30318,11 @@ void gc_heap::plan_phase (int condemned_gen_number) } else { -#ifndef USE_REGIONS +#ifdef USE_REGIONS + bool joined_special_sweep_p = false; +#else settings.demotion = FALSE; -#endif //!USE_REGIONS +#endif //USE_REGIONS int pol_max = policy_sweep; #ifdef GC_CONFIG_DRIVEN BOOL is_compaction_mandatory = FALSE; @@ -30099,14 +30333,16 @@ void gc_heap::plan_phase (int condemned_gen_number) { if (pol_max < g_heaps[i]->gc_policy) pol_max = policy_compact; -#ifndef USE_REGIONS +#ifdef USE_REGIONS + joined_special_sweep_p |= g_heaps[i]->special_sweep_p; +#else // set the demotion flag is any of the heap has demotion if (g_heaps[i]->demotion_high >= g_heaps[i]->demotion_low) { (g_heaps[i]->get_gc_data_per_heap())->set_mechanism_bit (gc_demotion_bit); settings.demotion = TRUE; } -#endif //!USE_REGIONS +#endif //USE_REGIONS #ifdef GC_CONFIG_DRIVEN if (!is_compaction_mandatory) @@ -30141,7 +30377,8 @@ void gc_heap::plan_phase (int condemned_gen_number) for (i = 0; i < n_heaps; i++) { #ifdef USE_REGIONS - if (special_sweep_p) + g_heaps[i]->special_sweep_p = joined_special_sweep_p; + if (joined_special_sweep_p) { g_heaps[i]->gc_policy = policy_sweep; } @@ -30204,13 +30441,13 @@ void gc_heap::plan_phase (int condemned_gen_number) should_expand = (gc_policy >= policy_expand); #else //MULTIPLE_HEAPS - +#ifndef USE_REGIONS //safe place to delete large heap segments if (condemned_gen_number == max_generation) { rearrange_uoh_segments (); } - +#endif //!USE_REGIONS if (maxgen_size_inc_p && provisional_mode_triggered #ifdef BACKGROUND_GC && !is_bgc_in_progress() @@ -30258,8 +30495,50 @@ void gc_heap::plan_phase (int condemned_gen_number) gc_time_info[time_plan] = gc_time_info[time_sweep] - gc_time_info[time_plan]; } #endif //FEATURE_EVENT_TRACE + +#ifdef USE_REGIONS + if (special_sweep_p) + { + should_compact = FALSE; + } +#endif //!USE_REGIONS #endif //MULTIPLE_HEAPS +#ifdef FEATURE_LOH_COMPACTION + loh_compacted_p = FALSE; +#endif //FEATURE_LOH_COMPACTION + + if (condemned_gen_number == max_generation) + { +#ifdef FEATURE_LOH_COMPACTION + if (settings.loh_compaction) + { + if (should_compact && plan_loh()) + { + loh_compacted_p = TRUE; + } + else + { + GCToEEInterface::DiagWalkUOHSurvivors(__this, loh_generation); + sweep_uoh_objects (loh_generation); + } + } + else + { + if (loh_pinned_queue) + { + loh_pinned_queue_decay--; + + if (!loh_pinned_queue_decay) + { + delete loh_pinned_queue; + loh_pinned_queue = 0; + } + } + } +#endif //FEATURE_LOH_COMPACTION + } + if (!pm_trigger_full_gc && pm_stress_on && provisional_mode_triggered) { if ((settings.condemned_generation == (max_generation - 1)) && @@ -30433,10 +30712,16 @@ void gc_heap::plan_phase (int condemned_gen_number) #ifdef MULTIPLE_HEAPS for (int i = 0; i < n_heaps; i++) { - g_heaps[i]->rearrange_heap_segments(TRUE); +#ifdef USE_REGIONS + g_heaps [i]->rearrange_uoh_segments(); +#endif //USE_REGIONS + g_heaps [i]->rearrange_heap_segments (TRUE); } #else //MULTIPLE_HEAPS - rearrange_heap_segments(TRUE); +#ifdef USE_REGIONS + rearrange_uoh_segments(); +#endif //USE_REGIONS + rearrange_heap_segments (TRUE); #endif //MULTIPLE_HEAPS #ifdef MULTIPLE_HEAPS @@ -30458,6 +30743,10 @@ void gc_heap::plan_phase (int condemned_gen_number) #endif //!USE_REGIONS { +#ifdef USE_REGIONS + end_gen0_region_committed_space = get_gen0_end_space (memory_type_committed); + dprintf(REGIONS_LOG, ("h%d computed the end_gen0_region_committed_space value to be %Id", heap_number, end_gen0_region_committed_space)); +#endif //USE_REGIONS #ifdef MULTIPLE_HEAPS dprintf(3, ("Joining after end of compaction")); gc_t_join.join(this, gc_join_adjust_handle_age_compact); @@ -30717,6 +31006,11 @@ void gc_heap::plan_phase (int condemned_gen_number) generation_free_obj_space (generation_of (max_generation)))); } +#ifdef USE_REGIONS + end_gen0_region_committed_space = get_gen0_end_space (memory_type_committed); + dprintf(REGIONS_LOG, ("h%d computed the end_gen0_region_committed_space value to be %Id", heap_number, end_gen0_region_committed_space)); +#endif //USE_REGIONS + #ifdef MULTIPLE_HEAPS dprintf(3, ("Joining after end of sweep")); gc_t_join.join(this, gc_join_adjust_handle_age_sweep); @@ -30731,7 +31025,9 @@ void gc_heap::plan_phase (int condemned_gen_number) } #endif //FEATURE_EVENT_TRACE +#ifdef USE_REGIONS if (!special_sweep_p) +#endif //USE_REGIONS { GCScan::GcPromotionsGranted(condemned_gen_number, max_generation, &sc); @@ -30763,13 +31059,17 @@ void gc_heap::plan_phase (int condemned_gen_number) } #ifdef FEATURE_PREMORTEM_FINALIZATION +#ifdef USE_REGIONS if (!special_sweep_p) +#endif //USE_REGIONS { finalize_queue->UpdatePromotedGenerations (condemned_gen_number, TRUE); } #endif // FEATURE_PREMORTEM_FINALIZATION +#ifdef USE_REGIONS if (!special_sweep_p) +#endif //USE_REGIONS { clear_gen1_cards(); } @@ -31048,7 +31348,7 @@ heap_segment* gc_heap::find_first_valid_region (heap_segment* region, bool compa if (heap_segment_swept_in_plan (current_region)) { int gen_num = heap_segment_gen_num (current_region); - dprintf (REGIONS_LOG, ("threading SIP region %Ix surv %Id onto gen%d", + dprintf (REGIONS_LOG, ("threading SIP region %Ix surv %zd onto gen%d", heap_segment_mem (current_region), heap_segment_survived (current_region), gen_num)); generation* gen = generation_of (gen_num); @@ -31188,6 +31488,7 @@ void gc_heap::thread_final_regions (bool compact_p) else { start_region = get_free_region (gen_idx); + assert (start_region); thread_start_region (gen, start_region); dprintf (REGIONS_LOG, ("creating new gen%d at %Ix", gen_idx, heap_segment_mem (start_region))); } @@ -31374,7 +31675,7 @@ bool gc_heap::should_sweep_in_plan (heap_segment* region) assert (heap_segment_gen_num (region) == heap_segment_plan_gen_num (region)); int surv_ratio = (int)(((double)heap_segment_survived (region) * 100.0) / (double)basic_region_size); - dprintf (2222, ("SSIP: region %Ix surv %Id / %Id = %d%%(%d)", + dprintf (2222, ("SSIP: region %p surv %d / %zd = %d%%(%d)", heap_segment_mem (region), heap_segment_survived (region), basic_region_size, @@ -31582,14 +31883,14 @@ void gc_heap::sweep_region_in_plan (heap_segment* region, #ifdef _DEBUG size_t region_index = get_basic_region_index_for_address (heap_segment_mem (region)); - dprintf (REGIONS_LOG, ("region #%d %Ix survived %Id, %s recorded %Id", + dprintf (REGIONS_LOG, ("region #%d %Ix survived %zd, %s recorded %Id", region_index, heap_segment_mem (region), survived, - ((survived == (size_t)heap_segment_survived (region)) ? "same as" : "diff from"), + ((survived == heap_segment_survived (region)) ? "same as" : "diff from"), heap_segment_survived (region))); #ifdef MULTIPLE_HEAPS - assert (survived <= (size_t)heap_segment_survived (region)); + assert (survived <= heap_segment_survived (region)); #else - assert (survived == (size_t)heap_segment_survived (region)); + assert (survived == heap_segment_survived (region)); #endif //MULTIPLE_HEAPS #endif //_DEBUG @@ -31738,14 +32039,17 @@ void gc_heap::make_free_lists (int condemned_gen_number) size_t end_brick = brick_of (end_address-1); int current_gen_num = i; +#ifdef USE_REGIONS args.free_list_gen_number = (special_sweep_p ? current_gen_num : get_plan_gen_num (current_gen_num)); +#else + args.free_list_gen_number = get_plan_gen_num (current_gen_num); +#endif //USE_REGIONS args.free_list_gen = generation_of (args.free_list_gen_number); args.highest_plug = 0; #ifdef USE_REGIONS dprintf (REGIONS_LOG, ("starting at gen%d %Ix -> %Ix", i, start_address, end_address)); #else - assert (!special_sweep_p); args.current_gen_limit = (((current_gen_num == max_generation)) ? MAX_PTR : (generation_limit (args.free_list_gen_number))); @@ -31837,8 +32141,6 @@ void gc_heap::make_free_lists (int condemned_gen_number) generation* gen_gen0 = generation_of (0); ephemeral_heap_segment = generation_start_segment (gen_gen0); alloc_allocated = heap_segment_allocated (ephemeral_heap_segment); - // Since we didn't compact, we should recalculate the end_gen0_region_space. - end_gen0_region_space = get_gen0_end_space(); #else //USE_REGIONS int bottom_gen = 0; args.free_list_gen_number--; @@ -33980,23 +34282,35 @@ void gc_heap::background_scan_dependent_handles (ScanContext *sc) if (!s_fScanRequired) { -#ifndef USE_REGIONS +#ifdef USE_REGIONS + BOOL all_heaps_background_overflow_p = FALSE; +#else //USE_REGIONS uint8_t* all_heaps_max = 0; uint8_t* all_heaps_min = MAX_PTR; +#endif //USE_REGIONS int i; for (i = 0; i < n_heaps; i++) { +#ifdef USE_REGIONS + // in the regions case, compute the OR of all the per-heap flags + if (g_heaps[i]->background_overflow_p) + all_heaps_background_overflow_p = TRUE; +#else //USE_REGIONS if (all_heaps_max < g_heaps[i]->background_max_overflow_address) all_heaps_max = g_heaps[i]->background_max_overflow_address; if (all_heaps_min > g_heaps[i]->background_min_overflow_address) all_heaps_min = g_heaps[i]->background_min_overflow_address; +#endif //USE_REGIONS } for (i = 0; i < n_heaps; i++) { +#ifdef USE_REGIONS + g_heaps[i]->background_overflow_p = all_heaps_background_overflow_p; +#else //USE_REGIONS g_heaps[i]->background_max_overflow_address = all_heaps_max; g_heaps[i]->background_min_overflow_address = all_heaps_min; +#endif //USE_REGIONS } -#endif //!USE_REGIONS } dprintf(2, ("Starting all gc thread mark stack overflow processing")); @@ -34200,6 +34514,9 @@ BOOL gc_heap::commit_mark_array_new_seg (gc_heap* hp, dprintf (GC_TABLE_LOG, ("partially in bgc range: seg %Ix-%Ix, bgc: %Ix-%Ix", start, end, lowest, highest)); commit_flag = heap_segment_flags_ma_pcommitted; +#ifdef USE_REGIONS + assert (!"Region should not have its mark array partially committed."); +#endif } commit_start = max (lowest, start); @@ -34740,15 +35057,24 @@ void gc_heap::background_mark_phase () enable_preemptive (); -#if defined(MULTIPLE_HEAPS) && !defined(USE_REGIONS) +#if defined(MULTIPLE_HEAPS) bgc_t_join.join(this, gc_join_concurrent_overflow); if (bgc_t_join.joined()) { +#ifdef USE_REGIONS + BOOL all_heaps_background_overflow_p = FALSE; +#else //USE_REGIONS uint8_t* all_heaps_max = 0; uint8_t* all_heaps_min = MAX_PTR; +#endif //USE_REGIONS int i; for (i = 0; i < n_heaps; i++) { +#ifdef USE_REGIONS + // in the regions case, compute the OR of all the per-heap flags + if (g_heaps[i]->background_overflow_p) + all_heaps_background_overflow_p = TRUE; +#else //USE_REGIONS dprintf (3, ("heap %d overflow max is %Ix, min is %Ix", i, g_heaps[i]->background_max_overflow_address, @@ -34757,17 +35083,21 @@ void gc_heap::background_mark_phase () all_heaps_max = g_heaps[i]->background_max_overflow_address; if (all_heaps_min > g_heaps[i]->background_min_overflow_address) all_heaps_min = g_heaps[i]->background_min_overflow_address; - +#endif //USE_REGIONS } for (i = 0; i < n_heaps; i++) { +#ifdef USE_REGIONS + g_heaps[i]->background_overflow_p = all_heaps_background_overflow_p; +#else //USE_REGIONS g_heaps[i]->background_max_overflow_address = all_heaps_max; g_heaps[i]->background_min_overflow_address = all_heaps_min; +#endif //USE_REGIONS } dprintf(3, ("Starting all bgc threads after updating the overflow info")); bgc_t_join.restart(); } -#endif //MULTIPLE_HEAPS && !USE_REGIONS +#endif //MULTIPLE_HEAPS disable_preemptive (true); @@ -40376,9 +40706,14 @@ void gc_heap::compute_new_dynamic_data (int gen_number) } dd_gc_new_allocation (dd) = dd_desired_allocation (dd); +#ifdef USE_REGIONS // we may have had some incoming objects during this GC - // adjust the consumed budget for these dd_new_allocation (dd) = dd_gc_new_allocation (dd) - in; +#else //USE_REGIONS + // for segments, we want to keep the .NET 6.0 behavior where we did not adjust + dd_new_allocation (dd) = dd_gc_new_allocation (dd); +#endif //USE_REGIONS } gen_data->pinned_surv = dd_pinned_survived_size (dd); @@ -40529,9 +40864,12 @@ void gc_heap::decommit_ephemeral_segment_pages() { gradual_decommit_in_progress_p = TRUE; - dprintf (1, ("h%2d gen %d reduce_commit by %IdkB", + dprintf (1, ("h%2d gen %d region %Ix allocated %IdkB committed %IdkB reduce_commit by %IdkB", heap_number, gen_number, + get_region_start (tail_region), + (heap_segment_allocated (tail_region) - get_region_start (tail_region))/1024, + (heap_segment_committed (tail_region) - get_region_start (tail_region))/1024, (heap_segment_committed (tail_region) - decommit_target)/1024)); } dprintf(3, ("h%2d gen %d allocated: %IdkB committed: %IdkB target: %IdkB", @@ -40541,7 +40879,7 @@ void gc_heap::decommit_ephemeral_segment_pages() (heap_segment_committed (tail_region) - heap_segment_mem (tail_region))/1024, (decommit_target - heap_segment_mem (tail_region))/1024)); } -#else //MULTIPLE_HEAPS && USE_REGIONS +#elif !defined(USE_REGIONS) dynamic_data* dd0 = dynamic_data_of (0); @@ -40608,12 +40946,12 @@ void gc_heap::decommit_ephemeral_segment_pages() } // return true if we actually decommitted anything -bool gc_heap::decommit_step () +bool gc_heap::decommit_step (uint64_t step_milliseconds) { size_t decommit_size = 0; #ifdef USE_REGIONS - const size_t max_decommit_step_size = DECOMMIT_SIZE_PER_MILLISECOND * DECOMMIT_TIME_STEP_MILLISECONDS; + const size_t max_decommit_step_size = DECOMMIT_SIZE_PER_MILLISECOND * step_milliseconds; for (int kind = basic_free_region; kind < count_free_region_kinds; kind++) { dprintf (REGIONS_LOG, ("decommit_step %d, regions_to_decommit = %Id", @@ -40621,31 +40959,7 @@ bool gc_heap::decommit_step () while (global_regions_to_decommit[kind].get_num_free_regions() > 0) { heap_segment* region = global_regions_to_decommit[kind].unlink_region_front(); - - uint8_t* page_start = align_lower_page(get_region_start(region)); - uint8_t* end = use_large_pages_p ? heap_segment_used(region) : heap_segment_committed(region); - size_t size = end - page_start; - bool decommit_succeeded_p = false; - if (!use_large_pages_p) - { - decommit_succeeded_p = virtual_decommit(page_start, size, recorded_committed_free_bucket); - dprintf(REGIONS_LOG, ("decommitted region %Ix(%Ix-%Ix) (%Iu bytes) - success: %d", - region, - page_start, - end, - size, - decommit_succeeded_p)); - } - if (!decommit_succeeded_p) - { - memclr(page_start, size); - dprintf(REGIONS_LOG, ("cleared region %Ix(%Ix-%Ix) (%Iu bytes)", - region, - page_start, - end, - size)); - } - global_region_allocator.delete_region(get_region_start(region)); + size_t size = decommit_region (region, recorded_committed_free_bucket, -1); decommit_size += size; if (decommit_size >= max_decommit_step_size) { @@ -40672,6 +40986,68 @@ bool gc_heap::decommit_step () return (decommit_size != 0); } +#ifdef USE_REGIONS +size_t gc_heap::decommit_region (heap_segment* region, int bucket, int h_number) +{ + uint8_t* page_start = align_lower_page (get_region_start (region)); + uint8_t* end = use_large_pages_p ? heap_segment_used (region) : heap_segment_committed (region); + size_t size = end - page_start; + bool decommit_succeeded_p = false; + if (!use_large_pages_p) + { + decommit_succeeded_p = virtual_decommit (page_start, size, bucket, h_number); + } + dprintf (REGIONS_LOG, ("decommitted region %p(%p-%p) (%zu bytes) - success: %d", + region, + page_start, + end, + size, + decommit_succeeded_p)); + if (decommit_succeeded_p) + { + heap_segment_committed (region) = heap_segment_mem (region); + } + else + { + memclr (page_start, size); + heap_segment_used (region) = heap_segment_mem (region); + dprintf(REGIONS_LOG, ("cleared region %p(%p-%p) (%zu bytes)", + region, + page_start, + end, + size)); + } + + // Under USE_REGIONS, mark array is never partially committed. So we are only checking for this + // flag here. + if ((region->flags & heap_segment_flags_ma_committed) != 0) + { +#ifdef MULTIPLE_HEAPS + // In return_free_region, we set heap_segment_heap (region) to nullptr so we cannot use it here. + // but since all heaps share the same mark array we simply pick the 0th heap to use.  + gc_heap* hp = g_heaps [0]; +#else + gc_heap* hp = pGenGCHeap; +#endif + hp->decommit_mark_array_by_seg (region); + region->flags &= ~(heap_segment_flags_ma_committed); + } + + if (use_large_pages_p) + { + assert (heap_segment_used (region) == heap_segment_mem (region)); + } + else + { + assert (heap_segment_committed (region) == heap_segment_mem (region)); + } + assert ((region->flags & heap_segment_flags_ma_committed) == 0); + global_region_allocator.delete_region (get_region_start (region)); + + return size; +} +#endif //USE_REGIONS + #ifdef MULTIPLE_HEAPS // return the decommitted size size_t gc_heap::decommit_ephemeral_segment_pages_step () @@ -41000,7 +41376,6 @@ BOOL gc_heap::decide_on_compacting (int condemned_gen_number, #ifdef USE_REGIONS if (special_sweep_p) { - last_gc_before_oom = FALSE; return FALSE; } #endif //USE_REGIONS @@ -41017,7 +41392,9 @@ BOOL gc_heap::decide_on_compacting (int condemned_gen_number, if ((condemned_gen_number == max_generation) && last_gc_before_oom) { should_compact = TRUE; +#ifndef USE_REGIONS last_gc_before_oom = FALSE; +#endif //!USE_REGIONS get_gc_data_per_heap()->set_mechanism (gc_heap_compact, compact_last_gc); } @@ -41223,9 +41600,36 @@ bool gc_heap::check_against_hard_limit (size_t space_required) } #ifdef USE_REGIONS +bool gc_heap::sufficient_space_regions_for_allocation (size_t end_space, size_t end_space_required) +{ + // REGIONS PERF TODO: we can repurpose large regions here too, if needed. + size_t free_regions_space = (free_regions[basic_free_region].get_num_free_regions() * ((size_t)1 << min_segment_size_shr)) + + global_region_allocator.get_free(); + size_t total_alloc_space = end_space + free_regions_space; + dprintf (REGIONS_LOG, ("h%d required %Id, end %Id + free %Id=%Id", + heap_number, end_space_required, end_space, free_regions_space, total_alloc_space)); + size_t total_commit_space = end_gen0_region_committed_space + free_regions[basic_free_region].get_size_committed_in_free(); + if (total_alloc_space > end_space_required) + { + if (end_space_required > total_commit_space) + { + return check_against_hard_limit (end_space_required - total_commit_space); + } + else + { + return true; + } + } + else + { + return false; + } +} + bool gc_heap::sufficient_space_regions (size_t end_space, size_t end_space_required) { // REGIONS PERF TODO: we can repurpose large regions here too, if needed. + // REGIONS PERF TODO: for callsites other than allocation, we should also take commit into account size_t free_regions_space = (free_regions[basic_free_region].get_num_free_regions() * ((size_t)1 << min_segment_size_shr)) + global_region_allocator.get_free(); size_t total_alloc_space = end_space + free_regions_space; @@ -41236,7 +41640,9 @@ bool gc_heap::sufficient_space_regions (size_t end_space, size_t end_space_requi return check_against_hard_limit (end_space_required); } else + { return false; + } } #else //USE_REGIONS BOOL gc_heap::sufficient_space_end_seg (uint8_t* start, uint8_t* committed, uint8_t* reserved, size_t end_space_required) @@ -41411,7 +41817,7 @@ BOOL gc_heap::ephemeral_gen_fit_p (gc_tuning_point tp) } #ifdef USE_REGIONS - size_t gen0_end_space = get_gen0_end_space(); + size_t gen0_end_space = get_gen0_end_space (memory_type_reserved); BOOL can_fit = sufficient_space_regions (gen0_end_space, end_space); #else //USE_REGIONS BOOL can_fit = sufficient_space_end_seg (start, heap_segment_committed (ephemeral_heap_segment), heap_segment_reserved (ephemeral_heap_segment), end_space); @@ -43520,16 +43926,21 @@ BOOL gc_heap::bgc_mark_array_range (heap_segment* seg, } } -void gc_heap::bgc_verify_mark_array_cleared (heap_segment* seg) +void gc_heap::bgc_verify_mark_array_cleared (heap_segment* seg, bool always_verify_p) { #ifdef _DEBUG - if (gc_heap::background_running_p()) + if (gc_heap::background_running_p() || always_verify_p) { uint8_t* range_beg = 0; uint8_t* range_end = 0; - if (bgc_mark_array_range (seg, TRUE, &range_beg, &range_end)) + if (bgc_mark_array_range (seg, TRUE, &range_beg, &range_end) || always_verify_p) { + if (always_verify_p) + { + range_beg = heap_segment_mem (seg); + range_end = heap_segment_reserved (seg); + } size_t markw = mark_word_of (range_beg); size_t markw_end = mark_word_of (range_end); while (markw < markw_end) @@ -43550,7 +43961,7 @@ void gc_heap::bgc_verify_mark_array_cleared (heap_segment* seg) } } } -#endif //VERIFY_HEAP +#endif //_DEBUG } void gc_heap::verify_mark_bits_cleared (uint8_t* obj, size_t s) @@ -44756,10 +45167,20 @@ HRESULT GCHeap::Initialize() { if (gc_heap::heap_hard_limit) { - gc_heap::regions_range = 2 * gc_heap::heap_hard_limit; + if (gc_heap::heap_hard_limit_oh[soh]) + { + gc_heap::regions_range = gc_heap::heap_hard_limit; + } + else + { + // We use this calculation because it's close to what we used for segments. + gc_heap::regions_range = ((gc_heap::use_large_pages_p) ? (2 * gc_heap::heap_hard_limit) + : (5 * gc_heap::heap_hard_limit)); + } } else { + // If no hard_limit is configured the reservation size is max of 256gb or 2x physical limit gc_heap::regions_range = max(((size_t)256 * 1024 * 1024 * 1024), (size_t)(2 * gc_heap::total_physical_mem)); } gc_heap::regions_range = align_on_page(gc_heap::regions_range); @@ -44769,16 +45190,13 @@ HRESULT GCHeap::Initialize() #endif //HOST_64BIT GCConfig::SetGCLargePages(gc_heap::use_large_pages_p); - GCConfig::SetGCHeapHardLimit(static_cast(gc_heap::heap_hard_limit)); - GCConfig::SetGCHeapHardLimitSOH(static_cast(gc_heap::heap_hard_limit_oh[soh])); - GCConfig::SetGCHeapHardLimitLOH(static_cast(gc_heap::heap_hard_limit_oh[loh])); - GCConfig::SetGCHeapHardLimitPOH(static_cast(gc_heap::heap_hard_limit_oh[poh])); uint32_t nhp = 1; uint32_t nhp_from_config = 0; -#ifdef MULTIPLE_HEAPS - +#ifndef MULTIPLE_HEAPS + GCConfig::SetServerGC(false); +#else //!MULTIPLE_HEAPS GCConfig::SetServerGC(true); AffinitySet config_affinity_set; GCConfigStringHolder cpu_index_ranges_holder(GCConfig::GetGCHeapAffinitizeRanges()); @@ -44808,7 +45226,9 @@ HRESULT GCHeap::Initialize() nhp_from_config = static_cast(GCConfig::GetHeapCount()); - g_num_active_processors = GCToEEInterface::GetCurrentProcessCpuCount(); + // The CPU count may be overriden by the user. Ensure that we create no more than g_num_processors + // heaps as that is the number of slots we have allocated for handle tables. + g_num_active_processors = min (GCToEEInterface::GetCurrentProcessCpuCount(), g_num_processors); if (nhp_from_config) { @@ -44833,7 +45253,7 @@ HRESULT GCHeap::Initialize() nhp = min(nhp, num_affinitized_processors); } } -#endif //MULTIPLE_HEAPS +#endif //!MULTIPLE_HEAPS size_t seg_size = 0; size_t large_seg_size = 0; @@ -44907,7 +45327,37 @@ HRESULT GCHeap::Initialize() #ifdef USE_REGIONS gc_heap::enable_special_regions_p = (bool)GCConfig::GetGCEnableSpecialRegions(); size_t gc_region_size = (size_t)GCConfig::GetGCRegionSize(); - if (!power_of_two_p(gc_region_size) || ((gc_region_size * nhp * 19) > gc_heap::regions_range)) + + // Constraining the size of region size to be < 2 GB. + if (gc_region_size >= MAX_REGION_SIZE) + { + return CLR_E_GC_BAD_REGION_SIZE; + } + + // Adjust GCRegionSize based on how large each heap would be, for smaller heaps we would + // like to keep Region sizes small. We choose between 4, 2 and 1mb based on the calculations + // below (unless its configured explictly) such that there are at least 2 regions available + // except for the smallest case. Now the lowest limit possible is 4mb. + if (gc_region_size == 0) + { + // We have a minimum amount of basic regions we have to fit per heap, and we'd like to have the initial + // regions only take up half of the space. + size_t max_region_size = gc_heap::regions_range / 2 / nhp / min_regions_per_heap; + if (max_region_size >= (4 * 1024 * 1024)) + { + gc_region_size = 4 * 1024 * 1024; + } + else if (max_region_size >= (2 * 1024 * 1024)) + { + gc_region_size = 2 * 1024 * 1024; + } + else + { + gc_region_size = 1 * 1024 * 1024; + } + } + + if (!power_of_two_p(gc_region_size) || ((gc_region_size * nhp * min_regions_per_heap) > gc_heap::regions_range)) { return E_OUTOFMEMORY; } @@ -44917,12 +45367,18 @@ HRESULT GCHeap::Initialize() #endif //USE_REGIONS #ifdef MULTIPLE_HEAPS + assert (nhp <= g_num_processors); gc_heap::n_heaps = nhp; hr = gc_heap::initialize_gc (seg_size, large_seg_size, pin_seg_size, nhp); #else hr = gc_heap::initialize_gc (seg_size, large_seg_size, pin_seg_size); #endif //MULTIPLE_HEAPS + GCConfig::SetGCHeapHardLimit(static_cast(gc_heap::heap_hard_limit)); + GCConfig::SetGCHeapHardLimitSOH(static_cast(gc_heap::heap_hard_limit_oh[soh])); + GCConfig::SetGCHeapHardLimitLOH(static_cast(gc_heap::heap_hard_limit_oh[loh])); + GCConfig::SetGCHeapHardLimitPOH(static_cast(gc_heap::heap_hard_limit_oh[poh])); + if (hr != S_OK) return hr; @@ -44951,7 +45407,7 @@ HRESULT GCHeap::Initialize() int available_mem_th = 10; if (gc_heap::total_physical_mem >= ((uint64_t)80 * 1024 * 1024 * 1024)) { - int adjusted_available_mem_th = 3 + (int)((float)47 / (float)(GCToOSInterface::GetTotalProcessorCount())); + int adjusted_available_mem_th = 3 + (int)((float)47 / (float)g_num_processors); available_mem_th = min (available_mem_th, adjusted_available_mem_th); } @@ -44976,6 +45432,7 @@ HRESULT GCHeap::Initialize() if (!WaitForGCEvent->CreateManualEventNoThrow(TRUE)) { + GCToEEInterface::LogErrorToHost("Creation of WaitForGCEvent failed"); return E_FAIL; } @@ -45057,9 +45514,15 @@ HRESULT GCHeap::Initialize() int hb_info_size_per_node = hb_info_size_per_proc * procs_per_numa_node; uint8_t* numa_mem = (uint8_t*)GCToOSInterface::VirtualReserve (hb_info_size_per_node, 0, 0, numa_node_index); if (!numa_mem) + { + GCToEEInterface::LogErrorToHost("Reservation of numa_mem failed"); return E_FAIL; + } if (!GCToOSInterface::VirtualCommit (numa_mem, hb_info_size_per_node, numa_node_index)) + { + GCToEEInterface::LogErrorToHost("Commit of numa_mem failed"); return E_FAIL; + } heap_balance_info_proc* hb_info_procs = (heap_balance_info_proc*)numa_mem; hb_info_numa_nodes[numa_node_index].hb_info_procs = hb_info_procs; @@ -45122,15 +45585,10 @@ HRESULT GCHeap::Initialize() // GC callback functions bool GCHeap::IsPromoted(Object* object) { -#ifdef _DEBUG - if (object) - { - ((CObjectHeader*)object)->Validate(); - } -#endif //_DEBUG - uint8_t* o = (uint8_t*)object; + bool is_marked; + if (gc_heap::settings.condemned_generation == max_generation) { #ifdef MULTIPLE_HEAPS @@ -45142,27 +45600,35 @@ bool GCHeap::IsPromoted(Object* object) #ifdef BACKGROUND_GC if (gc_heap::settings.concurrent) { - bool is_marked = (!((o < hp->background_saved_highest_address) && (o >= hp->background_saved_lowest_address))|| + is_marked = (!((o < hp->background_saved_highest_address) && (o >= hp->background_saved_lowest_address))|| hp->background_marked (o)); - return is_marked; } else #endif //BACKGROUND_GC { - return (!((o < hp->highest_address) && (o >= hp->lowest_address)) - || hp->is_mark_set (o)); + is_marked = (!((o < hp->highest_address) && (o >= hp->lowest_address)) + || hp->is_mark_set (o)); } } else { #ifdef USE_REGIONS - return (gc_heap::is_in_gc_range (o) ? (gc_heap::is_in_condemned_gc (o) ? gc_heap::is_mark_set (o) : true) : true); + is_marked = (gc_heap::is_in_gc_range (o) ? (gc_heap::is_in_condemned_gc (o) ? gc_heap::is_mark_set (o) : true) : true); #else gc_heap* hp = gc_heap::heap_of (o); - return (!((o < hp->gc_high) && (o >= hp->gc_low)) - || hp->is_mark_set (o)); + is_marked = (!((o < hp->gc_high) && (o >= hp->gc_low)) + || hp->is_mark_set (o)); #endif //USE_REGIONS } + +#ifdef _DEBUG + if (o) + { + ((CObjectHeader*)o)->Validate(TRUE, TRUE, is_marked); + } +#endif //_DEBUG + + return is_marked; } size_t GCHeap::GetPromotedBytes(int heap_index) @@ -45187,14 +45653,17 @@ size_t GCHeap::GetPromotedBytes(int heap_index) void GCHeap::SetYieldProcessorScalingFactor (float scalingFactor) { - assert (yp_spin_count_unit != 0); - uint32_t saved_yp_spin_count_unit = yp_spin_count_unit; - yp_spin_count_unit = (uint32_t)((float)original_spin_count_unit * scalingFactor / (float)9); - - // It's very suspicious if it becomes 0 and also, we don't want to spin too much. - if ((yp_spin_count_unit == 0) || (yp_spin_count_unit > 32768)) + if (!gc_heap::spin_count_unit_config_p) { - yp_spin_count_unit = saved_yp_spin_count_unit; + assert (yp_spin_count_unit != 0); + uint32_t saved_yp_spin_count_unit = yp_spin_count_unit; + yp_spin_count_unit = (uint32_t)((float)original_spin_count_unit * scalingFactor / (float)9); + + // It's very suspicious if it becomes 0 and also, we don't want to spin too much. + if ((yp_spin_count_unit == 0) || (yp_spin_count_unit > MAX_YP_SPIN_COUNT_UNIT)) + { + yp_spin_count_unit = saved_yp_spin_count_unit; + } } } diff --git a/src/coreclr/gc/gccommon.cpp b/src/coreclr/gc/gccommon.cpp index 27eb8f935c3372..0d29e32c7b494a 100644 --- a/src/coreclr/gc/gccommon.cpp +++ b/src/coreclr/gc/gccommon.cpp @@ -18,6 +18,7 @@ IGCHandleManager* g_theGCHandleManager; #ifdef BUILD_AS_STANDALONE IGCToCLR* g_theGCToCLR; +VersionInfo g_runtimeSupportedVersion; #endif // BUILD_AS_STANDALONE #ifdef GC_CONFIG_DRIVEN diff --git a/src/coreclr/gc/gcconfig.cpp b/src/coreclr/gc/gcconfig.cpp index abf4b28e4f5d76..9957e43dc5c5db 100644 --- a/src/coreclr/gc/gcconfig.cpp +++ b/src/coreclr/gc/gcconfig.cpp @@ -7,13 +7,18 @@ #define BOOL_CONFIG(name, unused_private_key, unused_public_key, default, unused_doc) \ bool GCConfig::Get##name() { return s_##name; } \ + bool GCConfig::Get##name(bool defaultValue) \ + { \ + return s_##name##Provided ? s_##name : defaultValue; \ + } \ void GCConfig::Set##name(bool value) { s_Updated##name = value; } \ bool GCConfig::s_##name = default; \ + bool GCConfig::s_##name##Provided = false; \ bool GCConfig::s_Updated##name = default; #define INT_CONFIG(name, unused_private_key, unused_public_key, default, unused_doc) \ int64_t GCConfig::Get##name() { return s_##name; } \ - void GCConfig::Set##name(int64_t value) { s_Updated##name = value; } \ + void GCConfig::Set##name(int64_t value) { s_Updated##name = value; } \ int64_t GCConfig::s_##name = default; \ int64_t GCConfig::s_Updated##name = default; @@ -36,7 +41,7 @@ GC_CONFIGURATION_KEYS void GCConfig::EnumerateConfigurationValues(void* context, ConfigurationValueFunc configurationValueFunc) { -#define INT_CONFIG(name, unused_private_key, public_key, default, unused_doc) \ +#define INT_CONFIG(name, unused_private_key, public_key, unused_default, unused_doc) \ configurationValueFunc(context, (void*)(#name), (void*)(public_key), GCConfigurationType::Int64, static_cast(s_Updated##name)); #define STRING_CONFIG(name, private_key, public_key, unused_doc) \ @@ -47,7 +52,7 @@ void GCConfig::EnumerateConfigurationValues(void* context, ConfigurationValueFun configurationValueFunc(context, (void*)(#name), (void*)(public_key), GCConfigurationType::StringUtf8, reinterpret_cast(resultStr)); \ } -#define BOOL_CONFIG(name, unused_private_key, public_key, default, unused_doc) \ +#define BOOL_CONFIG(name, unused_private_key, public_key, unused_default, unused_doc) \ configurationValueFunc(context, (void*)(#name), (void*)(public_key), GCConfigurationType::Boolean, static_cast(s_Updated##name)); GC_CONFIGURATION_KEYS @@ -59,10 +64,10 @@ GC_CONFIGURATION_KEYS void GCConfig::Initialize() { -#define BOOL_CONFIG(name, private_key, public_key, default, unused_doc) \ - GCToEEInterface::GetBooleanConfigValue(private_key, public_key, &s_##name); +#define BOOL_CONFIG(name, private_key, public_key, unused_default, unused_doc) \ + s_##name##Provided = GCToEEInterface::GetBooleanConfigValue(private_key, public_key, &s_##name); -#define INT_CONFIG(name, private_key, public_key, default, unused_doc) \ +#define INT_CONFIG(name, private_key, public_key, unused_default, unused_doc) \ GCToEEInterface::GetIntConfigValue(private_key, public_key, &s_##name); #define STRING_CONFIG(unused_name, unused_private_key, unused_public_key, unused_doc) diff --git a/src/coreclr/gc/gcconfig.h b/src/coreclr/gc/gcconfig.h index f1111a99eae5f6..c20febbb79dc61 100644 --- a/src/coreclr/gc/gcconfig.h +++ b/src/coreclr/gc/gcconfig.h @@ -104,7 +104,7 @@ class GCConfigStringHolder INT_CONFIG (GCHeapHardLimitPercent, "GCHeapHardLimitPercent", "System.GC.HeapHardLimitPercent", 0, "Specifies the GC heap usage as a percentage of the total memory") \ INT_CONFIG (GCTotalPhysicalMemory, "GCTotalPhysicalMemory", NULL, 0, "Specifies what the GC should consider to be total physical memory") \ INT_CONFIG (GCRegionRange, "GCRegionRange", NULL, 0, "Specifies the range for the GC heap") \ - INT_CONFIG (GCRegionSize, "GCRegionSize", NULL, 4194304, "Specifies the size for a basic GC region") \ + INT_CONFIG (GCRegionSize, "GCRegionSize", NULL, 0, "Specifies the size for a basic GC region") \ INT_CONFIG (GCEnableSpecialRegions, "GCEnableSpecialRegions", NULL, 0, "Specifies to enable special handling some regions like SIP") \ STRING_CONFIG(LogFile, "GCLogFile", NULL, "Specifies the name of the GC log file") \ STRING_CONFIG(ConfigLogFile, "GCConfigLogFile", NULL, "Specifies the name of the GC config log file") \ @@ -135,15 +135,20 @@ class GCConfigStringHolder INT_CONFIG (GCHeapHardLimitPOHPercent, "GCHeapHardLimitPOHPercent", "System.GC.HeapHardLimitPOHPercent", 0, "Specifies the GC heap POH usage as a percentage of the total memory") \ INT_CONFIG (GCEnabledInstructionSets, "GCEnabledInstructionSets", NULL, -1, "Specifies whether GC can use AVX2 or AVX512F - 0 for neither, 1 for AVX2, 3 for AVX512F")\ INT_CONFIG (GCConserveMem, "GCConserveMemory", "System.GC.ConserveMemory", 0, "Specifies how hard GC should try to conserve memory - values 0-9") \ - INT_CONFIG (GCWriteBarrier, "GCWriteBarrier", NULL, 0, "Specifies whether GC should use more precise but slower write barrier") + INT_CONFIG (GCWriteBarrier, "GCWriteBarrier", NULL, 0, "Specifies whether GC should use more precise but slower write barrier") \ + STRING_CONFIG(GCName, "GCName", "System.GC.Name", "Specifies the path of the standalone GC implementation.") \ + INT_CONFIG (GCSpinCountUnit, "GCSpinCountUnit", NULL, 0, "Specifies the spin count unit used by the GC.") + // This class is responsible for retreiving configuration information // for how the GC should operate. class GCConfig { #define BOOL_CONFIG(name, unused_private_key, unused_public_key, unused_default, unused_doc) \ public: static bool Get##name(); \ + public: static bool Get##name(bool defaultValue); \ public: static void Set##name(bool value); \ private: static bool s_##name; \ + private: static bool s_##name##Provided; \ private: static bool s_Updated##name; #define INT_CONFIG(name, unused_private_key, unused_public_key, unused_default, unused_doc) \ diff --git a/src/coreclr/gc/gcenv.ee.standalone.inl b/src/coreclr/gc/gcenv.ee.standalone.inl index 83b5406e76eed4..a1478a116e7b72 100644 --- a/src/coreclr/gc/gcenv.ee.standalone.inl +++ b/src/coreclr/gc/gcenv.ee.standalone.inl @@ -11,6 +11,9 @@ // will be forwarded to this interface instance. extern IGCToCLR* g_theGCToCLR; +// GC version that the current runtime supports +extern VersionInfo g_runtimeSupportedVersion; + struct StressLogMsg; // When we are building the GC in a standalone environment, we @@ -311,4 +314,12 @@ inline void GCToEEInterface::DiagAddNewRegion(int generation, uint8_t* rangeStar g_theGCToCLR->DiagAddNewRegion(generation, rangeStart, rangeEnd, rangeEndReserved); } +inline void GCToEEInterface::LogErrorToHost(const char *message) +{ + if (g_runtimeSupportedVersion.MajorVersion >= 1) + { + g_theGCToCLR->LogErrorToHost(message); + } +} + #endif // __GCTOENV_EE_STANDALONE_INL__ diff --git a/src/coreclr/gc/gcinterface.ee.h b/src/coreclr/gc/gcinterface.ee.h index e2019c8a1adbe6..5a67bf4e0dd7e7 100644 --- a/src/coreclr/gc/gcinterface.ee.h +++ b/src/coreclr/gc/gcinterface.ee.h @@ -446,6 +446,10 @@ class IGCToCLR { virtual void DiagAddNewRegion(int generation, uint8_t* rangeStart, uint8_t* rangeEnd, uint8_t* rangeEndReserved) = 0; + + // The following method is available only with EE_INTERFACE_MAJOR_VERSION >= 1 + virtual + void LogErrorToHost(const char *message) = 0; }; #endif // _GCINTERFACE_EE_H_ diff --git a/src/coreclr/gc/gcinterface.h b/src/coreclr/gc/gcinterface.h index 023f257d10fa58..365a619bbb1d8d 100644 --- a/src/coreclr/gc/gcinterface.h +++ b/src/coreclr/gc/gcinterface.h @@ -4,15 +4,19 @@ #ifndef _GC_INTERFACE_H_ #define _GC_INTERFACE_H_ -// The major version of the GC/EE interface. Breaking changes to this interface +// The major version of the IGCHeap interface. Breaking changes to this interface // require bumps in the major version number. #define GC_INTERFACE_MAJOR_VERSION 5 -// The minor version of the GC/EE interface. Non-breaking changes are required +// The minor version of the IGCHeap interface. Non-breaking changes are required // to bump the minor version number. GCs and EEs with minor version number -// mismatches can still interopate correctly, with some care. +// mismatches can still interoperate correctly, with some care. #define GC_INTERFACE_MINOR_VERSION 1 +// The major version of the IGCToCLR interface. Breaking changes to this interface +// require bumps in the major version number. +#define EE_INTERFACE_MAJOR_VERSION 1 + struct ScanContext; struct gc_alloc_context; class CrawlFrame; diff --git a/src/coreclr/gc/gcload.cpp b/src/coreclr/gc/gcload.cpp index 5a886d36ad3d9e..cd3a0b43a6d82d 100644 --- a/src/coreclr/gc/gcload.cpp +++ b/src/coreclr/gc/gcload.cpp @@ -43,8 +43,15 @@ extern void PopulateHandleTableDacVars(GcDacVars* dacVars); GC_EXPORT void -GC_VersionInfo(/* Out */ VersionInfo* info) +GC_VersionInfo(/* InOut */ VersionInfo* info) { +#ifdef BUILD_AS_STANDALONE + // On entry, the info argument contains the interface version that the runtime supports. + // It is later used to enable backwards compatibility between the GC and the runtime. + // For example, GC would only call functions on g_theGCToCLR interface that the runtime + // supports. + g_runtimeSupportedVersion = *info; +#endif info->MajorVersion = GC_INTERFACE_MAJOR_VERSION; info->MinorVersion = GC_INTERFACE_MINOR_VERSION; info->BuildVersion = 0; @@ -74,13 +81,14 @@ GC_Initialize( assert(clrToGC == nullptr); #endif +#ifndef FEATURE_NATIVEAOT // GCConfig and GCToOSInterface are initialized in PalInit // Initialize GCConfig before anything else - initialization of our // various components may want to query the current configuration. GCConfig::Initialize(); -#ifndef FEATURE_NATIVEAOT // GCToOSInterface is initialized directly if (!GCToOSInterface::Initialize()) { + GCToEEInterface::LogErrorToHost("Failed to initialize GCToOSInterface"); return E_FAIL; } #endif @@ -92,7 +100,7 @@ GC_Initialize( } #ifdef FEATURE_SVR_GC - if (GCConfig::GetServerGC()) + if (GCConfig::GetServerGC() && GCToEEInterface::GetCurrentProcessCpuCount() > 1) { #ifdef WRITE_BARRIER_CHECK g_GCShadow = 0; diff --git a/src/coreclr/gc/gcpriv.h b/src/coreclr/gc/gcpriv.h index 75b2388ffd87b4..ee0ea0dfa4a541 100644 --- a/src/coreclr/gc/gcpriv.h +++ b/src/coreclr/gc/gcpriv.h @@ -62,6 +62,7 @@ inline void FATAL_GC_ERROR() // + creates some ro segs // We can add more mechanisms here. //#define STRESS_REGIONS +#define MARK_PHASE_PREFETCH #endif //USE_REGIONS // FEATURE_STRUCTALIGN was added by Midori. In CLR we are not interested @@ -190,6 +191,10 @@ void GCLogConfig (const char *fmt, ... ); #define MAX_NUM_BUCKETS (MAX_INDEX_POWER2 - MIN_INDEX_POWER2 + 1) +#ifdef USE_REGIONS +#define MAX_REGION_SIZE 0x80000000 +#endif // USE_REGIONS + #define MAX_NUM_FREE_SPACES 200 #define MIN_NUM_FREE_SPACES 5 @@ -429,6 +434,12 @@ const int recorded_committed_bucket_counts = recorded_committed_bookkeeping_buck gc_oh_num gen_to_oh (int gen); +enum memory_type +{ + memory_type_reserved = 0, + memory_type_committed = 1 +}; + #if defined(TRACE_GC) && defined(BACKGROUND_GC) static const char * const str_bgc_state[] = { @@ -1218,9 +1229,11 @@ enum bookkeeping_element class mark_queue_t { +#ifdef MARK_PHASE_PREFETCH static const size_t slot_count = 16; uint8_t* slot_table[slot_count]; size_t curr_slot_index; +#endif //MARK_PHASE_PREFETCH public: mark_queue_t(); @@ -1370,6 +1383,8 @@ class gc_heap PER_HEAP bool sufficient_space_regions (size_t end_space, size_t end_space_required); PER_HEAP + bool sufficient_space_regions_for_allocation (size_t end_space, size_t end_space_required); + PER_HEAP bool initial_make_soh_regions (gc_heap* hp); PER_HEAP bool initial_make_uoh_regions (int gen, gc_heap* hp); @@ -1432,7 +1447,7 @@ class gc_heap PER_HEAP void get_gen0_end_plan_space(); PER_HEAP - size_t get_gen0_end_space(); + size_t get_gen0_end_space(memory_type type); PER_HEAP bool decide_on_compaction_space(); PER_HEAP @@ -2019,7 +2034,7 @@ class gc_heap PER_HEAP_ISOLATED void init_heap_segment (heap_segment* seg, gc_heap* hp #ifdef USE_REGIONS - , uint8_t* start, size_t size, int gen_num + , uint8_t* start, size_t size, int gen_num, bool existing_region_p=false #endif //USE_REGIONS ); PER_HEAP @@ -2065,7 +2080,11 @@ class gc_heap PER_HEAP size_t decommit_heap_segment_pages_worker (heap_segment* seg, uint8_t *new_committed); PER_HEAP_ISOLATED - bool decommit_step (); + bool decommit_step (uint64_t step_milliseconds); +#ifdef USE_REGIONS + PER_HEAP_ISOLATED + size_t decommit_region (heap_segment* region, int bucket, int h_number); +#endif //USE_REGIONS PER_HEAP void decommit_heap_segment (heap_segment* seg); PER_HEAP_ISOLATED @@ -2525,7 +2544,7 @@ class gc_heap uint8_t** range_beg, uint8_t** range_end); PER_HEAP - void bgc_verify_mark_array_cleared (heap_segment* seg); + void bgc_verify_mark_array_cleared (heap_segment* seg, bool always_verify_p = false); PER_HEAP void verify_mark_array_cleared(); PER_HEAP @@ -3414,7 +3433,7 @@ class gc_heap #ifdef USE_REGIONS PER_HEAP - uint8_t** get_region_mark_list (uint8_t* start, uint8_t* end, uint8_t*** mark_list_end); + uint8_t** get_region_mark_list (BOOL& use_mark_list, uint8_t* start, uint8_t* end, uint8_t*** mark_list_end); #endif //USE_REGIONS #ifdef BACKGROUND_GC @@ -3672,6 +3691,10 @@ class gc_heap // sweep. size_t end_gen0_region_space; + PER_HEAP + // After GC we calculate this + size_t end_gen0_region_committed_space; + // These are updated as we plan and will be used to make compaction // decision. PER_HEAP @@ -3852,8 +3875,10 @@ class gc_heap PER_HEAP_ISOLATED VOLATILE(bool) full_gc_approach_event_set; - PER_HEAP_ISOLATED +#ifdef USE_REGIONS + PER_HEAP bool special_sweep_p; +#endif #ifdef BACKGROUND_GC PER_HEAP_ISOLATED @@ -4217,7 +4242,7 @@ class gc_heap PER_HEAP size_t loh_pinned_queue_length; - PER_HEAP_ISOLATED + PER_HEAP int loh_pinned_queue_decay; PER_HEAP @@ -4834,6 +4859,9 @@ class gc_heap PER_HEAP_ISOLATED int conserve_mem_setting; + PER_HEAP_ISOLATED + bool spin_count_unit_config_p; + PER_HEAP BOOL gen0_bricks_cleared; PER_HEAP @@ -5711,6 +5739,7 @@ class heap_segment uint8_t* saved_allocated; uint8_t* saved_bg_allocated; #ifdef USE_REGIONS + size_t survived; // These generation numbers are initialized to -1. // For plan_gen_num: // for all regions in condemned generations it needs @@ -5725,7 +5754,6 @@ class heap_segment // swept_in_plan_p can be folded into gen_num. bool swept_in_plan_p; int plan_gen_num; - int survived; int old_card_survived; int pinned_survived; // at the end of each GC, we increase each region in the region free list @@ -5807,6 +5835,8 @@ class heap_segment #define region_alloc_free_bit (1 << (sizeof (uint32_t) * 8 - 1)) +const int min_regions_per_heap = ((ephemeral_generation_count + 1) + ((total_generation_count - uoh_start_generation) * LARGE_REGION_FACTOR)); + enum allocate_direction { allocate_forward = 1, @@ -6162,7 +6192,7 @@ int& heap_segment_age_in_free (heap_segment* inst) return inst->age_in_free; } inline -int& heap_segment_survived (heap_segment* inst) +size_t& heap_segment_survived (heap_segment* inst) { return inst->survived; } diff --git a/src/coreclr/gc/sample/gcenv.ee.cpp b/src/coreclr/gc/sample/gcenv.ee.cpp index 84274f2341c3e4..ac6d80bf034677 100644 --- a/src/coreclr/gc/sample/gcenv.ee.cpp +++ b/src/coreclr/gc/sample/gcenv.ee.cpp @@ -358,3 +358,7 @@ uint32_t GCToEEInterface::GetCurrentProcessCpuCount() void GCToEEInterface::DiagAddNewRegion(int generation, uint8_t* rangeStart, uint8_t* rangeEnd, uint8_t* rangeEndReserved) { } + +void GCToEEInterface::LogErrorToHost(const char *message) +{ +} diff --git a/src/coreclr/gc/unix/events.cpp b/src/coreclr/gc/unix/events.cpp index 7cc55680aaf2f5..06dc1d9b172018 100644 --- a/src/coreclr/gc/unix/events.cpp +++ b/src/coreclr/gc/unix/events.cpp @@ -131,6 +131,7 @@ class GCEvent::Impl if (milliseconds != INFINITE) { uint64_t nanoseconds = (uint64_t)milliseconds * tccMilliSecondsToNanoSeconds; + NanosecondsToTimeSpec(nanoseconds, &endTime); endMachTime = clock_gettime_nsec_np(CLOCK_UPTIME_RAW) + nanoseconds; } #elif HAVE_PTHREAD_CONDATTR_SETCLOCK @@ -219,10 +220,9 @@ class GCEvent::Impl { pthread_mutex_lock(&m_mutex); m_state = true; - pthread_mutex_unlock(&m_mutex); - // Unblock all threads waiting for the condition variable pthread_cond_broadcast(&m_condition); + pthread_mutex_unlock(&m_mutex); } void Reset() diff --git a/src/coreclr/gc/unix/gcenv.unix.cpp b/src/coreclr/gc/unix/gcenv.unix.cpp index 85d6a001b03822..68e76302031260 100644 --- a/src/coreclr/gc/unix/gcenv.unix.cpp +++ b/src/coreclr/gc/unix/gcenv.unix.cpp @@ -968,10 +968,11 @@ static size_t GetLogicalProcessorCacheSizeFromOS() int64_t cacheSizeFromSysctl = 0; size_t sz = sizeof(cacheSizeFromSysctl); const bool success = false - // macOS-arm64: Since macOS 12.0, Apple added ".perflevelX." to determinate cache sizes for efficiency + // macOS: Since macOS 12.0, Apple added ".perflevelX." to determinate cache sizes for efficiency // and performance cores separately. "perflevel0" stands for "performance" + || sysctlbyname("hw.perflevel0.l3cachesize", &cacheSizeFromSysctl, &sz, nullptr, 0) == 0 || sysctlbyname("hw.perflevel0.l2cachesize", &cacheSizeFromSysctl, &sz, nullptr, 0) == 0 - // macOS-arm64: these report cache sizes for efficiency cores only: + // macOS: these report cache sizes for efficiency cores only: || sysctlbyname("hw.l3cachesize", &cacheSizeFromSysctl, &sz, nullptr, 0) == 0 || sysctlbyname("hw.l2cachesize", &cacheSizeFromSysctl, &sz, nullptr, 0) == 0 || sysctlbyname("hw.l1dcachesize", &cacheSizeFromSysctl, &sz, nullptr, 0) == 0; diff --git a/src/coreclr/gc/windows/gcenv.windows.cpp b/src/coreclr/gc/windows/gcenv.windows.cpp index d8675e5e1060bb..57203a3ad19050 100644 --- a/src/coreclr/gc/windows/gcenv.windows.cpp +++ b/src/coreclr/gc/windows/gcenv.windows.cpp @@ -61,7 +61,6 @@ struct CPU_Group_Info }; static bool g_fEnableGCCPUGroups; -static bool g_fHadSingleProcessorAtStartup; static DWORD g_nGroups; static DWORD g_nProcessors; static CPU_Group_Info *g_CPUGroupInfoArray; @@ -220,26 +219,26 @@ void InitCPUGroupInfo() g_fEnableGCCPUGroups = false; #if (defined(TARGET_AMD64) || defined(TARGET_ARM64)) - if (!GCConfig::GetGCCpuGroup()) + USHORT groupCount = 0; + + // On Windows 11+ and Windows Server 2022+, a process is no longer restricted to a single processor group by default. + // If more than one processor group is available to the process (a non-affinitized process on Windows 11+), + // default to using multiple processor groups; otherwise, default to using a single processor group. This default + // behavior may be overridden by the configuration value below. + if (GetProcessGroupAffinity(GetCurrentProcess(), &groupCount, NULL) || GetLastError() != ERROR_INSUFFICIENT_BUFFER) + groupCount = 1; + + bool enableGCCPUGroups = GCConfig::GetGCCpuGroup(/* defaultValue */ groupCount > 1); + + if (!enableGCCPUGroups) return; if (!InitCPUGroupInfoArray()) return; - // only enable CPU groups if more than one group exists + // Enable processor groups only if more than one group exists g_fEnableGCCPUGroups = g_nGroups > 1; #endif // TARGET_AMD64 || TARGET_ARM64 - - // Determine if the process is affinitized to a single processor (or if the system has a single processor) - DWORD_PTR processAffinityMask, systemAffinityMask; - if (::GetProcessAffinityMask(::GetCurrentProcess(), &processAffinityMask, &systemAffinityMask)) - { - if (processAffinityMask != 0 && // only one CPU group is involved - (processAffinityMask & (processAffinityMask - 1)) == 0) // only one bit is set - { - g_fHadSingleProcessorAtStartup = true; - } - } } void GetProcessMemoryLoad(LPMEMORYSTATUSEX pMSEX) @@ -475,17 +474,12 @@ size_t GetLogicalProcessorCacheSizeFromOS() return cache_size; } -bool CanEnableGCCPUGroups() -{ - return g_fEnableGCCPUGroups; -} - // Get the CPU group for the specified processor void GetGroupForProcessor(uint16_t processor_number, uint16_t* group_number, uint16_t* group_processor_number) { assert(g_fEnableGCCPUGroups); -#if !defined(FEATURE_NATIVEAOT) && (defined(TARGET_AMD64) || defined(TARGET_ARM64)) +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) WORD bTemp = 0; WORD bDiff = processor_number - bTemp; @@ -1190,7 +1184,7 @@ bool GCToOSInterface::GetProcessorForHeap(uint16_t heap_number, uint16_t* proc_n // Locate heap_number-th available processor uint16_t procIndex = 0; size_t cnt = heap_number; - for (uint16_t i = 0; i < GCToOSInterface::GetTotalProcessorCount(); i++) + for (uint16_t i = 0; i < MAX_SUPPORTED_CPUS; i++) { if (g_processAffinitySet.Contains(i)) { diff --git a/src/coreclr/hosts/corerun/corerun.cpp b/src/coreclr/hosts/corerun/corerun.cpp index 60b5c5522d1857..49f549de5eb663 100644 --- a/src/coreclr/hosts/corerun/corerun.cpp +++ b/src/coreclr/hosts/corerun/corerun.cpp @@ -205,6 +205,11 @@ class logger_t final static void* CurrentClrInstance; static unsigned int CurrentAppDomainId; +static void log_error_info(const char* line) +{ + std::fprintf(stderr, "%s\n", line); +} + static int run(const configuration& config) { platform_specific_actions actions; @@ -282,6 +287,7 @@ static int run(const configuration& config) // Get CoreCLR exports coreclr_initialize_ptr coreclr_init_func = nullptr; coreclr_execute_assembly_ptr coreclr_execute_func = nullptr; + coreclr_set_error_writer_ptr coreclr_set_error_writer_func = nullptr; coreclr_shutdown_2_ptr coreclr_shutdown2_func = nullptr; if (!try_get_export(coreclr_mod, "coreclr_initialize", (void**)&coreclr_init_func) || !try_get_export(coreclr_mod, "coreclr_execute_assembly", (void**)&coreclr_execute_func) @@ -290,6 +296,9 @@ static int run(const configuration& config) return -1; } + // The coreclr_set_error_writer is optional + (void)try_get_export(coreclr_mod, "coreclr_set_error_writer", (void**)&coreclr_set_error_writer_func); + // Construct CoreCLR properties. pal::string_utf8_t tpa_list_utf8 = pal::convert_to_utf8(tpa_list.c_str()); pal::string_utf8_t app_path_utf8 = pal::convert_to_utf8(app_path.c_str()); @@ -344,6 +353,11 @@ static int run(const configuration& config) propertyCount, propertyKeys.data(), propertyValues.data(), entry_assembly_utf8.c_str(), config.entry_assembly_argc, argv_utf8.get() }; + if (coreclr_set_error_writer_func != nullptr) + { + coreclr_set_error_writer_func(log_error_info); + } + int result; result = coreclr_init_func( exe_path_utf8.c_str(), @@ -361,6 +375,11 @@ static int run(const configuration& config) return -1; } + if (coreclr_set_error_writer_func != nullptr) + { + coreclr_set_error_writer_func(nullptr); + } + int exit_code; { actions.before_execute_assembly(config.entry_assembly_fullpath); diff --git a/src/coreclr/hosts/inc/coreclrhost.h b/src/coreclr/hosts/inc/coreclrhost.h index 46a3119d628de0..01eeac600a2240 100644 --- a/src/coreclr/hosts/inc/coreclrhost.h +++ b/src/coreclr/hosts/inc/coreclrhost.h @@ -47,6 +47,24 @@ CORECLR_HOSTING_API(coreclr_initialize, void** hostHandle, unsigned int* domainId); +// +// Type of the callback function that can be set by the coreclr_set_error_writer +// +typedef void (*coreclr_error_writer_callback_fn) (const char *message); + +// +// Set callback for writing error logging +// +// Parameters: +// errorWriter - callback that will be called for each line of the error info +// - passing in NULL removes a callback that was previously set +// +// Returns: +// S_OK +// +CORECLR_HOSTING_API(coreclr_set_error_writer, + coreclr_error_writer_callback_fn errorWriter); + // // Shutdown CoreCLR. It unloads the app domain and stops the CoreCLR host. // diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index ce7d1836a363e9..5c4098dd07b5b0 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -490,7 +490,7 @@ RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_ProfAPI_ValidateNGENInstrumentation, W("Pro #ifdef FEATURE_PERFMAP RETAIL_CONFIG_DWORD_INFO(EXTERNAL_PerfMapEnabled, W("PerfMapEnabled"), 0, "This flag is used on Linux to enable writing /tmp/perf-$pid.map. It is disabled by default") -RETAIL_CONFIG_STRING_INFO(EXTERNAL_PerfMapJitDumpPath, W("PerfMapJitDumpPath"), "Specifies a path to write the perf jitdump file. Defaults to GetTempPathA()") +RETAIL_CONFIG_STRING_INFO(EXTERNAL_PerfMapJitDumpPath, W("PerfMapJitDumpPath"), "Specifies a path to write the perf jitdump file. Defaults to /tmp") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_PerfMapIgnoreSignal, W("PerfMapIgnoreSignal"), 0, "When perf map is enabled, this option will configure the specified signal to be accepted and ignored as a marker in the perf logs. It is disabled by default") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_PerfMapShowOptimizationTiers, W("PerfMapShowOptimizationTiers"), 1, "Shows optimization tiers in the perf map for methods, as part of the symbol name. Useful for seeing separate stack frames for different optimization tiers of each method.") RETAIL_CONFIG_STRING_INFO(EXTERNAL_NativeImagePerfMapFormat, W("NativeImagePerfMapFormat"), "Specifies the format of native image perfmap files generated by crossgen. Valid options are RVA or OFFSET.") @@ -690,6 +690,7 @@ RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeRundown, W("EventPipeRundown"), 1, "E RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeCircularMB, W("EventPipeCircularMB"), 1024, "The EventPipe circular buffer size in megabytes.") RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeProcNumbers, W("EventPipeProcNumbers"), 0, "Enable/disable capturing processor numbers in EventPipe event headers") RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeOutputStreaming, W("EventPipeOutputStreaming"), 0, "Enable/disable streaming for trace file set in COMPlus_EventPipeOutputPath. Non-zero values enable streaming.") +RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeEnableStackwalk, W("EventPipeEnableStackwalk"), 1, "Set to 0 to disable collecting stacks for EventPipe events.") #ifdef FEATURE_AUTO_TRACE RETAIL_CONFIG_DWORD_INFO_EX(INTERNAL_AutoTrace_N_Tracers, W("AutoTrace_N_Tracers"), 0, "", CLRConfig::LookupOptions::ParseIntegerAsBase10) diff --git a/src/coreclr/inc/clrdata.idl b/src/coreclr/inc/clrdata.idl index 2663a7b057fd7d..81b0e0bc7fc653 100644 --- a/src/coreclr/inc/clrdata.idl +++ b/src/coreclr/inc/clrdata.idl @@ -308,10 +308,12 @@ interface ICLRDataLoggingCallback : IUnknown typedef enum CLRDataEnumMemoryFlags { CLRDATA_ENUM_MEM_DEFAULT = 0x0, - CLRDATA_ENUM_MEM_MINI = CLRDATA_ENUM_MEM_DEFAULT, // generating skinny mini-dump - CLRDATA_ENUM_MEM_HEAP = 0x1, // generating heap dump - CLRDATA_ENUM_MEM_TRIAGE = 0x2, // generating triage mini-dump - + CLRDATA_ENUM_MEM_MINI = CLRDATA_ENUM_MEM_DEFAULT, // generating skinny mini-dump + CLRDATA_ENUM_MEM_HEAP = 0x1, // generating heap dump + CLRDATA_ENUM_MEM_TRIAGE = 0x2, // generating triage mini-dump + /* Generate heap dumps faster with less memory usage than CLRDATA_ENUM_MEM_HEAP by adding + the loader heaps instead of traversing all the individual runtime data structures. */ + CLRDATA_ENUM_MEM_HEAP2 = 0x3, /* More bits to be added here later */ } CLRDataEnumMemoryFlags; diff --git a/src/coreclr/inc/cordebug.idl b/src/coreclr/inc/cordebug.idl index 1393913b331b04..5d8c5e1fa0c6d3 100644 --- a/src/coreclr/inc/cordebug.idl +++ b/src/coreclr/inc/cordebug.idl @@ -6805,18 +6805,18 @@ interface ICorDebugArrayValue : ICorDebugHeapValue length_is(cdim)] ULONG32 dims[]); /* - * HasBaseIndices returns whether or not the array has base indices. + * HasBaseIndicies returns whether or not the array has base indices. * If the answer is no, then all dimensions have a base index of 0. */ - HRESULT HasBaseIndices([out] BOOL *pbHasBaseIndices); + HRESULT HasBaseIndicies([out] BOOL *pbHasBaseIndicies); /* - * GetBaseIndices returns the base index of each dimension in + * GetBaseIndicies returns the base index of each dimension in * the array */ - HRESULT GetBaseIndices([in] ULONG32 cdim, + HRESULT GetBaseIndicies([in] ULONG32 cdim, [out, size_is(cdim), length_is(cdim)] ULONG32 indices[]); diff --git a/src/coreclr/inc/corerror.xml b/src/coreclr/inc/corerror.xml index df8fae9b74d098..f9f604c28ae32c 100644 --- a/src/coreclr/inc/corerror.xml +++ b/src/coreclr/inc/corerror.xml @@ -2133,6 +2133,12 @@ During a GC initialization, GC large page support requires hard limit settings. + + CLR_E_GC_BAD_REGION_SIZE + "GC Region Size must be less than 2GB." + During a GC initialization, GC Region Size must be less than 2GB. + + COR_E_UNAUTHORIZEDACCESS 0x80070005 // Access is denied. diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 91ae75acc4f006..374fb7b8ab960f 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -2893,9 +2893,15 @@ class ICorStaticInfo CORINFO_METHOD_HANDLE hMethod ) = 0; - // this function is for debugging only. It returns the method name - // and if 'moduleName' is non-null, it sets it to something that will - // says which method (a class name, or a module name) + // This function returns the method name and if 'moduleName' is non-null, + // it sets it to something that contains the method (a class + // name, or a module name). Note that the moduleName parameter is for + // diagnostics only. + // + // The method name returned is the same as getMethodNameFromMetadata except + // in the case of functions without metadata (e.g. IL stubs), where this + // function still returns a reasonable name while getMethodNameFromMetadata + // returns null. virtual const char* getMethodName ( CORINFO_METHOD_HANDLE ftn, /* IN */ const char **moduleName /* OUT */ diff --git a/src/coreclr/inc/daccess.h b/src/coreclr/inc/daccess.h index c2053d748b0c73..a8056a5451561a 100644 --- a/src/coreclr/inc/daccess.h +++ b/src/coreclr/inc/daccess.h @@ -2466,7 +2466,7 @@ typedef DPTR(PTR_PCODE) PTR_PTR_PCODE; // Helper macro for tracking EnumMemoryRegions progress. #if 0 -#define EMEM_OUT(args) DacWarning args +#define EMEM_OUT(args) DacLogMessage args #else #define EMEM_OUT(args) #endif diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index c003b6be776b20..2447d63a10be31 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -43,11 +43,11 @@ typedef const GUID *LPCGUID; #define GUID_DEFINED #endif // !GUID_DEFINED -constexpr GUID JITEEVersionIdentifier = { /* 1b9551b8-21f4-4233-9c90-f3eabd6a322b */ - 0x1b9551b8, - 0x21f4, - 0x4233, - {0x9c, 0x90, 0xf3, 0xea, 0xbd, 0x6a, 0x32, 0x2b} +constexpr GUID JITEEVersionIdentifier = { /* 6be47e5d-a92b-4d16-9280-f63df646ada4 */ + 0x6be47e5d, + 0xa92b, + 0x4d16, + {0x92, 0x80, 0xf6, 0x3d, 0xf6, 0x46, 0xad, 0xa4} }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index 0934f2ea627481..20a1462125534c 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -15,10 +15,10 @@ #define READYTORUN_SIGNATURE 0x00525452 // 'RTR' // Keep these in sync with src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs -#define READYTORUN_MAJOR_VERSION 0x0007 -#define READYTORUN_MINOR_VERSION 0x0001 +#define READYTORUN_MAJOR_VERSION 0x0008 +#define READYTORUN_MINOR_VERSION 0x0000 -#define MINIMUM_READYTORUN_MAJOR_VERSION 0x006 +#define MINIMUM_READYTORUN_MAJOR_VERSION 0x008 // R2R Version 2.1 adds the InliningInfo section // R2R Version 2.2 adds the ProfileDataInfo section @@ -26,6 +26,7 @@ // R2R 3.0 is not backward compatible with 2.x. // R2R Version 6.0 changes managed layout for sequential types with any unmanaged non-blittable fields. // R2R 6.0 is not backward compatible with 5.x or earlier. +// R2R Version 8.0 Changes the alignment of the Int128 type struct READYTORUN_CORE_HEADER { diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 6850014a1d710a..40d6e7cbbadbdf 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -2412,8 +2412,14 @@ void CodeGen::genSetRegToConst(regNumber targetReg, var_types targetType, GenTre // Get a temp integer register to compute long address. regNumber addrReg = tree->GetSingleTempReg(); - simd16_t constValue = vecCon->gtSimd16Val; - CORINFO_FIELD_HANDLE hnd = emit->emitSimd16Const(constValue); + simd16_t constValue = {}; + + if (vecCon->TypeIs(TYP_SIMD12)) + memcpy(&constValue, &vecCon->gtSimd12Val, sizeof(simd12_t)); + else + constValue = vecCon->gtSimd16Val; + + CORINFO_FIELD_HANDLE hnd = emit->emitSimd16Const(constValue); emit->emitIns_R_C(INS_ldr, attr, targetReg, addrReg, hnd, 0); } diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index 536797eaba9593..4a24f88d398026 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -4404,12 +4404,26 @@ void CodeGen::genLeaInstruction(GenTreeAddrMode* lea) else { #ifdef TARGET_ARM64 - // Handle LEA with "contained" BFIZ - if (index->isContained() && index->OperIs(GT_BFIZ)) + + if (index->isContained()) { - assert(scale == 0); - scale = (DWORD)index->gtGetOp2()->AsIntConCommon()->IconValue(); - index = index->gtGetOp1()->gtGetOp1(); + if (index->OperIs(GT_BFIZ)) + { + // Handle LEA with "contained" BFIZ + assert(scale == 0); + scale = (DWORD)index->gtGetOp2()->AsIntConCommon()->IconValue(); + index = index->gtGetOp1()->gtGetOp1(); + } + else if (index->OperIs(GT_CAST)) + { + index = index->AsCast()->gtGetOp1(); + } + else + { + // Only BFIZ/CAST nodes should be present for for contained index on ARM64. + // If there are more, we need to handle them here. + unreached(); + } } #endif diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index f4c0a37ce224bb..7278506f86cf6d 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1370,65 +1370,46 @@ bool CodeGen::genCreateAddrMode( if (rv2) { - /* Make sure a GC address doesn't end up in 'rv2' */ - + // Make sure a GC address doesn't end up in 'rv2' if (varTypeIsGC(rv2->TypeGet())) { noway_assert(rv1 && !varTypeIsGC(rv1->TypeGet())); - - tmp = rv1; - rv1 = rv2; - rv2 = tmp; - + std::swap(rv1, rv2); rev = !rev; } - /* Special case: constant array index (that is range-checked) */ - + // Special case: constant array index (that is range-checked) if (fold) { - ssize_t tmpMul; - GenTree* index; + // By default, assume index is rv2 and indexScale is mul (or 1 if mul is zero) + GenTree* index = rv2; + ssize_t indexScale = mul == 0 ? 1 : mul; - if ((rv2->gtOper == GT_MUL || rv2->gtOper == GT_LSH) && (rv2->AsOp()->gtOp2->IsCnsIntOrI())) + if (rv2->OperIs(GT_MUL, GT_LSH) && (rv2->gtGetOp2()->IsCnsIntOrI())) { - /* For valuetype arrays where we can't use the scaled address - mode, rv2 will point to the scaled index. So we have to do - more work */ - - tmpMul = compiler->optGetArrayRefScaleAndIndex(rv2, &index DEBUGARG(false)); - if (mul) - { - tmpMul *= mul; - } + indexScale *= compiler->optGetArrayRefScaleAndIndex(rv2, &index DEBUGARG(false)); } - else - { - /* May be a simple array. rv2 will points to the actual index */ - index = rv2; - tmpMul = mul; + // "index * 0" means index is zero + if (indexScale == 0) + { + mul = 0; + rv2 = nullptr; } - - /* Get hold of the array index and see if it's a constant */ - if (index->IsIntCnsFitsInI32()) + else if (index->IsIntCnsFitsInI32()) { - /* Get hold of the index value */ - ssize_t ixv = index->AsIntConCommon()->IconValue(); - - /* Scale the index if necessary */ - if (tmpMul) + ssize_t constantIndex = index->AsIntConCommon()->IconValue() * indexScale; + if (constantIndex == 0) { - ixv *= tmpMul; + // while scale is a non-zero constant, the actual index is zero so drop it + mul = 0; + rv2 = nullptr; } - - if (FitsIn(cns + ixv)) + else if (FitsIn(cns + constantIndex)) { - /* Add the scaled index to the offset value */ - - cns += ixv; - - /* There is no scaled operand any more */ + // Add the constant index to the accumulated offset value + cns += constantIndex; + // and get rid of index mul = 0; rv2 = nullptr; } @@ -1887,31 +1868,9 @@ void CodeGen::genGenerateMachineCode() if (compiler->fgHaveProfileData()) { - const char* pgoKind; - switch (compiler->fgPgoSource) - { - case ICorJitInfo::PgoSource::Static: - pgoKind = "Static"; - break; - case ICorJitInfo::PgoSource::Dynamic: - pgoKind = "Dynamic"; - break; - case ICorJitInfo::PgoSource::Blend: - pgoKind = "Blend"; - break; - case ICorJitInfo::PgoSource::Text: - pgoKind = "Textual"; - break; - case ICorJitInfo::PgoSource::Sampling: - pgoKind = "Sample-based"; - break; - default: - pgoKind = "Unknown"; - break; - } - - printf("; with %s PGO: edge weights are %s, and fgCalledCount is " FMT_WT "\n", pgoKind, - compiler->fgHaveValidEdgeWeights ? "valid" : "invalid", compiler->fgCalledCount); + printf("; with %s: edge weights are %s, and fgCalledCount is " FMT_WT "\n", + compiler->compGetPgoSourceName(), compiler->fgHaveValidEdgeWeights ? "valid" : "invalid", + compiler->fgCalledCount); } if (compiler->fgPgoFailReason != nullptr) diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 9c95e410eb3d97..7a8c6b362c2943 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -561,8 +561,14 @@ void CodeGen::genSetRegToConst(regNumber targetReg, var_types targetType, GenTre case TYP_SIMD12: case TYP_SIMD16: { - simd16_t constValue = vecCon->gtSimd16Val; - CORINFO_FIELD_HANDLE hnd = emit->emitSimd16Const(constValue); + simd16_t constValue = {}; + + if (vecCon->TypeIs(TYP_SIMD12)) + memcpy(&constValue, &vecCon->gtSimd12Val, sizeof(simd12_t)); + else + constValue = vecCon->gtSimd16Val; + + CORINFO_FIELD_HANDLE hnd = emit->emitSimd16Const(constValue); emit->emitIns_R_C(ins_Load(targetType), attr, targetReg, hnd, 0); break; @@ -7886,9 +7892,9 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) for (GenTreeFieldList::Use& use : fieldList->Uses()) { - GenTree* const fieldNode = use.GetNode(); - const unsigned fieldOffset = use.GetOffset(); - var_types fieldType = use.GetType(); + GenTree* const fieldNode = use.GetNode(); + const unsigned fieldOffset = use.GetOffset(); + const var_types fieldType = use.GetType(); // Long-typed nodes should have been handled by the decomposition pass, and lowering should have sorted the // field list in descending order by offset. @@ -7911,8 +7917,7 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) int adjustment = roundUp(currentOffset - fieldOffset, 4); if (fieldIsSlot && !varTypeIsSIMD(fieldType)) { - fieldType = genActualType(fieldType); - unsigned pushSize = genTypeSize(fieldType); + unsigned pushSize = genTypeSize(genActualType(fieldType)); assert((pushSize % 4) == 0); adjustment -= pushSize; while (adjustment != 0) @@ -7960,13 +7965,22 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) } } - bool canStoreWithPush = fieldIsSlot; - bool canLoadWithPush = varTypeIsI(fieldNode) || genIsValidIntReg(argReg); + bool canStoreFullSlot = fieldIsSlot; + bool canLoadFullSlot = genIsValidIntReg(argReg); + if (argReg == REG_NA) + { + assert((genTypeSize(fieldNode) <= TARGET_POINTER_SIZE)); + assert(genTypeSize(genActualType(fieldNode)) == genTypeSize(genActualType(fieldType))); + + // We can widen local loads if the excess only affects padding bits. + canLoadFullSlot = (genTypeSize(fieldNode) == TARGET_POINTER_SIZE) || fieldNode->isUsedFromSpillTemp() || + (fieldNode->OperIsLocalRead() && (genTypeSize(fieldNode) >= genTypeSize(fieldType))); + } - if (canStoreWithPush && canLoadWithPush) + if (canStoreFullSlot && canLoadFullSlot) { assert(m_pushStkArg); - assert(genTypeSize(fieldNode) == TARGET_POINTER_SIZE); + assert(genTypeSize(fieldNode) <= TARGET_POINTER_SIZE); inst_TT(INS_push, emitActualTypeSize(fieldNode), fieldNode); currentOffset -= TARGET_POINTER_SIZE; @@ -7989,9 +8003,10 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) } else { - // TODO-XArch-CQ: using "ins_Load" here is conservative, as it will always - // extend, which we can avoid if the field type is smaller than the node type. - inst_RV_TT(ins_Load(fieldNode->TypeGet()), emitTypeSize(fieldNode), intTmpReg, fieldNode); + // Use the smaller "mov" instruction in case we do not need a sign/zero-extending load. + instruction loadIns = canLoadFullSlot ? INS_mov : ins_Load(fieldNode->TypeGet()); + emitAttr loadSize = canLoadFullSlot ? EA_PTRSIZE : emitTypeSize(fieldNode); + inst_RV_TT(loadIns, loadSize, intTmpReg, fieldNode); } argReg = intTmpReg; @@ -8006,13 +8021,16 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) else #endif // defined(FEATURE_SIMD) { - genStoreRegToStackArg(fieldType, argReg, fieldOffset - currentOffset); + // Using wide stores here avoids having to reserve a byteable register when we could not + // use "push" due to the field node being an indirection (i. e. for "!canLoadFullSlot"). + var_types storeType = canStoreFullSlot ? genActualType(fieldType) : fieldType; + genStoreRegToStackArg(storeType, argReg, fieldOffset - currentOffset); } if (m_pushStkArg) { - // We always push a slot-rounded size - currentOffset -= genTypeSize(fieldType); + // We always push a slot-rounded size. + currentOffset -= roundUp(genTypeSize(fieldType), TARGET_POINTER_SIZE); } } diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index e73052a780f60f..8ece59eefb1f8e 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -34,9 +34,7 @@ extern ICorJitHost* g_jitHost; #define COLUMN_FLAGS (COLUMN_KINDS + 32) #endif -#if defined(DEBUG) unsigned Compiler::jitTotalMethodCompiled = 0; -#endif // defined(DEBUG) #if defined(DEBUG) LONG Compiler::jitNestingLevel = 0; @@ -1771,6 +1769,7 @@ void Compiler::compInit(ArenaAllocator* pAlloc, info.compCompHnd = compHnd; info.compMethodHnd = methodHnd; info.compMethodInfo = methodInfo; + info.compClassHnd = compHnd->getMethodClass(methodHnd); #ifdef DEBUG bRangeAllowStress = false; @@ -1790,17 +1789,10 @@ void Compiler::compInit(ArenaAllocator* pAlloc, info.compClassName = nullptr; info.compFullName = nullptr; - const char* classNamePtr; - const char* methodName; - - methodName = eeGetMethodName(methodHnd, &classNamePtr); - unsigned len = (unsigned)roundUp(strlen(classNamePtr) + 1); - info.compClassName = getAllocator(CMK_DebugOnly).allocate(len); - info.compMethodName = methodName; - strcpy_s((char*)info.compClassName, len, classNamePtr); - - info.compFullName = eeGetMethodFullName(methodHnd); - info.compPerfScore = 0.0; + info.compMethodName = eeGetMethodName(methodHnd, nullptr); + info.compClassName = eeGetClassName(info.compClassHnd); + info.compFullName = eeGetMethodFullName(methodHnd); + info.compPerfScore = 0.0; info.compMethodSuperPMIIndex = g_jitHost->getIntConfigValue(W("SuperPMIMethodContextNumber"), -1); #endif // defined(DEBUG) || defined(LATE_DISASM) || DUMP_FLOWGRAPHS @@ -2539,7 +2531,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_ALT_JIT)) { - if (pfAltJit->contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (pfAltJit->contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.altJit = true; } @@ -2620,7 +2612,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) // if (compIsForImportOnly() && (!altJitConfig || opts.altJit)) { - if (JitConfig.JitImportBreak().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitImportBreak().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { assert(!"JitImportBreak reached"); } @@ -2635,7 +2627,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) // if (!compIsForInlining()) { - if (JitConfig.JitDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { verboseDump = true; } @@ -2870,32 +2862,32 @@ void Compiler::compInitOptions(JitFlags* jitFlags) opts.dspOrder = true; } - if (JitConfig.JitGCDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitGCDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.dspGCtbls = true; } - if (JitConfig.JitDisasm().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitDisasm().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.disAsm = true; } - if (JitConfig.JitDisasm().contains("SPILLED", nullptr, nullptr)) + if (JitConfig.JitDisasmSpilled()) { opts.disAsmSpilled = true; } - if (JitConfig.JitUnwindDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitUnwindDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.dspUnwind = true; } - if (JitConfig.JitEHDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitEHDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.dspEHTable = true; } - if (JitConfig.JitDebugDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitDebugDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.dspDebugInfo = true; } @@ -2933,7 +2925,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) opts.compLongAddress = true; } - if (JitConfig.JitOptRepeat().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitOptRepeat().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.optRepeat = true; } @@ -2943,7 +2935,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) // JitEarlyExpandMDArraysFilter. if (JitConfig.JitEarlyExpandMDArrays() == 0) { - if (JitConfig.JitEarlyExpandMDArraysFilter().contains(info.compMethodName, info.compClassName, + if (JitConfig.JitEarlyExpandMDArraysFilter().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.compJitEarlyExpandMDArrays = true; @@ -2984,7 +2976,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) printf(""); // in our logic this causes a flush } - if (JitConfig.JitBreak().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitBreak().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { assert(!"JitBreak reached"); } @@ -2996,8 +2988,8 @@ void Compiler::compInitOptions(JitFlags* jitFlags) } if (verbose || - JitConfig.JitDebugBreak().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args) || - JitConfig.JitBreak().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + JitConfig.JitDebugBreak().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args) || + JitConfig.JitBreak().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { compDebugBreak = true; } @@ -3016,14 +3008,9 @@ void Compiler::compInitOptions(JitFlags* jitFlags) s_pJitFunctionFileInitialized = true; } #else // DEBUG - if (!JitConfig.JitDisasm().isEmpty()) + if (JitConfig.JitDisasm().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { - const char* methodName = info.compCompHnd->getMethodName(info.compMethodHnd, nullptr); - const char* className = info.compCompHnd->getClassName(info.compClassHnd); - if (JitConfig.JitDisasm().contains(methodName, className, &info.compMethodInfo->args)) - { - opts.disAsm = true; - } + opts.disAsm = true; } #endif // !DEBUG @@ -3191,21 +3178,21 @@ void Compiler::compInitOptions(JitFlags* jitFlags) // JitForceProcedureSplitting is used to force procedure splitting on checked assemblies. // This is useful for debugging on a checked build. Note that we still only do procedure // splitting in the zapper. - if (JitConfig.JitForceProcedureSplitting().contains(info.compMethodName, info.compClassName, + if (JitConfig.JitForceProcedureSplitting().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.compProcedureSplitting = true; } // JitNoProcedureSplitting will always disable procedure splitting. - if (JitConfig.JitNoProcedureSplitting().contains(info.compMethodName, info.compClassName, + if (JitConfig.JitNoProcedureSplitting().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.compProcedureSplitting = false; } // // JitNoProcedureSplittingEH will disable procedure splitting in functions with EH. - if (JitConfig.JitNoProcedureSplittingEH().contains(info.compMethodName, info.compClassName, + if (JitConfig.JitNoProcedureSplittingEH().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.compProcedureSplittingEH = false; @@ -3213,6 +3200,21 @@ void Compiler::compInitOptions(JitFlags* jitFlags) #endif } +#ifdef TARGET_64BIT + opts.compCollect64BitCounts = JitConfig.JitCollect64BitCounts() != 0; + +#ifdef DEBUG + if (JitConfig.JitRandomlyCollect64BitCounts() != 0) + { + CLRRandom rng; + rng.Init(info.compMethodHash() ^ JitConfig.JitRandomlyCollect64BitCounts() ^ 0x3485e20e); + opts.compCollect64BitCounts = rng.Next(2) == 0; + } +#endif +#else + opts.compCollect64BitCounts = false; +#endif + #ifdef DEBUG // Now, set compMaxUncheckedOffsetForNullObject for STRESS_NULL_OBJECT_CHECK @@ -3306,7 +3308,7 @@ bool Compiler::compJitHaltMethod() /* This method returns true when we use an INS_BREAKPOINT to allow us to step into the generated native code */ /* Note that this these two "Jit" environment variables also work for ngen images */ - if (JitConfig.JitHalt().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitHalt().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { return true; } @@ -3410,7 +3412,7 @@ bool Compiler::compStressCompileHelper(compStressArea stressArea, unsigned weigh } if (!JitConfig.JitStressOnly().isEmpty() && - !JitConfig.JitStressOnly().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + !JitConfig.JitStressOnly().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { return false; } @@ -3693,7 +3695,7 @@ void Compiler::compSetOptimizationLevel() if (!theMinOptsValue) { - if (JitConfig.JitMinOptsName().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitMinOptsName().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { theMinOptsValue = true; } @@ -4082,8 +4084,9 @@ bool Compiler::compRsvdRegCheck(FrameLayoutState curState) // const char* Compiler::compGetTieringName(bool wantShortName) const { - const bool tier0 = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0); - const bool tier1 = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER1); + const bool tier0 = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0); + const bool tier1 = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER1); + const bool instrumenting = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR); if (!opts.compMinOptsIsSet) { @@ -4097,13 +4100,13 @@ const char* Compiler::compGetTieringName(bool wantShortName) const if (tier0) { - return "Tier0"; + return instrumenting ? "Instrumented Tier0" : "Tier0"; } else if (tier1) { if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_OSR)) { - return "Tier1-OSR"; + return instrumenting ? "Instrumented Tier1-OSR" : "Tier1-OSR"; } else { @@ -4149,6 +4152,31 @@ const char* Compiler::compGetTieringName(bool wantShortName) const } } +//------------------------------------------------------------------------ +// compGetPgoSourceName: get a string describing PGO source +// +// Returns: +// String describing describing PGO source (e.g. Dynamic, Static, etc) +// +const char* Compiler::compGetPgoSourceName() const +{ + switch (fgPgoSource) + { + case ICorJitInfo::PgoSource::Static: + return "Static PGO"; + case ICorJitInfo::PgoSource::Dynamic: + return "Dynamic PGO"; + case ICorJitInfo::PgoSource::Blend: + return "Blend PGO"; + case ICorJitInfo::PgoSource::Text: + return "Textual PGO"; + case ICorJitInfo::PgoSource::Sampling: + return "Sample-based PGO"; + default: + return ""; + } +} + //------------------------------------------------------------------------ // compGetStressMessage: get a string describing jitstress capability // for this method @@ -4171,8 +4199,7 @@ const char* Compiler::compGetStressMessage() const { // Or is it excluded via name? if (!JitConfig.JitStressOnly().isEmpty() || - !JitConfig.JitStressOnly().contains(info.compMethodName, info.compClassName, - &info.compMethodInfo->args)) + !JitConfig.JitStressOnly().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { // Not excluded -- stress can happen stressMessage = " JitStress"; @@ -5106,9 +5133,32 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl compJitTelemetry.NotifyEndOfCompilation(); #endif -#if defined(DEBUG) - ++Compiler::jitTotalMethodCompiled; -#endif // defined(DEBUG) + unsigned methodsCompiled = (unsigned)InterlockedIncrement((LONG*)&Compiler::jitTotalMethodCompiled); + + if (JitConfig.JitDisasmSummary() && !compIsForInlining()) + { + char osrBuffer[20] = {0}; + if (opts.IsOSR()) + { + // Tiering name already includes "OSR", we just want the IL offset + sprintf_s(osrBuffer, 20, " @0x%x", info.compILEntry); + } + +#ifdef DEBUG + const char* fullName = info.compFullName; +#else + const char* fullName = + eeGetMethodFullName(info.compMethodHnd, /* includeReturnType */ false, /* includeThisSpecifier */ false); +#endif + + char debugPart[128] = {0}; + INDEBUG(sprintf_s(debugPart, 128, ", hash=0x%08x%s", info.compMethodHash(), compGetStressMessage())); + + const bool hasProf = fgHaveProfileData(); + printf("%4d: JIT compiled %s [%s%s%s%s, IL size=%u, code size=%u%s]\n", methodsCompiled, fullName, + compGetTieringName(), osrBuffer, hasProf ? " with " : "", hasProf ? compGetPgoSourceName() : "", + info.compILCodeSize, *methodCodeSize, debugPart); + } compFunctionTraceEnd(*methodCodePtr, *methodCodeSize, false); JITDUMP("Method code size: %d\n", (unsigned)(*methodCodeSize)); @@ -5461,13 +5511,13 @@ bool Compiler::skipMethod() return true; } - if (JitConfig.JitExclude().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitExclude().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { return true; } if (!JitConfig.JitInclude().isEmpty() && - !JitConfig.JitInclude().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + !JitConfig.JitInclude().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { return true; } @@ -5773,9 +5823,7 @@ int Compiler::compCompile(CORINFO_MODULE_HANDLE classPtr, { impTokenLookupContextHandle = impInlineInfo->tokenLookupContextHandle; - assert(impInlineInfo->inlineCandidateInfo->clsHandle == info.compCompHnd->getMethodClass(info.compMethodHnd)); - info.compClassHnd = impInlineInfo->inlineCandidateInfo->clsHandle; - + assert(impInlineInfo->inlineCandidateInfo->clsHandle == info.compClassHnd); assert(impInlineInfo->inlineCandidateInfo->clsAttr == info.compCompHnd->getClassAttribs(info.compClassHnd)); // printf("%x != %x\n", impInlineInfo->inlineCandidateInfo->clsAttr, // info.compCompHnd->getClassAttribs(info.compClassHnd)); @@ -5785,7 +5833,6 @@ int Compiler::compCompile(CORINFO_MODULE_HANDLE classPtr, { impTokenLookupContextHandle = METHOD_BEING_COMPILED_CONTEXT(); - info.compClassHnd = info.compCompHnd->getMethodClass(info.compMethodHnd); info.compClassAttr = info.compCompHnd->getClassAttribs(info.compClassHnd); } @@ -6710,24 +6757,6 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr, } #ifdef DEBUG - if ((JitConfig.DumpJittedMethods() == 1) && !compIsForInlining()) - { - enum - { - BUFSIZE = 20 - }; - char osrBuffer[BUFSIZE] = {0}; - if (opts.IsOSR()) - { - // Tiering name already includes "OSR", we just want the IL offset - // - sprintf_s(osrBuffer, BUFSIZE, " @0x%x", info.compILEntry); - } - - printf("Compiling %4d %s::%s, IL size = %u, hash=0x%08x %s%s%s\n", Compiler::jitTotalMethodCompiled, - info.compClassName, info.compMethodName, info.compILCodeSize, info.compMethodHash(), - compGetTieringName(), osrBuffer, compGetStressMessage()); - } if (compIsForInlining()) { compGenTreeID = impInlineInfo->InlinerCompiler->compGenTreeID; diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 3291aca767c382..b8b2dfb0581786 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4145,9 +4145,7 @@ class Compiler regNumber getCallArgIntRegister(regNumber floatReg); regNumber getCallArgFloatRegister(regNumber intReg); -#if defined(DEBUG) static unsigned jitTotalMethodCompiled; -#endif #ifdef DEBUG static LONG jitNestingLevel; @@ -7519,10 +7517,31 @@ class Compiler var_types eeGetFieldType(CORINFO_FIELD_HANDLE fldHnd, CORINFO_CLASS_HANDLE* pStructHnd = nullptr); -#if defined(DEBUG) || defined(FEATURE_JIT_METHOD_PERF) || defined(FEATURE_SIMD) || defined(TRACK_LSRA_STATS) + void eePrintJitType(class StringPrinter* printer, var_types jitType); + void eePrintType(class StringPrinter* printer, + CORINFO_CLASS_HANDLE clsHnd, + bool includeNamespaces, + bool includeInstantiation); + void eePrintTypeOrJitAlias(class StringPrinter* printer, + CORINFO_CLASS_HANDLE clsHnd, + bool includeNamespaces, + bool includeInstantiation); + void eePrintMethod(class StringPrinter* printer, + CORINFO_CLASS_HANDLE clsHnd, + CORINFO_METHOD_HANDLE methodHnd, + CORINFO_SIG_INFO* sig, + bool includeNamespaces, + bool includeClassInstantiation, + bool includeMethodInstantiation, + bool includeSignature, + bool includeReturnType, + bool includeThisSpecifier); +#if defined(DEBUG) || defined(FEATURE_JIT_METHOD_PERF) || defined(FEATURE_SIMD) || defined(TRACK_LSRA_STATS) const char* eeGetMethodName(CORINFO_METHOD_HANDLE hnd, const char** className); - const char* eeGetMethodFullName(CORINFO_METHOD_HANDLE hnd); + const char* eeGetMethodFullName(CORINFO_METHOD_HANDLE hnd, + bool includeReturnType = true, + bool includeThisSpecifier = true); unsigned compMethodHash(CORINFO_METHOD_HANDLE methodHandle); bool eeIsNativeMethod(CORINFO_METHOD_HANDLE method); @@ -7748,6 +7767,12 @@ class Compiler return eeRunWithSPMIErrorTrapImp(reinterpret_cast(function), reinterpret_cast(param)); } + template + bool eeRunFunctorWithSPMIErrorTrap(Functor f) + { + return eeRunWithSPMIErrorTrap([](Functor* pf) { (*pf)(); }, &f); + } + bool eeRunWithSPMIErrorTrapImp(void (*function)(void*), void* param); // Utility functions @@ -9293,6 +9318,9 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // Use early multi-dimensional array operator expansion (expand after loop optimizations; before lowering). bool compJitEarlyExpandMDArrays; + // Collect 64 bit counts for PGO data. + bool compCollect64BitCounts; + } opts; static bool s_pAltJitExcludeAssembliesListInitialized; @@ -9472,6 +9500,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX } const char* compGetTieringName(bool wantShortName = false) const; + const char* compGetPgoSourceName() const; const char* compGetStressMessage() const; codeOptimize compCodeOpt() const @@ -11265,6 +11294,46 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ +class StringPrinter +{ + CompAllocator m_alloc; + char* m_buffer; + size_t m_bufferMax; + size_t m_bufferIndex = 0; + +public: + StringPrinter(CompAllocator alloc, char* buffer = nullptr, size_t bufferMax = 0) + : m_alloc(alloc), m_buffer(buffer), m_bufferMax(bufferMax) + { + if ((m_buffer == nullptr) || (m_bufferMax == 0)) + { + m_bufferMax = 128; + m_buffer = alloc.allocate(m_bufferMax); + } + + m_buffer[0] = '\0'; + } + + size_t GetLength() + { + return m_bufferIndex; + } + + char* GetBuffer() + { + assert(m_buffer[GetLength()] == '\0'); + return m_buffer; + } + void Truncate(size_t newLength) + { + assert(newLength <= m_bufferIndex); + m_bufferIndex = newLength; + m_buffer[m_bufferIndex] = '\0'; + } + + void Printf(const char* format, ...); +}; + /***************************************************************************** * * Variables to keep track of total code amounts. diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index 7b5b2961510907..11cfc55305e97c 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -1506,25 +1506,24 @@ const char* Compiler::eeGetFieldName(CORINFO_FIELD_HANDLE field, const char** cl return param.fieldOrMethodOrClassNamePtr; } +//------------------------------------------------------------------------ +// eeGetClassName: +// Get the name (including namespace and instantiation) of a type. +// If missing information (in SPMI), then return a placeholder string. +// +// Return value: +// The name string. +// const char* Compiler::eeGetClassName(CORINFO_CLASS_HANDLE clsHnd) { - FilterSuperPMIExceptionsParam_ee_il param; - - param.pThis = this; - param.pJitInfo = &info; - param.clazz = clsHnd; - - bool success = eeRunWithSPMIErrorTrap( - [](FilterSuperPMIExceptionsParam_ee_il* pParam) { - pParam->fieldOrMethodOrClassNamePtr = pParam->pJitInfo->compCompHnd->getClassName(pParam->clazz); - }, - ¶m); - - if (!success) + StringPrinter printer(getAllocator(CMK_DebugOnly)); + if (!eeRunFunctorWithSPMIErrorTrap([&]() { eePrintType(&printer, clsHnd, true, true); })) { - param.fieldOrMethodOrClassNamePtr = "hackishClassName"; + printer.Truncate(0); + printer.Printf("hackishClassName"); } - return param.fieldOrMethodOrClassNamePtr; + + return printer.GetBuffer(); } #endif // DEBUG || FEATURE_JIT_METHOD_PERF diff --git a/src/coreclr/jit/eeinterface.cpp b/src/coreclr/jit/eeinterface.cpp index 60c685e142b35a..df3a65af43be6d 100644 --- a/src/coreclr/jit/eeinterface.cpp +++ b/src/coreclr/jit/eeinterface.cpp @@ -19,228 +19,327 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #pragma hdrstop #endif -#if defined(DEBUG) || defined(FEATURE_JIT_METHOD_PERF) || defined(FEATURE_SIMD) +//------------------------------------------------------------------------ +// StringPrinter::Printf: +// Print a formatted string. +// +// Arguments: +// format - the format +// +void StringPrinter::Printf(const char* format, ...) +{ + va_list args; + va_start(args, format); -/*****************************************************************************/ + while (true) + { + size_t bufferLeft = m_bufferMax - m_bufferIndex; + assert(bufferLeft >= 1); // always fit null terminator + + va_list argsCopy; + va_copy(argsCopy, args); + int printed = _vsnprintf_s(m_buffer + m_bufferIndex, bufferLeft, _TRUNCATE, format, argsCopy); + va_end(argsCopy); + + if (printed < 0) + { + // buffer too small + size_t newSize = m_bufferMax * 2; + char* newBuffer = m_alloc.allocate(newSize); + memcpy(newBuffer, m_buffer, m_bufferIndex + 1); // copy null terminator too -/***************************************************************************** - * - * Filter wrapper to handle exception filtering. - * On Unix compilers don't support SEH. - */ + m_buffer = newBuffer; + m_bufferMax = newSize; + } + else + { + m_bufferIndex = m_bufferIndex + static_cast(printed); + break; + } + } + + va_end(args); +} -struct FilterSuperPMIExceptionsParam_eeinterface +#if defined(DEBUG) || defined(FEATURE_JIT_METHOD_PERF) || defined(FEATURE_SIMD) + +//------------------------------------------------------------------------ +// eePrintJitType: +// Print a JIT type. +// +// Arguments: +// printer - the printer +// jitType - the JIT type +// +void Compiler::eePrintJitType(StringPrinter* printer, var_types jitType) { - Compiler* pThis; - Compiler::Info* pJitInfo; - bool hasThis; - size_t siglength; - CORINFO_SIG_INFO sig; - CORINFO_ARG_LIST_HANDLE argLst; - CORINFO_METHOD_HANDLE hnd; - const char* returnType; - const char** pArgNames; - EXCEPTION_POINTERS exceptionPointers; -}; - -const char* Compiler::eeGetMethodFullName(CORINFO_METHOD_HANDLE hnd) + printer->Printf("%s", varTypeName(jitType)); +} + +//------------------------------------------------------------------------ +// eePrintType: +// Print a type given by a class handle. +// +// Arguments: +// printer - the printer +// clsHnd - Handle for the class +// includeNamespace - Whether to print namespaces before type names +// includeInstantiation - Whether to print the instantiation of the class +// +void Compiler::eePrintType(StringPrinter* printer, + CORINFO_CLASS_HANDLE clsHnd, + bool includeNamespace, + bool includeInstantiation) { - const char* className; - const char* methodName = eeGetMethodName(hnd, &className); - if ((eeGetHelperNum(hnd) != CORINFO_HELP_UNDEF) || eeIsNativeMethod(hnd)) + const char* namespaceName; + const char* className = info.compCompHnd->getClassNameFromMetadata(clsHnd, &namespaceName); + if (className == nullptr) { - return methodName; + namespaceName = nullptr; + className = ""; + } + + if (includeNamespace && (namespaceName != nullptr) && (namespaceName[0] != '\0')) + { + printer->Printf("%s.", namespaceName); } - FilterSuperPMIExceptionsParam_eeinterface param; - param.returnType = nullptr; - param.pThis = this; - param.hasThis = false; - param.siglength = 0; - param.hnd = hnd; - param.pJitInfo = &info; + printer->Printf("%s", className); + + if (!includeInstantiation) + { + return; + } - size_t length = 0; - unsigned i; + char pref = '['; + for (unsigned typeArgIndex = 0;; typeArgIndex++) + { + CORINFO_CLASS_HANDLE typeArg = info.compCompHnd->getTypeInstantiationArgument(clsHnd, typeArgIndex); - /* Generating the full signature is a two-pass process. First we have to walk - the components in order to assess the total size, then we allocate the buffer - and copy the elements into it. - */ + if (typeArg == NO_CLASS_HANDLE) + { + break; + } - /* Right now there is a race-condition in the EE, className can be nullptr */ + printer->Printf("%c", pref); + pref = ','; + eePrintTypeOrJitAlias(printer, typeArg, includeNamespace, true); + } - /* initialize length with length of className and '.' */ + if (pref != '[') + { + printer->Printf("]"); + } +} - if (className) +//------------------------------------------------------------------------ +// eePrintTypeOrJitAlias: +// Print a type given by a class handle. If the type is a primitive type, +// prints its JIT alias. +// +// Arguments: +// printer - the printer +// clsHnd - Handle for the class +// includeNamespace - Whether to print namespaces before type names +// includeInstantiation - Whether to print the instantiation of the class +// +void Compiler::eePrintTypeOrJitAlias(StringPrinter* printer, + CORINFO_CLASS_HANDLE clsHnd, + bool includeNamespace, + bool includeInstantiation) +{ + CorInfoType typ = info.compCompHnd->asCorInfoType(clsHnd); + if ((typ == CORINFO_TYPE_CLASS) || (typ == CORINFO_TYPE_VALUECLASS)) { - length = strlen(className) + 1; + eePrintType(printer, clsHnd, includeNamespace, includeInstantiation); } else { - assert(strlen(".") == 7); - length = 7; + eePrintJitType(printer, JitType2PreciseVarType(typ)); } +} - /* add length of methodName and opening bracket */ - length += strlen(methodName) + 1; - - bool success = eeRunWithSPMIErrorTrap( - [](FilterSuperPMIExceptionsParam_eeinterface* pParam) { - - /* figure out the signature */ - - pParam->pThis->eeGetMethodSig(pParam->hnd, &pParam->sig); +//------------------------------------------------------------------------ +// eePrintMethod: +// Print a method given by a method handle, its owning class handle and its +// signature. +// +// Arguments: +// printer - the printer +// clsHnd - Handle for the owning class, or NO_CLASS_HANDLE to not print the class. +// sig - The signature of the method. +// includeNamespaces - Whether to print namespaces before type names. +// includeClassInstantiation - Whether to print the class instantiation. Only valid when clsHnd is passed. +// includeMethodInstantiation - Whether to print the method instantiation. Requires the signature to be passed. +// includeSignature - Whether to print the signature. +// includeReturnType - Whether to include the return type at the end. +// includeThisSpecifier - Whether to include a specifier at the end for whether the method is an instance +// method. +// +void Compiler::eePrintMethod(StringPrinter* printer, + CORINFO_CLASS_HANDLE clsHnd, + CORINFO_METHOD_HANDLE methHnd, + CORINFO_SIG_INFO* sig, + bool includeNamespaces, + bool includeClassInstantiation, + bool includeMethodInstantiation, + bool includeSignature, + bool includeReturnType, + bool includeThisSpecifier) +{ + if (clsHnd != NO_CLASS_HANDLE) + { + eePrintType(printer, clsHnd, includeNamespaces, includeClassInstantiation); + printer->Printf(":"); + } - // allocate space to hold the class names for each of the parameters + const char* methName = info.compCompHnd->getMethodName(methHnd, nullptr); + printer->Printf("%s", methName); - if (pParam->sig.numArgs > 0) - { - pParam->pArgNames = - pParam->pThis->getAllocator(CMK_DebugOnly).allocate(pParam->sig.numArgs); - } - else + if (includeMethodInstantiation && (sig->sigInst.methInstCount > 0)) + { + printer->Printf("["); + for (unsigned i = 0; i < sig->sigInst.methInstCount; i++) + { + if (i > 0) { - pParam->pArgNames = nullptr; + printer->Printf(","); } - unsigned i; - pParam->argLst = pParam->sig.args; + eePrintTypeOrJitAlias(printer, sig->sigInst.methInst[i], includeNamespaces, true); + } + printer->Printf("]"); + } + + if (includeSignature) + { + printer->Printf("("); - for (i = 0; i < pParam->sig.numArgs; i++) + CORINFO_ARG_LIST_HANDLE argLst = sig->args; + for (unsigned i = 0; i < sig->numArgs; i++) + { + if (i > 0) + printer->Printf(","); + + CORINFO_CLASS_HANDLE vcClsHnd; + var_types type = JitType2PreciseVarType(strip(info.compCompHnd->getArgType(sig, argLst, &vcClsHnd))); + switch (type) { - var_types type = pParam->pThis->eeGetArgType(pParam->argLst, &pParam->sig); - switch (type) + case TYP_REF: + case TYP_STRUCT: { - case TYP_REF: - case TYP_STRUCT: + CORINFO_CLASS_HANDLE clsHnd = eeGetArgClass(sig, argLst); + // For some SIMD struct types we can get a nullptr back from eeGetArgClass on Linux/X64 + if (clsHnd != NO_CLASS_HANDLE) { - CORINFO_CLASS_HANDLE clsHnd = pParam->pThis->eeGetArgClass(&pParam->sig, pParam->argLst); - // For some SIMD struct types we can get a nullptr back from eeGetArgClass on Linux/X64 - if (clsHnd != NO_CLASS_HANDLE) - { - const char* clsName = pParam->pThis->eeGetClassName(clsHnd); - if (clsName != nullptr) - { - pParam->pArgNames[i] = clsName; - break; - } - } - } - FALLTHROUGH; - default: - pParam->pArgNames[i] = varTypeName(type); + eePrintType(printer, clsHnd, includeNamespaces, true); break; + } } - pParam->siglength += strlen(pParam->pArgNames[i]); - pParam->argLst = pParam->pJitInfo->compCompHnd->getArgNext(pParam->argLst); + + FALLTHROUGH; + default: + eePrintJitType(printer, type); + break; } - /* add ',' if there is more than one argument */ + argLst = info.compCompHnd->getArgNext(argLst); + } - if (pParam->sig.numArgs > 1) - { - pParam->siglength += (pParam->sig.numArgs - 1); - } + printer->Printf(")"); - var_types retType = JITtype2varType(pParam->sig.retType); + if (includeReturnType) + { + var_types retType = JitType2PreciseVarType(sig->retType); if (retType != TYP_VOID) { + printer->Printf(":"); switch (retType) { case TYP_REF: case TYP_STRUCT: { - CORINFO_CLASS_HANDLE clsHnd = pParam->sig.retTypeClass; + CORINFO_CLASS_HANDLE clsHnd = sig->retTypeClass; if (clsHnd != NO_CLASS_HANDLE) { - const char* clsName = pParam->pThis->eeGetClassName(clsHnd); - if (clsName != nullptr) - { - pParam->returnType = clsName; - break; - } + eePrintType(printer, clsHnd, includeNamespaces, true); + break; } } FALLTHROUGH; default: - pParam->returnType = varTypeName(retType); + eePrintJitType(printer, retType); break; } - pParam->siglength += strlen(pParam->returnType) + 1; // don't forget the delimiter ':' - } - - // Does it have a 'this' pointer? Don't count explicit this, which has the this pointer type as the first - // element of the arg type list - if (pParam->sig.hasThis() && !pParam->sig.hasExplicitThis()) - { - assert(strlen(":this") == 5); - pParam->siglength += 5; - pParam->hasThis = true; } - }, - ¶m); + } - if (!success) - { - param.siglength = 0; + // Does it have a 'this' pointer? Don't count explicit this, which has + // the this pointer type as the first element of the arg type list + if (includeThisSpecifier && sig->hasThis() && !sig->hasExplicitThis()) + { + printer->Printf(":this"); + } } +} - /* add closing bracket and null terminator */ - - length += param.siglength + 2; - - char* retName = getAllocator(CMK_DebugOnly).allocate(length); - - /* Now generate the full signature string in the allocated buffer */ - - if (className) - { - strcpy_s(retName, length, className); - strcat_s(retName, length, ":"); - } - else +//------------------------------------------------------------------------ +// eeGetMethodFullName: +// Get a string describing a method. +// +// Arguments: +// hnd - the method handle +// includeReturnType - Whether to include the return type in the string +// includeThisSpecifier - Whether to include a specifier for whether this is an instance method. +// +// Returns: +// The string. +// +const char* Compiler::eeGetMethodFullName(CORINFO_METHOD_HANDLE hnd, bool includeReturnType, bool includeThisSpecifier) +{ + const char* className; + const char* methodName = eeGetMethodName(hnd, &className); + if ((eeGetHelperNum(hnd) != CORINFO_HELP_UNDEF) || eeIsNativeMethod(hnd)) { - strcpy_s(retName, length, "."); + return methodName; } - strcat_s(retName, length, methodName); + StringPrinter p(getAllocator(CMK_DebugOnly)); + CORINFO_CLASS_HANDLE clsHnd = NO_CLASS_HANDLE; + bool success = eeRunFunctorWithSPMIErrorTrap([&]() { + clsHnd = info.compCompHnd->getMethodClass(hnd); + CORINFO_SIG_INFO sig; + eeGetMethodSig(hnd, &sig); + eePrintMethod(&p, clsHnd, hnd, &sig, + /* includeNamespaces */ true, + /* includeClassInstantiation */ true, + /* includeMethodInstantiation */ true, + /* includeSignature */ true, includeReturnType, includeThisSpecifier); - // append the signature - strcat_s(retName, length, "("); + }); - if (param.siglength > 0) + if (!success) { - param.argLst = param.sig.args; - - for (i = 0; i < param.sig.numArgs; i++) + // Try with bare minimum + p.Truncate(0); + + success = eeRunFunctorWithSPMIErrorTrap([&]() { + eePrintMethod(&p, clsHnd, hnd, + /* sig */ nullptr, + /* includeNamespaces */ true, + /* includeClassInstantiation */ false, + /* includeMethodInstantiation */ false, + /* includeSignature */ false, includeReturnType, includeThisSpecifier); + }); + + if (!success) { - var_types type = eeGetArgType(param.argLst, ¶m.sig); - strcat_s(retName, length, param.pArgNames[i]); - param.argLst = info.compCompHnd->getArgNext(param.argLst); - if (i + 1 < param.sig.numArgs) - { - strcat_s(retName, length, ","); - } + p.Truncate(0); + p.Printf("hackishClassName:hackishMethodName(?)"); } } - strcat_s(retName, length, ")"); - - if (param.returnType != nullptr) - { - strcat_s(retName, length, ":"); - strcat_s(retName, length, param.returnType); - } - - if (param.hasThis) - { - strcat_s(retName, length, ":this"); - } - - assert(strlen(retName) == (length - 1)); - - return (retName); + return p.GetBuffer(); } #endif // defined(DEBUG) || defined(FEATURE_JIT_METHOD_PERF) || defined(FEATURE_SIMD) diff --git a/src/coreclr/jit/emit.h b/src/coreclr/jit/emit.h index c5d245ba83c66d..92fb93df931b47 100644 --- a/src/coreclr/jit/emit.h +++ b/src/coreclr/jit/emit.h @@ -2179,6 +2179,20 @@ class emitter instrDesc* emitLastIns; + // Check if a peephole optimization involving emitLastIns is safe. + // + // We must have a lastInstr to consult. + // The emitForceNewIG check here prevents peepholes from crossing nogc boundaries. + // The final check prevents looking across an IG boundary unless we're in an extension IG. + bool emitCanPeepholeLastIns() + { + return (emitLastIns != nullptr) && // there is an emitLastInstr + !emitForceNewIG && // and we're not about to start a new IG + ((emitCurIGinsCnt > 0) || // and we're not at the start of a new IG + ((emitCurIG->igFlags & IGF_EXTEND) != 0)); // or we are at the start of a new IG, + // and it's an extension IG + } + #ifdef TARGET_ARMARCH instrDesc* emitLastMemBarrier; #endif diff --git a/src/coreclr/jit/emitarm64.cpp b/src/coreclr/jit/emitarm64.cpp index 499b0470ff298e..f94c703ed39225 100644 --- a/src/coreclr/jit/emitarm64.cpp +++ b/src/coreclr/jit/emitarm64.cpp @@ -15940,7 +15940,7 @@ bool emitter::IsRedundantMov(instruction ins, emitAttr size, regNumber dst, regN return false; } - const bool isFirstInstrInBlock = (emitCurIGinsCnt == 0) && ((emitCurIG->igFlags & IGF_EXTEND) == 0); + const bool canOptimize = emitCanPeepholeLastIns(); if (dst == src) { @@ -15960,8 +15960,8 @@ bool emitter::IsRedundantMov(instruction ins, emitAttr size, regNumber dst, regN else if (isGeneralRegisterOrSP(dst) && (size == EA_4BYTE)) { // See if the previous instruction already cleared upper 4 bytes for us unintentionally - if (!isFirstInstrInBlock && (emitLastIns != nullptr) && (emitLastIns->idReg1() == dst) && - (emitLastIns->idOpSize() == size) && emitLastIns->idInsIs(INS_ldr, INS_ldrh, INS_ldrb)) + if (canOptimize && (emitLastIns->idReg1() == dst) && (emitLastIns->idOpSize() == size) && + emitLastIns->idInsIs(INS_ldr, INS_ldrh, INS_ldrb)) { JITDUMP("\n -- suppressing mov because ldr already cleared upper 4 bytes\n"); return true; @@ -15969,8 +15969,7 @@ bool emitter::IsRedundantMov(instruction ins, emitAttr size, regNumber dst, regN } } - if (!isFirstInstrInBlock && // Don't optimize if instruction is not the first instruction in IG. - (emitLastIns != nullptr) && + if (canOptimize && // Don't optimize if unsafe. (emitLastIns->idIns() == INS_mov) && // Don't optimize if last instruction was not 'mov'. (emitLastIns->idOpSize() == size)) // Don't optimize if operand size is different than previous instruction. { @@ -16048,9 +16047,7 @@ bool emitter::IsRedundantMov(instruction ins, emitAttr size, regNumber dst, regN bool emitter::IsRedundantLdStr( instruction ins, regNumber reg1, regNumber reg2, ssize_t imm, emitAttr size, insFormat fmt) { - bool isFirstInstrInBlock = (emitCurIGinsCnt == 0) && ((emitCurIG->igFlags & IGF_EXTEND) == 0); - - if (((ins != INS_ldr) && (ins != INS_str)) || (isFirstInstrInBlock) || (emitLastIns == nullptr)) + if (((ins != INS_ldr) && (ins != INS_str)) || !emitCanPeepholeLastIns()) { return false; } diff --git a/src/coreclr/jit/emitxarch.cpp b/src/coreclr/jit/emitxarch.cpp index cbba666f80eb7b..86cf648e7302b4 100644 --- a/src/coreclr/jit/emitxarch.cpp +++ b/src/coreclr/jit/emitxarch.cpp @@ -314,10 +314,9 @@ bool emitter::IsFlagsAlwaysModified(instrDesc* id) bool emitter::AreUpper32BitsZero(regNumber reg) { - // If there are no instructions in this IG, we can look back at - // the previous IG's instructions if this IG is an extension. + // Only consider if safe // - if ((emitCurIGinsCnt == 0) && ((emitCurIG->igFlags & IGF_EXTEND) == 0)) + if (!emitCanPeepholeLastIns()) { return false; } @@ -397,8 +396,9 @@ bool emitter::AreFlagsSetToZeroCmp(regNumber reg, emitAttr opSize, genTreeOps tr return false; } - // Don't look back across IG boundaries (possible control flow) - if (emitCurIGinsCnt == 0 && ((emitCurIG->igFlags & IGF_EXTEND) == 0)) + // Only consider if safe + // + if (!emitCanPeepholeLastIns()) { return false; } @@ -480,8 +480,9 @@ bool emitter::AreFlagsSetForSignJumpOpt(regNumber reg, emitAttr opSize, GenTree* return false; } - // Don't look back across IG boundaries (possible control flow) - if (emitCurIGinsCnt == 0 && ((emitCurIG->igFlags & IGF_EXTEND) == 0)) + // Only consider if safe + // + if (!emitCanPeepholeLastIns()) { return false; } @@ -4698,13 +4699,10 @@ bool emitter::IsRedundantMov( return true; } - bool isFirstInstrInBlock = (emitCurIGinsCnt == 0) && ((emitCurIG->igFlags & IGF_EXTEND) == 0); - // TODO-XArch-CQ: Certain instructions, such as movaps vs movups, are equivalent in // functionality even if their actual identifier differs and we should optimize these - if (isFirstInstrInBlock || // Don't optimize if instruction is the first instruction in IG. - (emitLastIns == nullptr) || // or if a last instruction doesn't exist + if (!emitCanPeepholeLastIns() || // Don't optimize if unsafe (emitLastIns->idIns() != ins) || // or if the instruction is different from the last instruction (emitLastIns->idOpSize() != size) || // or if the operand size is different from the last instruction (emitLastIns->idInsFmt() != fmt)) // or if the format is different from the last instruction @@ -7343,14 +7341,10 @@ bool emitter::IsRedundantStackMov(instruction ins, insFormat fmt, emitAttr size, return false; } - bool hasSideEffect = HasSideEffect(ins, size); - - bool isFirstInstrInBlock = (emitCurIGinsCnt == 0) && ((emitCurIG->igFlags & IGF_EXTEND) == 0); // TODO-XArch-CQ: Certain instructions, such as movaps vs movups, are equivalent in // functionality even if their actual identifier differs and we should optimize these - if (isFirstInstrInBlock || // Don't optimize if instruction is the first instruction in IG. - (emitLastIns == nullptr) || // or if a last instruction doesn't exist + if (!emitCanPeepholeLastIns() || // Don't optimize if unsafe (emitLastIns->idIns() != ins) || // or if the instruction is different from the last instruction (emitLastIns->idOpSize() != size)) // or if the operand size is different from the last instruction { @@ -7367,6 +7361,8 @@ bool emitter::IsRedundantStackMov(instruction ins, insFormat fmt, emitAttr size, int varNum = emitLastIns->idAddr()->iiaLclVar.lvaVarNum(); int lastOffs = emitLastIns->idAddr()->iiaLclVar.lvaOffset(); + const bool hasSideEffect = HasSideEffect(ins, size); + // Check if the last instruction and current instructions use the same register and local memory. if (varNum == varx && lastReg1 == ireg && lastOffs == offs) { diff --git a/src/coreclr/jit/fgdiagnostic.cpp b/src/coreclr/jit/fgdiagnostic.cpp index b083f7869d2633..73445b88d223da 100644 --- a/src/coreclr/jit/fgdiagnostic.cpp +++ b/src/coreclr/jit/fgdiagnostic.cpp @@ -467,7 +467,7 @@ FILE* Compiler::fgOpenFlowGraphFile(bool* wbDontClose, Phases phase, PhasePositi } #ifdef DEBUG - dumpFunction = JitConfig.JitDumpFg().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args); + dumpFunction = JitConfig.JitDumpFg().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args); filename = JitConfig.JitDumpFgFile(); pathname = JitConfig.JitDumpFgDir(); diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index 86a42a2b87a88e..243eecf117675f 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -658,8 +658,8 @@ PhaseStatus Compiler::fgInline() } #ifdef DEBUG - fgPrintInlinedMethods = JitConfig.JitPrintInlinedMethods().contains(info.compMethodName, info.compClassName, - &info.compMethodInfo->args); + fgPrintInlinedMethods = + JitConfig.JitPrintInlinedMethods().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args); #endif // DEBUG noway_assert(fgFirstBB != nullptr); diff --git a/src/coreclr/jit/fgprofile.cpp b/src/coreclr/jit/fgprofile.cpp index bf3e77fc78405e..39190c6a48fa65 100644 --- a/src/coreclr/jit/fgprofile.cpp +++ b/src/coreclr/jit/fgprofile.cpp @@ -570,7 +570,7 @@ void BlockCountInstrumentor::BuildSchemaElements(BasicBlock* block, Schema& sche ICorJitInfo::PgoInstrumentationSchema schemaElem; schemaElem.Count = 1; schemaElem.Other = 0; - schemaElem.InstrumentationKind = JitConfig.JitCollect64BitCounts() + schemaElem.InstrumentationKind = m_comp->opts.compCollect64BitCounts ? ICorJitInfo::PgoInstrumentationKind::BasicBlockLongCount : ICorJitInfo::PgoInstrumentationKind::BasicBlockIntCount; schemaElem.ILOffset = offset; @@ -1314,7 +1314,7 @@ void EfficientEdgeCountInstrumentor::BuildSchemaElements(BasicBlock* block, Sche ICorJitInfo::PgoInstrumentationSchema schemaElem; schemaElem.Count = 1; schemaElem.Other = targetKey; - schemaElem.InstrumentationKind = JitConfig.JitCollect64BitCounts() + schemaElem.InstrumentationKind = m_comp->opts.compCollect64BitCounts ? ICorJitInfo::PgoInstrumentationKind::EdgeLongCount : ICorJitInfo::PgoInstrumentationKind::EdgeIntCount; schemaElem.ILOffset = sourceKey; @@ -1503,7 +1503,7 @@ class BuildHandleHistogramProbeSchemaGen schemaElem.Other |= ICorJitInfo::HandleHistogram32::DELEGATE_FLAG; } - schemaElem.InstrumentationKind = JitConfig.JitCollect64BitCounts() + schemaElem.InstrumentationKind = compiler->opts.compCollect64BitCounts ? ICorJitInfo::PgoInstrumentationKind::HandleHistogramLongCount : ICorJitInfo::PgoInstrumentationKind::HandleHistogramIntCount; schemaElem.ILOffset = (int32_t)call->gtHandleHistogramProfileCandidateInfo->ilOffset; diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 3b7c12f25ccb12..dbe9fdbb57737e 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -18631,10 +18631,45 @@ bool GenTree::isCommutativeHWIntrinsic() const assert(gtOper == GT_HWINTRINSIC); #ifdef TARGET_XARCH - return HWIntrinsicInfo::IsCommutative(AsHWIntrinsic()->GetHWIntrinsicId()); -#else - return false; + const GenTreeHWIntrinsic* node = AsHWIntrinsic(); + NamedIntrinsic id = node->GetHWIntrinsicId(); + + if (HWIntrinsicInfo::IsCommutative(id)) + { + return true; + } + + if (HWIntrinsicInfo::IsMaybeCommutative(id)) + { + switch (id) + { + case NI_SSE_Max: + case NI_SSE_Min: + { + return false; + } + + case NI_SSE2_Max: + case NI_SSE2_Min: + { + return !varTypeIsFloating(node->GetSimdBaseType()); + } + + case NI_AVX_Max: + case NI_AVX_Min: + { + return false; + } + + default: + { + unreached(); + } + } + } #endif // TARGET_XARCH + + return false; } bool GenTree::isContainableHWIntrinsic() const @@ -21795,13 +21830,11 @@ GenTree* Compiler::gtNewSimdShuffleNode(var_types type, if (needsZero) { - assert(!compIsaSupportedDebugOnly(InstructionSet_SSSE3)); + assert((simdSize == 32) || !compIsaSupportedDebugOnly(InstructionSet_SSSE3)); op2 = gtNewVconNode(type, simdBaseJitType); - op2->AsVecCon()->gtSimd16Val = mskCns.v128[0]; - - GenTree* zero = gtNewZeroConNode(type, simdBaseJitType); - retNode = gtNewSimdCndSelNode(type, op2, retNode, zero, simdBaseJitType, simdSize, isSimdAsHWIntrinsic); + op2->AsVecCon()->gtSimd32Val = mskCns; + retNode = gtNewSimdBinOpNode(GT_AND, type, op2, retNode, simdBaseJitType, simdSize, isSimdAsHWIntrinsic); } return retNode; diff --git a/src/coreclr/jit/hwintrinsic.h b/src/coreclr/jit/hwintrinsic.h index 88e5b4ae575059..b1299df1c1f1cf 100644 --- a/src/coreclr/jit/hwintrinsic.h +++ b/src/coreclr/jit/hwintrinsic.h @@ -153,6 +153,11 @@ enum HWIntrinsicFlag : unsigned int // the intrinsic can be used on hardware with AVX but not AVX2 support HW_Flag_AvxOnlyCompatible = 0x40000, + // MaybeCommutative + // - if a binary-op intrinsic is maybe commutative (e.g., Max or Min for float/double), its op1 can possibly be + // contained + HW_Flag_MaybeCommutative = 0x80000, + #elif defined(TARGET_ARM64) // The intrinsic has an immediate operand // - the value can be (and should be) encoded in a corresponding instruction when the operand value is constant @@ -626,6 +631,18 @@ struct HWIntrinsicInfo return (flags & HW_Flag_Commutative) != 0; } + static bool IsMaybeCommutative(NamedIntrinsic id) + { + HWIntrinsicFlag flags = lookupFlags(id); +#if defined(TARGET_XARCH) + return (flags & HW_Flag_MaybeCommutative) != 0; +#elif defined(TARGET_ARM64) + return false; +#else +#error Unsupported platform +#endif + } + static bool RequiresCodegen(NamedIntrinsic id) { HWIntrinsicFlag flags = lookupFlags(id); diff --git a/src/coreclr/jit/hwintrinsiclistxarch.h b/src/coreclr/jit/hwintrinsiclistxarch.h index 7068e39bd1b560..0fff3a98d3bec3 100644 --- a/src/coreclr/jit/hwintrinsiclistxarch.h +++ b/src/coreclr/jit/hwintrinsiclistxarch.h @@ -290,9 +290,9 @@ HARDWARE_INTRINSIC(SSE, LoadHigh, HARDWARE_INTRINSIC(SSE, LoadLow, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movlps, INS_invalid}, HW_Category_MemoryLoad, HW_Flag_NoRMWSemantics) HARDWARE_INTRINSIC(SSE, LoadScalarVector128, 16, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movss, INS_invalid}, HW_Category_MemoryLoad, HW_Flag_NoRMWSemantics) HARDWARE_INTRINSIC(SSE, LoadVector128, 16, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movups, INS_invalid}, HW_Category_MemoryLoad, HW_Flag_NoRMWSemantics) -HARDWARE_INTRINSIC(SSE, Max, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_maxps, INS_invalid}, HW_Category_SimpleSIMD, HW_Flag_Commutative) +HARDWARE_INTRINSIC(SSE, Max, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_maxps, INS_invalid}, HW_Category_SimpleSIMD, HW_Flag_MaybeCommutative) HARDWARE_INTRINSIC(SSE, MaxScalar, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_maxss, INS_invalid}, HW_Category_SIMDScalar, HW_Flag_CopyUpperBits) -HARDWARE_INTRINSIC(SSE, Min, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_minps, INS_invalid}, HW_Category_SimpleSIMD, HW_Flag_Commutative) +HARDWARE_INTRINSIC(SSE, Min, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_minps, INS_invalid}, HW_Category_SimpleSIMD, HW_Flag_MaybeCommutative) HARDWARE_INTRINSIC(SSE, MinScalar, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_minss, INS_invalid}, HW_Category_SIMDScalar, HW_Flag_CopyUpperBits) HARDWARE_INTRINSIC(SSE, MoveHighToLow, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movhlps, INS_invalid}, HW_Category_SimpleSIMD, HW_Flag_NoContainment) HARDWARE_INTRINSIC(SSE, MoveLowToHigh, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movlhps, INS_invalid}, HW_Category_SimpleSIMD, HW_Flag_NoContainment) @@ -403,10 +403,10 @@ HARDWARE_INTRINSIC(SSE2, LoadLow, HARDWARE_INTRINSIC(SSE2, LoadScalarVector128, 16, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movd, INS_movd, INS_movq, INS_movq, INS_invalid, INS_movsdsse2}, HW_Category_MemoryLoad, HW_Flag_NoRMWSemantics) HARDWARE_INTRINSIC(SSE2, LoadVector128, 16, 1, {INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_invalid, INS_movupd}, HW_Category_MemoryLoad, HW_Flag_NoRMWSemantics) HARDWARE_INTRINSIC(SSE2, MaskMove, 16, 3, {INS_maskmovdqu, INS_maskmovdqu, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_MemoryStore, HW_Flag_NoContainment|HW_Flag_NoRMWSemantics|HW_Flag_BaseTypeFromSecondArg) -HARDWARE_INTRINSIC(SSE2, Max, 16, 2, {INS_invalid, INS_pmaxub, INS_pmaxsw, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_maxpd}, HW_Category_SimpleSIMD, HW_Flag_Commutative) +HARDWARE_INTRINSIC(SSE2, Max, 16, 2, {INS_invalid, INS_pmaxub, INS_pmaxsw, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_maxpd}, HW_Category_SimpleSIMD, HW_Flag_MaybeCommutative) HARDWARE_INTRINSIC(SSE2, MemoryFence, 0, 0, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Special, HW_Flag_NoContainment|HW_Flag_NoRMWSemantics) HARDWARE_INTRINSIC(SSE2, MaxScalar, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_maxsd}, HW_Category_SIMDScalar, HW_Flag_CopyUpperBits) -HARDWARE_INTRINSIC(SSE2, Min, 16, 2, {INS_invalid, INS_pminub, INS_pminsw, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_minpd}, HW_Category_SimpleSIMD, HW_Flag_Commutative) +HARDWARE_INTRINSIC(SSE2, Min, 16, 2, {INS_invalid, INS_pminub, INS_pminsw, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_minpd}, HW_Category_SimpleSIMD, HW_Flag_MaybeCommutative) HARDWARE_INTRINSIC(SSE2, MinScalar, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_minsd}, HW_Category_SIMDScalar, HW_Flag_CopyUpperBits) HARDWARE_INTRINSIC(SSE2, MoveMask, 16, 1, {INS_pmovmskb, INS_pmovmskb, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movmskpd}, HW_Category_SimpleSIMD, HW_Flag_NoContainment|HW_Flag_NoRMWSemantics|HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(SSE2, MoveScalar, 16, -1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movq, INS_movq, INS_invalid, INS_movsdsse2}, HW_Category_SIMDScalar, HW_Flag_NoContainment) @@ -598,8 +598,8 @@ HARDWARE_INTRINSIC(AVX, InsertVector128, HARDWARE_INTRINSIC(AVX, LoadAlignedVector256, 32, 1, {INS_movdqa, INS_movdqa, INS_movdqa, INS_movdqa, INS_movdqa, INS_movdqa, INS_movdqa, INS_movdqa, INS_movaps, INS_movapd}, HW_Category_MemoryLoad, HW_Flag_NoRMWSemantics) HARDWARE_INTRINSIC(AVX, LoadDquVector256, 32, 1, {INS_lddqu, INS_lddqu, INS_lddqu, INS_lddqu, INS_lddqu, INS_lddqu, INS_lddqu, INS_lddqu, INS_invalid, INS_invalid}, HW_Category_MemoryLoad, HW_Flag_NoRMWSemantics) HARDWARE_INTRINSIC(AVX, LoadVector256, 32, 1, {INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movups, INS_movupd}, HW_Category_MemoryLoad, HW_Flag_NoRMWSemantics) -HARDWARE_INTRINSIC(AVX, Max, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_maxps, INS_maxpd}, HW_Category_SimpleSIMD, HW_Flag_Commutative) -HARDWARE_INTRINSIC(AVX, Min, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_minps, INS_minpd}, HW_Category_SimpleSIMD, HW_Flag_Commutative) +HARDWARE_INTRINSIC(AVX, Max, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_maxps, INS_maxpd}, HW_Category_SimpleSIMD, HW_Flag_MaybeCommutative) +HARDWARE_INTRINSIC(AVX, Min, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_minps, INS_minpd}, HW_Category_SimpleSIMD, HW_Flag_MaybeCommutative) HARDWARE_INTRINSIC(AVX, MaskLoad, -1, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_vmaskmovps, INS_vmaskmovpd}, HW_Category_MemoryLoad, HW_Flag_NoFlag) HARDWARE_INTRINSIC(AVX, MaskStore, -1, 3, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_vmaskmovps, INS_vmaskmovpd}, HW_Category_MemoryStore, HW_Flag_NoContainment|HW_Flag_BaseTypeFromSecondArg) HARDWARE_INTRINSIC(AVX, MoveMask, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movmskps, INS_movmskpd}, HW_Category_SimpleSIMD, HW_Flag_NoContainment|HW_Flag_BaseTypeFromFirstArg) diff --git a/src/coreclr/jit/hwintrinsicxarch.cpp b/src/coreclr/jit/hwintrinsicxarch.cpp index f8e8a38695ba04..0992e70bec3d97 100644 --- a/src/coreclr/jit/hwintrinsicxarch.cpp +++ b/src/coreclr/jit/hwintrinsicxarch.cpp @@ -702,8 +702,11 @@ GenTree* Compiler::impBaseIntrinsic(NamedIntrinsic intrinsic, { assert(sig->numArgs == 1); assert(HWIntrinsicInfo::BaseTypeFromFirstArg(intrinsic)); - assert(simdBaseJitType == - getBaseJitTypeAndSizeOfSIMDType(info.compCompHnd->getArgClass(sig, sig->args), &simdSize)); + + CorInfoType op1SimdBaseJitType = + getBaseJitTypeAndSizeOfSIMDType(info.compCompHnd->getArgClass(sig, sig->args), &simdSize); + + assert(simdBaseJitType == op1SimdBaseJitType); switch (getSIMDTypeForSize(simdSize)) { diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 6fbafbcc993313..6cd927dedb619e 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -21589,8 +21589,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // Optionally, print info on devirtualization Compiler* const rootCompiler = impInlineRoot(); - const bool doPrint = JitConfig.JitPrintDevirtualizedMethods().contains(rootCompiler->info.compMethodName, - rootCompiler->info.compClassName, + const bool doPrint = JitConfig.JitPrintDevirtualizedMethods().contains(rootCompiler->info.compMethodHnd, + rootCompiler->info.compClassHnd, &rootCompiler->info.compMethodInfo->args); #endif // DEBUG diff --git a/src/coreclr/jit/importer_vectorization.cpp b/src/coreclr/jit/importer_vectorization.cpp index f394859ca7292d..217e22104e6ae4 100644 --- a/src/coreclr/jit/importer_vectorization.cpp +++ b/src/coreclr/jit/importer_vectorization.cpp @@ -380,7 +380,7 @@ GenTree* Compiler::impExpandHalfConstEqualsSWAR( // [ ch1 ] // [value] // - return impCreateCompareInd(data, TYP_SHORT, dataOffset, cns[0], cmpMode); + return impCreateCompareInd(data, TYP_USHORT, dataOffset, cns[0], cmpMode); } if (len == 2) { diff --git a/src/coreclr/jit/instr.cpp b/src/coreclr/jit/instr.cpp index 6d9494e59b541b..572a556c0858d6 100644 --- a/src/coreclr/jit/instr.cpp +++ b/src/coreclr/jit/instr.cpp @@ -765,7 +765,13 @@ CodeGen::OperandDesc CodeGen::genOperandDesc(GenTree* op) case TYP_SIMD12: case TYP_SIMD16: { - simd16_t constValue = op->AsVecCon()->gtSimd16Val; + simd16_t constValue = {}; + + if (op->TypeIs(TYP_SIMD12)) + memcpy(&constValue, &op->AsVecCon()->gtSimd12Val, sizeof(simd12_t)); + else + constValue = op->AsVecCon()->gtSimd16Val; + return OperandDesc(emit->emitSimd16Const(constValue)); } diff --git a/src/coreclr/jit/jitconfig.cpp b/src/coreclr/jit/jitconfig.cpp index 6fdbad427fe8a4..7e055f0726bb93 100644 --- a/src/coreclr/jit/jitconfig.cpp +++ b/src/coreclr/jit/jitconfig.cpp @@ -36,261 +36,51 @@ void JitConfigValues::MethodSet::initialize(const WCHAR* list, ICorJitHost* host } } - const char SEP_CHAR = ' '; // character used to separate each entry - const char WILD_CHAR = '*'; // character used as the wildcard match everything - char currChar = '?'; // The current character - int nameStart = -1; // Index of the start of the current class or method name - MethodName** lastName = &m_names; // Last entry inserted into the list - bool isQuoted = false; // true while parsing inside a quote "this-is-a-quoted-region" - MethodName currentName; // Buffer used while parsing the current entry - - currentName.m_next = nullptr; - currentName.m_methodNameStart = -1; - currentName.m_methodNameLen = -1; - currentName.m_methodNameWildcardAtEnd = false; - currentName.m_classNameStart = -1; - currentName.m_classNameLen = -1; - currentName.m_classNameWildcardAtEnd = false; - currentName.m_numArgs = -1; - - enum State - { - NO_NAME, - CLS_NAME, - FUNC_NAME, - ARG_LIST - }; // parsing state machine - - State state = NO_NAME; - for (int i = 0; (currChar != '\0'); i++) - { - currChar = m_list[i]; + auto commitPattern = [this, host](const char* start, const char* end) { + if (end <= start) + { + return; + } + + MethodName* name = static_cast(host->allocateMemory(sizeof(MethodName))); + name->m_next = m_names; + name->m_patternStart = start; + name->m_patternEnd = end; + const char* colon = static_cast(memchr(start, ':', end - start)); + const char* startOfMethodName = colon != nullptr ? colon + 1 : start; - switch (state) + const char* parens = static_cast(memchr(startOfMethodName, '(', end - startOfMethodName)); + const char* endOfMethodName = parens != nullptr ? parens : end; + name->m_methodNameContainsInstantiation = + memchr(startOfMethodName, '[', endOfMethodName - startOfMethodName) != nullptr; + + if (colon != nullptr) + { + name->m_containsClassName = true; + name->m_classNameContainsInstantiation = memchr(start, '[', colon - start) != nullptr; + } + else { - case NO_NAME: - // skip over zero or more blanks, then expect CLS_NAME - if (currChar != SEP_CHAR) - { - nameStart = i; - state = CLS_NAME; // we have found the start of the next entry - } - break; - - case CLS_NAME: - // Check for a quoted Class Name: (i.e. "MyClass") - if (m_list[nameStart] == '"') - { - // Advance until we see the second " - // - for (; (currChar != '\0'); i++) - { - currChar = m_list[i]; - // Advance until we see the second " - if (currChar == '"') - { - break; - } - // or until we see the end of string - if (currChar == '\0') - { - break; - } - } - - // skip the initial " - nameStart++; - isQuoted = true; - } - - // A colon denotes the end of the Class name and the start of the Method name - if (currChar == ':') - { - // Record the class name - currentName.m_classNameStart = nameStart; - currentName.m_classNameLen = i - nameStart; - - // Also accept the double colon syntax as well (i.e class::method) - // - if (m_list[i + 1] == ':') - { - i++; - } - - if (isQuoted) - { - // Remove the trailing " - currentName.m_classNameLen--; - isQuoted = false; - } - - // Is the first character a wildcard? - if (m_list[currentName.m_classNameStart] == WILD_CHAR) - { - // The class name is a full wildcard; mark it as such. - currentName.m_classNameStart = -1; - currentName.m_classNameLen = -1; - } - // Is there a wildcard at the end of the class name? - // - else if (m_list[currentName.m_classNameStart + currentName.m_classNameLen - 1] == WILD_CHAR) - { - // i.e. bar*:method, will match any class that starts with "bar" - - // Remove the trailing WILD_CHAR from class name - currentName.m_classNameWildcardAtEnd = true; - currentName.m_classNameLen--; // backup for WILD_CHAR - } - - // The method name will start at the next character - nameStart = i + 1; - - // Now expect FUNC_NAME - state = FUNC_NAME; - } - else if ((currChar == '\0') || (currChar == SEP_CHAR) || (currChar == '(')) - { - // Treat this as a method name without a class name. - currentName.m_classNameStart = -1; - currentName.m_classNameLen = -1; - goto DONE_FUNC_NAME; - } - break; - - case FUNC_NAME: - // Check for a quoted method name: i.e. className:"MyFunc" - // - // Note that we may have already parsed a quoted string above in CLS_NAME, i.e. "Func": - if (!isQuoted && (m_list[nameStart] == '"')) - { - // Advance until we see the second " - // - for (; (currChar != '\0'); i++) - { - currChar = m_list[i]; - // Advance until we see the second " - if (currChar == '"') - { - break; - } - // or until we see the end of string - if (currChar == '\0') - { - break; - } - } - - // skip the initial " - nameStart++; - isQuoted = true; - } - - if ((currChar == '\0') || (currChar == SEP_CHAR) || (currChar == '(')) - { - DONE_FUNC_NAME: - assert((currChar == '\0') || (currChar == SEP_CHAR) || (currChar == '(')); - - // Record the method name - currentName.m_methodNameStart = nameStart; - currentName.m_methodNameLen = i - nameStart; - - if (isQuoted) - { - // Remove the trailing " - currentName.m_methodNameLen--; - isQuoted = false; - } - - // Is the first character a wildcard? - if (m_list[currentName.m_methodNameStart] == WILD_CHAR) - { - // The method name is a full wildcard; mark it as such. - currentName.m_methodNameStart = -1; - currentName.m_methodNameLen = -1; - } - // Is there a wildcard at the end of the method name? - // - else if (m_list[currentName.m_methodNameStart + currentName.m_methodNameLen - 1] == WILD_CHAR) - { - // i.e. class:foo*, will match any method that starts with "foo" - - // Remove the trailing WILD_CHAR from method name - currentName.m_methodNameLen--; // backup for WILD_CHAR - currentName.m_methodNameWildcardAtEnd = true; - } - - // should we expect an ARG_LIST? - // - if (currChar == '(') - { - currentName.m_numArgs = -1; - // Expect an ARG_LIST - state = ARG_LIST; - } - else // reached the end of string or a SEP_CHAR - { - assert((currChar == '\0') || (currChar == SEP_CHAR)); - - currentName.m_numArgs = -1; - - // There isn't an ARG_LIST - goto DONE_ARG_LIST; - } - } - break; - - case ARG_LIST: - if ((currChar == '\0') || (currChar == ')')) - { - if (currentName.m_numArgs == -1) - { - currentName.m_numArgs = 0; - } - - DONE_ARG_LIST: - assert((currChar == '\0') || (currChar == SEP_CHAR) || (currChar == ')')); - - // We have parsed an entire method name; create a new entry in the list for it. - MethodName* name = static_cast(host->allocateMemory(sizeof(MethodName))); - *name = currentName; - - assert(name->m_next == nullptr); - *lastName = name; - lastName = &name->m_next; - - state = NO_NAME; - - // Skip anything after the argument list until we find the next - // separator character. Otherwise if we see "func(a,b):foo" we - // would create entries for "func(a,b)" as well as ":foo". - if (currChar == ')') - { - do - { - currChar = m_list[++i]; - } while ((currChar != '\0') && (currChar != SEP_CHAR)); - } - } - else // We are looking at the ARG_LIST - { - if ((currChar != SEP_CHAR) && (currentName.m_numArgs == -1)) - { - currentName.m_numArgs = 1; - } - - // A comma means that there is an additional arg - if (currChar == ',') - { - currentName.m_numArgs++; - } - } - break; - - default: - assert(!"Bad state"); - break; + name->m_containsClassName = false; + name->m_classNameContainsInstantiation = false; + } + + name->m_containsSignature = parens != nullptr; + m_names = name; + }; + + const char* curPatternStart = m_list; + const char* curChar; + for (curChar = curPatternStart; *curChar != '\0'; curChar++) + { + if (*curChar == ' ') + { + commitPattern(curPatternStart, curChar); + curPatternStart = curChar + 1; } } + + commitPattern(curPatternStart, curChar); } void JitConfigValues::MethodSet::destroy(ICorJitHost* host) @@ -309,83 +99,91 @@ void JitConfigValues::MethodSet::destroy(ICorJitHost* host) m_names = nullptr; } -static bool matchesName(const char* const name, int nameLen, bool wildcardAtEnd, const char* const s2) +// Quadratic string matching algorithm that supports * and ? wildcards +static bool matchGlob(const char* pattern, const char* patternEnd, const char* str) { - // 's2' must start with 'nameLen' characters of 'name' - if (strncmp(name, s2, nameLen) != 0) + // Invariant: [patternStart..backtrackPattern) matches [stringStart..backtrackStr) + const char* backtrackPattern = nullptr; + const char* backtrackStr = nullptr; + + while (true) { - return false; + if (pattern == patternEnd) + { + if (*str == '\0') + return true; + } + else if (*pattern == '*') + { + backtrackPattern = ++pattern; + backtrackStr = str; + continue; + } + else if (*str == '\0') + { + // No match since pattern needs at least one char in remaining cases. + } + else if ((*pattern == '?') || (*pattern == *str)) + { + pattern++; + str++; + continue; + } + + // In this case there was no match, see if we can backtrack to a wild + // card and consume one more character from the string. + if ((backtrackPattern == nullptr) || (*backtrackStr == '\0')) + return false; + + // Consume one more character for the wildcard. + pattern = backtrackPattern; + str = ++backtrackStr; } +} - // if we don't have a wildcardAtEnd then s2 also need to be zero terminated - if (!wildcardAtEnd && (s2[nameLen] != '\0')) +bool JitConfigValues::MethodSet::contains(CORINFO_METHOD_HANDLE methodHnd, + CORINFO_CLASS_HANDLE classHnd, + CORINFO_SIG_INFO* sigInfo) const +{ + if (isEmpty()) { return false; } - // we have a successful match - return true; -} - -bool JitConfigValues::MethodSet::contains(const char* methodName, - const char* className, - CORINFO_SIG_INFO* sigInfo) const -{ - int numArgs = sigInfo != nullptr ? sigInfo->numArgs : -1; + Compiler* comp = JitTls::GetCompiler(); + char buffer[1024]; + StringPrinter printer(comp->getAllocator(CMK_DebugOnly), buffer, ArrLen(buffer)); + MethodName* prevPattern = nullptr; - // Try to match any the entries in the list. for (MethodName* name = m_names; name != nullptr; name = name->m_next) { - // If m_numArgs is valid, check for a mismatch - if (name->m_numArgs != -1 && name->m_numArgs != numArgs) - { - continue; - } - - // If m_methodNameStart is valid, check for a mismatch - if (name->m_methodNameStart != -1) + if ((prevPattern == nullptr) || (name->m_containsClassName != prevPattern->m_containsClassName) || + (name->m_classNameContainsInstantiation != prevPattern->m_classNameContainsInstantiation) || + (name->m_methodNameContainsInstantiation != prevPattern->m_methodNameContainsInstantiation) || + (name->m_containsSignature != prevPattern->m_containsSignature)) { - const char* expectedMethodName = &m_list[name->m_methodNameStart]; - if (!matchesName(expectedMethodName, name->m_methodNameLen, name->m_methodNameWildcardAtEnd, methodName)) - { - // C++ embeds the class name into the method name; deal with that here. - const char* colon = strchr(methodName, ':'); - if (colon != nullptr && colon[1] == ':' && - matchesName(expectedMethodName, name->m_methodNameLen, name->m_methodNameWildcardAtEnd, methodName)) - { - int classLen = (int)(colon - methodName); - if (name->m_classNameStart == -1 || - (classLen == name->m_classNameLen && - strncmp(&m_list[name->m_classNameStart], methodName, classLen) == 0)) - { - return true; - } - } + printer.Truncate(0); + bool success = comp->eeRunFunctorWithSPMIErrorTrap([&]() { + comp->eePrintMethod(&printer, name->m_containsClassName ? classHnd : NO_CLASS_HANDLE, methodHnd, + sigInfo, + /* includeNamespaces */ true, + /* includeClassInstantiation */ name->m_classNameContainsInstantiation, + /* includeMethodInstantiation */ name->m_methodNameContainsInstantiation, + /* includeSignature */ name->m_containsSignature, + /* includeReturnType */ false, + /* includeThis */ false); + }); + + if (!success) continue; - } - } - // If m_classNameStart is valid, check for a mismatch - if (className == nullptr || name->m_classNameStart == -1 || - matchesName(&m_list[name->m_classNameStart], name->m_classNameLen, name->m_classNameWildcardAtEnd, - className)) - { - return true; + prevPattern = name; } -#ifdef _DEBUG - // Maybe className doesn't include the namespace. Try to match that - const char* nsSep = strrchr(className, '.'); - if (nsSep != nullptr && nsSep != className) + if (matchGlob(name->m_patternStart, name->m_patternEnd, printer.GetBuffer())) { - const char* onlyClass = nsSep[-1] == '.' ? nsSep : &nsSep[1]; - if (matchesName(&m_list[name->m_classNameStart], name->m_classNameLen, name->m_classNameWildcardAtEnd, - onlyClass)) - { - return true; - } + return true; } -#endif } return false; diff --git a/src/coreclr/jit/jitconfig.h b/src/coreclr/jit/jitconfig.h index 12d327d292b399..e19021cd52f22b 100644 --- a/src/coreclr/jit/jitconfig.h +++ b/src/coreclr/jit/jitconfig.h @@ -7,6 +7,8 @@ #include "switches.h" struct CORINFO_SIG_INFO; +typedef struct CORINFO_CLASS_STRUCT_* CORINFO_CLASS_HANDLE; +typedef struct CORINFO_METHOD_STRUCT_* CORINFO_METHOD_HANDLE; class ICorJitHost; class JitConfigValues @@ -18,13 +20,12 @@ class JitConfigValues struct MethodName { MethodName* m_next; - int m_methodNameStart; - int m_methodNameLen; - bool m_methodNameWildcardAtEnd; - int m_classNameStart; - int m_classNameLen; - bool m_classNameWildcardAtEnd; - int m_numArgs; + const char* m_patternStart; + const char* m_patternEnd; + bool m_containsClassName; + bool m_classNameContainsInstantiation; + bool m_methodNameContainsInstantiation; + bool m_containsSignature; }; char* m_list; @@ -50,7 +51,7 @@ class JitConfigValues { return m_names == nullptr; } - bool contains(const char* methodName, const char* className, CORINFO_SIG_INFO* sigInfo) const; + bool contains(CORINFO_METHOD_HANDLE methodHnd, CORINFO_CLASS_HANDLE classHnd, CORINFO_SIG_INFO* sigInfo) const; }; private: diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 3e6df61b1f71fd..ce7df4f88e54f6 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -26,15 +26,14 @@ CONFIG_INTEGER(DiffableDasm, W("JitDiffableDasm"), 0) // Make the disas CONFIG_INTEGER(JitDasmWithAddress, W("JitDasmWithAddress"), 0) // Print the process address next to each instruction of // the disassembly CONFIG_INTEGER(DisplayLoopHoistStats, W("JitLoopHoistStats"), 0) // Display JIT loop hoisting statistics -CONFIG_INTEGER(DisplayLsraStats, W("JitLsraStats"), 0) // Display JIT Linear Scan Register Allocator statistics - // If set to "1", display the stats in textual format. - // If set to "2", display the stats in csv format. - // If set to "3", display the stats in summarize format. - // Recommended to use with JitStdOutFile flag. -CONFIG_STRING(JitLsraOrdering, W("JitLsraOrdering")) // LSRA heuristics ordering -CONFIG_INTEGER(DumpJittedMethods, W("DumpJittedMethods"), 0) // Prints all jitted methods to the console -CONFIG_INTEGER(EnablePCRelAddr, W("JitEnablePCRelAddr"), 1) // Whether absolute addr be encoded as PC-rel offset by - // RyuJIT where possible +CONFIG_INTEGER(DisplayLsraStats, W("JitLsraStats"), 0) // Display JIT Linear Scan Register Allocator statistics + // If set to "1", display the stats in textual format. + // If set to "2", display the stats in csv format. + // If set to "3", display the stats in summarize format. + // Recommended to use with JitStdOutFile flag. +CONFIG_STRING(JitLsraOrdering, W("JitLsraOrdering")) // LSRA heuristics ordering +CONFIG_INTEGER(EnablePCRelAddr, W("JitEnablePCRelAddr"), 1) // Whether absolute addr be encoded as PC-rel offset by + // RyuJIT where possible CONFIG_INTEGER(JitAssertOnMaxRAPasses, W("JitAssertOnMaxRAPasses"), 0) CONFIG_INTEGER(JitBreakEmitOutputInstr, W("JitBreakEmitOutputInstr"), -1) CONFIG_INTEGER(JitBreakMorphTree, W("JitBreakMorphTree"), 0xffffffff) @@ -191,10 +190,11 @@ CONFIG_STRING(JitDisasmAssemblies, W("JitDisasmAssemblies")) // Only show JitDis CONFIG_INTEGER(JitDisasmWithGC, W("JitDisasmWithGC"), 0) // Dump interleaved GC Info for any method disassembled. CONFIG_INTEGER(JitDisasmWithDebugInfo, W("JitDisasmWithDebugInfo"), 0) // Dump interleaved debug info for any method // disassembled. -CONFIG_METHODSET(JitDump, W("JitDump")) // Dumps trees for specified method -CONFIG_INTEGER(JitDumpTier0, W("JitDumpTier0"), 1) // Dump tier0 requests -CONFIG_INTEGER(JitDumpAtOSROffset, W("JitDumpAtOSROffset"), -1) // Only dump OSR requests for this offset -CONFIG_INTEGER(JitDumpInlinePhases, W("JitDumpInlinePhases"), 1) // Dump inline compiler phases +CONFIG_INTEGER(JitDisasmSpilled, W("JitDisasmSpilled"), 0) // Display native code when any register spilling occurs +CONFIG_METHODSET(JitDump, W("JitDump")) // Dumps trees for specified method +CONFIG_INTEGER(JitDumpTier0, W("JitDumpTier0"), 1) // Dump tier0 requests +CONFIG_INTEGER(JitDumpAtOSROffset, W("JitDumpAtOSROffset"), -1) // Only dump OSR requests for this offset +CONFIG_INTEGER(JitDumpInlinePhases, W("JitDumpInlinePhases"), 1) // Dump inline compiler phases CONFIG_METHODSET(JitEHDump, W("JitEHDump")) // Dump the EH table for the method, as reported to the VM CONFIG_METHODSET(JitExclude, W("JitExclude")) CONFIG_INTEGER(JitFakeProcedureSplitting, W("JitFakeProcedureSplitting"), 0) // Do code splitting independent of VM. @@ -257,6 +257,8 @@ CONFIG_INTEGER(EnableIncompleteISAClass, W("EnableIncompleteISAClass"), 0) // En CONFIG_METHODSET(JitDisasm, W("JitDisasm")) #endif // !defined(DEBUG) +CONFIG_INTEGER(JitDisasmSummary, W("JitDisasmSummary"), 0) // Prints all jitted methods to the console + CONFIG_INTEGER(RichDebugInfo, W("RichDebugInfo"), 0) // If 1, keep rich debug info and report it back to the EE #ifdef DEBUG @@ -563,7 +565,9 @@ CONFIG_STRING(JitEnablePgoRange, W("JitEnablePgoRange")) // Enable pgo d CONFIG_INTEGER(JitRandomEdgeCounts, W("JitRandomEdgeCounts"), 0) // Substitute random values for edge counts CONFIG_INTEGER(JitCrossCheckDevirtualizationAndPGO, W("JitCrossCheckDevirtualizationAndPGO"), 0) CONFIG_INTEGER(JitNoteFailedExactDevirtualization, W("JitNoteFailedExactDevirtualization"), 0) -#endif // debug +CONFIG_INTEGER(JitRandomlyCollect64BitCounts, W("JitRandomlyCollect64BitCounts"), 0) // Collect 64-bit counts randomly + // for some methods. +#endif // debug // Devirtualize virtual calls with getExactClasses (NativeAOT only for now) CONFIG_INTEGER(JitEnableExactDevirtualization, W("JitEnableExactDevirtualization"), 1) diff --git a/src/coreclr/jit/loopcloning.cpp b/src/coreclr/jit/loopcloning.cpp index b0027ad6717106..d2f3c2519dbb7c 100644 --- a/src/coreclr/jit/loopcloning.cpp +++ b/src/coreclr/jit/loopcloning.cpp @@ -191,7 +191,15 @@ GenTree* LC_Condition::ToGenTree(Compiler* comp, BasicBlock* bb, bool invert) GenTree* op1Tree = op1.ToGenTree(comp, bb); GenTree* op2Tree = op2.ToGenTree(comp, bb); assert(genTypeSize(genActualType(op1Tree->TypeGet())) == genTypeSize(genActualType(op2Tree->TypeGet()))); - return comp->gtNewOperNode(invert ? GenTree::ReverseRelop(oper) : oper, TYP_INT, op1Tree, op2Tree); + + GenTree* result = comp->gtNewOperNode(invert ? GenTree::ReverseRelop(oper) : oper, TYP_INT, op1Tree, op2Tree); + + if (compareUnsigned) + { + result->gtFlags |= GTF_UNSIGNED; + } + + return result; } //-------------------------------------------------------------------------------------------------- @@ -941,12 +949,14 @@ void LC_ArrayDeref::DeriveLevelConditions(JitExpandArrayStack= 0) && (i < a.len). + // We fold the two compares into one using unsigned compare, since we know a.len is non-negative. + // LC_Array arrLen = array; arrLen.oper = LC_Array::ArrLen; arrLen.dim = level - 1; - (*conds)[level * 2 - 1]->Push( - LC_Condition(GT_LT, LC_Expr(LC_Ident(Lcl(), LC_Ident::Var)), LC_Expr(LC_Ident(arrLen)))); + (*conds)[level * 2 - 1]->Push(LC_Condition(GT_LT, LC_Expr(LC_Ident(Lcl(), LC_Ident::Var)), + LC_Expr(LC_Ident(arrLen)), /* unsigned */ true)); // Push condition (a[i] != null) LC_Array arrTmp = array; @@ -1481,7 +1491,7 @@ bool Compiler::optComputeDerefConditions(unsigned loopNum, LoopCloneContext* con assert(maxRank != -1); // First level will always yield the null-check, since it is made of the array base variables. - // All other levels (dimensions) will yield two conditions ex: (i < a.length && a[i] != null) + // All other levels (dimensions) will yield two conditions ex: ((unsigned) i < a.length && a[i] != null) // So add 1 after rank * 2. const unsigned condBlocks = (unsigned)maxRank * 2 + 1; diff --git a/src/coreclr/jit/loopcloning.h b/src/coreclr/jit/loopcloning.h index 103454a89ce95a..e2867f7490efc7 100644 --- a/src/coreclr/jit/loopcloning.h +++ b/src/coreclr/jit/loopcloning.h @@ -625,12 +625,13 @@ struct LC_Condition LC_Expr op1; LC_Expr op2; genTreeOps oper; + bool compareUnsigned; #ifdef DEBUG void Print() { op1.Print(); - printf(" %s ", GenTree::OpName(oper)); + printf(" %s%s ", GenTree::OpName(oper), compareUnsigned ? "U" : ""); op2.Print(); } #endif @@ -646,7 +647,8 @@ struct LC_Condition LC_Condition() { } - LC_Condition(genTreeOps oper, const LC_Expr& op1, const LC_Expr& op2) : op1(op1), op2(op2), oper(oper) + LC_Condition(genTreeOps oper, const LC_Expr& op1, const LC_Expr& op2, bool asUnsigned = false) + : op1(op1), op2(op2), oper(oper), compareUnsigned(asUnsigned) { } diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 2871a506679acd..478ce8ee859dea 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -1425,7 +1425,7 @@ GenTree* Lowering::LowerFloatArg(GenTree** pArg, CallArg* callArg) break; } GenTree* node = use.GetNode(); - if (varTypeIsFloating(node)) + if (varTypeUsesFloatReg(node)) { GenTree* intNode = LowerFloatArgReg(node, currRegNumber); assert(intNode != nullptr); @@ -1447,7 +1447,7 @@ GenTree* Lowering::LowerFloatArg(GenTree** pArg, CallArg* callArg) // List fields were replaced in place. return arg; } - else if (varTypeIsFloating(arg)) + else if (varTypeUsesFloatReg(arg)) { GenTree* intNode = LowerFloatArgReg(arg, callArg->AbiInfo.GetRegNum()); assert(intNode != nullptr); @@ -1470,11 +1470,13 @@ GenTree* Lowering::LowerFloatArg(GenTree** pArg, CallArg* callArg) // GenTree* Lowering::LowerFloatArgReg(GenTree* arg, regNumber regNum) { + assert(varTypeUsesFloatReg(arg)); + var_types floatType = arg->TypeGet(); - assert(varTypeIsFloating(floatType)); - var_types intType = (floatType == TYP_DOUBLE) ? TYP_LONG : TYP_INT; - GenTree* intArg = comp->gtNewBitCastNode(intType, arg); + var_types intType = (floatType == TYP_FLOAT) ? TYP_INT : TYP_LONG; + GenTree* intArg = comp->gtNewBitCastNode(intType, arg); intArg->SetRegNum(regNum); + #ifdef TARGET_ARM if (floatType == TYP_DOUBLE) { @@ -3819,6 +3821,11 @@ void Lowering::LowerCallStruct(GenTreeCall* call) assert(user->TypeIs(origType) || (returnType == user->TypeGet())); break; + case GT_CALL: + // Argument lowering will deal with register file mismatches if needed. + assert(varTypeIsSIMD(origType)); + break; + case GT_STOREIND: #ifdef FEATURE_SIMD if (varTypeIsSIMD(user)) @@ -5345,28 +5352,43 @@ bool Lowering::TryCreateAddrMode(GenTree* addr, bool isContainable, GenTree* par } #ifdef TARGET_ARM64 - if ((index != nullptr) && index->OperIs(GT_CAST) && (scale == 1) && (offset == 0) && varTypeIsByte(targetType)) - { - MakeSrcContained(addrMode, index); - } - // Check if we can "contain" LEA(BFIZ) in order to extend 32bit index to 64bit as part of load/store. - if ((index != nullptr) && index->OperIs(GT_BFIZ) && index->gtGetOp1()->OperIs(GT_CAST) && - index->gtGetOp2()->IsCnsIntOrI() && !varTypeIsStruct(targetType)) + if (index != nullptr) { - // BFIZ node is a binary op where op1 is GT_CAST and op2 is GT_CNS_INT - GenTreeCast* cast = index->gtGetOp1()->AsCast(); - assert(cast->isContained()); + if (index->OperIs(GT_CAST) && (scale == 1) && (offset == 0) && varTypeIsByte(targetType)) + { + if (IsSafeToContainMem(parent, index)) + { + // Check containment safety against the parent node - this will ensure that LEA with the contained + // index will itself always be contained. We do not support uncontained LEAs with contained indices. + index->AsCast()->CastOp()->ClearContained(); // Uncontain any memory operands. + MakeSrcContained(addrMode, index); + } + } + else if (index->OperIs(GT_BFIZ) && index->gtGetOp1()->OperIs(GT_CAST) && index->gtGetOp2()->IsCnsIntOrI() && + !varTypeIsStruct(targetType)) + { + // Check if we can "contain" LEA(BFIZ) in order to extend 32bit index to 64bit as part of load/store. + // BFIZ node is a binary op where op1 is GT_CAST and op2 is GT_CNS_INT + GenTreeCast* cast = index->gtGetOp1()->AsCast(); + assert(cast->isContained()); - const unsigned shiftBy = (unsigned)index->gtGetOp2()->AsIntCon()->IconValue(); + const unsigned shiftBy = (unsigned)index->gtGetOp2()->AsIntCon()->IconValue(); - // 'scale' and 'offset' have to be unset since we're going to use [base + index * SXTW/UXTW scale] form - // where there is no room for additional offsets/scales on ARM64. 'shiftBy' has to match target's width. - if (cast->CastOp()->TypeIs(TYP_INT) && cast->TypeIs(TYP_LONG) && (genTypeSize(targetType) == (1U << shiftBy)) && - (scale == 1) && (offset == 0)) - { - // TODO: Make sure that genCreateAddrMode marks such BFIZ candidates as GTF_DONT_CSE for better CQ. - MakeSrcContained(addrMode, index); + // 'scale' and 'offset' have to be unset since we're going to use [base + index * SXTW/UXTW scale] form + // where there is no room for additional offsets/scales on ARM64. 'shiftBy' has to match target's width. + if (cast->CastOp()->TypeIs(TYP_INT) && cast->TypeIs(TYP_LONG) && + (genTypeSize(targetType) == (1U << shiftBy)) && (scale == 1) && (offset == 0)) + { + if (IsSafeToContainMem(parent, index)) + { + // Check containment safety against the parent node - this will ensure that LEA with the contained + // index will itself always be contained. We do not support uncontained LEAs with contained indices. + + // TODO: Make sure that genCreateAddrMode marks such BFIZ candidates as GTF_DONT_CSE for better CQ. + MakeSrcContained(addrMode, index); + } + } } } #endif diff --git a/src/coreclr/jit/lowerarmarch.cpp b/src/coreclr/jit/lowerarmarch.cpp index 0ff88151503201..3cc7000f442595 100644 --- a/src/coreclr/jit/lowerarmarch.cpp +++ b/src/coreclr/jit/lowerarmarch.cpp @@ -2086,7 +2086,8 @@ bool Lowering::IsValidCompareChain(GenTree* child, GenTree* parent) return IsValidCompareChain(child->AsOp()->gtGetOp2(), child) && IsValidCompareChain(child->AsOp()->gtGetOp1(), child); } - else if (child->OperIsCmpCompare()) + else if (child->OperIsCmpCompare() && varTypeIsIntegral(child->gtGetOp1()) && + varTypeIsIntegral(child->gtGetOp2())) { // Can the child compare be contained. return IsSafeToContainMem(parent, child); @@ -2148,7 +2149,8 @@ bool Lowering::ContainCheckCompareChain(GenTree* child, GenTree* parent, GenTree child->SetContained(); return true; } - else if (child->OperIsCmpCompare()) + else if (child->OperIsCmpCompare() && varTypeIsIntegral(child->gtGetOp1()) && + varTypeIsIntegral(child->gtGetOp2())) { child->AsOp()->SetContained(); diff --git a/src/coreclr/jit/lowerxarch.cpp b/src/coreclr/jit/lowerxarch.cpp index 5f2e384daa7ce7..7800ee166d2cc8 100644 --- a/src/coreclr/jit/lowerxarch.cpp +++ b/src/coreclr/jit/lowerxarch.cpp @@ -507,18 +507,6 @@ void Lowering::LowerPutArgStk(GenTreePutArgStk* putArgStk) // registers to be consumed atomically by the call. if (varTypeIsIntegralOrI(fieldNode)) { - // If we are loading from an in-memory local, we would like to use "push", but this - // is only legal if we can safely load all 4 bytes. Retype the local node here to - // TYP_INT for such legal cases to make downstream (LSRA & codegen) logic simpler. - // Retyping is ok because we model this node as STORE(LOAD). - // If the field came from promotion, we allow the padding to remain undefined, if - // from decomposition, the field type will be INT (naturally blocking the retyping). - if (varTypeIsSmall(fieldNode) && (genTypeSize(fieldType) <= genTypeSize(fieldNode)) && - fieldNode->OperIsLocalRead()) - { - fieldNode->ChangeType(TYP_INT); - } - if (IsContainableImmed(putArgStk, fieldNode)) { MakeSrcContained(putArgStk, fieldNode); @@ -3419,6 +3407,20 @@ GenTree* Lowering::LowerHWIntrinsicDot(GenTreeHWIntrinsic* node) { assert(simdBaseType == TYP_FLOAT); + // We need to mask off the most significant element to avoid the shuffle + add + // from including it in the computed result. We need to do this for both op1 and + // op2 in case one of them is `NaN` (because Zero * NaN == NaN) + + simd16_t simd16Val = {}; + + simd16Val.i32[0] = -1; + simd16Val.i32[1] = -1; + simd16Val.i32[2] = -1; + simd16Val.i32[3] = +0; + + simdType = TYP_SIMD16; + simdSize = 16; + // We will be constructing the following parts: // ... // +--* CNS_INT int -1 @@ -3426,7 +3428,7 @@ GenTree* Lowering::LowerHWIntrinsicDot(GenTreeHWIntrinsic* node) // +--* CNS_INT int -1 // +--* CNS_INT int 0 // tmp1 = * HWINTRINSIC simd16 T Create - // /--* op2 simd16 + // /--* op1 simd16 // +--* tmp1 simd16 // op1 = * HWINTRINSIC simd16 T And // ... @@ -3434,30 +3436,48 @@ GenTree* Lowering::LowerHWIntrinsicDot(GenTreeHWIntrinsic* node) // This is roughly the following managed code: // ... // tmp1 = Vector128.Create(-1, -1, -1, 0); - // op1 = Sse.And(op1, tmp2); + // op1 = Sse.And(op1, tmp1); // ... - GenTree* cns0 = comp->gtNewIconNode(-1, TYP_INT); - BlockRange().InsertAfter(op1, cns0); + GenTreeVecCon* vecCon1 = comp->gtNewVconNode(simdType, simdBaseJitType); + vecCon1->gtSimd16Val = simd16Val; + + BlockRange().InsertAfter(op1, vecCon1); + + op1 = comp->gtNewSimdHWIntrinsicNode(simdType, op1, vecCon1, NI_SSE_And, simdBaseJitType, simdSize); + BlockRange().InsertAfter(vecCon1, op1); - GenTree* cns1 = comp->gtNewIconNode(-1, TYP_INT); - BlockRange().InsertAfter(cns0, cns1); + LowerNode(vecCon1); + LowerNode(op1); + + // We will be constructing the following parts: + // ... + // +--* CNS_INT int -1 + // +--* CNS_INT int -1 + // +--* CNS_INT int -1 + // +--* CNS_INT int 0 + // tmp2 = * HWINTRINSIC simd16 T Create + // /--* op2 simd16 + // +--* tmp2 simd16 + // op2 = * HWINTRINSIC simd16 T And + // ... - GenTree* cns2 = comp->gtNewIconNode(-1, TYP_INT); - BlockRange().InsertAfter(cns1, cns2); + // This is roughly the following managed code: + // ... + // tmp2 = Vector128.Create(-1, -1, -1, 0); + // op2 = Sse.And(op2, tmp2); + // ... - GenTree* cns3 = comp->gtNewIconNode(0, TYP_INT); - BlockRange().InsertAfter(cns2, cns3); + GenTreeVecCon* vecCon2 = comp->gtNewVconNode(simdType, simdBaseJitType); + vecCon2->gtSimd16Val = simd16Val; - tmp1 = comp->gtNewSimdHWIntrinsicNode(simdType, cns0, cns1, cns2, cns3, NI_Vector128_Create, - CORINFO_TYPE_INT, 16); - BlockRange().InsertAfter(cns3, tmp1); + BlockRange().InsertAfter(op2, vecCon2); - op1 = comp->gtNewSimdHWIntrinsicNode(simdType, op1, tmp1, NI_SSE_And, simdBaseJitType, simdSize); - BlockRange().InsertAfter(tmp1, op1); + op2 = comp->gtNewSimdHWIntrinsicNode(simdType, op2, vecCon2, NI_SSE_And, simdBaseJitType, simdSize); + BlockRange().InsertAfter(vecCon2, op2); - LowerNode(tmp1); - LowerNode(op1); + LowerNode(vecCon2); + LowerNode(op2); } } diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index 144d5f3ab17753..4417858838d548 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -2721,7 +2721,11 @@ bool LinearScan::isMatchingConstant(RegRecord* physRegRecord, RefPosition* refPo case GT_CNS_VEC: { - return GenTreeVecCon::Equals(refPosition->treeNode->AsVecCon(), otherTreeNode->AsVecCon()); + return +#if FEATURE_PARTIAL_SIMD_CALLEE_SAVE + !Compiler::varTypeNeedsPartialCalleeSave(physRegRecord->assignedInterval->registerType) && +#endif + GenTreeVecCon::Equals(refPosition->treeNode->AsVecCon(), otherTreeNode->AsVecCon()); } default: diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index d90320db3b3e08..8adb111ebc88b7 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -3524,11 +3524,11 @@ int LinearScan::BuildStoreLoc(GenTreeLclVarCommon* storeLoc) else if (op1->isContained() && op1->OperIs(GT_BITCAST)) { GenTree* bitCastSrc = op1->gtGetOp1(); - RegisterType registerType = bitCastSrc->TypeGet(); + RegisterType registerType = regType(bitCastSrc->TypeGet()); singleUseRef = BuildUse(bitCastSrc, allRegs(registerType)); Interval* srcInterval = singleUseRef->getInterval(); - assert(srcInterval->registerType == registerType); + assert(regType(srcInterval->registerType) == registerType); srcCount = 1; } #ifndef TARGET_64BIT diff --git a/src/coreclr/jit/lsraxarch.cpp b/src/coreclr/jit/lsraxarch.cpp index 9fdfe585c68e8e..1c97652c5a4764 100644 --- a/src/coreclr/jit/lsraxarch.cpp +++ b/src/coreclr/jit/lsraxarch.cpp @@ -1512,17 +1512,18 @@ int LinearScan::BuildPutArgStk(GenTreePutArgStk* putArgStk) // We can treat as a slot any field that is stored at a slot boundary, where the previous // field is not in the same slot. (Note that we store the fields in reverse order.) - const bool fieldIsSlot = ((fieldOffset % 4) == 0) && ((prevOffset - fieldOffset) >= 4); - const bool canStoreWithPush = fieldIsSlot; - const bool canLoadWithPush = varTypeIsI(fieldNode); + const bool canStoreFullSlot = ((fieldOffset % 4) == 0) && ((prevOffset - fieldOffset) >= 4); + const bool canLoadFullSlot = + (genTypeSize(fieldNode) == TARGET_POINTER_SIZE) || + (fieldNode->OperIsLocalRead() && (genTypeSize(fieldNode) >= genTypeSize(fieldType))); - if ((!canStoreWithPush || !canLoadWithPush) && (intTemp == nullptr)) + if ((!canStoreFullSlot || !canLoadFullSlot) && (intTemp == nullptr)) { intTemp = buildInternalIntRegisterDefForNode(putArgStk); } // We can only store bytes using byteable registers. - if (!canStoreWithPush && varTypeIsByte(fieldType)) + if (!canStoreFullSlot && varTypeIsByte(fieldType)) { intTemp->registerAssignment &= allByteRegs(); } diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index fff00343e888ab..31fe8c0b4ac186 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -11198,13 +11198,14 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) temp = nullptr; } } - else if (op1->OperGet() == GT_ADD) + else { #ifdef TARGET_ARM + GenTree* effOp1 = op1->gtEffectiveVal(true); // Check for a misalignment floating point indirection. - if (varTypeIsFloating(typ)) + if (effOp1->OperIs(GT_ADD) && varTypeIsFloating(typ)) { - GenTree* addOp2 = op1->AsOp()->gtGetOp2(); + GenTree* addOp2 = effOp1->gtGetOp2(); if (addOp2->IsCnsIntOrI()) { ssize_t offset = addOp2->AsIntCon()->gtIconVal; @@ -13493,8 +13494,8 @@ GenTree* Compiler::fgMorphModToSubMulDiv(GenTreeOp* tree) GenTree* dividend = div->IsReverseOp() ? opB : opA; GenTree* divisor = div->IsReverseOp() ? opA : opB; - div->gtOp1 = gtClone(dividend); - div->gtOp2 = gtClone(divisor); + div->gtOp1 = gtCloneExpr(dividend); + div->gtOp2 = gtCloneExpr(divisor); var_types type = div->gtType; GenTree* const mul = gtNewOperNode(GT_MUL, type, div, divisor); diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.SingleEntry.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.SingleEntry.targets index 0018c70f987796..edfec8d0c9a4a6 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.SingleEntry.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.SingleEntry.targets @@ -36,25 +36,35 @@ SetupProperties - - - <_PackageReferenceExceptILCompiler Include="@(PackageReference)" Exclude="Microsoft.DotNet.ILCompiler" /> - <_ILCompilerPackageReference Include="@(PackageReference)" Exclude="@(_PackageReferenceExceptILCompiler)" /> - - @(_ILCompilerPackageReference->'%(Version)') + + + + $([System.IO.Path]::GetFileName($([System.IO.Path]::GetDirectoryName($([System.IO.Path]::GetDirectoryName($(ILCompilerTargetsPath))))))) + + + + + - - + + - + @(ResolvedILCompilerPack->'%(PackageDirectory)') @(ResolvedTargetILCompilerPack->'%(PackageDirectory)') @(ResolvedILCompilerPack->'%(PackageDirectory)') + + %(PackageDefinitions.ResolvedPath) + %(PackageDefinitions.ResolvedPath) + + diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.props b/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.props index da6b72b12d7ce1..c2f2f45bce605b 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.props +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.props @@ -11,8 +11,6 @@ Copyright (c) .NET Foundation. All rights reserved. --> - - true $(MSBuildThisFileDirectory)Microsoft.DotNet.ILCompiler.SingleEntry.targets diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.targets new file mode 100644 index 00000000000000..28ca04be98b91b --- /dev/null +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.targets @@ -0,0 +1,18 @@ + + + + + $(NETCoreSdkVersion.StartsWith('6')) + + + diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets index 690ef724af07e1..603a459ec54419 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets @@ -47,6 +47,11 @@ The .NET Foundation licenses this file to you under the MIT license. true false + false + + + + false @@ -241,6 +246,8 @@ The .NET Foundation licenses this file to you under the MIT license. + + diff --git a/src/coreclr/nativeaot/Runtime/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/CMakeLists.txt index 4ccefca81f5328..2e37b1708d195e 100644 --- a/src/coreclr/nativeaot/Runtime/CMakeLists.txt +++ b/src/coreclr/nativeaot/Runtime/CMakeLists.txt @@ -50,6 +50,7 @@ set(COMMON_RUNTIME_SOURCES ${GC_DIR}/handletablecore.cpp ${GC_DIR}/handletablescan.cpp ${GC_DIR}/objecthandle.cpp + ${GC_DIR}/softwarewritewatch.cpp ) set(SERVER_GC_SOURCES @@ -206,6 +207,12 @@ include_directories(${ARCH_SOURCES_DIR}) add_definitions(-DFEATURE_BASICFREEZE) add_definitions(-DFEATURE_CONSERVATIVE_GC) + +if(CLR_CMAKE_TARGET_UNIX) + add_definitions(-DFEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP) + add_definitions(-DFEATURE_MANUALLY_MANAGED_CARD_BUNDLES) +endif() + add_definitions(-DFEATURE_CUSTOM_IMPORTS) add_definitions(-DFEATURE_DYNAMIC_CODE) add_compile_definitions($<$,$>:FEATURE_GC_STRESS>) diff --git a/src/coreclr/nativeaot/Runtime/EHHelpers.cpp b/src/coreclr/nativeaot/Runtime/EHHelpers.cpp index 507cb074046a91..10c9906f5a0835 100644 --- a/src/coreclr/nativeaot/Runtime/EHHelpers.cpp +++ b/src/coreclr/nativeaot/Runtime/EHHelpers.cpp @@ -91,9 +91,19 @@ COOP_PINVOKE_HELPER(int32_t, RhGetModuleFileName, (HANDLE moduleHandle, _Out_ co COOP_PINVOKE_HELPER(void, RhpCopyContextFromExInfo, (void * pOSContext, int32_t cbOSContext, PAL_LIMITED_CONTEXT * pPalContext)) { - UNREFERENCED_PARAMETER(cbOSContext); ASSERT((size_t)cbOSContext >= sizeof(CONTEXT)); CONTEXT* pContext = (CONTEXT *)pOSContext; + +#ifndef HOST_WASM + + memset(pOSContext, 0, cbOSContext); + pContext->ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; + + // Fill in CONTEXT_CONTROL registers that were not captured in PAL_LIMITED_CONTEXT. + PopulateControlSegmentRegisters(pContext); + +#endif // !HOST_WASM + #if defined(UNIX_AMD64_ABI) pContext->Rip = pPalContext->IP; pContext->Rsp = pPalContext->Rsp; diff --git a/src/coreclr/nativeaot/Runtime/GCMemoryHelpers.inl b/src/coreclr/nativeaot/Runtime/GCMemoryHelpers.inl index aff497778d99e0..8bf7eb68585cfc 100644 --- a/src/coreclr/nativeaot/Runtime/GCMemoryHelpers.inl +++ b/src/coreclr/nativeaot/Runtime/GCMemoryHelpers.inl @@ -229,7 +229,7 @@ FORCEINLINE void InlinedBulkWriteBarrier(void* pMemStart, size_t cbMemSize) // Compute the shadow heap address corresponding to the beginning of the range of heap addresses modified // and in the process range check it to make sure we have the shadow version allocated. uintptr_t* shadowSlot = (uintptr_t*)(g_GCShadow + ((uint8_t*)pMemStart - g_lowest_address)); - if (shadowSlot <= (uintptr_t*)g_GCShadowEnd) + if (shadowSlot < (uintptr_t*)g_GCShadowEnd) { // Iterate over every pointer sized slot in the range, copying data from the real heap to the shadow heap. // As we perform each copy we need to recheck the real heap contents with an ordered read to ensure we're @@ -239,6 +239,7 @@ FORCEINLINE void InlinedBulkWriteBarrier(void* pMemStart, size_t cbMemSize) uintptr_t* realSlot = (uintptr_t*)pMemStart; uintptr_t slotCount = cbMemSize / sizeof(uintptr_t); + ASSERT(slotCount < (uintptr_t*)g_GCShadowEnd - shadowSlot); do { // Update shadow slot from real slot. diff --git a/src/coreclr/nativeaot/Runtime/PalRedhawk.h b/src/coreclr/nativeaot/Runtime/PalRedhawk.h index 5e8751f694544e..43e9db47992c8f 100644 --- a/src/coreclr/nativeaot/Runtime/PalRedhawk.h +++ b/src/coreclr/nativeaot/Runtime/PalRedhawk.h @@ -112,6 +112,11 @@ struct FILETIME #ifdef HOST_AMD64 +#define CONTEXT_AMD64 0x00100000L + +#define CONTEXT_CONTROL (CONTEXT_AMD64 | 0x00000001L) +#define CONTEXT_INTEGER (CONTEXT_AMD64 | 0x00000002L) + typedef struct DECLSPEC_ALIGN(16) _XSAVE_FORMAT { uint16_t ControlWord; uint16_t StatusWord; @@ -224,6 +229,11 @@ typedef struct DECLSPEC_ALIGN(16) _CONTEXT { } CONTEXT, *PCONTEXT; #elif defined(HOST_ARM) +#define CONTEXT_ARM 0x00200000L + +#define CONTEXT_CONTROL (CONTEXT_ARM | 0x1L) +#define CONTEXT_INTEGER (CONTEXT_ARM | 0x2L) + #define ARM_MAX_BREAKPOINTS 8 #define ARM_MAX_WATCHPOINTS 1 @@ -267,6 +277,12 @@ typedef struct DECLSPEC_ALIGN(8) _CONTEXT { } CONTEXT, *PCONTEXT; #elif defined(HOST_X86) + +#define CONTEXT_i386 0x00010000L + +#define CONTEXT_CONTROL (CONTEXT_i386 | 0x00000001L) // SS:SP, CS:IP, FLAGS, BP +#define CONTEXT_INTEGER (CONTEXT_i386 | 0x00000002L) // AX, BX, CX, DX, SI, DI + #define SIZE_OF_80387_REGISTERS 80 #define MAXIMUM_SUPPORTED_EXTENSION 512 @@ -321,6 +337,11 @@ typedef struct _CONTEXT { #elif defined(HOST_ARM64) +#define CONTEXT_ARM64 0x00400000L + +#define CONTEXT_CONTROL (CONTEXT_ARM64 | 0x1L) +#define CONTEXT_INTEGER (CONTEXT_ARM64 | 0x2L) + // Specify the number of breakpoints and watchpoints that the OS // will track. Architecturally, ARM64 supports up to 16. In practice, // however, almost no one implements more than 4 of each. @@ -596,6 +617,11 @@ REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalGetCompleteThreadContext(HANDLE hThread REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalSetThreadContext(HANDLE hThread, _Out_ CONTEXT * pCtx); REDHAWK_PALIMPORT void REDHAWK_PALAPI PalRestoreContext(CONTEXT * pCtx); +// For platforms that have segment registers in the CONTEXT_CONTROL set that +// are not saved in PAL_LIMITED_CONTEXT, this captures them from the current +// thread and saves them in `pContext`. +REDHAWK_PALIMPORT void REDHAWK_PALAPI PopulateControlSegmentRegisters(CONTEXT* pContext); + REDHAWK_PALIMPORT int32_t REDHAWK_PALAPI PalGetProcessCpuCount(); // Retrieves the entire range of memory dedicated to the calling thread's stack. This does @@ -674,6 +700,7 @@ EXTERN_C void * __cdecl _alloca(size_t); REDHAWK_PALIMPORT _Ret_maybenull_ _Post_writable_byte_size_(size) void* REDHAWK_PALAPI PalVirtualAlloc(_In_opt_ void* pAddress, uintptr_t size, uint32_t allocationType, uint32_t protect); REDHAWK_PALIMPORT UInt32_BOOL REDHAWK_PALAPI PalVirtualFree(_In_ void* pAddress, uintptr_t size, uint32_t freeType); REDHAWK_PALIMPORT UInt32_BOOL REDHAWK_PALAPI PalVirtualProtect(_In_ void* pAddress, uintptr_t size, uint32_t protect); +REDHAWK_PALIMPORT void PalFlushInstructionCache(_In_ void* pAddress, size_t size); REDHAWK_PALIMPORT void REDHAWK_PALAPI PalSleep(uint32_t milliseconds); REDHAWK_PALIMPORT UInt32_BOOL REDHAWK_PALAPI PalSwitchToThread(); REDHAWK_PALIMPORT HANDLE REDHAWK_PALAPI PalCreateEventW(_In_opt_ LPSECURITY_ATTRIBUTES pEventAttributes, UInt32_BOOL manualReset, UInt32_BOOL initialState, _In_opt_z_ LPCWSTR pName); diff --git a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp index 2fcceff6d0dd85..3da12598ba196f 100644 --- a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp +++ b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp @@ -1105,13 +1105,13 @@ struct UniversalTransitionStackFrame // Conservative GC reporting must be applied to everything between the base of the // ReturnBlock and the top of the StackPassedArgs. private: - uintptr_t m_pushedFP; // ChildSP+000 CallerSP-0C0 (0x08 bytes) (fp) - uintptr_t m_pushedLR; // ChildSP+008 CallerSP-0B8 (0x08 bytes) (lr) - uint64_t m_fpArgRegs[8]; // ChildSP+010 CallerSP-0B0 (0x40 bytes) (d0-d7) - uintptr_t m_returnBlock[4]; // ChildSP+050 CallerSP-070 (0x40 bytes) - uintptr_t m_intArgRegs[9]; // ChildSP+070 CallerSP-050 (0x48 bytes) (x0-x8) - uintptr_t m_alignmentPad; // ChildSP+0B8 CallerSP-008 (0x08 bytes) - uintptr_t m_stackPassedArgs[1]; // ChildSP+0C0 CallerSP+000 (unknown size) + uintptr_t m_pushedFP; // ChildSP+000 CallerSP-100 (0x08 bytes) (fp) + uintptr_t m_pushedLR; // ChildSP+008 CallerSP-0F8 (0x08 bytes) (lr) + Fp128 m_fpArgRegs[8]; // ChildSP+010 CallerSP-0F0 (0x80 bytes) (q0-q7) + uintptr_t m_returnBlock[4]; // ChildSP+090 CallerSP-070 (0x40 bytes) + uintptr_t m_intArgRegs[9]; // ChildSP+0B0 CallerSP-050 (0x48 bytes) (x0-x8) + uintptr_t m_alignmentPad; // ChildSP+0F8 CallerSP-008 (0x08 bytes) + uintptr_t m_stackPassedArgs[1]; // ChildSP+100 CallerSP+000 (unknown size) public: PTR_UIntNative get_CallerSP() { return GET_POINTER_TO_FIELD(m_stackPassedArgs[0]); } diff --git a/src/coreclr/nativeaot/Runtime/ThunksMapping.cpp b/src/coreclr/nativeaot/Runtime/ThunksMapping.cpp index 6fe0e768f15fa1..760dbc3b0ad8e7 100644 --- a/src/coreclr/nativeaot/Runtime/ThunksMapping.cpp +++ b/src/coreclr/nativeaot/Runtime/ThunksMapping.cpp @@ -229,6 +229,8 @@ EXTERN_C NATIVEAOT_API void* __cdecl RhAllocateThunksMapping() return NULL; } + PalFlushInstructionCache(pThunksSection, THUNKS_MAP_SIZE); + return pThunksSection; } diff --git a/src/coreclr/nativeaot/Runtime/UniversalTransitionHelpers.cpp b/src/coreclr/nativeaot/Runtime/UniversalTransitionHelpers.cpp index 30a1ff269290ab..649aac21ac8d18 100644 --- a/src/coreclr/nativeaot/Runtime/UniversalTransitionHelpers.cpp +++ b/src/coreclr/nativeaot/Runtime/UniversalTransitionHelpers.cpp @@ -21,7 +21,7 @@ // // In the absence of trashing, such bugs can become undetectable if the code that // dispatches the call happens to never touch the impacted argument register (e.g., xmm3 on -// amd64 or d5 on arm32). In such a case, the original enregistered argument will flow +// amd64 or q5 on arm64). In such a case, the original enregistered argument will flow // unmodified into the eventual callee, obscuring the fact that the dispatcher failed to // propagate the transition frame copy of this register. // diff --git a/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.S b/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.S index ac4c4b496252a4..c31a95c9bec5a3 100644 --- a/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.S +++ b/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.S @@ -27,7 +27,7 @@ jb LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_PopThenDone_\REFREG) add \DESTREG, [C_VAR(g_GCShadow)] cmp \DESTREG, [C_VAR(g_GCShadowEnd)] - ja LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_PopThenDone_\REFREG) + jae LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_PopThenDone_\REFREG) // Update the shadow heap. mov [\DESTREG], \REFREG @@ -84,6 +84,21 @@ LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_Done_\REFREG): // we're in a debug build and write barrier checking has been enabled). UPDATE_GC_SHADOW \BASENAME, \REFREG, rdi +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + mov r11, [C_VAR(g_write_watch_table)] + cmp r11, 0x0 + je LOCAL_LABEL(\BASENAME\()_CheckCardTable_\REFREG) + + mov r10, rdi + shr r10, 0xC // SoftwareWriteWatch::AddressToTableByteIndexShift + add r10, r11 + cmp byte ptr [r10], 0x0 + jne LOCAL_LABEL(\BASENAME\()_CheckCardTable_\REFREG) + mov byte ptr [r10], 0xFF +#endif + +LOCAL_LABEL(\BASENAME\()_CheckCardTable_\REFREG): + // If the reference is to an object that's not in an ephemeral generation we have no need to track it // (since the object won't be collected or moved by an ephemeral collection). cmp \REFREG, [C_VAR(g_ephemeral_low)] @@ -95,17 +110,25 @@ LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_Done_\REFREG): // track this write. The location address is translated into an offset in the card table bitmap. We set // an entire byte in the card table since it's quicker than messing around with bitmasks and we only write // the byte if it hasn't already been done since writes are expensive and impact scaling. - shr rdi, 11 - add rdi, [C_VAR(g_card_table)] - cmp byte ptr [rdi], 0x0FF - jne LOCAL_LABEL(\BASENAME\()_UpdateCardTable_\REFREG) - -LOCAL_LABEL(\BASENAME\()_NoBarrierRequired_\REFREG): - ret + shr rdi, 0x0B + mov r10, [C_VAR(g_card_table)] + cmp byte ptr [rdi + r10], 0x0FF + je LOCAL_LABEL(\BASENAME\()_NoBarrierRequired_\REFREG) // We get here if it's necessary to update the card table. -LOCAL_LABEL(\BASENAME\()_UpdateCardTable_\REFREG): - mov byte ptr [rdi], 0x0FF + mov byte ptr [rdi + r10], 0xFF + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + // Shift rdi by 0x0A more to get the card bundle byte (we shifted by 0x0B already) + shr rdi, 0x0A + add rdi, [C_VAR(g_card_bundle_table)] + cmp byte ptr [rdi], 0xFF + je LOCAL_LABEL(\BASENAME\()_NoBarrierRequired_\REFREG) + + mov byte ptr [rdi], 0xFF +#endif + +LOCAL_LABEL(\BASENAME\()_NoBarrierRequired_\REFREG): ret .endm @@ -252,6 +275,21 @@ LEAF_ENTRY RhpByRefAssignRef, _TEXT // we're in a debug build and write barrier checking has been enabled). UPDATE_GC_SHADOW BASENAME, rcx, rdi +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + mov r11, [C_VAR(g_write_watch_table)] + cmp r11, 0x0 + je LOCAL_LABEL(RhpByRefAssignRef_CheckCardTable) + + mov r10, rdi + shr r10, 0xC // SoftwareWriteWatch::AddressToTableByteIndexShift + add r10, r11 + cmp byte ptr [r10], 0x0 + jne LOCAL_LABEL(RhpByRefAssignRef_CheckCardTable) + mov byte ptr [r10], 0xFF +#endif + +LOCAL_LABEL(RhpByRefAssignRef_CheckCardTable): + // If the reference is to an object that's not in an ephemeral generation we have no need to track it // (since the object won't be collected or moved by an ephemeral collection). cmp rcx, [C_VAR(g_ephemeral_low)] @@ -259,25 +297,30 @@ LEAF_ENTRY RhpByRefAssignRef, _TEXT cmp rcx, [C_VAR(g_ephemeral_high)] jae LOCAL_LABEL(RhpByRefAssignRef_NotInHeap) - // move current rdi value into rcx and then increment the pointers + // move current rdi value into rcx, we need to keep rdi and eventually increment by 8 mov rcx, rdi - add rsi, 0x8 - add rdi, 0x8 // We have a location on the GC heap being updated with a reference to an ephemeral object so we must // track this write. The location address is translated into an offset in the card table bitmap. We set // an entire byte in the card table since it's quicker than messing around with bitmasks and we only write // the byte if it hasn't already been done since writes are expensive and impact scaling. - shr rcx, 11 - add rcx, [C_VAR(g_card_table)] - cmp byte ptr [rcx], 0x0FF - jne LOCAL_LABEL(RhpByRefAssignRef_UpdateCardTable) - ret + shr rcx, 0x0B + mov r10, [C_VAR(g_card_table)] + cmp byte ptr [rcx + r10], 0x0FF + je LOCAL_LABEL(RhpByRefAssignRef_NotInHeap) // We get here if it's necessary to update the card table. -LOCAL_LABEL(RhpByRefAssignRef_UpdateCardTable): - mov byte ptr [rcx], 0x0FF - ret + mov byte ptr [rcx + r10], 0xFF + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + // Shift rcx by 0x0A more to get the card bundle byte (we shifted by 0x0B already) + shr rcx, 0x0A + add rcx, [C_VAR(g_card_bundle_table)] + cmp byte ptr [rcx], 0xFF + je LOCAL_LABEL(RhpByRefAssignRef_NotInHeap) + + mov byte ptr [rcx], 0xFF +#endif LOCAL_LABEL(RhpByRefAssignRef_NotInHeap): // Increment the pointers before leaving diff --git a/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.asm b/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.asm index a249f0f043d29b..5a6dcc666fec5f 100644 --- a/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.asm +++ b/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.asm @@ -43,7 +43,7 @@ UPDATE_GC_SHADOW macro BASENAME, REFREG, DESTREG jb &BASENAME&_UpdateShadowHeap_PopThenDone_&REFREG& add DESTREG, [g_GCShadow] cmp DESTREG, [g_GCShadowEnd] - ja &BASENAME&_UpdateShadowHeap_PopThenDone_&REFREG& + jae &BASENAME&_UpdateShadowHeap_PopThenDone_&REFREG& ;; Update the shadow heap. mov [DESTREG], REFREG diff --git a/src/coreclr/nativeaot/Runtime/arm/WriteBarriers.S b/src/coreclr/nativeaot/Runtime/arm/WriteBarriers.S index 1db0dbdc01231a..863e17cc9fdf9f 100644 --- a/src/coreclr/nativeaot/Runtime/arm/WriteBarriers.S +++ b/src/coreclr/nativeaot/Runtime/arm/WriteBarriers.S @@ -38,7 +38,7 @@ ldr r12, =C_FUNC(g_GCShadowEnd) ldr r12, [r12] cmp \DESTREG, r12 - jhi LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_PopThenDone_\REFREG) + bhs LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_PopThenDone_\REFREG) // Update the shadow heap. str \REFREG, [\DESTREG] @@ -105,15 +105,15 @@ LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_Done_\REFREG): // If the reference is to an object that's not in an ephemeral generation we have no need to track it // (since the object won't be collected or moved by an ephemeral collection). - ldr r12, =C_FUNC(g_ephemeral_low) + ldr r12, =C_FUNC(g_ephemeral_low) ldr r12, [r12] cmp \REFREG, r12 blo LOCAL_LABEL(\BASENAME\()_EXIT_\REFREG) - ldr r12, =C_FUNC(g_ephemeral_high) + ldr r12, =C_FUNC(g_ephemeral_high) ldr r12, [r12] - cmp \REFREG, r12 - bhi LOCAL_LABEL(\BASENAME\()_EXIT_\REFREG) + cmp \REFREG, r12 + bhs LOCAL_LABEL(\BASENAME\()_EXIT_\REFREG) // We have a location on the GC heap being updated with a reference to an ephemeral object so we must // track this write. The location address is translated into an offset in the card table bitmap. We set @@ -167,11 +167,11 @@ ALTERNATE_ENTRY RhpAssignRef // // Note that none of this is relevant for single cpu machines. We may choose to implement a // uniprocessor specific version of this barrier if uni-proc becomes a significant scenario again. - dmb + dmb // Write the reference into the location. Note that we rely on the fact that no GC can occur between here // and the card table update we may perform below. -ALTERNATE_ENTRY "RhpAssignRefAvLocation"\EXPORT_REG_NAME // WriteBarrierFunctionAvLocation +ALTERNATE_ENTRY "RhpAssignRefAvLocation"\EXPORT_REG_NAME // WriteBarrierFunctionAvLocation .ifc \REFREG, r1 ALTERNATE_ENTRY RhpAssignRefAVLocation .endif @@ -198,14 +198,14 @@ DEFINE_UNCHECKED_WRITE_BARRIER r1, r1 // The location being updated might not even lie in the GC heap (a handle or stack location for instance), // in which case no write barrier is required. - ldr r12, =C_FUNC(g_lowest_address) + ldr r12, =C_FUNC(g_lowest_address) ldr r12, [r12] cmp r0, r12 blo LOCAL_LABEL(\BASENAME\()_NoBarrierRequired_\REFREG) - ldr r12, =C_FUNC(g_highest_address) + ldr r12, =C_FUNC(g_highest_address) ldr r12, [r12] cmp r0, r12 - bhi LOCAL_LABEL(\BASENAME\()_NoBarrierRequired_\REFREG) + bhs LOCAL_LABEL(\BASENAME\()_NoBarrierRequired_\REFREG) DEFINE_UNCHECKED_WRITE_BARRIER_CORE \BASENAME, \REFREG @@ -270,7 +270,7 @@ LEAF_ENTRY RhpCheckedLockCmpXchg, _TEXT // barrier must occur before the object reference update, so we have to do it unconditionally even // though the update may fail below. dmb -ALTERNATE_ENTRY RhpCheckedLockCmpXchgAVLocation +ALTERNATE_ENTRY RhpCheckedLockCmpXchgAVLocation LOCAL_LABEL(RhpCheckedLockCmpXchgRetry): ldrex r3, [r0] cmp r2, r3 @@ -337,7 +337,7 @@ LEAF_ENTRY RhpByRefAssignRef, _TEXT ldr r3, =C_FUNC(g_highest_address) ldr r3, [r3] cmp r0, r3 - bhi LOCAL_LABEL(RhpByRefAssignRef_NotInHeap) + bhs LOCAL_LABEL(RhpByRefAssignRef_NotInHeap) // Update the shadow copy of the heap with the same value just written to the same heap. (A no-op unless // we're in a debug build and write barrier checking has been enabled). @@ -352,7 +352,7 @@ LEAF_ENTRY RhpByRefAssignRef, _TEXT ldr r3, =C_FUNC(g_ephemeral_high) ldr r3, [r3] cmp r2, r3 - bhi LOCAL_LABEL(RhpByRefAssignRef_NotInHeap) + bhs LOCAL_LABEL(RhpByRefAssignRef_NotInHeap) // move current r0 value into r2 and then increment the pointers mov r2, r0 diff --git a/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.S b/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.S index 12fa42365f4c2e..8274b6b1110a6a 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.S +++ b/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.S @@ -23,7 +23,7 @@ #define RETURN_BLOCK_SIZE (32) #define COUNT_FLOAT_ARG_REGISTERS (8) -#define FLOAT_REGISTER_SIZE (8) +#define FLOAT_REGISTER_SIZE (16) #define FLOAT_ARG_REGISTERS_SIZE (COUNT_FLOAT_ARG_REGISTERS * FLOAT_REGISTER_SIZE) #define PUSHED_LR_SIZE (8) @@ -50,7 +50,7 @@ // // RhpUniversalTransition // -// At input to this function, x0-8, d0-7 and the stack may contain any number of arguments. +// At input to this function, x0-8, q0-7 and the stack may contain any number of arguments. // // In addition, there are 2 extra arguments passed in the intra-procedure-call scratch register: // xip0 will contain the managed function that is to be called by this transition function @@ -63,16 +63,16 @@ // // Frame layout is: // -// {StackPassedArgs} ChildSP+0C0 CallerSP+000 -// {AlignmentPad (0x8 bytes)} ChildSP+0B8 CallerSP-008 -// {IntArgRegs (x0-x8) (0x48 bytes)} ChildSP+070 CallerSP-050 -// {ReturnBlock (0x20 bytes)} ChildSP+050 CallerSP-070 +// {StackPassedArgs} ChildSP+100 CallerSP+000 +// {AlignmentPad (0x8 bytes)} ChildSP+0F8 CallerSP-008 +// {IntArgRegs (x0-x8) (0x48 bytes)} ChildSP+0B0 CallerSP-050 +// {ReturnBlock (0x20 bytes)} ChildSP+090 CallerSP-070 // -- The base address of the Return block is the TransitionBlock pointer, the floating point args are // in the neg space of the TransitionBlock pointer. Note that the callee has knowledge of the exact // layout of all pieces of the frame that lie at or above the pushed floating point registers. -// {FpArgRegs (d0-d7) (0x40 bytes)} ChildSP+010 CallerSP-0B0 -// {PushedLR} ChildSP+008 CallerSP-0B8 -// {PushedFP} ChildSP+000 CallerSP-0C0 +// {FpArgRegs (q0-q7) (0x80 bytes)} ChildSP+010 CallerSP-0F0 +// {PushedLR} ChildSP+008 CallerSP-0F8 +// {PushedFP} ChildSP+000 CallerSP-100 // // NOTE: If the frame layout ever changes, the C++ UniversalTransitionStackFrame structure // must be updated as well. @@ -95,10 +95,10 @@ PROLOG_SAVE_REG_PAIR_INDEXED fp, lr, -STACK_SIZE // ;; Push down stack pointer and store FP and LR // Floating point registers - stp d0, d1, [sp, #(FLOAT_ARG_OFFSET )] - stp d2, d3, [sp, #(FLOAT_ARG_OFFSET + 0x10)] - stp d4, d5, [sp, #(FLOAT_ARG_OFFSET + 0x20)] - stp d6, d7, [sp, #(FLOAT_ARG_OFFSET + 0x30)] + stp q0, q1, [sp, #(FLOAT_ARG_OFFSET )] + stp q2, q3, [sp, #(FLOAT_ARG_OFFSET + 0x20)] + stp q4, q5, [sp, #(FLOAT_ARG_OFFSET + 0x40)] + stp q6, q7, [sp, #(FLOAT_ARG_OFFSET + 0x60)] // Space for return buffer data (0x40 bytes) @@ -112,10 +112,10 @@ #ifdef TRASH_SAVED_ARGUMENT_REGISTERS PREPARE_EXTERNAL_VAR RhpFpTrashValues, x1 - ldp d0,d1, [x1, 0x0] - ldp d2,d3, [x1, 0x10] - ldp d4,d5, [x1, 0x20] - ldp d6,d7, [x1, 0x30] + ldp q0,q1, [x1, 0x0] + ldp q2,q3, [x1, 0x20] + ldp q4,q5, [x1, 0x40] + ldp q6,q7, [x1, 0x60] PREPARE_EXTERNAL_VAR RhpIntegerTrashValues, x1 @@ -139,10 +139,10 @@ mov x12, x0 // Restore floating point registers - ldp d0, d1, [sp, #(FLOAT_ARG_OFFSET )] - ldp d2, d3, [sp, #(FLOAT_ARG_OFFSET + 0x10)] - ldp d4, d5, [sp, #(FLOAT_ARG_OFFSET + 0x20)] - ldp d6, d7, [sp, #(FLOAT_ARG_OFFSET + 0x30)] + ldp q0, q1, [sp, #(FLOAT_ARG_OFFSET )] + ldp q2, q3, [sp, #(FLOAT_ARG_OFFSET + 0x20)] + ldp q4, q5, [sp, #(FLOAT_ARG_OFFSET + 0x40)] + ldp q6, q7, [sp, #(FLOAT_ARG_OFFSET + 0x60)] // Restore the argument registers ldp x0, x1, [sp, #(ARGUMENT_REGISTERS_OFFSET )] diff --git a/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.asm b/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.asm index 6f1fc0953cd985..2e23ea4302a4fc 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.asm +++ b/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.asm @@ -23,7 +23,7 @@ #define RETURN_BLOCK_SIZE (32) #define COUNT_FLOAT_ARG_REGISTERS (8) -#define FLOAT_REGISTER_SIZE (8) +#define FLOAT_REGISTER_SIZE (16) #define FLOAT_ARG_REGISTERS_SIZE (COUNT_FLOAT_ARG_REGISTERS * FLOAT_REGISTER_SIZE) #define PUSHED_LR_SIZE (8) @@ -51,7 +51,7 @@ ;; ;; RhpUniversalTransition ;; -;; At input to this function, x0-8, d0-7 and the stack may contain any number of arguments. +;; At input to this function, x0-8, q0-7 and the stack may contain any number of arguments. ;; ;; In addition, there are 2 extra arguments passed in the intra-procedure-call scratch register: ;; xip0 will contain the managed function that is to be called by this transition function @@ -64,16 +64,16 @@ ;; ;; Frame layout is: ;; -;; {StackPassedArgs} ChildSP+0C0 CallerSP+000 -;; {AlignmentPad (0x8 bytes)} ChildSP+0B8 CallerSP-008 -;; {IntArgRegs (x0-x8) (0x48 bytes)} ChildSP+070 CallerSP-050 -;; {ReturnBlock (0x20 bytes)} ChildSP+050 CallerSP-070 +;; {StackPassedArgs} ChildSP+100 CallerSP+000 +;; {AlignmentPad (0x8 bytes)} ChildSP+0F8 CallerSP-008 +;; {IntArgRegs (x0-x8) (0x48 bytes)} ChildSP+0A0 CallerSP-050 +;; {ReturnBlock (0x20 bytes)} ChildSP+090 CallerSP-070 ;; -- The base address of the Return block is the TransitionBlock pointer, the floating point args are ;; in the neg space of the TransitionBlock pointer. Note that the callee has knowledge of the exact ;; layout of all pieces of the frame that lie at or above the pushed floating point registers. -;; {FpArgRegs (d0-d7) (0x40 bytes)} ChildSP+010 CallerSP-0B0 -;; {PushedLR} ChildSP+008 CallerSP-0B8 -;; {PushedFP} ChildSP+000 CallerSP-0C0 +;; {FpArgRegs (q0-q7) (0x80 bytes)} ChildSP+010 CallerSP-0F0 +;; {PushedLR} ChildSP+008 CallerSP-0F8 +;; {PushedFP} ChildSP+000 CallerSP-100 ;; ;; NOTE: If the frame layout ever changes, the C++ UniversalTransitionStackFrame structure ;; must be updated as well. @@ -97,10 +97,10 @@ PROLOG_SAVE_REG_PAIR fp, lr, #-STACK_SIZE! ;; Push down stack pointer and store FP and LR ;; Floating point registers - stp d0, d1, [sp, #(FLOAT_ARG_OFFSET )] - stp d2, d3, [sp, #(FLOAT_ARG_OFFSET + 0x10)] - stp d4, d5, [sp, #(FLOAT_ARG_OFFSET + 0x20)] - stp d6, d7, [sp, #(FLOAT_ARG_OFFSET + 0x30)] + stp q0, q1, [sp, #(FLOAT_ARG_OFFSET )] + stp q2, q3, [sp, #(FLOAT_ARG_OFFSET + 0x20)] + stp q4, q5, [sp, #(FLOAT_ARG_OFFSET + 0x40)] + stp q6, q7, [sp, #(FLOAT_ARG_OFFSET + 0x60)] ;; Space for return buffer data (0x40 bytes) @@ -130,10 +130,10 @@ mov x12, x0 ;; Restore floating point registers - ldp d0, d1, [sp, #(FLOAT_ARG_OFFSET )] - ldp d2, d3, [sp, #(FLOAT_ARG_OFFSET + 0x10)] - ldp d4, d5, [sp, #(FLOAT_ARG_OFFSET + 0x20)] - ldp d6, d7, [sp, #(FLOAT_ARG_OFFSET + 0x30)] + ldp q0, q1, [sp, #(FLOAT_ARG_OFFSET )] + ldp q2, q3, [sp, #(FLOAT_ARG_OFFSET + 0x20)] + ldp q4, q5, [sp, #(FLOAT_ARG_OFFSET + 0x40)] + ldp q6, q7, [sp, #(FLOAT_ARG_OFFSET + 0x60)] ;; Restore the argument registers ldp x0, x1, [sp, #(ARGUMENT_REGISTERS_OFFSET )] diff --git a/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.S b/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.S index b02737673caa60..c2d2bf4bdabfb2 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.S +++ b/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.S @@ -38,14 +38,14 @@ // Transform destReg into the equivalent address in the shadow heap. PREPARE_EXTERNAL_VAR_INDIRECT g_lowest_address, X9 subs \destReg, \destReg, x9 - blt 0f + blo 0f PREPARE_EXTERNAL_VAR_INDIRECT g_GCShadow, X9 add \destReg, \destReg, x9 PREPARE_EXTERNAL_VAR_INDIRECT g_GCShadowEnd, X9 cmp \destReg, x9 - bgt 0f + bhs 0f // Update the shadow heap. str \refReg, [\destReg] @@ -92,12 +92,11 @@ // destReg: location to be updated // refReg: objectref to be stored // trash: register nr than can be trashed - // trash2: register than can be trashed // // On exit: // destReg: trashed // - .macro INSERT_UNCHECKED_WRITE_BARRIER_CORE destReg, refReg, trash, trash2 + .macro INSERT_UNCHECKED_WRITE_BARRIER_CORE destReg, refReg, trash // Update the shadow copy of the heap with the same value just written to the same heap. (A no-op unless // we are in a debug build and write barrier checking has been enabled). @@ -120,36 +119,36 @@ // an object not on the epehemeral segment. PREPARE_EXTERNAL_VAR_INDIRECT g_ephemeral_low, x\trash cmp \refReg, x\trash - blt 0f + blo 0f PREPARE_EXTERNAL_VAR_INDIRECT g_ephemeral_high, x\trash cmp \refReg, x\trash - bge 0f + bhs 0f // Set this objects card, if it has not already been set. PREPARE_EXTERNAL_VAR_INDIRECT g_card_table, x\trash - add \trash2, x\trash, \destReg, lsr #11 + add x17, x\trash, \destReg, lsr #11 // Check that this card has not already been written. Avoiding useless writes is a big win on // multi-proc systems since it avoids cache thrashing. - ldrb w\trash, [\trash2] + ldrb w\trash, [x17] cmp x\trash, 0xFF beq 0f mov x\trash, 0xFF - strb w\trash, [\trash2] + strb w\trash, [x17] #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES // Check if we need to update the card bundle table PREPARE_EXTERNAL_VAR_INDIRECT g_card_bundle_table, x\trash - add \trash2, x\trash, \destReg, lsr #21 - ldrb w\trash, [\trash2] + add x17, x\trash, \destReg, lsr #21 + ldrb w\trash, [x17] cmp x\trash, 0xFF beq 0f mov x\trash, 0xFF - strb w\trash, [\trash2] + strb w\trash, [x17] #endif 0: @@ -160,25 +159,26 @@ // destReg: location to be updated // refReg: objectref to be stored // trash: register nr than can be trashed - // trash2: register than can be trashed // // On exit: // destReg: trashed // - .macro INSERT_CHECKED_WRITE_BARRIER_CORE destReg, refReg, trash, trash2 + .macro INSERT_CHECKED_WRITE_BARRIER_CORE destReg, refReg, trash // The "check" of this checked write barrier - is destReg // within the heap? if no, early out. PREPARE_EXTERNAL_VAR_INDIRECT g_lowest_address, x\trash cmp \destReg, x\trash - blt 0f PREPARE_EXTERNAL_VAR_INDIRECT g_highest_address, x\trash - cmp \destReg, x\trash - bgt 0f - INSERT_UNCHECKED_WRITE_BARRIER_CORE \destReg, \refReg, \trash, \trash2 + // If \destReg >= g_lowest_address, compare \destReg to g_highest_address. + // Otherwise, set the C flag (0x2) to take the next branch. + ccmp \destReg, x\trash, #0x2, hs + bhs 0f + + INSERT_UNCHECKED_WRITE_BARRIER_CORE \destReg, \refReg, \trash 0: // Exit label @@ -263,7 +263,7 @@ CmpXchgRetry: // The following barrier code takes the destination in x0 and the value in x1 so the arguments are // already correctly set up. - INSERT_CHECKED_WRITE_BARRIER_CORE x0, x1, 9, x0 + INSERT_CHECKED_WRITE_BARRIER_CORE x0, x1, 9 CmpXchgNoUpdate: // x10 still contains the original value. @@ -305,7 +305,7 @@ ExchangeRetry: // The following barrier code takes the destination in x0 and the value in x1 so the arguments are // already correctly set up. - INSERT_CHECKED_WRITE_BARRIER_CORE x0, x1, 9, x0 + INSERT_CHECKED_WRITE_BARRIER_CORE x0, x1, 9 // x10 still contains the original value. mov x0, x10 @@ -319,7 +319,7 @@ LEAF_ENTRY RhpAssignRefArm64, _TEXT ALTERNATE_ENTRY RhpAssignRefX1AVLocation stlr x15, [x14] - INSERT_UNCHECKED_WRITE_BARRIER_CORE x14, x15, 12, X14 + INSERT_UNCHECKED_WRITE_BARRIER_CORE x14, x15, 12 ret LEAF_END RhpAssignRefArm64, _TEXT @@ -341,9 +341,7 @@ LEAF_ENTRY RhpCheckedAssignRefArm64, _TEXT stlr x15, [x14] - INSERT_CHECKED_WRITE_BARRIER_CORE x14, x15, 12, X15 - - add x14, x14, #8 + INSERT_CHECKED_WRITE_BARRIER_CORE x14, x15, 12 ret LEAF_END RhpCheckedAssignRefArm64, _TEXT @@ -364,7 +362,7 @@ LEAF_ENTRY RhpByRefAssignRefArm64, _TEXT ldr x15, [x13] stlr x15, [x14] - INSERT_CHECKED_WRITE_BARRIER_CORE x14, x15, 12, X15 + INSERT_CHECKED_WRITE_BARRIER_CORE x14, x15, 12 add X13, x13, #8 add x14, x14, #8 diff --git a/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.asm b/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.asm index 9aff215ffc57be..3889b08efcf02a 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.asm +++ b/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.asm @@ -54,7 +54,7 @@ INVALIDGCVALUE EQU 0xCCCCCCCD adrp x12, g_lowest_address ldr x12, [x12, g_lowest_address] subs $destReg, $destReg, x12 - blt %ft0 + blo %ft0 adrp x12, $g_GCShadow ldr x12, [x12, $g_GCShadow] @@ -63,7 +63,7 @@ INVALIDGCVALUE EQU 0xCCCCCCCD adrp x12, $g_GCShadowEnd ldr x12, [x12, $g_GCShadowEnd] cmp $destReg, x12 - bgt %ft0 + bhs %ft0 ;; Update the shadow heap. str $refReg, [$destReg] @@ -127,12 +127,12 @@ INVALIDGCVALUE EQU 0xCCCCCCCD adrp x12, g_ephemeral_low ldr x12, [x12, g_ephemeral_low] cmp $refReg, x12 - blt %ft0 + blo %ft0 adrp x12, g_ephemeral_high ldr x12, [x12, g_ephemeral_high] cmp $refReg, x12 - bge %ft0 + bhs %ft0 ;; Set this object's card, if it hasn't already been set. adrp x12, g_card_table @@ -170,12 +170,14 @@ INVALIDGCVALUE EQU 0xCCCCCCCD adrp x12, g_lowest_address ldr x12, [x12, g_lowest_address] cmp $destReg, x12 - blt %ft0 adrp x12, g_highest_address ldr x12, [x12, g_highest_address] - cmp $destReg, x12 - bgt %ft0 + + ;; If $destReg >= g_lowest_address, compare $destReg to g_highest_address. + ;; Otherwise, set the C flag (0x2) to take the next branch. + ccmp $destReg, x12, #0x2, hs + bhs %ft0 INSERT_UNCHECKED_WRITE_BARRIER_CORE $destReg, $refReg, $trashReg diff --git a/src/coreclr/nativeaot/Runtime/gcrhenv.cpp b/src/coreclr/nativeaot/Runtime/gcrhenv.cpp index f3d3010ff5a0c2..99337cfbcc55b6 100644 --- a/src/coreclr/nativeaot/Runtime/gcrhenv.cpp +++ b/src/coreclr/nativeaot/Runtime/gcrhenv.cpp @@ -1018,8 +1018,8 @@ void GCToEEInterface::DiagWalkBGCSurvivors(void* gcContext) #endif // FEATURE_EVENT_TRACE } -#if defined(FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP) && (!defined(TARGET_ARM64) || !defined(TARGET_UNIX)) -#error FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP is only implemented for ARM64 and UNIX +#if defined(FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP) && !defined(TARGET_UNIX) +#error FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP is only implemented for UNIX #endif void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) @@ -1307,13 +1307,6 @@ MethodTable* GCToEEInterface::GetFreeObjectMethodTable() bool GCToEEInterface::GetBooleanConfigValue(const char* privateKey, const char* publicKey, bool* value) { - // these configuration values are given to us via startup flags. - if (strcmp(privateKey, "gcServer") == 0) - { - *value = g_heap_type == GC_HEAP_SVR; - return true; - } - if (strcmp(privateKey, "gcConservative") == 0) { *value = true; @@ -1356,6 +1349,10 @@ bool GCToEEInterface::GetIntConfigValue(const char* privateKey, const char* publ return true; } +void GCToEEInterface::LogErrorToHost(const char *message) +{ +} + bool GCToEEInterface::GetStringConfigValue(const char* privateKey, const char* publicKey, const char** value) { UNREFERENCED_PARAMETER(privateKey); diff --git a/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm b/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm index a7038354094567..d718f7aa085f0a 100644 --- a/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm +++ b/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm @@ -48,7 +48,7 @@ UPDATE_GC_SHADOW macro BASENAME, DESTREG, REFREG jb &BASENAME&_UpdateShadowHeap_PopThenDone_&DESTREG&_&REFREG& add DESTREG, [g_GCShadow] cmp DESTREG, [g_GCShadowEnd] - ja &BASENAME&_UpdateShadowHeap_PopThenDone_&DESTREG&_&REFREG& + jae &BASENAME&_UpdateShadowHeap_PopThenDone_&DESTREG&_&REFREG& ;; Update the shadow heap. mov [DESTREG], REFREG diff --git a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h index 176aef2ad1033b..6f77813cd06143 100644 --- a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h +++ b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h @@ -10,8 +10,8 @@ struct ReadyToRunHeaderConstants { static const uint32_t Signature = 0x00525452; // 'RTR' - static const uint32_t CurrentMajorVersion = 7; - static const uint32_t CurrentMinorVersion = 1; + static const uint32_t CurrentMajorVersion = 8; + static const uint32_t CurrentMinorVersion = 0; }; struct ReadyToRunHeader diff --git a/src/coreclr/nativeaot/Runtime/startup.cpp b/src/coreclr/nativeaot/Runtime/startup.cpp index 0cfa7efe36003f..62a0f3ba9221cb 100644 --- a/src/coreclr/nativeaot/Runtime/startup.cpp +++ b/src/coreclr/nativeaot/Runtime/startup.cpp @@ -401,7 +401,7 @@ static void UninitDLL() #endif // PROFILE_STARTUP } -volatile bool g_processShutdownHasStarted = false; +volatile Thread* g_threadPerformingShutdown = NULL; static void DllThreadDetach() { @@ -413,7 +413,7 @@ static void DllThreadDetach() { // Once shutdown starts, RuntimeThreadShutdown callbacks are ignored, implying that // it is no longer guaranteed that exiting threads will be detached. - if (!g_processShutdownHasStarted) + if (g_threadPerformingShutdown != NULL) { ASSERT_UNCONDITIONALLY("Detaching thread whose home fiber has not been detached"); RhFailFast(); @@ -439,9 +439,17 @@ void RuntimeThreadShutdown(void* thread) } #else ASSERT((Thread*)thread == ThreadStore::GetCurrentThread()); + + // Do not do shutdown for the thread that performs the shutdown. + // other threads could be terminated before it and could leave TLS locked + if ((Thread*)thread == g_threadPerformingShutdown) + { + return; + } + #endif - ThreadStore::DetachCurrentThread(g_processShutdownHasStarted); + ThreadStore::DetachCurrentThread(g_threadPerformingShutdown != NULL); } extern "C" bool RhInitialize() @@ -474,11 +482,11 @@ COOP_PINVOKE_HELPER(void, RhpEnableConservativeStackReporting, ()) COOP_PINVOKE_HELPER(void, RhpShutdown, ()) { // Indicate that runtime shutdown is complete and that the caller is about to start shutting down the entire process. - g_processShutdownHasStarted = true; + g_threadPerformingShutdown = ThreadStore::RawGetCurrentThread(); } #ifdef _WIN32 -EXTERN_C UInt32_BOOL WINAPI RtuDllMain(HANDLE hPalInstance, uint32_t dwReason, void* /*pvReserved*/) +EXTERN_C UInt32_BOOL WINAPI RtuDllMain(HANDLE hPalInstance, uint32_t dwReason, void* pvReserved) { switch (dwReason) { diff --git a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp index 526810177a5ed7..0d19436d3b84a2 100644 --- a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp @@ -13,6 +13,8 @@ #include "UnixHandle.h" #include #include "gcenv.h" +#include "gcenv.ee.h" +#include "gcconfig.h" #include "holder.h" #include "UnixSignals.h" #include "UnixContext.h" @@ -314,10 +316,9 @@ class UnixEvent { pthread_mutex_lock(&m_mutex); m_state = true; - pthread_mutex_unlock(&m_mutex); - // Unblock all threads waiting for the condition variable pthread_cond_broadcast(&m_condition); + pthread_mutex_unlock(&m_mutex); } void Reset() @@ -417,6 +418,8 @@ REDHAWK_PALEXPORT bool REDHAWK_PALAPI PalInit() ConfigureSignals(); + GCConfig::Initialize(); + if (!GCToOSInterface::Initialize()) { return false; @@ -828,6 +831,33 @@ REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalVirtualProtect(_In_ void* pAddre return mprotect(pPageStart, memSize, unixProtect) == 0; } +REDHAWK_PALEXPORT void PalFlushInstructionCache(_In_ void* pAddress, size_t size) +{ +#if defined(__linux__) && defined(HOST_ARM) + // On Linux/arm (at least on 3.10) we found that there is a problem with __do_cache_op (arch/arm/kernel/traps.c) + // implementing cacheflush syscall. cacheflush flushes only the first page in range [pAddress, pAddress + size) + // and leaves other pages in undefined state which causes random tests failures (often due to SIGSEGV) with no particular pattern. + // + // As a workaround, we call __builtin___clear_cache on each page separately. + + const size_t pageSize = getpagesize(); + uint8_t* begin = (uint8_t*)pAddress; + uint8_t* end = begin + size; + + while (begin < end) + { + uint8_t* endOrNextPageBegin = ALIGN_UP(begin + 1, pageSize); + if (endOrNextPageBegin > end) + endOrNextPageBegin = end; + + __builtin___clear_cache((char *)begin, (char *)endOrNextPageBegin); + begin = endOrNextPageBegin; + } +#else + __builtin___clear_cache((char *)pAddress, (char *)pAddress + size); +#endif +} + REDHAWK_PALEXPORT _Ret_maybenull_ void* REDHAWK_PALAPI PalSetWerDataBuffer(_In_ void* pNewBuffer) { static void* pBuffer; @@ -869,7 +899,22 @@ extern "C" UInt32_BOOL DuplicateHandle( extern "C" UInt32_BOOL InitializeCriticalSection(CRITICAL_SECTION * lpCriticalSection) { - return pthread_mutex_init(&lpCriticalSection->mutex, NULL) == 0; + pthread_mutexattr_t mutexAttributes; + int st = pthread_mutexattr_init(&mutexAttributes); + if (st != 0) + { + return false; + } + + st = pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE); + if (st == 0) + { + st = pthread_mutex_init(&lpCriticalSection->mutex, &mutexAttributes); + } + + pthread_mutexattr_destroy(&mutexAttributes); + + return (st == 0); } extern "C" UInt32_BOOL InitializeCriticalSectionEx(CRITICAL_SECTION * lpCriticalSection, uint32_t arg2, uint32_t arg3) @@ -1054,6 +1099,14 @@ extern "C" int32_t _stricmp(const char *string1, const char *string2) return strcasecmp(string1, string2); } +REDHAWK_PALIMPORT void REDHAWK_PALAPI PopulateControlSegmentRegisters(CONTEXT* pContext) +{ +#if defined(TARGET_X86) || defined(TARGET_AMD64) + // Currently the CONTEXT is only used on Windows for RaiseFailFastException. + // So we punt on filling in SegCs and SegSs for now. +#endif +} + uint32_t g_RhNumberOfProcessors; REDHAWK_PALEXPORT int32_t PalGetProcessCpuCount() diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp index 95e6d6778b9752..8ca31f5ae05bcc 100644 --- a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp @@ -222,7 +222,7 @@ uintptr_t UnixNativeCodeManager::GetConservativeUpperBoundForOutgoingArgs(Method UnixNativeMethodInfo * pNativeMethodInfo = (UnixNativeMethodInfo *)pMethodInfo; - PTR_UInt8 p = pNativeMethodInfo->pMainLSDA; + PTR_UInt8 p = pNativeMethodInfo->pLSDA; uint8_t unwindBlockFlags = *p++; @@ -283,7 +283,7 @@ bool UnixNativeCodeManager::UnwindStackFrame(MethodInfo * pMethodInfo, { UnixNativeMethodInfo * pNativeMethodInfo = (UnixNativeMethodInfo *)pMethodInfo; - PTR_UInt8 p = pNativeMethodInfo->pMainLSDA; + PTR_UInt8 p = pNativeMethodInfo->pLSDA; uint8_t unwindBlockFlags = *p++; @@ -868,7 +868,7 @@ PTR_VOID UnixNativeCodeManager::GetAssociatedData(PTR_VOID ControlPC) if (!FindMethodInfo(ControlPC, (MethodInfo*)&methodInfo)) return NULL; - PTR_UInt8 p = methodInfo.pMainLSDA; + PTR_UInt8 p = methodInfo.pLSDA; uint8_t unwindBlockFlags = *p++; if ((unwindBlockFlags & UBF_FUNC_HAS_ASSOCIATED_DATA) == 0) diff --git a/src/coreclr/nativeaot/Runtime/unix/unixasmmacrosarm64.inc b/src/coreclr/nativeaot/Runtime/unix/unixasmmacrosarm64.inc index 1fb8e47aa628f3..c69149cc69f743 100644 --- a/src/coreclr/nativeaot/Runtime/unix/unixasmmacrosarm64.inc +++ b/src/coreclr/nativeaot/Runtime/unix/unixasmmacrosarm64.inc @@ -164,49 +164,6 @@ C_FUNC(\Name): brk #0 .endm -//----------------------------------------------------------------------------- -// The Following sets of SAVE_*_REGISTERS expect the memory to be reserved and -// base address to be passed in $reg -// - -// Reserve 64 bytes of memory before calling SAVE_ARGUMENT_REGISTERS -.macro SAVE_ARGUMENT_REGISTERS reg, ofs - - stp x0, x1, [\reg, #(\ofs)] - stp x2, x3, [\reg, #(\ofs + 16)] - stp x4, x5, [\reg, #(\ofs + 32)] - stp x6, x7, [\reg, #(\ofs + 48)] - -.endm - -// Reserve 64 bytes of memory before calling SAVE_FLOAT_ARGUMENT_REGISTERS -.macro SAVE_FLOAT_ARGUMENT_REGISTERS reg, ofs - - stp d0, d1, [\reg, #(\ofs)] - stp d2, d3, [\reg, #(\ofs + 16)] - stp d4, d5, [\reg, #(\ofs + 32)] - stp d6, d7, [\reg, #(\ofs + 48)] - -.endm - -.macro RESTORE_ARGUMENT_REGISTERS reg, ofs - - ldp x0, x1, [\reg, #(\ofs)] - ldp x2, x3, [\reg, #(\ofs + 16)] - ldp x4, x5, [\reg, #(\ofs + 32)] - ldp x6, x7, [\reg, #(\ofs + 48)] - -.endm - -.macro RESTORE_FLOAT_ARGUMENT_REGISTERS reg, ofs - - ldp d0, d1, [\reg, #(\ofs)] - ldp d2, d3, [\reg, #(\ofs + 16)] - ldp d4, d5, [\reg, #(\ofs + 32)] - ldp d6, d7, [\reg, #(\ofs + 48)] - -.endm - .macro EPILOG_BRANCH_REG reg br \reg diff --git a/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp b/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp index 32c072d05d06a2..d1119264588898 100644 --- a/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp +++ b/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp @@ -30,6 +30,8 @@ uint32_t PalEventWrite(REGHANDLE arg1, const EVENT_DESCRIPTOR * arg2, uint32_t a } #include "gcenv.h" +#include "gcenv.ee.h" +#include "gcconfig.h" #define REDHAWK_PALEXPORT extern "C" @@ -71,29 +73,36 @@ void InitializeCurrentProcessCpuCount() } else { - DWORD_PTR pmask, smask; - - if (!GetProcessAffinityMask(GetCurrentProcess(), &pmask, &smask)) + if (GCToOSInterface::CanEnableGCCPUGroups()) { - count = 1; + count = GCToOSInterface::GetTotalProcessorCount(); } else { - count = 0; + DWORD_PTR pmask, smask; - while (pmask) + if (!GetProcessAffinityMask(GetCurrentProcess(), &pmask, &smask)) { - pmask &= (pmask - 1); - count++; + count = 1; + } + else + { + count = 0; + + while (pmask) + { + pmask &= (pmask - 1); + count++; + } + + // GetProcessAffinityMask can return pmask=0 and smask=0 on systems with more + // than 64 processors, which would leave us with a count of 0. Since the GC + // expects there to be at least one processor to run on (and thus at least one + // heap), we'll return 64 here if count is 0, since there are likely a ton of + // processors available in that case. + if (count == 0) + count = 64; } - - // GetProcessAffinityMask can return pmask=0 and smask=0 on systems with more - // than 64 processors, which would leave us with a count of 0. Since the GC - // expects there to be at least one processor to run on (and thus at least one - // heap), we'll return 64 here if count is 0, since there are likely a ton of - // processors available in that case. - if (count == 0) - count = 64; } JOBOBJECT_CPU_RATE_CONTROL_INFORMATION cpuRateControl; @@ -119,10 +128,7 @@ void InitializeCurrentProcessCpuCount() if (0 < maxRate && maxRate < MAXIMUM_CPU_RATE) { - SYSTEM_INFO systemInfo; - GetSystemInfo(&systemInfo); - - DWORD cpuLimit = (maxRate * systemInfo.dwNumberOfProcessors + MAXIMUM_CPU_RATE - 1) / MAXIMUM_CPU_RATE; + DWORD cpuLimit = (maxRate * GCToOSInterface::GetTotalProcessorCount() + MAXIMUM_CPU_RATE - 1) / MAXIMUM_CPU_RATE; if (cpuLimit < count) count = cpuLimit; } @@ -145,6 +151,8 @@ REDHAWK_PALEXPORT bool REDHAWK_PALAPI PalInit() return false; } + GCConfig::Initialize(); + if (!GCToOSInterface::Initialize()) { return false; @@ -429,6 +437,18 @@ REDHAWK_PALEXPORT void REDHAWK_PALAPI PalRestoreContext(CONTEXT * pCtx) RtlRestoreContext(pCtx, NULL); } +REDHAWK_PALIMPORT void REDHAWK_PALAPI PopulateControlSegmentRegisters(CONTEXT* pContext) +{ +#if defined(TARGET_X86) || defined(TARGET_AMD64) + CONTEXT ctx; + + RtlCaptureContext(&ctx); + + pContext->SegCs = ctx.SegCs; + pContext->SegSs = ctx.SegSs; +#endif //defined(TARGET_X86) || defined(TARGET_AMD64) +} + static PalHijackCallback g_pHijackCallback; REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalRegisterHijackCallback(_In_ PalHijackCallback callback) @@ -591,6 +611,11 @@ REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalVirtualProtect(_In_ void* pAddre return VirtualProtect(pAddress, size, protect, &oldProtect); } +REDHAWK_PALEXPORT void PalFlushInstructionCache(_In_ void* pAddress, size_t size) +{ + FlushInstructionCache(GetCurrentProcess(), pAddress, size); +} + REDHAWK_PALEXPORT _Ret_maybenull_ void* REDHAWK_PALAPI PalSetWerDataBuffer(_In_ void* pNewBuffer) { static void* pBuffer; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs index 4e8bfcd492dde1..0c1a2a520e2cf0 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs @@ -55,8 +55,8 @@ internal static unsafe void StringToByValAnsiString(string str, byte* pNative, i public static unsafe string ByValAnsiStringToString(byte* buffer, int length) { - int end = SpanHelpers.IndexOf(ref *(byte*)buffer, 0, length); - if (end != -1) + int end = new ReadOnlySpan(buffer, length).IndexOf((byte)0); + if (end >= 0) { length = end; } @@ -77,8 +77,8 @@ internal static unsafe void StringToUnicodeFixedArray(string str, ushort* buffer internal static unsafe string UnicodeToStringFixedArray(ushort* buffer, int length) { - int end = SpanHelpers.IndexOf(ref *(char*)buffer, '\0', length); - if (end != -1) + int end = new ReadOnlySpan(buffer, length).IndexOf('\0'); + if (end >= 0) { length = end; } @@ -309,8 +309,8 @@ internal static unsafe void FixupModuleCell(ModuleFixupCell* pCell) hModule = NativeLibrary.LoadBySearch( callingAssembly, - searchAssemblyDirectory: false, - dllImportSearchPathFlags: 0, + searchAssemblyDirectory: (dllImportSearchPath & (uint)DllImportSearchPath.AssemblyDirectory) != 0, + dllImportSearchPathFlags: (int)(dllImportSearchPath & ~(uint)DllImportSearchPath.AssemblyDirectory), ref loadLibErrorTracker, moduleName); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs index 234f8a16e9c5c0..4f0106980837d4 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs @@ -235,11 +235,15 @@ public DynamicInvokeInfo(MethodBase method, IntPtr invokeThunk) StackAllocedArguments argStorage = default; StackAllocatedByRefs byrefStorage = default; +#pragma warning disable 8500 CheckArguments(ref argStorage._arg0!, (ByReference*)&byrefStorage, parameters, binderBundle); +#pragma warning restore 8500 try { +#pragma warning disable 8500 ret = ref RawCalliHelper.Call(InvokeThunk, (void*)methodToCall, ref thisArg, ref ret, &byrefStorage); +#pragma warning restore 8500 DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); } catch (Exception e) when (wrapInTargetInvocationException) @@ -268,7 +272,9 @@ private unsafe ref byte InvokeWithManyArguments( IntPtr* pStorage = stackalloc IntPtr[2 * argCount]; NativeMemory.Clear(pStorage, (nuint)(2 * argCount) * (nuint)sizeof(IntPtr)); - ByReference* pByRefStorage = (ByReference*)(pStorage + argCount); +#pragma warning disable 8500 + void* pByRefStorage = (ByReference*)(pStorage + argCount); +#pragma warning restore 8500 RuntimeImports.GCFrameRegistration regArgStorage = new(pStorage, (uint)argCount, areByRefs: false); RuntimeImports.GCFrameRegistration regByRefStorage = new(pByRefStorage, (uint)argCount, areByRefs: true); @@ -326,7 +332,7 @@ private unsafe ref byte InvokeWithManyArguments( private unsafe void CheckArguments( ref object copyOfParameters, - ByReference* byrefParameters, + void* byrefParameters, object?[] parameters, BinderBundle binderBundle) { @@ -398,8 +404,10 @@ private unsafe void CheckArguments( Unsafe.Add(ref copyOfParameters, i) = arg!; - byrefParameters[i] = new ByReference(ref (argumentInfo.Transform & Transform.Reference) != 0 ? +#pragma warning disable 8500, 9094 + ((ByReference*)byrefParameters)[i] = new ByReference(ref (argumentInfo.Transform & Transform.Reference) != 0 ? ref Unsafe.As(ref Unsafe.Add(ref copyOfParameters, i)) : ref arg.GetRawData()); +#pragma warning restore 8500, 9094 } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/NativeFormat/NativeFormatCustomAttributeData.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/NativeFormat/NativeFormatCustomAttributeData.cs index 07801d02a50f52..a055d0c7882c4b 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/NativeFormat/NativeFormatCustomAttributeData.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/NativeFormat/NativeFormatCustomAttributeData.cs @@ -120,7 +120,7 @@ internal sealed override IList GetConstructorArgum { Handle typeHandle = ctorTypeHandles[index]; Exception? exception = null; - RuntimeTypeInfo? argumentType = typeHandle.TryResolve(_reader, new TypeContext(null, null), ref exception); + RuntimeTypeInfo? argumentType = typeHandle.TryResolve(_reader, AttributeType.CastToRuntimeTypeInfo().TypeContext, ref exception); if (argumentType == null) { if (throwIfMissingMetadata) @@ -162,7 +162,7 @@ internal sealed override IList GetNamedArguments(b bool isField = (namedArgument.Flags == NamedArgumentMemberKind.Field); Exception? exception = null; - RuntimeTypeInfo? argumentType = namedArgument.Type.TryResolve(_reader, new TypeContext(null, null), ref exception); + RuntimeTypeInfo? argumentType = namedArgument.Type.TryResolve(_reader, AttributeType.CastToRuntimeTypeInfo().TypeContext, ref exception); if (argumentType == null) { if (throwIfMissingMetadata) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.cs index 3708b256808e88..1c95dbc088b4fd 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.cs @@ -20,7 +20,7 @@ internal static IntPtr LoadLibraryByName(string libraryName, Assembly assembly, bool searchAssemblyDirectory; if (searchPath.HasValue) { - searchPathFlags = (int)(searchPath.Value & ~DllImportSearchPath.AssemblyDirectory); + searchPathFlags = (int)(searchPath!.Value & ~DllImportSearchPath.AssemblyDirectory); searchAssemblyDirectory = (searchPath.Value & DllImportSearchPath.AssemblyDirectory) != 0; } else diff --git a/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/MethodNameFormatter.cs b/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/MethodNameFormatter.cs index ebfbab7a8e2c02..41226385ea86ac 100644 --- a/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/MethodNameFormatter.cs +++ b/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/MethodNameFormatter.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; -using System.Collections.Generic; using System.Diagnostics; using System.Text; @@ -49,7 +47,6 @@ public static string FormatMethodName(MetadataReader metadataReader, TypeDefinit MethodNameFormatter formatter = new MethodNameFormatter(metadataReader, SigTypeContext.FromMethod(metadataReader, enclosingTypeHandle, methodHandle)); Method method = metadataReader.GetMethod(methodHandle); - MethodSignature methodSignature = metadataReader.GetMethodSignature(method.Signature); formatter.EmitTypeName(enclosingTypeHandle, namespaceQualified: true); formatter._outputBuilder.Append('.'); formatter.EmitString(method.Name); @@ -64,7 +61,7 @@ public static string FormatMethodName(MetadataReader metadataReader, TypeDefinit } else { - formatter._outputBuilder.Append(", "); + formatter._outputBuilder.Append(','); } formatter.EmitTypeName(handle, namespaceQualified: false); } @@ -73,7 +70,7 @@ public static string FormatMethodName(MetadataReader metadataReader, TypeDefinit formatter._outputBuilder.Append(']'); } - formatter.EmitMethodParameters(methodSignature); + formatter.EmitMethodParameters(methodHandle); return formatter._outputBuilder.ToString(); } @@ -124,28 +121,30 @@ private void EmitMethodReferenceName(MemberReferenceHandle memberRefHandle) private void EmitMethodInstantiationName(MethodInstantiationHandle methodInstHandle) { MethodInstantiation methodInst = _metadataReader.GetMethodInstantiation(methodInstHandle); - MethodSignature methodSignature; + if (methodInst.Method.HandleType == HandleType.MemberReference) { MemberReferenceHandle methodRefHandle = methodInst.Method.ToMemberReferenceHandle(_metadataReader); MemberReference methodRef = methodRefHandle.GetMemberReference(_metadataReader); - EmitContainingTypeAndMethodName(methodRef, out methodSignature); + EmitContainingTypeAndMethodName(methodRef, out MethodSignature methodSignature); + EmitGenericArguments(methodInst.GenericTypeArguments); + EmitMethodParameters(methodSignature); } else { QualifiedMethodHandle qualifiedMethodHandle = methodInst.Method.ToQualifiedMethodHandle(_metadataReader); QualifiedMethod qualifiedMethod = _metadataReader.GetQualifiedMethod(qualifiedMethodHandle); - EmitContainingTypeAndMethodName(qualifiedMethod, out methodSignature); + EmitContainingTypeAndMethodName(qualifiedMethod); + EmitGenericArguments(methodInst.GenericTypeArguments); + EmitMethodParameters(qualifiedMethod.Method); } - EmitGenericArguments(methodInst.GenericTypeArguments); - EmitMethodParameters(methodSignature); } private void EmitMethodDefinitionName(QualifiedMethodHandle qualifiedMethodHandle) { QualifiedMethod qualifiedMethod = _metadataReader.GetQualifiedMethod(qualifiedMethodHandle); - EmitContainingTypeAndMethodName(qualifiedMethod, out MethodSignature methodSignature); - EmitMethodParameters(methodSignature); + EmitContainingTypeAndMethodName(qualifiedMethod); + EmitMethodParameters(qualifiedMethod.Method); } /// @@ -161,10 +160,9 @@ private void EmitContainingTypeAndMethodName(MemberReference methodRef, out Meth EmitString(methodRef.Name); } - private void EmitContainingTypeAndMethodName(QualifiedMethod qualifiedMethod, out MethodSignature methodSignature) + private void EmitContainingTypeAndMethodName(QualifiedMethod qualifiedMethod) { Method method = _metadataReader.GetMethod(qualifiedMethod.Method); - methodSignature = _metadataReader.GetMethodSignature(method.Signature); EmitTypeName(qualifiedMethod.EnclosingType, namespaceQualified: true); _outputBuilder.Append('.'); EmitString(method.Name); @@ -181,6 +179,57 @@ private void EmitMethodParameters(MethodSignature methodSignature) _outputBuilder.Append(')'); } + /// + /// Emit parenthesized method argument type list with parameter names. + /// + /// Method handle to use for parameter formatting + private void EmitMethodParameters(MethodHandle methodHandle) + { + bool TryGetNextParameter(ref ParameterHandleCollection.Enumerator enumerator, out Parameter parameter) + { + bool hasNext = enumerator.MoveNext(); + parameter = hasNext ? enumerator.Current.GetParameter(_metadataReader) : default; + return hasNext; + } + + Method method = methodHandle.GetMethod(_metadataReader); + HandleCollection typeVector = method.Signature.GetMethodSignature(_metadataReader).Parameters; + ParameterHandleCollection.Enumerator parameters = method.Parameters.GetEnumerator(); + + bool hasParameter = TryGetNextParameter(ref parameters, out Parameter parameter); + if (hasParameter && parameter.Sequence == 0) + { + hasParameter = TryGetNextParameter(ref parameters, out parameter); + } + + _outputBuilder.Append('('); + + uint typeIndex = 0; + foreach (Handle type in typeVector) + { + if (typeIndex != 0) + { + _outputBuilder.Append(", "); + } + + EmitTypeName(type, namespaceQualified: false); + + if (++typeIndex == parameter.Sequence && hasParameter) + { + string name = parameter.Name.GetConstantStringValue(_metadataReader).Value; + hasParameter = TryGetNextParameter(ref parameters, out parameter); + + if (!string.IsNullOrEmpty(name)) + { + _outputBuilder.Append(' '); + _outputBuilder.Append(name); + } + } + } + + _outputBuilder.Append(')'); + } + /// /// Emit comma-separated list of type names into the output string builder. /// diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.GVMResolution.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.GVMResolution.cs index 99f7272b3ec9d7..7c411f987bd9d3 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.GVMResolution.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.GVMResolution.cs @@ -72,13 +72,13 @@ public bool TryGetGenericVirtualTargetForTypeAndSlot(RuntimeTypeHandle targetHan var sb = new System.Text.StringBuilder(); sb.AppendLine("Generic virtual method pointer lookup failure."); sb.AppendLine(); - sb.AppendLine("Declaring type handle: " + declaringType.LowLevelToStringRawEETypeAddress()); - sb.AppendLine("Target type handle: " + targetHandle.LowLevelToStringRawEETypeAddress()); + sb.AppendLine("Declaring type handle: " + RuntimeAugments.GetLastResortString(declaringType)); + sb.AppendLine("Target type handle: " + RuntimeAugments.GetLastResortString(targetHandle)); sb.AppendLine("Method name: " + methodNameAndSignature.Name); sb.AppendLine("Instantiation:"); for (int i = 0; i < genericArguments.Length; i++) { - sb.AppendLine(" Argument " + i.LowLevelToString() + ": " + genericArguments[i].LowLevelToStringRawEETypeAddress()); + sb.AppendLine(" Argument " + i.LowLevelToString() + ": " + RuntimeAugments.GetLastResortString(genericArguments[i])); } Environment.FailFast(sb.ToString()); @@ -616,13 +616,13 @@ private unsafe bool ResolveGenericVirtualMethodTarget_Static(RuntimeTypeHandle t var sb = new System.Text.StringBuilder(); sb.AppendLine("Generic virtual method pointer lookup failure."); sb.AppendLine(); - sb.AppendLine("Declaring type handle: " + declaringType.LowLevelToStringRawEETypeAddress()); - sb.AppendLine("Target type handle: " + targetTypeHandle.LowLevelToStringRawEETypeAddress()); + sb.AppendLine("Declaring type handle: " + RuntimeAugments.GetLastResortString(declaringType)); + sb.AppendLine("Target type handle: " + RuntimeAugments.GetLastResortString(targetTypeHandle)); sb.AppendLine("Method name: " + targetMethodNameAndSignature.Name); sb.AppendLine("Instantiation:"); for (int i = 0; i < genericArguments.Length; i++) { - sb.AppendLine(" Argument " + i.LowLevelToString() + ": " + genericArguments[i].LowLevelToStringRawEETypeAddress()); + sb.AppendLine(" Argument " + i.LowLevelToString() + ": " + RuntimeAugments.GetLastResortString(genericArguments[i])); } Environment.FailFast(sb.ToString()); diff --git a/src/coreclr/nativeaot/docs/compiling.md b/src/coreclr/nativeaot/docs/compiling.md index 1b4475a89362d3..f142fa3afecb17 100644 --- a/src/coreclr/nativeaot/docs/compiling.md +++ b/src/coreclr/nativeaot/docs/compiling.md @@ -2,8 +2,7 @@ Please consult [documentation](https://docs.microsoft.com/dotnet/core/deploying/native-aot) for instructions how to compile and publish application. -The rest of this document covers advanced topics only. - +The rest of this document covers advanced topics only. Adding an explicit package reference to `Microsoft.DotNet.ILCompiler` will generate warning when publishing and it can run into version errors. When possible, use the PublishAot property to publish a native AOT application. ## Using daily builds @@ -34,7 +33,21 @@ or by adding the following element to the project file: ## Cross-architecture compilation -Native AOT toolchain allows targeting ARM64 on an x64 host and vice versa for both Windows and Linux. Cross-OS compilation, such as targeting Linux on a Windows host, is not supported. To target win-arm64 on a Windows x64 host, in addition to the `Microsoft.DotNet.ILCompiler` package reference, also add the `runtime.win-x64.Microsoft.DotNet.ILCompiler` package reference to get the x64-hosted compiler: +Native AOT toolchain allows targeting ARM64 on an x64 host and vice versa for both Windows and Linux and is now supported in the SDK. Cross-OS compilation, such as targeting Linux on a Windows host, is not supported. For SDK support, add the following to your project file, + +```xml + + true + +``` + +Targeting win-arm64 on a Windows x64 host machine, + +```bash +> dotnet publish -r win-arm64 -c Release +``` + +To target win-arm64 on a Windows x64 host on an advanced scenario where the SDK support is not sufficient (note that these scenarios will generate warnings for using explicit package references), in addition to the `Microsoft.DotNet.ILCompiler` package reference, also add the `runtime.win-x64.Microsoft.DotNet.ILCompiler` package reference to get the x64-hosted compiler: ```xml ``` diff --git a/src/coreclr/pal/prebuilt/corerror/mscorurt.rc b/src/coreclr/pal/prebuilt/corerror/mscorurt.rc index c4bcccc6e24a80..6f808d6f8f84d5 100644 --- a/src/coreclr/pal/prebuilt/corerror/mscorurt.rc +++ b/src/coreclr/pal/prebuilt/corerror/mscorurt.rc @@ -301,5 +301,6 @@ BEGIN MSG_FOR_URT_HR(CLR_E_GC_BAD_AFFINITY_CONFIG_FORMAT) "GCHeapAffinitizeRanges configuration string has invalid format." MSG_FOR_URT_HR(CLR_E_GC_BAD_HARD_LIMIT) "GC heap hard limit configuration is invalid." MSG_FOR_URT_HR(CLR_E_GC_LARGE_PAGE_MISSING_HARD_LIMIT) "GC large page support requires hard limit settings." + MSG_FOR_URT_HR(CLR_E_GC_BAD_REGION_SIZE) "GC Region Size must be less than 2GB." MSG_FOR_URT_HR(COR_E_BADIMAGEFORMAT) "The format of a DLL or executable being loaded is invalid." END diff --git a/src/coreclr/pal/prebuilt/inc/clrdata.h b/src/coreclr/pal/prebuilt/inc/clrdata.h index 09b24af8ccf094..802c34a87bf0cb 100644 --- a/src/coreclr/pal/prebuilt/inc/clrdata.h +++ b/src/coreclr/pal/prebuilt/inc/clrdata.h @@ -1299,7 +1299,8 @@ enum CLRDataEnumMemoryFlags CLRDATA_ENUM_MEM_DEFAULT = 0, CLRDATA_ENUM_MEM_MINI = CLRDATA_ENUM_MEM_DEFAULT, CLRDATA_ENUM_MEM_HEAP = 0x1, - CLRDATA_ENUM_MEM_TRIAGE = 0x2 + CLRDATA_ENUM_MEM_TRIAGE = 0x2, + CLRDATA_ENUM_MEM_HEAP2 = 0x3 } CLRDataEnumMemoryFlags; diff --git a/src/coreclr/pal/prebuilt/inc/cordebug.h b/src/coreclr/pal/prebuilt/inc/cordebug.h index 2d71f01fe85fe3..fa4b434eccf554 100644 --- a/src/coreclr/pal/prebuilt/inc/cordebug.h +++ b/src/coreclr/pal/prebuilt/inc/cordebug.h @@ -15644,10 +15644,10 @@ EXTERN_C const IID IID_ICorDebugArrayValue; /* [in] */ ULONG32 cdim, /* [length_is][size_is][out] */ ULONG32 dims[ ]) = 0; - virtual HRESULT STDMETHODCALLTYPE HasBaseIndices( - /* [out] */ BOOL *pbHasBaseIndices) = 0; + virtual HRESULT STDMETHODCALLTYPE HasBaseIndicies( + /* [out] */ BOOL *pbHasBaseIndicies) = 0; - virtual HRESULT STDMETHODCALLTYPE GetBaseIndices( + virtual HRESULT STDMETHODCALLTYPE GetBaseIndicies( /* [in] */ ULONG32 cdim, /* [length_is][size_is][out] */ ULONG32 indices[ ]) = 0; @@ -15722,11 +15722,11 @@ EXTERN_C const IID IID_ICorDebugArrayValue; /* [in] */ ULONG32 cdim, /* [length_is][size_is][out] */ ULONG32 dims[ ]); - HRESULT ( STDMETHODCALLTYPE *HasBaseIndices )( + HRESULT ( STDMETHODCALLTYPE *HasBaseIndicies )( ICorDebugArrayValue * This, - /* [out] */ BOOL *pbHasBaseIndices); + /* [out] */ BOOL *pbHasBaseIndicies); - HRESULT ( STDMETHODCALLTYPE *GetBaseIndices )( + HRESULT ( STDMETHODCALLTYPE *GetBaseIndicies )( ICorDebugArrayValue * This, /* [in] */ ULONG32 cdim, /* [length_is][size_is][out] */ ULONG32 indices[ ]); @@ -15797,11 +15797,11 @@ EXTERN_C const IID IID_ICorDebugArrayValue; #define ICorDebugArrayValue_GetDimensions(This,cdim,dims) \ ( (This)->lpVtbl -> GetDimensions(This,cdim,dims) ) -#define ICorDebugArrayValue_HasBaseIndices(This,pbHasBaseIndices) \ - ( (This)->lpVtbl -> HasBaseIndices(This,pbHasBaseIndices) ) +#define ICorDebugArrayValue_HasBaseIndicies(This,pbHasBaseIndicies) \ + ( (This)->lpVtbl -> HasBaseIndicies(This,pbHasBaseIndicies) ) -#define ICorDebugArrayValue_GetBaseIndices(This,cdim,indices) \ - ( (This)->lpVtbl -> GetBaseIndices(This,cdim,indices) ) +#define ICorDebugArrayValue_GetBaseIndicies(This,cdim,indices) \ + ( (This)->lpVtbl -> GetBaseIndicies(This,cdim,indices) ) #define ICorDebugArrayValue_GetElement(This,cdim,indices,ppValue) \ ( (This)->lpVtbl -> GetElement(This,cdim,indices,ppValue) ) diff --git a/src/coreclr/pal/prebuilt/inc/corerror.h b/src/coreclr/pal/prebuilt/inc/corerror.h index 2bfebe10feeb2d..6f0be602583ee1 100644 --- a/src/coreclr/pal/prebuilt/inc/corerror.h +++ b/src/coreclr/pal/prebuilt/inc/corerror.h @@ -374,6 +374,7 @@ #define CLR_E_GC_BAD_AFFINITY_CONFIG_FORMAT EMAKEHR(0x200b) #define CLR_E_GC_BAD_HARD_LIMIT EMAKEHR(0x200d) #define CLR_E_GC_LARGE_PAGE_MISSING_HARD_LIMIT EMAKEHR(0x200e) +#define CLR_E_GC_BAD_REGION_SIZE EMAKEHR(0x200f) #define COR_E_UNAUTHORIZEDACCESS E_ACCESSDENIED #define COR_E_ARGUMENT E_INVALIDARG #define COR_E_INVALIDCAST E_NOINTERFACE diff --git a/src/coreclr/pal/src/config.h.in b/src/coreclr/pal/src/config.h.in index 75507804d23a00..3f881d2b3ec6ba 100644 --- a/src/coreclr/pal/src/config.h.in +++ b/src/coreclr/pal/src/config.h.in @@ -68,6 +68,7 @@ #cmakedefine01 HAVE_SCHED_SETAFFINITY #cmakedefine HAVE_UNW_GET_SAVE_LOC #cmakedefine HAVE_UNW_GET_ACCESSORS +#cmakedefine HAVE_UNW_AARCH64_X19 #cmakedefine01 HAVE_XSWDEV #cmakedefine01 HAVE_XSW_USAGE #cmakedefine01 HAVE_PUBLIC_XSTATE_STRUCT diff --git a/src/coreclr/pal/src/configure.cmake b/src/coreclr/pal/src/configure.cmake index 4f900a5555ef63..37165783b476e2 100644 --- a/src/coreclr/pal/src/configure.cmake +++ b/src/coreclr/pal/src/configure.cmake @@ -1047,6 +1047,15 @@ int main(int argc, char **argv) check_symbol_exists(unw_get_save_loc libunwind.h HAVE_UNW_GET_SAVE_LOC) check_symbol_exists(unw_get_accessors libunwind.h HAVE_UNW_GET_ACCESSORS) +check_cxx_source_compiles(" +#include + +int main(int argc, char **argv) +{ + int flag = (int)UNW_AARCH64_X19; + return 0; +}" HAVE_UNW_AARCH64_X19) + if(NOT CLR_CMAKE_USE_SYSTEM_LIBUNWIND) list(REMOVE_AT CMAKE_REQUIRED_INCLUDES 0 1) endif() diff --git a/src/coreclr/pal/src/exception/seh-unwind.cpp b/src/coreclr/pal/src/exception/seh-unwind.cpp index f718be6af54c27..fb437c3b5b1fa2 100644 --- a/src/coreclr/pal/src/exception/seh-unwind.cpp +++ b/src/coreclr/pal/src/exception/seh-unwind.cpp @@ -54,7 +54,7 @@ Module Name: #endif // HOST_UNIX -#if defined(TARGET_OSX) && defined(TARGET_ARM64) +#if defined(TARGET_OSX) && defined(TARGET_ARM64) && !defined(HAVE_UNW_AARCH64_X19) // MacOS uses ARM64 instead of AARCH64 to describe these registers // Create aliases to reuse more code enum diff --git a/src/coreclr/pal/src/exception/signal.cpp b/src/coreclr/pal/src/exception/signal.cpp index 108b64ae9954d1..c3bdcc793eb95d 100644 --- a/src/coreclr/pal/src/exception/signal.cpp +++ b/src/coreclr/pal/src/exception/signal.cpp @@ -294,22 +294,6 @@ void SEHCleanupSignals() } } -/*++ -Function : - SEHCleanupAbort() - - Restore default SIGABORT signal handlers - - (no parameters, no return value) ---*/ -void SEHCleanupAbort() -{ - if (g_registered_signal_handlers) - { - restore_signal(SIGABRT, &g_previous_sigabrt); - } -} - /* internal function definitions **********************************************/ /*++ @@ -396,7 +380,7 @@ static void invoke_previous_action(struct sigaction* action, int code, siginfo_t if (signalRestarts) { // This signal mustn't be ignored because it will be restarted. - PROCAbort(code); + PROCAbort(code, siginfo); } return; } @@ -411,7 +395,7 @@ static void invoke_previous_action(struct sigaction* action, int code, siginfo_t { // We can't invoke the original handler because returning from the // handler doesn't restart the exception. - PROCAbort(code); + PROCAbort(code, siginfo); } } else if (IsSaSigInfo(action)) @@ -429,7 +413,7 @@ static void invoke_previous_action(struct sigaction* action, int code, siginfo_t PROCNotifyProcessShutdown(IsRunningOnAlternateStack(context)); - PROCCreateCrashDumpIfEnabled(code); + PROCCreateCrashDumpIfEnabled(code, siginfo); } /*++ @@ -601,13 +585,13 @@ static void sigsegv_handler(int code, siginfo_t *siginfo, void *context) if (SwitchStackAndExecuteHandler(code | StackOverflowFlag, siginfo, context, (size_t)handlerStackTop)) { - PROCAbort(SIGSEGV); + PROCAbort(SIGSEGV, siginfo); } } else { (void)!write(STDERR_FILENO, StackOverflowMessage, sizeof(StackOverflowMessage) - 1); - PROCAbort(SIGSEGV); + PROCAbort(SIGSEGV, siginfo); } } @@ -762,7 +746,7 @@ static void sigterm_handler(int code, siginfo_t *siginfo, void *context) DWORD val = 0; if (enableDumpOnSigTerm.IsSet() && enableDumpOnSigTerm.TryAsInteger(10, val) && val == 1) { - PROCCreateCrashDumpIfEnabled(code); + PROCCreateCrashDumpIfEnabled(code, siginfo); } // g_pSynchronizationManager shouldn't be null if PAL is initialized. _ASSERTE(g_pSynchronizationManager != nullptr); diff --git a/src/coreclr/pal/src/include/pal/process.h b/src/coreclr/pal/src/include/pal/process.h index b1de472ad427f1..71788cb2d00866 100644 --- a/src/coreclr/pal/src/include/pal/process.h +++ b/src/coreclr/pal/src/include/pal/process.h @@ -151,11 +151,12 @@ BOOL PROCAbortInitialize(); Parameters: signal - POSIX signal number + siginfo - POSIX signal info Does not return --*/ PAL_NORETURN -VOID PROCAbort(int signal = SIGABRT); +VOID PROCAbort(int signal = SIGABRT, siginfo_t* siginfo = nullptr); /*++ Function: @@ -180,7 +181,7 @@ VOID PROCNotifyProcessShutdown(bool isExecutingOnAltStack = false); (no return value) --*/ -VOID PROCCreateCrashDumpIfEnabled(int signal); +VOID PROCCreateCrashDumpIfEnabled(int signal, siginfo_t* siginfo); /*++ Function: diff --git a/src/coreclr/pal/src/include/pal/sharedmemory.h b/src/coreclr/pal/src/include/pal/sharedmemory.h index 88834b93d06738..cbdd816dcf2e73 100644 --- a/src/coreclr/pal/src/include/pal/sharedmemory.h +++ b/src/coreclr/pal/src/include/pal/sharedmemory.h @@ -88,9 +88,11 @@ class SharedMemoryException class SharedMemoryHelpers { private: + static const mode_t PermissionsMask_CurrentUser_ReadWrite; static const mode_t PermissionsMask_CurrentUser_ReadWriteExecute; static const mode_t PermissionsMask_AllUsers_ReadWrite; static const mode_t PermissionsMask_AllUsers_ReadWriteExecute; + public: static const UINT32 InvalidProcessId; static const SIZE_T InvalidThreadId; @@ -106,12 +108,12 @@ class SharedMemoryHelpers static void BuildSharedFilesPath(PathCharString& destination, const char *suffix, int suffixByteCount); static bool AppendUInt32String(PathCharString& destination, UINT32 value); - static bool EnsureDirectoryExists(const char *path, bool isGlobalLockAcquired, bool createIfNotExist = true, bool isSystemDirectory = false); + static bool EnsureDirectoryExists(const char *path, bool isGlobalLockAcquired, bool hasCurrentUserAccessOnly, bool setStickyFlag, bool createIfNotExist = true, bool isSystemDirectory = false); private: static int Open(LPCSTR path, int flags, mode_t mode = static_cast(0)); public: static int OpenDirectory(LPCSTR path); - static int CreateOrOpenFile(LPCSTR path, bool createIfNotExist = true, bool *createdRef = nullptr); + static int CreateOrOpenFile(LPCSTR path, bool createIfNotExist = true, bool isSessionScope = true, bool *createdRef = nullptr); static void CloseFile(int fileDescriptor); static SIZE_T GetFileSize(int fileDescriptor); diff --git a/src/coreclr/pal/src/include/pal/signal.hpp b/src/coreclr/pal/src/include/pal/signal.hpp index 9dca625635df3c..6a3e41b4de473f 100644 --- a/src/coreclr/pal/src/include/pal/signal.hpp +++ b/src/coreclr/pal/src/include/pal/signal.hpp @@ -117,14 +117,4 @@ Function : --*/ void SEHCleanupSignals(); -/*++ -Function : - SEHCleanupAbort() - - Restore default SIGABORT signal handlers - - (no parameters, no return value) ---*/ -void SEHCleanupAbort(); - #endif /* _PAL_SIGNAL_HPP_ */ diff --git a/src/coreclr/pal/src/include/pal/virtual.h b/src/coreclr/pal/src/include/pal/virtual.h index 5eeb51f3400925..9a30d083b8494c 100644 --- a/src/coreclr/pal/src/include/pal/virtual.h +++ b/src/coreclr/pal/src/include/pal/virtual.h @@ -34,13 +34,6 @@ typedef struct _CMI { DWORD accessProtection; /* Initial allocation access protection. */ DWORD allocationType; /* Initial allocation type. */ - - BYTE * pAllocState; /* Individual allocation type tracking for each */ - /* page in the region. */ - - BYTE * pProtectionState; /* Individual allocation type tracking for each */ - /* page in the region. */ - } CMI, * PCMI; enum VIRTUAL_CONSTANTS diff --git a/src/coreclr/pal/src/map/virtual.cpp b/src/coreclr/pal/src/map/virtual.cpp index e11f39cd4be16f..f033ba9a50b12e 100644 --- a/src/coreclr/pal/src/map/virtual.cpp +++ b/src/coreclr/pal/src/map/virtual.cpp @@ -202,8 +202,6 @@ void VIRTUALCleanup() { WARN( "The memory at %d was not freed through a call to VirtualFree.\n", pEntry->startBoundary ); - free(pEntry->pAllocState); - free(pEntry->pProtectionState ); pTempEntry = pEntry; pEntry = pEntry->pNext; free(pTempEntry ); @@ -236,235 +234,6 @@ static BOOL VIRTUALContainsInvalidProtectionFlags( IN DWORD flProtect ) } -/**** - * - * VIRTUALIsPageCommitted - * - * SIZE_T nBitToRetrieve - Which page to check. - * - * Returns TRUE if committed, FALSE otherwise. - * - */ -static BOOL VIRTUALIsPageCommitted( SIZE_T nBitToRetrieve, CONST PCMI pInformation ) -{ - SIZE_T nByteOffset = 0; - UINT nBitOffset = 0; - UINT byteMask = 0; - - if ( !pInformation ) - { - ERROR( "pInformation was NULL!\n" ); - return FALSE; - } - - nByteOffset = nBitToRetrieve / CHAR_BIT; - nBitOffset = nBitToRetrieve % CHAR_BIT; - - byteMask = 1 << nBitOffset; - - if ( pInformation->pAllocState[ nByteOffset ] & byteMask ) - { - return TRUE; - } - else - { - return FALSE; - } -} - -/********* - * - * VIRTUALGetAllocationType - * - * IN SIZE_T Index - The page within the range to retrieve - * the state for. - * - * IN pInformation - The virtual memory object. - * - */ -static INT VIRTUALGetAllocationType( SIZE_T Index, CONST PCMI pInformation ) -{ - if ( VIRTUALIsPageCommitted( Index, pInformation ) ) - { - return MEM_COMMIT; - } - else - { - return MEM_RESERVE; - } -} - -/**** - * - * VIRTUALSetPageBits - * - * IN UINT nStatus - Bit set / reset [0: reset, any other value: set]. - * IN SIZE_T nStartingBit - The bit to set. - * - * IN SIZE_T nNumberOfBits - The range of bits to set. - * IN BYTE* pBitArray - A pointer the array to be manipulated. - * - * Returns TRUE on success, FALSE otherwise. - * Turn on/off memory status bits. - * - */ -static BOOL VIRTUALSetPageBits ( UINT nStatus, SIZE_T nStartingBit, - SIZE_T nNumberOfBits, BYTE * pBitArray ) -{ - /* byte masks for optimized modification of partial bytes (changing less - than 8 bits in a single byte). note that bits are treated in little - endian order : value 1 is bit 0; value 128 is bit 7. in the binary - representations below, bit 0 is on the right */ - - /* start masks : for modifying bits >= n while preserving bits < n. - example : if nStartignBit%8 is 3, then bits 0, 1, 2 remain unchanged - while bits 3..7 are changed; startmasks[3] can be used for this. */ - static const BYTE startmasks[8] = { - 0xff, /* start at 0 : 1111 1111 */ - 0xfe, /* start at 1 : 1111 1110 */ - 0xfc, /* start at 2 : 1111 1100 */ - 0xf8, /* start at 3 : 1111 1000 */ - 0xf0, /* start at 4 : 1111 0000 */ - 0xe0, /* start at 5 : 1110 0000 */ - 0xc0, /* start at 6 : 1100 0000 */ - 0x80 /* start at 7 : 1000 0000 */ - }; - - /* end masks : for modifying bits <= n while preserving bits > n. - example : if the last bit to change is 5, then bits 6 & 7 stay unchanged - while bits 1..5 are changed; endmasks[5] can be used for this. */ - static const BYTE endmasks[8] = { - 0x01, /* end at 0 : 0000 0001 */ - 0x03, /* end at 1 : 0000 0011 */ - 0x07, /* end at 2 : 0000 0111 */ - 0x0f, /* end at 3 : 0000 1111 */ - 0x1f, /* end at 4 : 0001 1111 */ - 0x3f, /* end at 5 : 0011 1111 */ - 0x7f, /* end at 6 : 0111 1111 */ - 0xff /* end at 7 : 1111 1111 */ - }; - /* last example : if only the middle of a byte must be changed, both start - and end masks can be combined (bitwise AND) to obtain the correct mask. - if we want to change bits 2 to 4 : - startmasks[2] : 0xfc 1111 1100 (change 2,3,4,5,6,7) - endmasks[4]: 0x1f 0001 1111 (change 0,1,2,3,4) - bitwise AND : 0x1c 0001 1100 (change 2,3,4) - */ - - BYTE byte_mask; - SIZE_T nLastBit; - SIZE_T nFirstByte; - SIZE_T nLastByte; - SIZE_T nFullBytes; - - TRACE( "VIRTUALSetPageBits( nStatus = %d, nStartingBit = %d, " - "nNumberOfBits = %d, pBitArray = 0x%p )\n", - nStatus, nStartingBit, nNumberOfBits, pBitArray ); - - if ( 0 == nNumberOfBits ) - { - ERROR( "nNumberOfBits was 0!\n" ); - return FALSE; - } - - nLastBit = nStartingBit+nNumberOfBits-1; - nFirstByte = nStartingBit / 8; - nLastByte = nLastBit / 8; - - /* handle partial first byte (if any) */ - if(0 != (nStartingBit % 8)) - { - byte_mask = startmasks[nStartingBit % 8]; - - /* if 1st byte is the only changing byte, combine endmask to preserve - trailing bits (see 3rd example above) */ - if( nLastByte == nFirstByte) - { - byte_mask &= endmasks[nLastBit % 8]; - } - - /* byte_mask contains 1 for bits to change, 0 for bits to leave alone */ - if(0 == nStatus) - { - /* bits to change must be set to 0 : invert byte_mask (giving 0 for - bits to change), use bitwise AND */ - pBitArray[nFirstByte] &= ~byte_mask; - } - else - { - /* bits to change must be set to 1 : use bitwise OR */ - pBitArray[nFirstByte] |= byte_mask; - } - - /* stop right away if only 1 byte is being modified */ - if(nLastByte == nFirstByte) - { - return TRUE; - } - - /* we're done with the 1st byte; skip over it */ - nFirstByte++; - } - - /* number of bytes to change, excluding the last byte (handled separately)*/ - nFullBytes = nLastByte - nFirstByte; - - if(0 != nFullBytes) - { - // Turn off/on dirty bits - memset( &(pBitArray[nFirstByte]), (0 == nStatus) ? 0 : 0xFF, nFullBytes ); - } - - /* handle last (possibly partial) byte */ - byte_mask = endmasks[nLastBit % 8]; - - /* byte_mask contains 1 for bits to change, 0 for bits to leave alone */ - if(0 == nStatus) - { - /* bits to change must be set to 0 : invert byte_mask (giving 0 for - bits to change), use bitwise AND */ - pBitArray[nLastByte] &= ~byte_mask; - } - else - { - /* bits to change must be set to 1 : use bitwise OR */ - pBitArray[nLastByte] |= byte_mask; - } - - return TRUE; -} - -/**** - * - * VIRTUALSetAllocState - * - * IN UINT nAction - Which action to perform. - * IN SIZE_T nStartingBit - The bit to set. - * - * IN SIZE_T nNumberOfBits - The range of bits to set. - * IN PCMI pStateArray - A pointer the array to be manipulated. - * - * Returns TRUE on success, FALSE otherwise. - * Turn bit on to indicate committed, turn bit off to indicate reserved. - * - */ -static BOOL VIRTUALSetAllocState( UINT nAction, SIZE_T nStartingBit, - SIZE_T nNumberOfBits, CONST PCMI pInformation ) -{ - TRACE( "VIRTUALSetAllocState( nAction = %d, nStartingBit = %d, " - "nNumberOfBits = %d, pStateArray = 0x%p )\n", - nAction, nStartingBit, nNumberOfBits, pInformation ); - - if ( !pInformation ) - { - ERROR( "pInformation was invalid!\n" ); - return FALSE; - } - - return VIRTUALSetPageBits((MEM_COMMIT == nAction) ? 1 : 0, nStartingBit, - nNumberOfBits, pInformation->pAllocState); -} - /**** * * VIRTUALFindRegionInformation( ) @@ -541,99 +310,12 @@ static BOOL VIRTUALReleaseMemory( PCMI pMemoryToBeReleased ) } } - free( pMemoryToBeReleased->pAllocState ); - pMemoryToBeReleased->pAllocState = NULL; - - free( pMemoryToBeReleased->pProtectionState ); - pMemoryToBeReleased->pProtectionState = NULL; - free( pMemoryToBeReleased ); pMemoryToBeReleased = NULL; return bRetVal; } -/**** - * VIRTUALConvertWinFlags() - - * Converts win32 protection flags to - * internal VIRTUAL flags. - * - */ -static BYTE VIRTUALConvertWinFlags( IN DWORD flProtect ) -{ - BYTE MemAccessControl = 0; - - switch ( flProtect & 0xff ) - { - case PAGE_NOACCESS : - MemAccessControl = VIRTUAL_NOACCESS; - break; - case PAGE_READONLY : - MemAccessControl = VIRTUAL_READONLY; - break; - case PAGE_READWRITE : - MemAccessControl = VIRTUAL_READWRITE; - break; - case PAGE_EXECUTE : - MemAccessControl = VIRTUAL_EXECUTE; - break; - case PAGE_EXECUTE_READ : - MemAccessControl = VIRTUAL_EXECUTE_READ; - break; - case PAGE_EXECUTE_READWRITE: - MemAccessControl = VIRTUAL_EXECUTE_READWRITE; - break; - - default : - MemAccessControl = 0; - ERROR( "Incorrect or no protection flags specified.\n" ); - break; - } - return MemAccessControl; -} - -/**** - * VIRTUALConvertVirtualFlags() - - * Converts internal virtual protection - * flags to their win32 counterparts. - */ -static DWORD VIRTUALConvertVirtualFlags( IN BYTE VirtualProtect ) -{ - DWORD MemAccessControl = 0; - - if ( VirtualProtect == VIRTUAL_READONLY ) - { - MemAccessControl = PAGE_READONLY; - } - else if ( VirtualProtect == VIRTUAL_READWRITE ) - { - MemAccessControl = PAGE_READWRITE; - } - else if ( VirtualProtect == VIRTUAL_EXECUTE_READWRITE ) - { - MemAccessControl = PAGE_EXECUTE_READWRITE; - } - else if ( VirtualProtect == VIRTUAL_EXECUTE_READ ) - { - MemAccessControl = PAGE_EXECUTE_READ; - } - else if ( VirtualProtect == VIRTUAL_EXECUTE ) - { - MemAccessControl = PAGE_EXECUTE; - } - else if ( VirtualProtect == VIRTUAL_NOACCESS ) - { - MemAccessControl = PAGE_NOACCESS; - } - - else - { - MemAccessControl = 0; - ERROR( "Incorrect or no protection flags specified.\n" ); - } - return MemAccessControl; -} - /*** * Displays the linked list. * @@ -659,17 +341,6 @@ static void VIRTUALDisplayList( void ) DBGOUT( "\t startBoundary %#x \n", p->startBoundary ); DBGOUT( "\t memSize %d \n", p->memSize ); - DBGOUT( "\t pAllocState " ); - for ( index = 0; index < p->memSize / GetVirtualPageSize(); index++) - { - DBGOUT( "[%d] ", VIRTUALGetAllocationType( index, p ) ); - } - DBGOUT( "\t pProtectionState " ); - for ( index = 0; index < p->memSize / GetVirtualPageSize(); index++ ) - { - DBGOUT( "[%d] ", (UINT)p->pProtectionState[ index ] ); - } - DBGOUT( "\n" ); DBGOUT( "\t accessProtection %d \n", p->accessProtection ); DBGOUT( "\t allocationType %d \n", p->allocationType ); DBGOUT( "\t pNext %p \n", p->pNext ); @@ -740,39 +411,6 @@ static BOOL VIRTUALStoreAllocationInfo( pNewEntry->allocationType = flAllocationType; pNewEntry->accessProtection = flProtection; - nBufferSize = memSize / GetVirtualPageSize() / CHAR_BIT; - if ((memSize / GetVirtualPageSize()) % CHAR_BIT != 0) - { - nBufferSize++; - } - - pNewEntry->pAllocState = (BYTE*)InternalMalloc(nBufferSize); - pNewEntry->pProtectionState = (BYTE*)InternalMalloc((memSize / GetVirtualPageSize())); - - if (pNewEntry->pAllocState && pNewEntry->pProtectionState) - { - /* Set the initial allocation state, and initial allocation protection. */ - VIRTUALSetAllocState(MEM_RESERVE, 0, nBufferSize * CHAR_BIT, pNewEntry); - memset(pNewEntry->pProtectionState, - VIRTUALConvertWinFlags(flProtection), - memSize / GetVirtualPageSize()); - } - else - { - ERROR( "Unable to allocate memory for the structure.\n"); - - if (pNewEntry->pProtectionState) free(pNewEntry->pProtectionState); - pNewEntry->pProtectionState = nullptr; - - if (pNewEntry->pAllocState) free(pNewEntry->pAllocState); - pNewEntry->pAllocState = nullptr; - - free(pNewEntry); - pNewEntry = nullptr; - - return FALSE; - } - pMemInfo = pVirtualMemory; if (pMemInfo && pMemInfo->startBoundary < startBoundary) @@ -1062,15 +700,7 @@ VIRTUALCommitMemory( PCMI pInformation = 0; LPVOID pRetVal = NULL; BOOL IsLocallyReserved = FALSE; - SIZE_T totalPages; - INT allocationType, curAllocationType; - INT protectionState, curProtectionState; - SIZE_T initialRunStart; - SIZE_T runStart; - SIZE_T runLength; - SIZE_T index; INT nProtect; - INT vProtect; if ( lpAddress ) { @@ -1124,104 +754,21 @@ VIRTUALCommitMemory( TRACE( "Committing the memory now..\n"); - // Pages that aren't already committed need to be committed. Pages that - // are committed don't need to be committed, but they might need to have - // their permissions changed. - // To get this right, we find runs of pages with similar states and - // permissions. If a run is not committed, we commit it and then set - // its permissions. If a run is committed but has different permissions - // from what we're trying to set, we set its permissions. Finally, - // if a run is already committed and has the right permissions, - // we don't need to do anything to it. - - totalPages = MemSize / GetVirtualPageSize(); - runStart = (StartBoundary - pInformation->startBoundary) / - GetVirtualPageSize(); // Page index - initialRunStart = runStart; - allocationType = VIRTUALGetAllocationType(runStart, pInformation); - protectionState = pInformation->pProtectionState[runStart]; - curAllocationType = allocationType; - curProtectionState = protectionState; - runLength = 1; nProtect = W32toUnixAccessControl(flProtect); - vProtect = VIRTUALConvertWinFlags(flProtect); - if (totalPages > pInformation->memSize / GetVirtualPageSize() - runStart) + // Commit the pages + if (mprotect((void *) StartBoundary, MemSize, nProtect) != 0) { - ERROR("Trying to commit beyond the end of the region!\n"); + ERROR("mprotect() failed! Error(%d)=%s\n", errno, strerror(errno)); goto error; } - while(runStart < initialRunStart + totalPages) - { - // Find the next run of pages - for(index = runStart + 1; index < initialRunStart + totalPages; - index++) - { - curAllocationType = VIRTUALGetAllocationType(index, pInformation); - curProtectionState = pInformation->pProtectionState[index]; - if (curAllocationType != allocationType || - curProtectionState != protectionState) - { - break; - } - runLength++; - } - - StartBoundary = pInformation->startBoundary + runStart * GetVirtualPageSize(); - pRetVal = (void *)StartBoundary; - MemSize = runLength * GetVirtualPageSize(); - - if (allocationType != MEM_COMMIT) - { - // Commit the pages - if (mprotect((void *) StartBoundary, MemSize, PROT_WRITE | PROT_READ) != 0) - { - ERROR("mprotect() failed! Error(%d)=%s\n", errno, strerror(errno)); - goto error; - } - #ifdef MADV_DODUMP - // Include committed memory in coredump. - madvise((void *) StartBoundary, MemSize, MADV_DODUMP); + // Include committed memory in coredump. + madvise((void *) StartBoundary, MemSize, MADV_DODUMP); #endif - VIRTUALSetAllocState(MEM_COMMIT, runStart, runLength, pInformation); - - if (nProtect == (PROT_WRITE | PROT_READ)) - { - // Handle this case specially so we don't bother - // mprotect'ing the region. - memset(pInformation->pProtectionState + runStart, - vProtect, runLength); - } - - protectionState = VIRTUAL_READWRITE; - } - - if (protectionState != vProtect) - { - // Change permissions. - if (mprotect((void *) StartBoundary, MemSize, nProtect) != -1) - { - memset(pInformation->pProtectionState + runStart, - vProtect, runLength); - } - else - { - ERROR("mprotect() failed! Error(%d)=%s\n", - errno, strerror(errno)); - goto error; - } - } - - runStart = index; - runLength = 1; - allocationType = curAllocationType; - protectionState = curProtectionState; - } - - pRetVal = (void *) (pInformation->startBoundary + initialRunStart * GetVirtualPageSize()); + pRetVal = (void *) StartBoundary; goto done; error: @@ -1571,17 +1118,6 @@ VirtualFree( // Do not include freed memory in coredump. madvise((LPVOID) StartBoundary, MemSize, MADV_DONTDUMP); #endif - - SIZE_T index = 0; - SIZE_T nNumOfPagesToChange = 0; - - /* We can now commit this memory by calling VirtualAlloc().*/ - index = (StartBoundary - pUnCommittedMem->startBoundary) / GetVirtualPageSize(); - - nNumOfPagesToChange = MemSize / GetVirtualPageSize(); - VIRTUALSetAllocState( MEM_RESERVE, index, - nNumOfPagesToChange, pUnCommittedMem ); - goto VirtualFreeExit; } else @@ -1707,27 +1243,6 @@ VirtualProtect( } pEntry = VIRTUALFindRegionInformation( StartBoundary ); - if ( NULL != pEntry ) - { - /* See if the pages are committed. */ - Index = OffSet = StartBoundary - pEntry->startBoundary == 0 ? - 0 : ( StartBoundary - pEntry->startBoundary ) / GetVirtualPageSize(); - NumberOfPagesToChange = MemSize / GetVirtualPageSize(); - - TRACE( "Number of pages to check %d, starting page %d \n", NumberOfPagesToChange, Index ); - - for ( ; Index < NumberOfPagesToChange; Index++ ) - { - if ( !VIRTUALIsPageCommitted( Index, pEntry ) ) - { - ERROR( "You can only change the protection attributes" - " on committed memory.\n" ) - SetLastError( ERROR_INVALID_ADDRESS ); - goto ExitVirtualProtect; - } - } - } - if ( 0 == mprotect( (LPVOID)StartBoundary, MemSize, W32toUnixAccessControl( flNewProtect ) ) ) { @@ -1739,19 +1254,7 @@ VirtualProtect( * if there were several regions with each with different flags only the * first region's protection flag will be returned. */ - if ( pEntry ) - { - *lpflOldProtect = - VIRTUALConvertVirtualFlags( pEntry->pProtectionState[ OffSet ] ); - - memset( pEntry->pProtectionState + OffSet, - VIRTUALConvertWinFlags( flNewProtect ), - NumberOfPagesToChange ); - } - else - { - *lpflOldProtect = PAGE_EXECUTE_READWRITE; - } + *lpflOldProtect = PAGE_EXECUTE_READWRITE; #ifdef MADV_DONTDUMP // Include or exclude memory from coredump based on the protection. @@ -2036,37 +1539,15 @@ VirtualQuery( } else { - /* Starting page. */ - SIZE_T Index = ( StartBoundary - pEntry->startBoundary ) / GetVirtualPageSize(); - - /* Attributes to check for. */ - BYTE AccessProtection = pEntry->pProtectionState[ Index ]; - INT AllocationType = VIRTUALGetAllocationType( Index, pEntry ); - SIZE_T RegionSize = 0; - - TRACE( "Index = %d, Number of Pages = %d. \n", - Index, pEntry->memSize / GetVirtualPageSize() ); - - while ( Index < pEntry->memSize / GetVirtualPageSize() && - VIRTUALGetAllocationType( Index, pEntry ) == AllocationType && - pEntry->pProtectionState[ Index ] == AccessProtection ) - { - RegionSize += GetVirtualPageSize(); - Index++; - } - - TRACE( "RegionSize = %d.\n", RegionSize ); - /* Fill the structure.*/ lpBuffer->AllocationProtect = pEntry->accessProtection; lpBuffer->BaseAddress = (LPVOID)StartBoundary; - lpBuffer->Protect = AllocationType == MEM_COMMIT ? - VIRTUALConvertVirtualFlags( AccessProtection ) : 0; - - lpBuffer->RegionSize = RegionSize; - lpBuffer->State = - ( AllocationType == MEM_COMMIT ? MEM_COMMIT : MEM_RESERVE ); + lpBuffer->Protect = pEntry->allocationType == MEM_COMMIT ? + pEntry->accessProtection : 0; + lpBuffer->RegionSize = pEntry->memSize; + lpBuffer->State = pEntry->allocationType == MEM_COMMIT ? + MEM_COMMIT : MEM_RESERVE; WARN( "Ignoring lpBuffer->Type. \n" ); } diff --git a/src/coreclr/pal/src/misc/sysinfo.cpp b/src/coreclr/pal/src/misc/sysinfo.cpp index f5d81cef557210..d9ddb02f521666 100644 --- a/src/coreclr/pal/src/misc/sysinfo.cpp +++ b/src/coreclr/pal/src/misc/sysinfo.cpp @@ -636,10 +636,11 @@ PAL_GetLogicalProcessorCacheSizeFromOS() int64_t cacheSizeFromSysctl = 0; size_t sz = sizeof(cacheSizeFromSysctl); const bool success = false - // macOS-arm64: Since macOS 12.0, Apple added ".perflevelX." to determinate cache sizes for efficiency + // macOS: Since macOS 12.0, Apple added ".perflevelX." to determinate cache sizes for efficiency // and performance cores separately. "perflevel0" stands for "performance" + || sysctlbyname("hw.perflevel0.l3cachesize", &cacheSizeFromSysctl, &sz, nullptr, 0) == 0 || sysctlbyname("hw.perflevel0.l2cachesize", &cacheSizeFromSysctl, &sz, nullptr, 0) == 0 - // macOS-arm64: these report cache sizes for efficiency cores only: + // macOS: these report cache sizes for efficiency cores only: || sysctlbyname("hw.l3cachesize", &cacheSizeFromSysctl, &sz, nullptr, 0) == 0 || sysctlbyname("hw.l2cachesize", &cacheSizeFromSysctl, &sz, nullptr, 0) == 0 || sysctlbyname("hw.l1dcachesize", &cacheSizeFromSysctl, &sz, nullptr, 0) == 0; diff --git a/src/coreclr/pal/src/sharedmemory/sharedmemory.cpp b/src/coreclr/pal/src/sharedmemory/sharedmemory.cpp index 9d7581788e9d60..12a3892c9318c5 100644 --- a/src/coreclr/pal/src/sharedmemory/sharedmemory.cpp +++ b/src/coreclr/pal/src/sharedmemory/sharedmemory.cpp @@ -62,6 +62,7 @@ DWORD SharedMemoryException::GetErrorCode() const //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // SharedMemoryHelpers +const mode_t SharedMemoryHelpers::PermissionsMask_CurrentUser_ReadWrite = S_IRUSR | S_IWUSR; const mode_t SharedMemoryHelpers::PermissionsMask_CurrentUser_ReadWriteExecute = S_IRUSR | S_IWUSR | S_IXUSR; const mode_t SharedMemoryHelpers::PermissionsMask_AllUsers_ReadWrite = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; @@ -96,6 +97,8 @@ SIZE_T SharedMemoryHelpers::AlignUp(SIZE_T value, SIZE_T alignment) bool SharedMemoryHelpers::EnsureDirectoryExists( const char *path, bool isGlobalLockAcquired, + bool hasCurrentUserAccessOnly, + bool setStickyFlag, bool createIfNotExist, bool isSystemDirectory) { @@ -103,6 +106,13 @@ bool SharedMemoryHelpers::EnsureDirectoryExists( _ASSERTE(!(isSystemDirectory && createIfNotExist)); // should not create or change permissions on system directories _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); _ASSERTE(!isGlobalLockAcquired || SharedMemoryManager::IsCreationDeletionFileLockAcquired()); + _ASSERTE(!(setStickyFlag && hasCurrentUserAccessOnly)); // Sticky bit doesn't make sense with current user access only + + mode_t mode = hasCurrentUserAccessOnly ? PermissionsMask_CurrentUser_ReadWriteExecute : PermissionsMask_AllUsers_ReadWriteExecute; + if (setStickyFlag) + { + mode |= S_ISVTX; + } // Check if the path already exists struct stat statInfo; @@ -123,11 +133,11 @@ bool SharedMemoryHelpers::EnsureDirectoryExists( if (isGlobalLockAcquired) { - if (mkdir(path, PermissionsMask_AllUsers_ReadWriteExecute) != 0) + if (mkdir(path, mode) != 0) { throw SharedMemoryException(static_cast(SharedMemoryError::IO)); } - if (chmod(path, PermissionsMask_AllUsers_ReadWriteExecute) != 0) + if (chmod(path, mode) != 0) { rmdir(path); throw SharedMemoryException(static_cast(SharedMemoryError::IO)); @@ -142,7 +152,7 @@ bool SharedMemoryHelpers::EnsureDirectoryExists( { throw SharedMemoryException(static_cast(SharedMemoryError::IO)); } - if (chmod(tempPath, PermissionsMask_AllUsers_ReadWriteExecute) != 0) + if (chmod(tempPath, mode) != 0) { rmdir(tempPath); throw SharedMemoryException(static_cast(SharedMemoryError::IO)); @@ -182,13 +192,18 @@ bool SharedMemoryHelpers::EnsureDirectoryExists( // For non-system directories (such as gSharedFilesPath/SHARED_MEMORY_RUNTIME_TEMP_DIRECTORY_NAME), // require sufficient permissions for all users and try to update them if requested to create the directory, so that // shared memory files may be shared by all processes on the system. - if ((statInfo.st_mode & PermissionsMask_AllUsers_ReadWriteExecute) == PermissionsMask_AllUsers_ReadWriteExecute) + if ((statInfo.st_mode & mode) == mode) { return true; } - if (!createIfNotExist || chmod(path, PermissionsMask_AllUsers_ReadWriteExecute) != 0) + if (!createIfNotExist || chmod(path, mode) != 0) { - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); + // We were not asked to create the path or we weren't able to set the new permissions. + // As a last resort, check that at least the current user has full access. + if ((statInfo.st_mode & PermissionsMask_CurrentUser_ReadWriteExecute) != PermissionsMask_CurrentUser_ReadWriteExecute) + { + throw SharedMemoryException(static_cast(SharedMemoryError::IO)); + } } return true; } @@ -238,7 +253,7 @@ int SharedMemoryHelpers::OpenDirectory(LPCSTR path) return fileDescriptor; } -int SharedMemoryHelpers::CreateOrOpenFile(LPCSTR path, bool createIfNotExist, bool *createdRef) +int SharedMemoryHelpers::CreateOrOpenFile(LPCSTR path, bool createIfNotExist, bool isSessionScope, bool *createdRef) { _ASSERTE(path != nullptr); _ASSERTE(path[0] != '\0'); @@ -268,12 +283,13 @@ int SharedMemoryHelpers::CreateOrOpenFile(LPCSTR path, bool createIfNotExist, bo // File does not exist, create the file openFlags |= O_CREAT | O_EXCL; - fileDescriptor = Open(path, openFlags, PermissionsMask_AllUsers_ReadWrite); + mode_t mode = isSessionScope ? PermissionsMask_CurrentUser_ReadWrite : PermissionsMask_AllUsers_ReadWrite; + fileDescriptor = Open(path, openFlags, mode); _ASSERTE(fileDescriptor != -1); // The permissions mask passed to open() is filtered by the process' permissions umask, so open() may not set all of // the requested permissions. Use chmod() to set the proper permissions. - if (chmod(path, PermissionsMask_AllUsers_ReadWrite) != 0) + if (chmod(path, mode) != 0) { CloseFile(fileDescriptor); unlink(path); @@ -659,7 +675,7 @@ SharedMemoryProcessDataHeader *SharedMemoryProcessDataHeader::CreateOrOpen( SharedMemoryHelpers::VerifyStringOperation(SharedMemoryManager::CopySharedMemoryBasePath(filePath)); SharedMemoryHelpers::VerifyStringOperation(filePath.Append('/')); SharedMemoryHelpers::VerifyStringOperation(id.AppendSessionDirectoryName(filePath)); - if (!SharedMemoryHelpers::EnsureDirectoryExists(filePath, true /* isGlobalLockAcquired */, createIfNotExist)) + if (!SharedMemoryHelpers::EnsureDirectoryExists(filePath, true /* isGlobalLockAcquired */, id.IsSessionScope(), false /* setStickyFlag */, createIfNotExist)) { _ASSERTE(!createIfNotExist); return nullptr; @@ -672,7 +688,7 @@ SharedMemoryProcessDataHeader *SharedMemoryProcessDataHeader::CreateOrOpen( SharedMemoryHelpers::VerifyStringOperation(filePath.Append(id.GetName(), id.GetNameCharCount())); bool createdFile; - int fileDescriptor = SharedMemoryHelpers::CreateOrOpenFile(filePath, createIfNotExist, &createdFile); + int fileDescriptor = SharedMemoryHelpers::CreateOrOpenFile(filePath, createIfNotExist, id.IsSessionScope(), &createdFile); if (fileDescriptor == -1) { _ASSERTE(!createIfNotExist); @@ -1147,6 +1163,8 @@ void SharedMemoryManager::AcquireCreationDeletionFileLock() if (!SharedMemoryHelpers::EnsureDirectoryExists( *gSharedFilesPath, false /* isGlobalLockAcquired */, + false /* hasCurrentUserAccessOnly */, + true /* setStickyFlag */, false /* createIfNotExist */, true /* isSystemDirectory */)) { @@ -1154,10 +1172,14 @@ void SharedMemoryManager::AcquireCreationDeletionFileLock() } SharedMemoryHelpers::EnsureDirectoryExists( *s_runtimeTempDirectoryPath, - false /* isGlobalLockAcquired */); + false /* isGlobalLockAcquired */, + false /* hasCurrentUserAccessOnly */, + false /* setStickyFlag */); SharedMemoryHelpers::EnsureDirectoryExists( *s_sharedMemoryDirectoryPath, - false /* isGlobalLockAcquired */); + false /* isGlobalLockAcquired */, + false /* hasCurrentUserAccessOnly */, + true /* setStickyFlag */); s_creationDeletionLockFileDescriptor = SharedMemoryHelpers::OpenDirectory(*s_sharedMemoryDirectoryPath); if (s_creationDeletionLockFileDescriptor == -1) { diff --git a/src/coreclr/pal/src/synchobj/mutex.cpp b/src/coreclr/pal/src/synchobj/mutex.cpp index 267176b15118aa..f103ea818c4e5e 100644 --- a/src/coreclr/pal/src/synchobj/mutex.cpp +++ b/src/coreclr/pal/src/synchobj/mutex.cpp @@ -1117,7 +1117,7 @@ SharedMemoryProcessDataHeader *NamedMutexProcessData::CreateOrOpen( SharedMemoryHelpers::BuildSharedFilesPath(lockFilePath, SHARED_MEMORY_LOCK_FILES_DIRECTORY_NAME); if (created) { - SharedMemoryHelpers::EnsureDirectoryExists(lockFilePath, true /* isGlobalLockAcquired */); + SharedMemoryHelpers::EnsureDirectoryExists(lockFilePath, true /* isGlobalLockAcquired */, false /* hasCurrentUserAccessOnly */, true /* setStickyFlag */); } // Create the session directory @@ -1126,7 +1126,7 @@ SharedMemoryProcessDataHeader *NamedMutexProcessData::CreateOrOpen( SharedMemoryHelpers::VerifyStringOperation(id->AppendSessionDirectoryName(lockFilePath)); if (created) { - SharedMemoryHelpers::EnsureDirectoryExists(lockFilePath, true /* isGlobalLockAcquired */); + SharedMemoryHelpers::EnsureDirectoryExists(lockFilePath, true /* isGlobalLockAcquired */, id->IsSessionScope(), false /* setStickyFlag */); autoCleanup.m_lockFilePath = &lockFilePath; autoCleanup.m_sessionDirectoryPathCharCount = lockFilePath.GetCount(); } @@ -1134,7 +1134,7 @@ SharedMemoryProcessDataHeader *NamedMutexProcessData::CreateOrOpen( // Create or open the lock file SharedMemoryHelpers::VerifyStringOperation(lockFilePath.Append('/')); SharedMemoryHelpers::VerifyStringOperation(lockFilePath.Append(id->GetName(), id->GetNameCharCount())); - int lockFileDescriptor = SharedMemoryHelpers::CreateOrOpenFile(lockFilePath, created); + int lockFileDescriptor = SharedMemoryHelpers::CreateOrOpenFile(lockFilePath, created, id->IsSessionScope()); if (lockFileDescriptor == -1) { _ASSERTE(!created); diff --git a/src/coreclr/pal/src/thread/context.cpp b/src/coreclr/pal/src/thread/context.cpp index 184116239ca736..14873f5e660e0f 100644 --- a/src/coreclr/pal/src/thread/context.cpp +++ b/src/coreclr/pal/src/thread/context.cpp @@ -1398,68 +1398,6 @@ CONTEXT_SetThreadContextOnPort( mach_msg_type_number_t StateCount; thread_state_flavor_t StateFlavor; - if (lpContext->ContextFlags & (CONTEXT_CONTROL|CONTEXT_INTEGER) & CONTEXT_AREA_MASK) - { -#ifdef HOST_AMD64 - x86_thread_state64_t State; - StateFlavor = x86_THREAD_STATE64; - - State.__rax = lpContext->Rax; - State.__rbx = lpContext->Rbx; - State.__rcx = lpContext->Rcx; - State.__rdx = lpContext->Rdx; - State.__rdi = lpContext->Rdi; - State.__rsi = lpContext->Rsi; - State.__rbp = lpContext->Rbp; - State.__rsp = lpContext->Rsp; - State.__r8 = lpContext->R8; - State.__r9 = lpContext->R9; - State.__r10 = lpContext->R10; - State.__r11 = lpContext->R11; - State.__r12 = lpContext->R12; - State.__r13 = lpContext->R13; - State.__r14 = lpContext->R14; - State.__r15 = lpContext->R15; -// State.ss = lpContext->SegSs; - State.__rflags = lpContext->EFlags; - State.__rip = lpContext->Rip; - State.__cs = lpContext->SegCs; -// State.ds = lpContext->SegDs_PAL_Undefined; -// State.es = lpContext->SegEs_PAL_Undefined; - State.__fs = lpContext->SegFs; - State.__gs = lpContext->SegGs; -#elif defined(HOST_ARM64) - arm_thread_state64_t State; - StateFlavor = ARM_THREAD_STATE64; - - memcpy(&State.__x[0], &lpContext->X0, 29 * 8); - State.__cpsr = lpContext->Cpsr; - arm_thread_state64_set_fp(State, lpContext->Fp); - arm_thread_state64_set_sp(State, lpContext->Sp); - arm_thread_state64_set_lr_fptr(State, lpContext->Lr); - arm_thread_state64_set_pc_fptr(State, lpContext->Pc); -#else -#error Unexpected architecture. -#endif - - StateCount = sizeof(State) / sizeof(natural_t); - - do - { - MachRet = thread_set_state(Port, - StateFlavor, - (thread_state_t)&State, - StateCount); - } - while (MachRet == KERN_ABORTED); - - if (MachRet != KERN_SUCCESS) - { - ASSERT("thread_set_state(THREAD_STATE) failed: %d\n", MachRet); - goto EXIT; - } - } - if (lpContext->ContextFlags & CONTEXT_ALL_FLOATING & CONTEXT_AREA_MASK) { @@ -1496,26 +1434,6 @@ CONTEXT_SetThreadContextOnPort( #error Unexpected architecture. #endif - // If we're setting only one of the floating point or extended registers (of which Mach supports only - // the xmm values) then we don't have values for the other set. This is a problem since Mach only - // supports setting both groups as a single unit. So in this case we'll need to fetch the current - // values first. - if ((lpContext->ContextFlags & CONTEXT_ALL_FLOATING) != - CONTEXT_ALL_FLOATING) - { - mach_msg_type_number_t StateCountGet = StateCount; - MachRet = thread_get_state(Port, - StateFlavor, - (thread_state_t)&State, - &StateCountGet); - if (MachRet != KERN_SUCCESS) - { - ASSERT("thread_get_state(FLOAT_STATE) failed: %d\n", MachRet); - goto EXIT; - } - _ASSERTE(StateCountGet == StateCount); - } - if (lpContext->ContextFlags & CONTEXT_FLOATING_POINT & CONTEXT_AREA_MASK) { #ifdef HOST_AMD64 @@ -1569,6 +1487,65 @@ CONTEXT_SetThreadContextOnPort( } } + if (lpContext->ContextFlags & (CONTEXT_CONTROL|CONTEXT_INTEGER) & CONTEXT_AREA_MASK) + { +#ifdef HOST_AMD64 + x86_thread_state64_t State; + StateFlavor = x86_THREAD_STATE64; + + State.__rax = lpContext->Rax; + State.__rbx = lpContext->Rbx; + State.__rcx = lpContext->Rcx; + State.__rdx = lpContext->Rdx; + State.__rdi = lpContext->Rdi; + State.__rsi = lpContext->Rsi; + State.__rbp = lpContext->Rbp; + State.__rsp = lpContext->Rsp; + State.__r8 = lpContext->R8; + State.__r9 = lpContext->R9; + State.__r10 = lpContext->R10; + State.__r11 = lpContext->R11; + State.__r12 = lpContext->R12; + State.__r13 = lpContext->R13; + State.__r14 = lpContext->R14; + State.__r15 = lpContext->R15; + State.__rflags = lpContext->EFlags; + State.__rip = lpContext->Rip; + State.__cs = lpContext->SegCs; + State.__fs = lpContext->SegFs; + State.__gs = lpContext->SegGs; +#elif defined(HOST_ARM64) + arm_thread_state64_t State; + StateFlavor = ARM_THREAD_STATE64; + + memcpy(&State.__x[0], &lpContext->X0, 29 * 8); + State.__cpsr = lpContext->Cpsr; + arm_thread_state64_set_fp(State, lpContext->Fp); + arm_thread_state64_set_sp(State, lpContext->Sp); + arm_thread_state64_set_lr_fptr(State, lpContext->Lr); + arm_thread_state64_set_pc_fptr(State, lpContext->Pc); +#else +#error Unexpected architecture. +#endif + + StateCount = sizeof(State) / sizeof(natural_t); + + do + { + MachRet = thread_set_state(Port, + StateFlavor, + (thread_state_t)&State, + StateCount); + } + while (MachRet == KERN_ABORTED); + + if (MachRet != KERN_SUCCESS) + { + ASSERT("thread_set_state(THREAD_STATE) failed: %d\n", MachRet); + goto EXIT; + } + } + EXIT: return MachRet; } @@ -1637,10 +1614,7 @@ DBG_FlushInstructionCache( IN LPCVOID lpBaseAddress, IN SIZE_T dwSize) { -#ifndef HOST_ARM - // Intrinsic should do the right thing across all platforms (except Linux arm) - __builtin___clear_cache((char *)lpBaseAddress, (char *)((INT_PTR)lpBaseAddress + dwSize)); -#else // HOST_ARM +#if defined(__linux__) && defined(HOST_ARM) // On Linux/arm (at least on 3.10) we found that there is a problem with __do_cache_op (arch/arm/kernel/traps.c) // implementing cacheflush syscall. cacheflush flushes only the first page in range [lpBaseAddress, lpBaseAddress + dwSize) // and leaves other pages in undefined state which causes random tests failures (often due to SIGSEGV) with no particular pattern. @@ -1660,6 +1634,8 @@ DBG_FlushInstructionCache( __builtin___clear_cache((char *)begin, (char *)endOrNextPageBegin); begin = endOrNextPageBegin; } -#endif // HOST_ARM +#else + __builtin___clear_cache((char *)lpBaseAddress, (char *)((INT_PTR)lpBaseAddress + dwSize)); +#endif return TRUE; } diff --git a/src/coreclr/pal/src/thread/process.cpp b/src/coreclr/pal/src/thread/process.cpp index 69c7fe04326a4e..a7e666d1cb7d3a 100644 --- a/src/coreclr/pal/src/thread/process.cpp +++ b/src/coreclr/pal/src/thread/process.cpp @@ -2250,6 +2250,28 @@ PROCFormatInt(ULONG32 value) return buffer; } +/*++ +Function: + PROCFormatInt64 + + Helper function to format an ULONG64 as a string. + +--*/ +char* +PROCFormatInt64(ULONG64 value) +{ + char* buffer = (char*)InternalMalloc(128); + if (buffer != nullptr) + { + if (sprintf_s(buffer, 128, "%lld", value) == -1) + { + free(buffer); + buffer = nullptr; + } + } + return buffer; +} + static const INT UndefinedDumpType = 0; /*++ @@ -2617,7 +2639,7 @@ PAL_GenerateCoreDump( (no return value) --*/ VOID -PROCCreateCrashDumpIfEnabled(int signal) +PROCCreateCrashDumpIfEnabled(int signal, siginfo_t* siginfo) { // If enabled, launch the create minidump utility and wait until it completes if (!g_argvCreateDump.empty()) @@ -2625,13 +2647,16 @@ PROCCreateCrashDumpIfEnabled(int signal) std::vector argv(g_argvCreateDump); char* signalArg = nullptr; char* crashThreadArg = nullptr; + char* signalCodeArg = nullptr; + char* signalErrnoArg = nullptr; + char* signalAddressArg = nullptr; if (signal != 0) { // Remove the terminating nullptr argv.pop_back(); - // Add the Windows exception code to the command line + // Add the signal number to the command line signalArg = PROCFormatInt(signal); if (signalArg != nullptr) { @@ -2646,6 +2671,29 @@ PROCCreateCrashDumpIfEnabled(int signal) argv.push_back("--crashthread"); argv.push_back(crashThreadArg); } + + if (siginfo != nullptr) + { + signalCodeArg = PROCFormatInt(siginfo->si_code); + if (signalCodeArg != nullptr) + { + argv.push_back("--code"); + argv.push_back(signalCodeArg); + } + signalErrnoArg = PROCFormatInt(siginfo->si_errno); + if (signalErrnoArg != nullptr) + { + argv.push_back("--errno"); + argv.push_back(signalErrnoArg); + } + signalAddressArg = PROCFormatInt64((ULONG64)siginfo->si_addr); + if (signalAddressArg != nullptr) + { + argv.push_back("--address"); + argv.push_back(signalAddressArg); + } + } + argv.push_back(nullptr); } @@ -2653,6 +2701,9 @@ PROCCreateCrashDumpIfEnabled(int signal) free(signalArg); free(crashThreadArg); + free(signalCodeArg); + free(signalErrnoArg); + free(signalAddressArg); } } @@ -2670,15 +2721,16 @@ PROCCreateCrashDumpIfEnabled(int signal) --*/ PAL_NORETURN VOID -PROCAbort(int signal) +PROCAbort(int signal, siginfo_t* siginfo) { // Do any shutdown cleanup before aborting or creating a core dump PROCNotifyProcessShutdown(); - PROCCreateCrashDumpIfEnabled(signal); + PROCCreateCrashDumpIfEnabled(signal, siginfo); - // Restore the SIGABORT handler to prevent recursion - SEHCleanupAbort(); + // Restore all signals; the SIGABORT handler to prevent recursion and + // the others to prevent multiple core dumps from being generated. + SEHCleanupSignals(); // Abort the process after waiting for the core dump to complete abort(); diff --git a/src/coreclr/runtime-prereqs.proj b/src/coreclr/runtime-prereqs.proj index 5d660044a096c7..f81457730a475b 100644 --- a/src/coreclr/runtime-prereqs.proj +++ b/src/coreclr/runtime-prereqs.proj @@ -6,7 +6,8 @@ $(ArtifactsObjDir)runtime_version.h $(ArtifactsObjDir)native.sourcelink.json false - true + + true .NET Runtime diff --git a/src/coreclr/scripts/superpmi_collect_setup.py b/src/coreclr/scripts/superpmi_collect_setup.py index 497ff9f64d57bd..62ded06cdf066c 100644 --- a/src/coreclr/scripts/superpmi_collect_setup.py +++ b/src/coreclr/scripts/superpmi_collect_setup.py @@ -22,12 +22,12 @@ # 4. Lastly, it sets the pipeline variables. # # Below are the helix queues it sets depending on the OS/architecture: -# | Arch | windows | Linux | macOS | -# |-------|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------|----------------| -# | x86 | Windows.10.Amd64.X86.Rt | | - | -# | x64 | Windows.10.Amd64.X86.Rt | Ubuntu.1804.Amd64 | OSX.1014.Amd64 | -# | arm | - | (Ubuntu.1804.Arm32)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm32v7-bfcd90a-20200121150440 | - | -# | arm64 | Windows.10.Arm64 | (Ubuntu.1804.Arm64)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm64v8-20210531091519-97d8652 | OSX.1100.ARM64 | +# | Arch | windows | Linux | macOS | +# |-------|-------------------------|---------------------------------------------------------------------------------------------------------------|----------------| +# | x86 | Windows.10.Amd64.X86.Rt | | - | +# | x64 | Windows.10.Amd64.X86.Rt | Ubuntu.1804.Amd64 | OSX.1014.Amd64 | +# | arm | - | (Ubuntu.1804.Arm32)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm32v7 | - | +# | arm64 | Windows.10.Arm64 | (Ubuntu.1804.Arm64)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm64v8 | OSX.1100.ARM64 | # ################################################################################ ################################################################################ @@ -403,9 +403,9 @@ def main(main_args): helix_queue = "Windows.10.Arm64" if arch == "arm64" else "Windows.10.Amd64.X86.Rt" elif platform_name == "linux": if arch == "arm": - helix_queue = "(Ubuntu.1804.Arm32)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm32v7-bfcd90a-20200121150440" + helix_queue = "(Ubuntu.1804.Arm32)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm32v7" elif arch == "arm64": - helix_queue = "(Ubuntu.1804.Arm64)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm64v8-20210531091519-97d8652" + helix_queue = "(Ubuntu.1804.Arm64)Ubuntu.1804.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm64v8" else: helix_queue = "Ubuntu.1804.Amd64" elif platform_name == "osx": diff --git a/src/coreclr/tools/Common/Compiler/Int128FieldLayoutAlgorithm.cs b/src/coreclr/tools/Common/Compiler/Int128FieldLayoutAlgorithm.cs index 60be29fdce95c9..18f794a6f354e8 100644 --- a/src/coreclr/tools/Common/Compiler/Int128FieldLayoutAlgorithm.cs +++ b/src/coreclr/tools/Common/Compiler/Int128FieldLayoutAlgorithm.cs @@ -28,8 +28,11 @@ public override ComputedInstanceFieldLayout ComputeInstanceLayout(DefType defTyp ComputedInstanceFieldLayout layoutFromMetadata = _fallbackAlgorithm.ComputeInstanceLayout(defType, layoutKind); - if (defType.Context.Target.IsWindows || (defType.Context.Target.PointerSize == 4)) + // 32bit platforms use standard metadata layout engine + if (defType.Context.Target.Architecture == TargetArchitecture.ARM) { + layoutFromMetadata.LayoutAbiStable = false; // Int128 parameter passing ABI is unstable at this time + layoutFromMetadata.IsInt128OrHasInt128Fields = true; return layoutFromMetadata; } @@ -42,7 +45,8 @@ public override ComputedInstanceFieldLayout ComputeInstanceLayout(DefType defTyp FieldAlignment = new LayoutInt(16), FieldSize = layoutFromMetadata.FieldSize, Offsets = layoutFromMetadata.Offsets, - LayoutAbiStable = true + LayoutAbiStable = false, // Int128 parameter passing ABI is unstable at this time + IsInt128OrHasInt128Fields = true }; } @@ -72,7 +76,7 @@ public override ValueTypeShapeCharacteristics ComputeValueTypeShapeCharacteristi public static bool IsIntegerType(DefType type) { return type.IsIntrinsic - && type.Namespace == "System." + && type.Namespace == "System" && ((type.Name == "Int128") || (type.Name == "UInt128")); } } diff --git a/src/coreclr/tools/Common/Compiler/NativeAotNameMangler.cs b/src/coreclr/tools/Common/Compiler/NativeAotNameMangler.cs index 65e58642acaf45..dcd464d5753d5a 100644 --- a/src/coreclr/tools/Common/Compiler/NativeAotNameMangler.cs +++ b/src/coreclr/tools/Common/Compiler/NativeAotNameMangler.cs @@ -303,9 +303,8 @@ private string ComputeMangledTypeName(TypeDesc type) mangledName = GetMangledTypeName(((PointerType)type).ParameterType) + NestMangledName("Pointer"); break; case TypeFlags.FunctionPointer: - // TODO: need to also encode calling convention (or all modopts?) var fnPtrType = (FunctionPointerType)type; - mangledName = "__FnPtr" + EnterNameScopeSequence; + mangledName = "__FnPtr_" + ((int)fnPtrType.Signature.Flags).ToString("X2") + EnterNameScopeSequence; mangledName += GetMangledTypeName(fnPtrType.Signature.ReturnType); mangledName += EnterNameScopeSequence; diff --git a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs index c5fdfda3d0f58f..cc286f81b03619 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs @@ -14,8 +14,8 @@ internal struct ReadyToRunHeaderConstants { public const uint Signature = 0x00525452; // 'RTR' - public const ushort CurrentMajorVersion = 7; - public const ushort CurrentMinorVersion = 1; + public const ushort CurrentMajorVersion = 8; + public const ushort CurrentMinorVersion = 0; } #pragma warning disable 0169 diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index c1f59c124b6769..6d287dee31f44f 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -776,27 +776,25 @@ private void Get_CORINFO_SIG_INFO(MethodDesc method, CORINFO_SIG_INFO* sig, Meth if (method.IsArrayAddressMethod()) hasHiddenParameter = true; - - // We only populate sigInst for intrinsic methods because most of the time, - // JIT doesn't care what the instantiation is and this is expensive. - Instantiation owningTypeInst = method.OwningType.Instantiation; - sig->sigInst.classInstCount = (uint)owningTypeInst.Length; - if (owningTypeInst.Length != 0) - { - sig->sigInst.classInst = GetJitInstantiation(owningTypeInst); - } - - sig->sigInst.methInstCount = (uint)method.Instantiation.Length; - if (method.Instantiation.Length != 0) - { - sig->sigInst.methInst = GetJitInstantiation(method.Instantiation); - } } if (hasHiddenParameter) { sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_PARAMTYPE; } + + Instantiation owningTypeInst = method.OwningType.Instantiation; + sig->sigInst.classInstCount = (uint)owningTypeInst.Length; + if (owningTypeInst.Length != 0) + { + sig->sigInst.classInst = GetJitInstantiation(owningTypeInst); + } + + sig->sigInst.methInstCount = (uint)method.Instantiation.Length; + if (method.Instantiation.Length != 0) + { + sig->sigInst.methInst = GetJitInstantiation(method.Instantiation); + } } private void Get_CORINFO_SIG_INFO(MethodSignature signature, CORINFO_SIG_INFO* sig, MethodILScope scope) @@ -823,7 +821,7 @@ private void Get_CORINFO_SIG_INFO(MethodSignature signature, CORINFO_SIG_INFO* s sig->sigInst.classInst = null; // Not used by the JIT sig->sigInst.classInstCount = 0; // Not used by the JIT - sig->sigInst.methInst = null; // Not used by the JIT + sig->sigInst.methInst = null; sig->sigInst.methInstCount = (uint)signature.GenericParameterCount; sig->pSig = null; diff --git a/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs b/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs index 2190f9ac1a2785..4c3555c198af66 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs @@ -68,6 +68,11 @@ private static class FieldLayoutFlags /// True if the type transitively has any types with LayoutKind.Auto in its layout. /// public const int IsAutoLayoutOrHasAutoLayoutFields = 0x400; + + /// + /// True if the type transitively has an Int128 in it or is an Int128 + /// + public const int IsInt128OrHasInt128Fields = 0x800; } private class StaticBlockInfo @@ -135,6 +140,20 @@ public virtual bool IsAutoLayoutOrHasAutoLayoutFields } } + /// + /// Is a type Int128 or transitively have any fields of a type Int128. + /// + public virtual bool IsInt128OrHasInt128Fields + { + get + { + if (!_fieldLayoutFlags.HasFlags(FieldLayoutFlags.ComputedInstanceTypeLayout)) + { + ComputeInstanceLayout(InstanceLayoutKind.TypeAndFields); + } + return _fieldLayoutFlags.HasFlags(FieldLayoutFlags.IsInt128OrHasInt128Fields); + } + } /// /// The number of bytes required to hold a field of this type @@ -430,6 +449,10 @@ public void ComputeInstanceLayout(InstanceLayoutKind layoutKind) { _fieldLayoutFlags.AddFlags(FieldLayoutFlags.IsAutoLayoutOrHasAutoLayoutFields); } + if (computedLayout.IsInt128OrHasInt128Fields) + { + _fieldLayoutFlags.AddFlags(FieldLayoutFlags.IsInt128OrHasInt128Fields); + } if (computedLayout.Offsets != null) { diff --git a/src/coreclr/tools/Common/TypeSystem/Common/FieldLayoutAlgorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/FieldLayoutAlgorithm.cs index a19ec4b3603bf4..53388c915b85d8 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/FieldLayoutAlgorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/FieldLayoutAlgorithm.cs @@ -83,6 +83,7 @@ public struct ComputedInstanceFieldLayout public LayoutInt ByteCountAlignment; public bool LayoutAbiStable; // Is the layout stable such that it can safely be used in function calling conventions public bool IsAutoLayoutOrHasAutoLayoutFields; + public bool IsInt128OrHasInt128Fields; /// /// If Offsets is non-null, then all field based layout is complete. diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs index 8da028a5190fb5..1b779dea61bdfc 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs @@ -110,6 +110,7 @@ out instanceByteSizeAndAlignment FieldSize = sizeAndAlignment.Size, LayoutAbiStable = true, IsAutoLayoutOrHasAutoLayoutFields = false, + IsInt128OrHasInt128Fields = false, }; if (numInstanceFields > 0) @@ -211,7 +212,7 @@ public override ComputedStaticFieldLayout ComputeStaticFieldLayout(DefType defTy } ref StaticsBlock block = ref GetStaticsBlockForField(ref result, field); - SizeAndAlignment sizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType, hasLayout: false, context.Target.DefaultPackingSize, out bool _, out bool _); + SizeAndAlignment sizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType, hasLayout: false, context.Target.DefaultPackingSize, out bool _, out bool _, out bool _); block.Size = LayoutInt.AlignUp(block.Size, sizeAndAlignment.Alignment, context.Target); result.Offsets[index] = new FieldAndOffset(field, block.Size); @@ -303,15 +304,18 @@ protected ComputedInstanceFieldLayout ComputeExplicitFieldLayout(MetadataType ty int fieldOrdinal = 0; bool layoutAbiStable = true; bool hasAutoLayoutField = false; + bool hasInt128Field = type.BaseType == null ? false : type.BaseType.IsInt128OrHasInt128Fields; foreach (var fieldAndOffset in layoutMetadata.Offsets) { TypeDesc fieldType = fieldAndOffset.Field.FieldType; - var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType.UnderlyingType, hasLayout: true, packingSize, out bool fieldLayoutAbiStable, out bool fieldHasAutoLayout); + var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType.UnderlyingType, hasLayout: true, packingSize, out bool fieldLayoutAbiStable, out bool fieldHasAutoLayout, out bool fieldHasInt128Field); if (!fieldLayoutAbiStable) layoutAbiStable = false; if (fieldHasAutoLayout) hasAutoLayoutField = true; + if (fieldHasInt128Field) + hasInt128Field = true; largestAlignmentRequired = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequired); @@ -357,6 +361,7 @@ protected ComputedInstanceFieldLayout ComputeExplicitFieldLayout(MetadataType ty ComputedInstanceFieldLayout computedLayout = new ComputedInstanceFieldLayout { IsAutoLayoutOrHasAutoLayoutFields = hasAutoLayoutField, + IsInt128OrHasInt128Fields = hasInt128Field, }; computedLayout.FieldAlignment = instanceSizeAndAlignment.Alignment; computedLayout.FieldSize = instanceSizeAndAlignment.Size; @@ -392,17 +397,20 @@ protected ComputedInstanceFieldLayout ComputeSequentialFieldLayout(MetadataType int packingSize = ComputePackingSize(type, layoutMetadata); bool layoutAbiStable = true; bool hasAutoLayoutField = false; + bool hasInt128Field = type.BaseType == null ? false : type.BaseType.IsInt128OrHasInt128Fields; foreach (var field in type.GetFields()) { if (field.IsStatic) continue; - var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(field.FieldType.UnderlyingType, hasLayout: true, packingSize, out bool fieldLayoutAbiStable, out bool fieldHasAutoLayout); + var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(field.FieldType.UnderlyingType, hasLayout: true, packingSize, out bool fieldLayoutAbiStable, out bool fieldHasAutoLayout, out bool fieldHasInt128Field); if (!fieldLayoutAbiStable) layoutAbiStable = false; if (fieldHasAutoLayout) hasAutoLayoutField = true; + if (fieldHasInt128Field) + hasInt128Field = true; largestAlignmentRequirement = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequirement); @@ -424,6 +432,7 @@ protected ComputedInstanceFieldLayout ComputeSequentialFieldLayout(MetadataType ComputedInstanceFieldLayout computedLayout = new ComputedInstanceFieldLayout { IsAutoLayoutOrHasAutoLayoutFields = hasAutoLayoutField, + IsInt128OrHasInt128Fields = hasInt128Field, }; computedLayout.FieldAlignment = instanceSizeAndAlignment.Alignment; computedLayout.FieldSize = instanceSizeAndAlignment.Size; @@ -459,6 +468,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, int instanceValueClassFieldCount = 0; int instanceGCPointerFieldsCount = 0; int[] instanceNonGCPointerFieldsCount = new int[maxLog2Size + 1]; + bool hasInt128Field = false; foreach (var field in type.GetFields()) { @@ -469,7 +479,10 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, if (IsByValueClass(fieldType)) { + // Valuetypes which are not primitives or enums instanceValueClassFieldCount++; + if (((DefType)fieldType).IsInt128OrHasInt128Fields) + hasInt128Field = true; } else if (fieldType.IsGCPointer) { @@ -479,7 +492,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, { Debug.Assert(fieldType.IsPrimitive || fieldType.IsPointer || fieldType.IsFunctionPointer || fieldType.IsEnum || fieldType.IsByRef); - var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType, hasLayout, packingSize, out bool _, out bool _); + var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType, hasLayout, packingSize, out bool _, out bool _, out bool _); instanceNonGCPointerFieldsCount[CalculateLog2(fieldSizeAndAlignment.Size.AsInt)]++; } } @@ -516,36 +529,67 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, TypeDesc fieldType = field.FieldType; - var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType, hasLayout, packingSize, out bool fieldLayoutAbiStable, out bool _); + var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType, hasLayout, packingSize, out bool fieldLayoutAbiStable, out bool _, out bool _); if (!fieldLayoutAbiStable) layoutAbiStable = false; - largestAlignmentRequired = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequired); - if (IsByValueClass(fieldType)) { + // This block handles valuetypes which are not primitives or enums, it only has a meaningful effect, if the + // type has an alignment greater than pointer size. + largestAlignmentRequired = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequired); instanceValueClassFieldsArr[instanceValueClassFieldCount++] = field; } - else if (fieldType.IsGCPointer) - { - instanceGCPointerFieldsArr[instanceGCPointerFieldsCount++] = field; - } else { - int log2size = CalculateLog2(fieldSizeAndAlignment.Size.AsInt); - instanceNonGCPointerFieldsArr[log2size][instanceNonGCPointerFieldsCount[log2size]++] = field; + // non-value-type (and primitive type) fields will add an alignment requirement of pointer size + // This alignment requirement will not be significant in the final alignment calculation unlesss the + // type is greater than the size of a single pointer. + // + // This does not account for types that are marked IsAlign8Candidate due to 8-byte fields + // but that is explicitly handled when we calculate the final alignment for the type. + + // This behavior is extremely strange for primitive types, as it makes a struct with a single byte in it + // have 8 byte alignment, but that is the current implementation. + + largestAlignmentRequired = LayoutInt.Max(new LayoutInt(context.Target.PointerSize), largestAlignmentRequired); + + if (fieldType.IsGCPointer) + { + instanceGCPointerFieldsArr[instanceGCPointerFieldsCount++] = field; + } + else + { + Debug.Assert(fieldType.IsPrimitive || fieldType.IsPointer || fieldType.IsFunctionPointer || fieldType.IsEnum || fieldType.IsByRef); + int log2size = CalculateLog2(fieldSizeAndAlignment.Size.AsInt); + instanceNonGCPointerFieldsArr[log2size][instanceNonGCPointerFieldsCount[log2size]++] = field; + + if (fieldType.IsPrimitive || fieldType.IsEnum) + { + // Handle alignment of long/ulong/double on ARM32 + largestAlignmentRequired = LayoutInt.Max(context.Target.GetObjectAlignment(fieldSizeAndAlignment.Size), largestAlignmentRequired); + } + } } } - largestAlignmentRequired = context.Target.GetObjectAlignment(largestAlignmentRequired); - bool requiresAlign8 = !largestAlignmentRequired.IsIndeterminate && largestAlignmentRequired.AsInt > 4; + bool requiresAlign8 = !largestAlignmentRequired.IsIndeterminate && context.Target.PointerSize == 4 && context.Target.GetObjectAlignment(largestAlignmentRequired).AsInt > 4 && context.Target.PointerSize == 4; // For types inheriting from another type, field offsets continue on from where they left off // Base alignment is not always required, it's only applied when there's a version bubble boundary // between base type and the current type. LayoutInt cumulativeInstanceFieldPos = CalculateFieldBaseOffset(type, requiresAlign8, requiresAlignedBase: false); LayoutInt offsetBias = LayoutInt.Zero; - if (!type.IsValueType && cumulativeInstanceFieldPos != LayoutInt.Zero && type.Context.Target.Architecture == TargetArchitecture.X86) + + // The following conditional statement mimics the behavior of MethodTableBuilder::PlaceInstanceFields; + // the fundamental difference between CoreCLR native runtime and Crossgen2 regarding field placement is + // that the native runtime doesn't count the method table pointer at the beginning of reference types as a 'field' + // so that the first field in a class has offset 0 while its 'real' offset from the 'this' pointer is LayoutPointerSize. + // On ARM32, native runtime employs a special logic internally calculating the field offsets relative to the 'this' + // pointer (the Crossgen2 way) to ensure 8-alignment for longs and doubles as required by the ARM32 ISA. Please note + // that for 16-alignment used by Vector128 this logic actually ensures that the fields are 16-misaligned + // (they are 16-aligned after the 4-byte or 8-byte method table pointer). + if (!type.IsValueType && cumulativeInstanceFieldPos != LayoutInt.Zero && type.Context.Target.Architecture != TargetArchitecture.ARM) { offsetBias = type.Context.Target.LayoutPointerSize; cumulativeInstanceFieldPos -= offsetBias; @@ -655,7 +699,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, for (int i = 0; i < instanceValueClassFieldsArr.Length; i++) { // Align the cumulative field offset to the indeterminate value - var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(instanceValueClassFieldsArr[i].FieldType, hasLayout, packingSize, out bool fieldLayoutAbiStable, out bool _); + var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(instanceValueClassFieldsArr[i].FieldType, hasLayout, packingSize, out bool fieldLayoutAbiStable, out bool _, out bool _); if (!fieldLayoutAbiStable) layoutAbiStable = false; @@ -706,6 +750,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, ComputedInstanceFieldLayout computedLayout = new ComputedInstanceFieldLayout { IsAutoLayoutOrHasAutoLayoutFields = true, + IsInt128OrHasInt128Fields = hasInt128Field, }; computedLayout.FieldAlignment = instanceSizeAndAlignment.Alignment; computedLayout.FieldSize = instanceSizeAndAlignment.Size; @@ -719,7 +764,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, private static void PlaceInstanceField(FieldDesc field, bool hasLayout, int packingSize, FieldAndOffset[] offsets, ref LayoutInt instanceFieldPos, ref int fieldOrdinal, LayoutInt offsetBias) { - var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(field.FieldType, hasLayout, packingSize, out bool _, out bool _); + var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(field.FieldType, hasLayout, packingSize, out bool _, out bool _, out bool _); instanceFieldPos = AlignUpInstanceFieldOffset(field.OwningType, instanceFieldPos, fieldSizeAndAlignment.Alignment, field.Context.Target); offsets[fieldOrdinal] = new FieldAndOffset(field, instanceFieldPos + offsetBias); @@ -779,11 +824,12 @@ public LayoutInt CalculateFieldBaseOffset(MetadataType type, bool requiresAlign8 return cumulativeInstanceFieldPos; } - private static SizeAndAlignment ComputeFieldSizeAndAlignment(TypeDesc fieldType, bool hasLayout, int packingSize, out bool layoutAbiStable, out bool fieldTypeHasAutoLayout) + private static SizeAndAlignment ComputeFieldSizeAndAlignment(TypeDesc fieldType, bool hasLayout, int packingSize, out bool layoutAbiStable, out bool fieldTypeHasAutoLayout, out bool fieldTypeHasInt128Field) { SizeAndAlignment result; layoutAbiStable = true; fieldTypeHasAutoLayout = true; + fieldTypeHasInt128Field = false; if (fieldType.IsDefType) { @@ -794,6 +840,7 @@ private static SizeAndAlignment ComputeFieldSizeAndAlignment(TypeDesc fieldType, result.Alignment = defType.InstanceFieldAlignment; layoutAbiStable = defType.LayoutAbiStable; fieldTypeHasAutoLayout = defType.IsAutoLayoutOrHasAutoLayoutFields; + fieldTypeHasInt128Field = defType.IsInt128OrHasInt128Fields; } else { @@ -898,7 +945,15 @@ public override ValueTypeShapeCharacteristics ComputeValueTypeShapeCharacteristi return ComputeHomogeneousAggregateCharacteristic(type); } - private ValueTypeShapeCharacteristics ComputeHomogeneousAggregateCharacteristic(DefType type) + /// + /// Identify whether a given type is a homogeneous floating-point aggregate. This code must be + /// kept in sync with the CoreCLR runtime method EEClass::CheckForHFA, as of this change it + /// can be found at + /// https://github.com/dotnet/runtime/blob/1928cd2b65c04ebe6fe528d4ebb581e46f1fed47/src/coreclr/vm/class.cpp#L1567 + /// + /// Type to analyze + /// HFA classification of the type parameter + private static ValueTypeShapeCharacteristics ComputeHomogeneousAggregateCharacteristic(DefType type) { // Use this constant to make the code below more laconic const ValueTypeShapeCharacteristics NotHA = ValueTypeShapeCharacteristics.None; @@ -913,12 +968,7 @@ private ValueTypeShapeCharacteristics ComputeHomogeneousAggregateCharacteristic( return NotHA; MetadataType metadataType = (MetadataType)type; - - // No HAs with explicit layout. There may be cases where explicit layout may be still - // eligible for HA, but it is hard to tell the real intent. Make it simple and just - // unconditionally disable HAs for explicit layout. - if (metadataType.IsExplicitLayout) - return NotHA; + int haElementSize = 0; switch (metadataType.Category) { @@ -931,12 +981,18 @@ private ValueTypeShapeCharacteristics ComputeHomogeneousAggregateCharacteristic( case TypeFlags.ValueType: // Find the common HA element type if any ValueTypeShapeCharacteristics haResultType = NotHA; + bool hasZeroOffsetField = false; foreach (FieldDesc field in metadataType.GetFields()) { if (field.IsStatic) continue; + if (field.Offset == LayoutInt.Zero) + { + hasZeroOffsetField = true; + } + // If a field isn't a DefType, then this type cannot be a HA type if (!(field.FieldType is DefType fieldType)) return NotHA; @@ -950,6 +1006,15 @@ private ValueTypeShapeCharacteristics ComputeHomogeneousAggregateCharacteristic( { // If we hadn't yet figured out what form of HA this type might be, we've now found one case haResultType = haFieldType; + + haElementSize = haResultType switch + { + ValueTypeShapeCharacteristics.Float32Aggregate => 4, + ValueTypeShapeCharacteristics.Float64Aggregate => 8, + ValueTypeShapeCharacteristics.Vector64Aggregate => 8, + ValueTypeShapeCharacteristics.Vector128Aggregate => 16, + _ => throw new ArgumentOutOfRangeException() + }; } else if (haResultType != haFieldType) { @@ -958,21 +1023,17 @@ private ValueTypeShapeCharacteristics ComputeHomogeneousAggregateCharacteristic( // be a HA type. return NotHA; } + + if (field.Offset.IsIndeterminate || field.Offset.AsInt % haElementSize != 0) + { + return NotHA; + } } - // If there are no instance fields, this is not a HA type - if (haResultType == NotHA) + // If the struct doesn't have a zero-offset field, it's not an HFA. + if (!hasZeroOffsetField) return NotHA; - int haElementSize = haResultType switch - { - ValueTypeShapeCharacteristics.Float32Aggregate => 4, - ValueTypeShapeCharacteristics.Float64Aggregate => 8, - ValueTypeShapeCharacteristics.Vector64Aggregate => 8, - ValueTypeShapeCharacteristics.Vector128Aggregate => 16, - _ => throw new ArgumentOutOfRangeException() - }; - // Types which are indeterminate in field size are not considered to be HA if (type.InstanceFieldSize.IsIndeterminate) return NotHA; @@ -981,8 +1042,13 @@ private ValueTypeShapeCharacteristics ComputeHomogeneousAggregateCharacteristic( // - Type of fields can be HA valuetype itself. // - Managed C++ HA valuetypes have just one of type float to signal that // the valuetype is HA and explicitly specified size. - int maxSize = haElementSize * type.Context.Target.MaxHomogeneousAggregateElementCount; - if (type.InstanceFieldSize.AsInt > maxSize) + int totalSize = type.InstanceFieldSize.AsInt; + + if (totalSize % haElementSize != 0) + return NotHA; + + // On ARM, HFAs can have a maximum of four fields regardless of whether those are float or double. + if (totalSize > haElementSize * type.Context.Target.MaxHomogeneousAggregateElementCount) return NotHA; // All the tests passed. This is a HA type. diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalHelpers.cs b/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalHelpers.cs index 658302d0faaf42..b70749272b0d38 100644 --- a/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalHelpers.cs +++ b/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalHelpers.cs @@ -434,6 +434,12 @@ internal static MarshallerKind GetMarshallerKind( return MarshallerKind.Invalid; } + if (!isField && InteropTypes.IsInt128Type(context, type)) + { + // Int128 types cannot be passed by value + return MarshallerKind.Invalid; + } + if (isBlittable) { if (nativeType != NativeTypeKind.Default && nativeType != NativeTypeKind.Struct) @@ -887,7 +893,7 @@ internal static MarshallerKind GetDisabledMarshallerKind( else if (underlyingType.IsValueType) { var defType = (DefType)underlyingType; - if (!defType.ContainsGCPointers && !defType.IsAutoLayoutOrHasAutoLayoutFields) + if (!defType.ContainsGCPointers && !defType.IsAutoLayoutOrHasAutoLayoutFields && !defType.IsInt128OrHasInt128Fields) { return MarshallerKind.BlittableValue; } diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.cs b/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.cs index 764e6e8bb8e570..b13711716b8179 100644 --- a/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.cs +++ b/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.cs @@ -1474,7 +1474,7 @@ private bool ShouldBePinned get { return MarshalDirection == MarshalDirection.Forward - && MarshallerType != MarshallerType.Field + && MarshallerType == MarshallerType.Argument && !IsManagedByRef && In && !Out; @@ -1672,7 +1672,11 @@ protected override void TransformManagedToNative(ILCodeStream codeStream) { ILEmitter emitter = _ilCodeStreams.Emitter; - if (In && !Out && !IsManagedByRef) + if (MarshalDirection == MarshalDirection.Forward + && MarshallerType == MarshallerType.Argument + && !IsManagedByRef + && In + && !Out) { TypeDesc marshallerIn = MarshallerIn; diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/InteropTypes.cs b/src/coreclr/tools/Common/TypeSystem/Interop/InteropTypes.cs index f69879a10ee15f..9b21bf91c26496 100644 --- a/src/coreclr/tools/Common/TypeSystem/Interop/InteropTypes.cs +++ b/src/coreclr/tools/Common/TypeSystem/Interop/InteropTypes.cs @@ -137,6 +137,11 @@ public static bool IsSystemRuntimeIntrinsicsVector64T(TypeSystemContext context, return IsCoreNamedType(context, type, "System.Runtime.Intrinsics", "Vector64`1"); } + public static bool IsInt128Type(TypeSystemContext context, TypeDesc type) + { + return type is DefType defType && defType.IsInt128OrHasInt128Fields; + } + public static bool IsSystemRuntimeIntrinsicsVector128T(TypeSystemContext context, TypeDesc type) { return IsCoreNamedType(context, type, "System.Runtime.Intrinsics", "Vector128`1"); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj index ea167e092b0318..605d407443da2a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj @@ -8,6 +8,7 @@ netstandard2.0 true + noRefSafetyRulesAttribute=true diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj index 5967b093841b15..b09ef313126615 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj @@ -14,6 +14,7 @@ x86;x64 AnyCPU true + noRefSafetyRulesAttribute=true diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs index c41d5fa11d7a31..90584110173b88 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs @@ -36,6 +36,19 @@ internal static bool IsStateMachineType(string typeName) return typeName.Length > i + 1 && typeName[i + 1] == 'd'; } + internal static bool IsStateMachineCurrentField(string fieldName) + { + if (!IsGeneratedMemberName(fieldName)) + return false; + + int i = fieldName.LastIndexOf('>'); + if (i == -1) + return false; + + // Current field is <>2__current + return fieldName.Length > i + 1 && fieldName[i + 1] == '2'; + } + internal static bool IsGeneratedType(string name) => IsStateMachineType(name) || IsLambdaDisplayClass(name); internal static bool IsLambdaOrLocalFunction(string methodName) => IsLambdaMethod(methodName) || IsLocalFunction(methodName); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs index 6b6c6e36cd6674..c62233bd957a9c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -500,10 +500,17 @@ static IEnumerable GetCompilerGeneratedNestedTypes(MetadataType ty public static bool IsHoistedLocal(FieldDesc field) { - // Treat all fields on compiler-generated types as hoisted locals. - // This avoids depending on the name mangling scheme for hoisted locals. - var declaringTypeName = field.OwningType.Name; - return CompilerGeneratedNames.IsLambdaDisplayClass(declaringTypeName) || CompilerGeneratedNames.IsStateMachineType(declaringTypeName); + if (CompilerGeneratedNames.IsLambdaDisplayClass(field.OwningType.Name)) + return true; + + if (CompilerGeneratedNames.IsStateMachineType(field.OwningType.Name)) + { + // Don't track the "current" field which is used for state machine return values, + // because this can be expensive to track. + return !CompilerGeneratedNames.IsStateMachineCurrentField(field.Name); + } + + return false; } // "Nested function" refers to lambdas and local functions. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs index 46e6c9764782a5..afdb716f5af6ae 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs @@ -34,6 +34,19 @@ internal static bool IsStateMachineType (string typeName) return typeName.Length > i + 1 && typeName[i + 1] == 'd'; } + internal static bool IsStateMachineCurrentField (string fieldName) + { + if (!IsGeneratedMemberName (fieldName)) + return false; + + int i = fieldName.LastIndexOf ('>'); + if (i == -1) + return false; + + // Current field is <>2__current + return fieldName.Length > i + 1 && fieldName[i + 1] == '2'; + } + internal static bool IsGeneratedType (string name) => IsStateMachineType (name) || IsLambdaDisplayClass (name); internal static bool IsLambdaOrLocalFunction (string methodName) => IsLambdaMethod (methodName) || IsLocalFunction (methodName); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs index 3213f49fdc4931..3033cef70153c0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs @@ -55,10 +55,16 @@ static IEnumerable GetCompilerGeneratedNestedTypes (TypeDefiniti public static bool IsHoistedLocal (FieldDefinition field) { - // Treat all fields on compiler-generated types as hoisted locals. - // This avoids depending on the name mangling scheme for hoisted locals. - var declaringTypeName = field.DeclaringType.Name; - return CompilerGeneratedNames.IsLambdaDisplayClass (declaringTypeName) || CompilerGeneratedNames.IsStateMachineType (declaringTypeName); + if (CompilerGeneratedNames.IsLambdaDisplayClass (field.DeclaringType.Name)) + return true; + + if (CompilerGeneratedNames.IsStateMachineType (field.DeclaringType.Name)) { + // Don't track the "current" field which is used for state machine return values, + // because this can be expensive to track. + return !CompilerGeneratedNames.IsStateMachineCurrentField (field.Name); + } + + return false; } // "Nested function" refers to lambdas and local functions. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs index a74192ec045124..913c3e3812661e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs @@ -123,7 +123,10 @@ public override ObjectNodeSection Section } } - public int MinimumObjectSize => _type.Context.Target.PointerSize * 3; + public int MinimumObjectSize => GetMinimumObjectSize(_type.Context); + + public static int GetMinimumObjectSize(TypeSystemContext typeSystemContext) + => typeSystemContext.Target.PointerSize * 3; protected virtual bool EmitVirtualSlotsAndInterfaces => false; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenObjectNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenObjectNode.cs index 567ecb7595c513..dbd298b4b801cb 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenObjectNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenObjectNode.cs @@ -48,11 +48,20 @@ int ISymbolDefinitionNode.Offset public override void EncodeData(ref ObjectDataBuilder dataBuilder, NodeFactory factory, bool relocsOnly) { + int initialOffset = dataBuilder.CountBytes; + // Sync Block dataBuilder.EmitZeroPointer(); // byte contents _data.WriteContent(ref dataBuilder, this, factory); + + int objectSize = dataBuilder.CountBytes - initialOffset; + int minimumObjectSize = EETypeNode.GetMinimumObjectSize(factory.TypeSystemContext); + if (objectSize < minimumObjectSize) + { + dataBuilder.EmitZeros(minimumObjectSize - objectSize); + } } protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenStringNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenStringNode.cs index 40765b9b17be8f..d1978383f236b2 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenStringNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenStringNode.cs @@ -59,6 +59,8 @@ private static IEETypeNode GetEETypeNode(NodeFactory factory) public override void EncodeData(ref ObjectDataBuilder dataBuilder, NodeFactory factory, bool relocsOnly) { + int initialOffset = dataBuilder.CountBytes; + dataBuilder.EmitZeroPointer(); // Sync block dataBuilder.EmitPointerReloc(GetEETypeNode(factory)); @@ -73,6 +75,12 @@ public override void EncodeData(ref ObjectDataBuilder dataBuilder, NodeFactory f // Null-terminate for friendliness with interop dataBuilder.EmitShort(0); + int objectSize = dataBuilder.CountBytes - initialOffset; + int minimumObjectSize = EETypeNode.GetMinimumObjectSize(factory.TypeSystemContext); + if (objectSize < minimumObjectSize) + { + dataBuilder.EmitZeros(minimumObjectSize - objectSize); + } } protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs index d3e4ec30779124..51e42b8676b774 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs @@ -953,7 +953,8 @@ private static TypeDesc GetActualTemplateTypeForType(NodeFactory factory, TypeDe private ISymbolNode GetStaticsNode(NodeFactory context, out BagElementKind staticsBagKind) { - ISymbolNode symbol = context.GCStaticEEType(GCPointerMap.FromStaticLayout(_type.GetClosestDefType())); + DefType closestCanonDefType = (DefType)_type.GetClosestDefType().ConvertToCanonForm(CanonicalFormKind.Specific); + ISymbolNode symbol = context.GCStaticEEType(GCPointerMap.FromStaticLayout(closestCanonDefType)); staticsBagKind = BagElementKind.GcStaticDesc; return symbol; @@ -961,7 +962,8 @@ private ISymbolNode GetStaticsNode(NodeFactory context, out BagElementKind stati private ISymbolNode GetThreadStaticsNode(NodeFactory context, out BagElementKind staticsBagKind) { - ISymbolNode symbol = context.GCStaticEEType(GCPointerMap.FromThreadStaticLayout(_type.GetClosestDefType())); + DefType closestCanonDefType = (DefType)_type.GetClosestDefType().ConvertToCanonForm(CanonicalFormKind.Specific); + ISymbolNode symbol = context.GCStaticEEType(GCPointerMap.FromThreadStaticLayout(closestCanonDefType)); staticsBagKind = BagElementKind.ThreadStaticDesc; return symbol; @@ -997,13 +999,14 @@ public override IEnumerable GetStaticDependencies(NodeFacto if (!_isUniversalCanon) { - if (_type.GetClosestDefType().GCStaticFieldSize.AsInt > 0) + DefType closestCanonDefType = (DefType)_type.GetClosestDefType().ConvertToCanonForm(CanonicalFormKind.Specific); + if (closestCanonDefType.GCStaticFieldSize.AsInt > 0) { BagElementKind ignored; yield return new DependencyListEntry(GetStaticsNode(context, out ignored), "type gc static info"); } - if (_type.GetClosestDefType().ThreadGcStaticFieldSize.AsInt > 0) + if (closestCanonDefType.ThreadGcStaticFieldSize.AsInt > 0) { BagElementKind ignored; yield return new DependencyListEntry(GetThreadStaticsNode(context, out ignored), "type thread static info"); @@ -1207,24 +1210,24 @@ public override Vertex WriteVertex(NodeFactory factory) if (!_isUniversalCanon) { - DefType closestDefType = _type.GetClosestDefType(); - if (closestDefType.NonGCStaticFieldSize.AsInt != 0) + DefType closestCanonDefType = (DefType)_type.GetClosestDefType().ConvertToCanonForm(CanonicalFormKind.Specific); + if (closestCanonDefType.NonGCStaticFieldSize.AsInt != 0) { - layoutInfo.AppendUnsigned(BagElementKind.NonGcStaticDataSize, checked((uint)closestDefType.NonGCStaticFieldSize.AsInt)); + layoutInfo.AppendUnsigned(BagElementKind.NonGcStaticDataSize, checked((uint)closestCanonDefType.NonGCStaticFieldSize.AsInt)); } - if (closestDefType.GCStaticFieldSize.AsInt != 0) + if (closestCanonDefType.GCStaticFieldSize.AsInt != 0) { - layoutInfo.AppendUnsigned(BagElementKind.GcStaticDataSize, checked((uint)closestDefType.GCStaticFieldSize.AsInt)); + layoutInfo.AppendUnsigned(BagElementKind.GcStaticDataSize, checked((uint)closestCanonDefType.GCStaticFieldSize.AsInt)); BagElementKind staticDescBagType; ISymbolNode staticsDescSymbol = GetStaticsNode(factory, out staticDescBagType); uint gcStaticsSymbolIndex = factory.MetadataManager.NativeLayoutInfo.StaticsReferences.GetIndex(staticsDescSymbol); layoutInfo.AppendUnsigned(staticDescBagType, gcStaticsSymbolIndex); } - if (closestDefType.ThreadGcStaticFieldSize.AsInt != 0) + if (closestCanonDefType.ThreadGcStaticFieldSize.AsInt != 0) { - layoutInfo.AppendUnsigned(BagElementKind.ThreadStaticDataSize, checked((uint)closestDefType.ThreadGcStaticFieldSize.AsInt)); + layoutInfo.AppendUnsigned(BagElementKind.ThreadStaticDataSize, checked((uint)closestCanonDefType.ThreadGcStaticFieldSize.AsInt)); BagElementKind threadStaticDescBagType; ISymbolNode threadStaticsDescSymbol = GetThreadStaticsNode(factory, out threadStaticDescBagType); uint threadStaticsSymbolIndex = factory.MetadataManager.NativeLayoutInfo.StaticsReferences.GetIndex(threadStaticsDescSymbol); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/PInvokeMethodFixupNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/PInvokeMethodFixupNode.cs index 4971ca48763fe9..4be5ad213a1b84 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/PInvokeMethodFixupNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/PInvokeMethodFixupNode.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Reflection.Metadata; using System.Runtime.InteropServices; using Internal.IL.Stubs; @@ -97,8 +98,16 @@ public PInvokeMethodData(PInvokeLazyFixupField pInvokeLazyFixupField) PInvokeMetadata metadata = pInvokeLazyFixupField.PInvokeMetadata; ModuleDesc declaringModule = ((MetadataType)pInvokeLazyFixupField.TargetMethod.OwningType).Module; - DllImportSearchPath? dllImportSearchPath = default; - if (declaringModule.Assembly is EcmaAssembly asm) + CustomAttributeValue? decodedAttr = null; + + // Look for DefaultDllImportSearchPath on the method + if (pInvokeLazyFixupField.TargetMethod is EcmaMethod method) + { + decodedAttr = method.GetDecodedCustomAttribute("System.Runtime.InteropServices", "DefaultDllImportSearchPathsAttribute"); + } + + // If the attribute it wasn't found on the method, look for it on the assembly + if (!decodedAttr.HasValue && declaringModule.Assembly is EcmaAssembly asm) { // We look for [assembly:DefaultDllImportSearchPaths(...)] var attrHandle = asm.MetadataReader.GetCustomAttributeHandle(asm.AssemblyDefinition.GetCustomAttributes(), @@ -106,14 +115,18 @@ public PInvokeMethodData(PInvokeLazyFixupField pInvokeLazyFixupField) if (!attrHandle.IsNil) { var attr = asm.MetadataReader.GetCustomAttribute(attrHandle); - var decoded = attr.DecodeValue(new CustomAttributeTypeProvider(asm)); - if (decoded.FixedArguments.Length == 1 && - decoded.FixedArguments[0].Value is int searchPath) - { - dllImportSearchPath = (DllImportSearchPath)searchPath; - } + decodedAttr = attr.DecodeValue(new CustomAttributeTypeProvider(asm)); } } + + DllImportSearchPath? dllImportSearchPath = default; + if (decodedAttr.HasValue + && decodedAttr.Value.FixedArguments.Length == 1 + && decodedAttr.Value.FixedArguments[0].Value is int searchPath) + { + dllImportSearchPath = (DllImportSearchPath)searchPath; + } + ModuleData = new PInvokeModuleData(metadata.Module, dllImportSearchPath, declaringModule); EntryPointName = metadata.Name; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs index 0482731b0432b3..fac33d4b58cbcf 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs @@ -26,6 +26,7 @@ public class Logger private readonly CompilerGeneratedState _compilerGeneratedState; private readonly HashSet _suppressedWarnings; + private readonly HashSet _suppressedCategories; private readonly bool _isSingleWarn; private readonly HashSet _singleWarnEnabledAssemblies; @@ -44,7 +45,8 @@ public Logger( IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, - IEnumerable singleWarnDisabledModules) + IEnumerable singleWarnDisabledModules, + IEnumerable suppressedCategories) { _logWriter = writer; _compilerGeneratedState = ilProvider == null ? null : new CompilerGeneratedState(ilProvider, this); @@ -53,15 +55,16 @@ public Logger( _isSingleWarn = singleWarn; _singleWarnEnabledAssemblies = new HashSet(singleWarnEnabledModules, StringComparer.OrdinalIgnoreCase); _singleWarnDisabledAssemblies = new HashSet(singleWarnDisabledModules, StringComparer.OrdinalIgnoreCase); + _suppressedCategories = new HashSet(suppressedCategories, StringComparer.Ordinal); } - public Logger(TextWriter writer, ILProvider ilProvider, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, IEnumerable singleWarnDisabledModules) - : this(new TextLogWriter(writer), ilProvider, isVerbose, suppressedWarnings, singleWarn, singleWarnEnabledModules, singleWarnDisabledModules) + public Logger(TextWriter writer, ILProvider ilProvider, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, IEnumerable singleWarnDisabledModules, IEnumerable suppressedCategories) + : this(new TextLogWriter(writer), ilProvider, isVerbose, suppressedWarnings, singleWarn, singleWarnEnabledModules, singleWarnDisabledModules, suppressedCategories) { } public Logger(ILogWriter writer, ILProvider ilProvider, bool isVerbose) - : this(writer, ilProvider, isVerbose, Array.Empty(), singleWarn: false, Array.Empty(), Array.Empty()) + : this(writer, ilProvider, isVerbose, Array.Empty(), singleWarn: false, Array.Empty(), Array.Empty(), Array.Empty()) { } @@ -141,6 +144,8 @@ public void LogError(string text, int code, TypeSystemEntity origin, string subc public void LogError(TypeSystemEntity origin, DiagnosticId id, params string[] args) => LogError(new MessageOrigin(origin), id, args); + internal bool IsWarningSubcategorySuppressed(string category) => _suppressedCategories.Contains(category); + internal bool IsWarningSuppressed(int code, MessageOrigin origin) { // This is causing too much noise diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageContainer.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageContainer.cs index 25d849a46dee05..8f6e4f33efd2fd 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageContainer.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageContainer.cs @@ -125,6 +125,9 @@ internal static MessageContainer CreateErrorMessage(MessageOrigin? origin, Diagn if (context.IsWarningSuppressed(code, origin)) return null; + if (context.IsWarningSubcategorySuppressed(subcategory)) + return null; + if (TryLogSingleWarning(context, code, origin, subcategory)) return null; @@ -139,6 +142,9 @@ internal static MessageContainer CreateErrorMessage(MessageOrigin? origin, Diagn if (context.IsWarningSuppressed((int)id, origin)) return null; + if (context.IsWarningSubcategorySuppressed(subcategory)) + return null; + if (TryLogSingleWarning(context, (int)id, origin, subcategory)) return null; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MstatObjectDumper.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MstatObjectDumper.cs index 0f0af381c2e6ed..22d7bd1cc79e0d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MstatObjectDumper.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MstatObjectDumper.cs @@ -21,7 +21,7 @@ namespace ILCompiler public class MstatObjectDumper : ObjectDumper { private const int VersionMajor = 1; - private const int VersionMinor = 0; + private const int VersionMinor = 1; private readonly string _fileName; private readonly TypeSystemMetadataEmitter _emitter; @@ -82,7 +82,8 @@ private void SerializeSimpleEntry(InstructionEncoder encoder, TypeSystemEntity e { encoder.OpCode(ILOpCode.Ldtoken); encoder.Token(_emitter.EmitMetadataHandleForTypeSystemEntity(entity)); - encoder.LoadString(_emitter.GetUserStringHandle(mangledName)); + // Would like to do this but mangled names are very long and go over the 16 MB string limit quickly. + // encoder.LoadString(_emitter.GetUserStringHandle(mangledName)); encoder.LoadConstantI4(blob.Data.Length); } @@ -93,7 +94,8 @@ internal override void End() { methods.OpCode(ILOpCode.Ldtoken); methods.Token(_emitter.EmitMetadataHandleForTypeSystemEntity(m.Key)); - methods.LoadString(_emitter.GetUserStringHandle(m.Value.MangledName)); + // Would like to do this but mangled names are very long and go over the 16 MB string limit quickly. + // methods.LoadString(_emitter.GetUserStringHandle(m.Value.MangledName)); methods.LoadConstantI4(m.Value.Size); methods.LoadConstantI4(m.Value.GcInfoSize); methods.LoadConstantI4(_methodEhInfo.GetValueOrDefault(m.Key)); diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs index 067486f858c168..f9fe3b7687439d 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs @@ -19,9 +19,16 @@ public class ArchitectureSpecificFieldLayoutTests ModuleDesc _testModuleX86; TestTypeSystemContext _contextX64; ModuleDesc _testModuleX64; + TestTypeSystemContext _contextX64Windows; + ModuleDesc _testModuleX64Windows; + TestTypeSystemContext _contextX64Linux; + ModuleDesc _testModuleX64Linux; TestTypeSystemContext _contextARM; ModuleDesc _testModuleARM; + TestTypeSystemContext _contextARM64; + ModuleDesc _testModuleARM64; + public ArchitectureSpecificFieldLayoutTests() { _contextX64 = new TestTypeSystemContext(TargetArchitecture.X64); @@ -30,6 +37,18 @@ public ArchitectureSpecificFieldLayoutTests() _testModuleX64 = systemModuleX64; + _contextX64Linux = new TestTypeSystemContext(TargetArchitecture.X64, TargetOS.Linux); + var systemModuleX64Linux = _contextX64Linux.CreateModuleForSimpleName("CoreTestAssembly"); + _contextX64Linux.SetSystemModule(systemModuleX64Linux); + + _testModuleX64Linux = systemModuleX64Linux; + + _contextX64Windows = new TestTypeSystemContext(TargetArchitecture.X64, TargetOS.Windows); + var systemModuleX64Windows = _contextX64Windows.CreateModuleForSimpleName("CoreTestAssembly"); + _contextX64Windows.SetSystemModule(systemModuleX64Windows); + + _testModuleX64Windows = systemModuleX64Windows; + _contextARM = new TestTypeSystemContext(TargetArchitecture.ARM); var systemModuleARM = _contextARM.CreateModuleForSimpleName("CoreTestAssembly"); _contextARM.SetSystemModule(systemModuleARM); @@ -41,6 +60,12 @@ public ArchitectureSpecificFieldLayoutTests() _contextX86.SetSystemModule(systemModuleX86); _testModuleX86 = systemModuleX86; + + _contextARM64 = new TestTypeSystemContext(TargetArchitecture.ARM64); + var systemModuleARM64 = _contextARM64.CreateModuleForSimpleName("CoreTestAssembly"); + _contextARM64.SetSystemModule(systemModuleARM64); + + _testModuleARM64 = systemModuleARM64; } [Fact] @@ -414,5 +439,116 @@ public void TestAlignmentBehavior_ShortByteEnumStructAuto() Assert.Equal(0x4, tX86FieldStruct.GetField("_struct").Offset.AsInt); Assert.Equal(0x4, tARMFieldStruct.GetField("_struct").Offset.AsInt); } + + [Fact] + public void TestAlignmentBehavior_StructStructByte_StructByteAuto() + { + string _namespace = "Sequential"; + string _type = "StructStructByte_StructByteAuto"; + + MetadataType tX64 = _testModuleX64.GetType(_namespace, _type); + MetadataType tX86 = _testModuleX86.GetType(_namespace, _type); + MetadataType tARM = _testModuleARM.GetType(_namespace, _type); + + Assert.Equal(0x1, tX64.InstanceFieldAlignment.AsInt); + Assert.Equal(0x1, tARM.InstanceFieldAlignment.AsInt); + Assert.Equal(0x1, tX86.InstanceFieldAlignment.AsInt); + + Assert.Equal(0x2, tX64.InstanceFieldSize.AsInt); + Assert.Equal(0x2, tARM.InstanceFieldSize.AsInt); + Assert.Equal(0x2, tX86.InstanceFieldSize.AsInt); + + Assert.Equal(0x0, tX64.GetField("fld1").Offset.AsInt); + Assert.Equal(0x0, tARM.GetField("fld1").Offset.AsInt); + Assert.Equal(0x0, tX86.GetField("fld1").Offset.AsInt); + + Assert.Equal(0x1, tX64.GetField("fld2").Offset.AsInt); + Assert.Equal(0x1, tARM.GetField("fld2").Offset.AsInt); + Assert.Equal(0x1, tX86.GetField("fld2").Offset.AsInt); + } + + [Theory] + [InlineData("StructStructByte_StructByteAuto", new int[]{1,1,1}, new int[]{2,2,2})] + [InlineData("StructStructByte_Struct2BytesAuto", new int[]{2,2,2}, new int[]{4,4,4})] + [InlineData("StructStructByte_Struct3BytesAuto", new int[]{4,4,4}, new int[]{8,8,8})] + [InlineData("StructStructByte_Struct4BytesAuto", new int[]{4,4,4}, new int[]{8,8,8})] + [InlineData("StructStructByte_Struct5BytesAuto", new int[]{8,4,4}, new int[]{16,12,12})] + [InlineData("StructStructByte_Struct8BytesAuto", new int[]{8,4,4}, new int[]{16,12,12})] + [InlineData("StructStructByte_Struct9BytesAuto", new int[]{8,4,4}, new int[]{24,16,16})] + public void TestAlignmentBehavior_AutoAlignmentRules(string wrapperType, int[] alignment, int[] size) + { + string _namespace = "Sequential"; + string _type = wrapperType; + + MetadataType tX64 = _testModuleX64.GetType(_namespace, _type); + MetadataType tX86 = _testModuleX86.GetType(_namespace, _type); + MetadataType tARM = _testModuleARM.GetType(_namespace, _type); + + Assert.Equal(alignment[0], tX64.InstanceFieldAlignment.AsInt); + Assert.Equal(alignment[1], tARM.InstanceFieldAlignment.AsInt); + Assert.Equal(alignment[2], tX86.InstanceFieldAlignment.AsInt); + + Assert.Equal(size[0], tX64.InstanceFieldSize.AsInt); + Assert.Equal(size[1], tARM.InstanceFieldSize.AsInt); + Assert.Equal(size[2], tX86.InstanceFieldSize.AsInt); + + Assert.Equal(0x0, tX64.GetField("fld1").Offset.AsInt); + Assert.Equal(0x0, tARM.GetField("fld1").Offset.AsInt); + Assert.Equal(0x0, tX86.GetField("fld1").Offset.AsInt); + + Assert.Equal(alignment[0], tX64.GetField("fld2").Offset.AsInt); + Assert.Equal(alignment[1], tARM.GetField("fld2").Offset.AsInt); + Assert.Equal(alignment[2], tX86.GetField("fld2").Offset.AsInt); + } + + [Theory] + [InlineData("StructStructByte_Int128StructAuto", "ARM64", 16, 32)] + [InlineData("StructStructByte_Int128StructAuto", "ARM", 8, 24)] + [InlineData("StructStructByte_Int128StructAuto", "X86", 16, 32)] + [InlineData("StructStructByte_Int128StructAuto", "X64Linux", 16, 32)] + [InlineData("StructStructByte_Int128StructAuto", "X64Windows", 16, 32)] + [InlineData("StructStructByte_UInt128StructAuto", "ARM64", 16, 32)] + [InlineData("StructStructByte_UInt128StructAuto", "ARM", 8, 24)] + [InlineData("StructStructByte_UInt128StructAuto", "X86", 16, 32)] + [InlineData("StructStructByte_UInt128StructAuto", "X64Linux", 16, 32)] + [InlineData("StructStructByte_UInt128StructAuto", "X64Windows", 16, 32)] + // Variation of TestAlignmentBehavior_AutoAlignmentRules above that is able to deal with os specific behavior + public void TestAlignmentBehavior_AutoAlignmentRulesWithOSDependence(string wrapperType, string osArch, int alignment, int size) + { + ModuleDesc testModule; + switch (osArch) + { + case "ARM64": + testModule = _testModuleARM64; + break; + case "ARM": + testModule = _testModuleARM; + break; + case "X64": + testModule = _testModuleX64; + break; + case "X64Linux": + testModule = _testModuleX64Linux; + break; + case "X64Windows": + testModule = _testModuleX64Windows; + break; + case "X86": + testModule = _testModuleX86; + break; + default: + throw new Exception(); + } + + string _namespace = "Sequential"; + string _type = wrapperType; + + MetadataType type = testModule.GetType(_namespace, _type); + + Assert.Equal(alignment, type.InstanceFieldAlignment.AsInt); + Assert.Equal(size, type.InstanceFieldSize.AsInt); + Assert.Equal(0x0, type.GetField("fld1").Offset.AsInt); + Assert.Equal(alignment, type.GetField("fld2").Offset.AsInt); + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/InstanceFieldLayout.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/InstanceFieldLayout.cs index b49dd4ff729309..249090e38eae51 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/InstanceFieldLayout.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/InstanceFieldLayout.cs @@ -3,56 +3,57 @@ using System; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; #pragma warning disable 169 namespace ContainsGCPointers { - struct NoPointers + public struct NoPointers { - int int1; - byte byte1; - char char1; + public int int1; + public byte byte1; + public char char1; } - struct StillNoPointers + public struct StillNoPointers { - NoPointers noPointers1; - bool bool1; + public NoPointers noPointers1; + public bool bool1; } - class ClassNoPointers + public class ClassNoPointers { - char char1; + public char char1; } - struct HasPointers + public struct HasPointers { - string string1; + public string string1; } - struct FieldHasPointers + public struct FieldHasPointers { - HasPointers hasPointers1; + public HasPointers hasPointers1; } - class ClassHasPointers + public class ClassHasPointers { - ClassHasPointers classHasPointers1; + public ClassHasPointers classHasPointers1; } - class BaseClassHasPointers : ClassHasPointers + public class BaseClassHasPointers : ClassHasPointers { } public class ClassHasIntArray { - int[] intArrayField; + public int[] intArrayField; } public class ClassHasArrayOfClassType { - ClassNoPointers[] classTypeArray; + public ClassNoPointers[] classTypeArray; } } @@ -61,29 +62,29 @@ namespace Explicit [StructLayout(LayoutKind.Explicit)] class Class1 { - static int Stat; + public static int Stat; [FieldOffset(4)] - bool Bar; + public bool Bar; [FieldOffset(10)] - char Baz; + public char Baz; } [StructLayout(LayoutKind.Explicit)] class Class2 : Class1 { [FieldOffset(0)] - int Lol; + public int Lol; [FieldOffset(20)] - byte Omg; + public byte Omg; } [StructLayout(LayoutKind.Explicit, Size = 40)] class ExplicitSize { [FieldOffset(0)] - int Lol; + public int Lol; [FieldOffset(20)] - byte Omg; + public byte Omg; } [StructLayout(LayoutKind.Explicit)] @@ -123,194 +124,356 @@ ref struct ByRefStruct namespace Sequential { [StructLayout(LayoutKind.Sequential)] - class Class1 + public class Class1 { - int MyInt; - bool MyBool; - char MyChar; - string MyString; - byte[] MyByteArray; - Class1 MyClass1SelfRef; + public int MyInt; + public bool MyBool; + public char MyChar; + public string MyString; + public byte[] MyByteArray; + public Class1 MyClass1SelfRef; } [StructLayout(LayoutKind.Sequential)] - class Class2 : Class1 + public class Class2 : Class1 { - int MyInt2; + public int MyInt2; } // [StructLayout(LayoutKind.Sequential)] is applied by default by the C# compiler - struct Struct0 + public struct Struct0 { - bool b1; - bool b2; - bool b3; - int i1; - string s1; + public bool b1; + public bool b2; + public bool b3; + public int i1; + public string s1; } // [StructLayout(LayoutKind.Sequential)] is applied by default by the C# compiler - struct Struct1 + public struct Struct1 { - Struct0 MyStruct0; - bool MyBool; + public Struct0 MyStruct0; + public bool MyBool; } [StructLayout(LayoutKind.Sequential)] public class ClassDoubleBool { - double double1; - bool bool1; + public double double1; + public bool bool1; } [StructLayout(LayoutKind.Sequential)] public class ClassBoolDoubleBool { - bool bool1; - double double1; - bool bool2; + public bool bool1; + public double double1; + public bool bool2; + } + + public struct StructByte + { + public byte fld1; + } + + public struct StructStructByte_StructByteAuto + { + public StructByte fld1; + public Auto.StructByte fld2; + } + public struct StructStructByte_Struct2BytesAuto + { + public StructByte fld1; + public Auto.Struct2Bytes fld2; + } + public struct StructStructByte_Struct3BytesAuto + { + public StructByte fld1; + public Auto.Struct3Bytes fld2; + } + public struct StructStructByte_Struct4BytesAuto + { + public StructByte fld1; + public Auto.Struct4Bytes fld2; + } + public struct StructStructByte_Struct5BytesAuto + { + public StructByte fld1; + public Auto.Struct5Bytes fld2; + } + public struct StructStructByte_Struct8BytesAuto + { + public StructByte fld1; + public Auto.Struct8Bytes fld2; + } + public struct StructStructByte_Struct9BytesAuto + { + public StructByte fld1; + public Auto.Struct9Bytes fld2; + } + + public struct StructStructByte_Int128StructAuto + { + public StructByte fld1; + public Auto.Int128Struct fld2; + } + + public struct StructStructByte_UInt128StructAuto + { + public StructByte fld1; + public Auto.UInt128Struct fld2; + } + + [StructLayout(LayoutKind.Sequential)] + public class Class16Align + { + Vector128 vector16Align; } } namespace Auto { [StructLayout(LayoutKind.Auto)] - struct StructWithBool + public struct StructWithBool { - bool MyStructBool; + public bool MyStructBool; } [StructLayout(LayoutKind.Auto)] - struct StructWithIntChar + public struct StructWithIntChar { - char MyStructChar; - int MyStructInt; + public char MyStructChar; + public int MyStructInt; } [StructLayout(LayoutKind.Auto)] - struct StructWithChar + public struct StructWithChar { - char MyStructChar; + public char MyStructChar; } - class ClassContainingStructs + public class ClassContainingStructs { - static int MyStaticInt; + public static int MyStaticInt; - StructWithBool MyStructWithBool; - bool MyBool1; - char MyChar1; - int MyInt; - double MyDouble; - long MyLong; - byte[] MyByteArray; - string MyString1; - bool MyBool2; - StructWithIntChar MyStructWithIntChar; - StructWithChar MyStructWithChar; + public StructWithBool MyStructWithBool; + public bool MyBool1; + public char MyChar1; + public int MyInt; + public double MyDouble; + public long MyLong; + public byte[] MyByteArray; + public string MyString1; + public bool MyBool2; + public StructWithIntChar MyStructWithIntChar; + public StructWithChar MyStructWithChar; } - class BaseClass7BytesRemaining + public class BaseClass7BytesRemaining { - bool MyBool1; - double MyDouble1; - long MyLong1; - byte[] MyByteArray1; - string MyString1; + public bool MyBool1; + public double MyDouble1; + public long MyLong1; + public byte[] MyByteArray1; + public string MyString1; } - class BaseClass4BytesRemaining + public class BaseClass4BytesRemaining { - long MyLong1; - uint MyUint1; + public long MyLong1; + public uint MyUint1; } - class BaseClass3BytesRemaining + public class BaseClass3BytesRemaining { - int MyInt1; - string MyString1; - bool MyBool1; + public int MyInt1; + public string MyString1; + public bool MyBool1; } - class OptimizePartial : BaseClass7BytesRemaining + public class OptimizePartial : BaseClass7BytesRemaining { - bool OptBool; - char OptChar; - long NoOptLong; - string NoOptString; + public bool OptBool; + public char OptChar; + public long NoOptLong; + public string NoOptString; } - class Optimize7Bools : BaseClass7BytesRemaining + public class Optimize7Bools : BaseClass7BytesRemaining { - bool OptBool1; - bool OptBool2; - bool OptBool3; - bool OptBool4; - bool OptBool5; - bool OptBool6; - bool OptBool7; - bool NoOptBool8; - string NoOptString; + public bool OptBool1; + public bool OptBool2; + public bool OptBool3; + public bool OptBool4; + public bool OptBool5; + public bool OptBool6; + public bool OptBool7; + public bool NoOptBool8; + public string NoOptString; } - class OptimizeAlignedFields : BaseClass7BytesRemaining + public class OptimizeAlignedFields : BaseClass7BytesRemaining { - bool OptBool1; - bool OptBool2; - bool OptBool3; - bool NoOptBool4; - char OptChar1; - char OptChar2; - string NoOptString; + public bool OptBool1; + public bool OptBool2; + public bool OptBool3; + public bool NoOptBool4; + public char OptChar1; + public char OptChar2; + public string NoOptString; } - class OptimizeLargestField : BaseClass4BytesRemaining + public class OptimizeLargestField : BaseClass4BytesRemaining { - bool NoOptBool; - char NoOptChar; - int OptInt; - string NoOptString; + public bool NoOptBool; + public char NoOptChar; + public int OptInt; + public string NoOptString; } - class NoOptimizeMisaligned : BaseClass3BytesRemaining + public class NoOptimizeMisaligned : BaseClass3BytesRemaining { - char NoOptChar; - int NoOptInt; - string NoOptString; + public char NoOptChar; + public int NoOptInt; + public string NoOptString; } - class NoOptimizeCharAtSize2Alignment : BaseClass3BytesRemaining + public class NoOptimizeCharAtSize2Alignment : BaseClass3BytesRemaining { - char NoOptChar; + public char NoOptChar; } [StructLayout(LayoutKind.Auto, Pack = 1)] - struct MinPacking + public struct MinPacking { public byte _byte; public T _value; } + + [StructLayout(LayoutKind.Auto)] + public struct int8x16x2 + { + public Vector128 _0; + public Vector128 _1; + } + + public struct Wrapper_int8x16x2 + { + public int8x16x2 fld; + } + + public struct Wrapper_int8x16x2_2 + { + public bool fld1; + public int8x16x2 fld2; + } + + [StructLayout(LayoutKind.Auto)] + public struct StructByte + { + public byte fld1; + } + + [StructLayout(LayoutKind.Auto)] + public struct Struct2Bytes + { + public byte fld1; + public byte fld2; + } + + [StructLayout(LayoutKind.Auto)] + public struct Struct3Bytes + { + public byte fld1; + public byte fld2; + public byte fld3; + } + + [StructLayout(LayoutKind.Auto)] + public struct Struct4Bytes + { + public byte fld1; + public byte fld2; + public byte fld3; + public byte fld4; + } + + [StructLayout(LayoutKind.Auto)] + public struct Struct5Bytes + { + public byte fld1; + public byte fld2; + public byte fld3; + public byte fld4; + public byte fld5; + } + + [StructLayout(LayoutKind.Auto)] + public struct Struct8Bytes + { + public byte fld1; + public byte fld2; + public byte fld3; + public byte fld4; + public byte fld5; + public byte fld6; + public byte fld7; + public byte fld8; + } + + [StructLayout(LayoutKind.Auto)] + public struct Struct9Bytes + { + public byte fld1; + public byte fld2; + public byte fld3; + public byte fld4; + public byte fld5; + public byte fld6; + public byte fld7; + public byte fld8; + public byte fld9; + } + + [StructLayout(LayoutKind.Auto)] + public struct UInt128Struct + { + UInt128 fld1; + } + + [StructLayout(LayoutKind.Auto)] + public struct Int128Struct + { + Int128 fld1; + } + + [StructLayout(LayoutKind.Sequential)] + public class Class16Align + { + Vector128 vector16Align; + } } namespace IsByRefLike { public ref struct ByRefLikeStruct { - ref object ByRef; + public ref object ByRef; } public struct NotByRefLike { - int X; + public int X; } } namespace EnumAlignment { - public enum ByteEnum : byte {} - public enum ShortEnum : short {} - public enum IntEnum : int {} - public enum LongEnum : long {} + public enum ByteEnum : byte { Val } + public enum ShortEnum : short { Val } + public enum IntEnum : int { Val } + public enum LongEnum : long { Val } public struct LongIntEnumStruct { diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs index 2850c56a10f3c9..2239925645c77c 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs @@ -1,5 +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.Runtime.CompilerServices; +using System.Runtime.InteropServices; #pragma warning disable 649 #pragma warning disable 169 @@ -59,6 +61,14 @@ public struct RuntimeMethodHandle { } public struct RuntimeFieldHandle { } public class Attribute { } + public class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets targets) { } + public bool AllowMultiple { get; set; } + public bool Inherited { get; set; } + } + + public enum AttributeTargets { } public class ThreadStaticAttribute : Attribute { } @@ -71,6 +81,24 @@ public ref struct TypedReference private readonly ref byte _value; private readonly RuntimeTypeHandle _typeHandle; } + + [Intrinsic] + [StructLayout(LayoutKind.Sequential)] + public readonly struct Int128 + { + + private readonly ulong _lower; + private readonly ulong _upper; + } + + [Intrinsic] + [StructLayout(LayoutKind.Sequential)] + public readonly struct UInt128 + { + + private readonly ulong _lower; + private readonly ulong _upper; + } } namespace System.Collections @@ -136,4 +164,22 @@ public static class RuntimeFeature public const string ByRefFields = nameof(ByRefFields); public const string VirtualStaticsInInterfaces = nameof(VirtualStaticsInInterfaces); } + + internal sealed class IntrinsicAttribute : Attribute + { + } +} + +namespace System.Runtime.Intrinsics +{ + [Intrinsic] + [StructLayout(LayoutKind.Sequential, Size = 16)] + public readonly struct Vector128 + where T : struct + { + // These fields exist to ensure the alignment is 8, rather than 1. + // This also allows the debug view to work https://github.com/dotnet/runtime/issues/9495) + private readonly ulong _00; + private readonly ulong _01; + } } diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj index a9e0faf8ac8177..1f6b33ff18ba39 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj @@ -46,6 +46,8 @@ + + diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/InstanceFieldLayoutTests.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/InstanceFieldLayoutTests.cs index 0a60a72a4a640b..083a4e65af3844 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/InstanceFieldLayoutTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/InstanceFieldLayoutTests.cs @@ -281,6 +281,28 @@ public void TestSequentialTypeLayoutStructEmbedded() } } + [Fact] + public void TestSequentialTypeLayoutClass16Align() + { + MetadataType classType = _testModule.GetType("Sequential", "Class16Align"); + Assert.Equal(0x18, classType.InstanceByteCount.AsInt); + foreach (var f in classType.GetFields()) + { + if (f.IsStatic) + continue; + + switch (f.Name) + { + case "vector16Align": + Assert.Equal(0x8, f.Offset.AsInt); + break; + default: + Assert.True(false); + break; + } + } + } + [Fact] public void TestAutoLayoutStruct() { @@ -782,6 +804,28 @@ public void TestAutoTypeLayoutMinPacking(WellKnownType type, int expectedSize) Assert.Equal(expectedSize, inst.InstanceFieldSize.AsInt); } + [Fact] + public void TestAutoTypeLayoutClass16Align() + { + MetadataType classType = _testModule.GetType("Auto", "Class16Align"); + Assert.Equal(0x18, classType.InstanceByteCount.AsInt); + foreach (var f in classType.GetFields()) + { + if (f.IsStatic) + continue; + + switch (f.Name) + { + case "vector16Align": + Assert.Equal(0x8, f.Offset.AsInt); + break; + default: + Assert.True(false); + break; + } + } + } + [Fact] public void TestTypeContainsGCPointers() { @@ -850,5 +894,25 @@ public void TestInvalidByRefLikeTypes() Assert.Throws(() => type.ComputeInstanceLayout(InstanceLayoutKind.TypeAndFields)); } } + + [Fact] + public void TestWrapperAroundVectorTypes() + { + { + MetadataType type = (MetadataType)_testModule.GetType("System.Runtime.Intrinsics", "Vector128`1"); + MetadataType instantiatedType = type.MakeInstantiatedType(_context.GetWellKnownType(WellKnownType.Byte)); + Assert.Equal(16, instantiatedType.InstanceFieldAlignment.AsInt); + } + + { + DefType type = _testModule.GetType("Auto", "int8x16x2"); + Assert.Equal(16, type.InstanceFieldAlignment.AsInt); + } + + { + DefType type = _testModule.GetType("Auto", "Wrapper_int8x16x2"); + Assert.Equal(16, type.InstanceFieldAlignment.AsInt); + } + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestTypeSystemContext.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestTypeSystemContext.cs index 0fbd0b6a35329e..3f963c24104eb1 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestTypeSystemContext.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestTypeSystemContext.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; +using ILCompiler; using Internal.TypeSystem; using System.Reflection; using System.Reflection.PortableExecutable; @@ -22,6 +23,9 @@ class TestTypeSystemContext : MetadataTypeSystemContext { Dictionary _modules = new Dictionary(StringComparer.OrdinalIgnoreCase); + private VectorFieldLayoutAlgorithm _vectorFieldLayoutAlgorithm; + private Int128FieldLayoutAlgorithm _int128FieldLayoutAlgorithm; + MetadataFieldLayoutAlgorithm _metadataFieldLayout = new TestMetadataFieldLayoutAlgorithm(); MetadataRuntimeInterfacesAlgorithm _metadataRuntimeInterfacesAlgorithm = new MetadataRuntimeInterfacesAlgorithm(); ArrayOfTRuntimeInterfacesAlgorithm _arrayOfTRuntimeInterfacesAlgorithm; @@ -29,9 +33,11 @@ class TestTypeSystemContext : MetadataTypeSystemContext public CanonicalizationMode CanonMode { get; set; } = CanonicalizationMode.RuntimeDetermined; - public TestTypeSystemContext(TargetArchitecture arch) - : base(new TargetDetails(arch, TargetOS.Unknown, TargetAbi.Unknown)) + public TestTypeSystemContext(TargetArchitecture arch, TargetOS targetOS = TargetOS.Unknown) + : base(new TargetDetails(arch, targetOS, TargetAbi.Unknown)) { + _vectorFieldLayoutAlgorithm = new VectorFieldLayoutAlgorithm(_metadataFieldLayout, true); + _int128FieldLayoutAlgorithm = new Int128FieldLayoutAlgorithm(_metadataFieldLayout); } public ModuleDesc GetModuleForSimpleName(string simpleName) @@ -67,6 +73,14 @@ public override FieldLayoutAlgorithm GetLayoutAlgorithmForType(DefType type) { if (type == UniversalCanonType) return UniversalCanonLayoutAlgorithm.Instance; + else if (VectorFieldLayoutAlgorithm.IsVectorType(type)) + { + return _vectorFieldLayoutAlgorithm; + } + else if (Int128FieldLayoutAlgorithm.IsIntegerType(type)) + { + return _int128FieldLayoutAlgorithm; + } return _metadataFieldLayout; } diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/UniversalGenericFieldLayoutTests.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/UniversalGenericFieldLayoutTests.cs index 541361160f2b58..6b81314b9f9765 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/UniversalGenericFieldLayoutTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/UniversalGenericFieldLayoutTests.cs @@ -372,6 +372,7 @@ private void CommonClassLayoutTestBits(ModuleDesc testModule, AssertClassIndeterminateSize(context, genOfUL, expectedIndeterminateByteAlignment); } + /* This test exercises universal shared generic layout that is currently unsupported and known to be buggy. [Fact] public void TestClassLayout() { @@ -511,5 +512,6 @@ public void TestClassLayout() Assert.Equal(LayoutInt.Indeterminate, genOfUI.GetFields().First().Offset); Assert.Equal(LayoutInt.Indeterminate, genOfUL.GetFields().First().Offset); } + */ } } diff --git a/src/coreclr/tools/aot/ILCompiler/ILCompiler.csproj b/src/coreclr/tools/aot/ILCompiler/ILCompiler.csproj index 3b70c7b427be19..eeb4b7923dae42 100644 --- a/src/coreclr/tools/aot/ILCompiler/ILCompiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler/ILCompiler.csproj @@ -1,7 +1,7 @@  $(RuntimeBinDir)ilc/ - $(OutputRid) + $(PackageRID) diff --git a/src/coreclr/tools/aot/ILCompiler/ILCompiler.props b/src/coreclr/tools/aot/ILCompiler/ILCompiler.props index 8c4fce523906e7..4fd1db03e8a489 100644 --- a/src/coreclr/tools/aot/ILCompiler/ILCompiler.props +++ b/src/coreclr/tools/aot/ILCompiler/ILCompiler.props @@ -48,12 +48,14 @@ true + + true - + $(ObjWriterVersion) @@ -62,7 +64,7 @@ $(NetStandardLibraryVersion) - + PreserveNewest false false diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index f33053fc898f17..f3c064835d8bff 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -15,9 +15,11 @@ using Internal.CommandLine; +using ILCompiler.Dataflow; +using ILLink.Shared; + using Debug = System.Diagnostics.Debug; using InstructionSet = Internal.JitInterface.InstructionSet; -using ILCompiler.Dataflow; namespace ILCompiler { @@ -103,6 +105,8 @@ internal class Program private IReadOnlyList _singleWarnEnabledAssemblies = Array.Empty(); private IReadOnlyList _singleWarnDisabledAssemblies = Array.Empty(); private bool _singleWarn; + private bool _noTrimWarn; + private bool _noAotWarn; private string _makeReproPath; @@ -228,6 +232,8 @@ private ArgumentSyntax ParseCommandLine(string[] args) syntax.DefineOption("nopreinitstatics", ref _noPreinitStatics, "Do not interpret static constructors at compile time"); syntax.DefineOptionList("nowarn", ref _suppressedWarnings, "Disable specific warning messages"); syntax.DefineOption("singlewarn", ref _singleWarn, "Generate single AOT/trimming warning per assembly"); + syntax.DefineOption("notrimwarn", ref _noTrimWarn, "Disable warnings related to trimming"); + syntax.DefineOption("noaotwarn", ref _noAotWarn, "Disable warnings related to AOT"); syntax.DefineOptionList("singlewarnassembly", ref _singleWarnEnabledAssemblies, "Generate single AOT/trimming warning for given assembly"); syntax.DefineOptionList("nosinglewarnassembly", ref _singleWarnDisabledAssemblies, "Expand AOT/trimming warnings for given assembly"); syntax.DefineOptionList("directpinvoke", ref _directPInvokes, "PInvoke to call directly"); @@ -768,7 +774,13 @@ static string ILLinkify(string rootedAssembly) } ilProvider = new FeatureSwitchManager(ilProvider, featureSwitches); - var logger = new Logger(Console.Out, ilProvider, _isVerbose, ProcessWarningCodes(_suppressedWarnings), _singleWarn, _singleWarnEnabledAssemblies, _singleWarnDisabledAssemblies); + var suppressedWarningCategories = new List(); + if (_noTrimWarn) + suppressedWarningCategories.Add(MessageSubCategory.TrimAnalysis); + if (_noAotWarn) + suppressedWarningCategories.Add(MessageSubCategory.AotAnalysis); + + var logger = new Logger(Console.Out, ilProvider, _isVerbose, ProcessWarningCodes(_suppressedWarnings), _singleWarn, _singleWarnEnabledAssemblies, _singleWarnDisabledAssemblies, suppressedWarningCategories); CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider, logger); var stackTracePolicy = _emitStackTraceData ? diff --git a/src/coreclr/tools/aot/crossgen2/crossgen2.csproj b/src/coreclr/tools/aot/crossgen2/crossgen2.csproj index 8696352abc03e8..0d50278228a310 100644 --- a/src/coreclr/tools/aot/crossgen2/crossgen2.csproj +++ b/src/coreclr/tools/aot/crossgen2/crossgen2.csproj @@ -7,6 +7,10 @@ false + + + false + true linux-x64;linux-musl-x64;linux-arm;linux-musl-arm;linux-arm64;linux-musl-arm64;freebsd-x64;osx-x64;osx-arm64;win-x64;win-x86;win-arm64;win-arm diff --git a/src/coreclr/tools/aot/crossgen2/crossgen2.props b/src/coreclr/tools/aot/crossgen2/crossgen2.props index b7aa445890d07e..20f60c316f7b97 100644 --- a/src/coreclr/tools/aot/crossgen2/crossgen2.props +++ b/src/coreclr/tools/aot/crossgen2/crossgen2.props @@ -7,12 +7,14 @@ 8002,NU1701 x64;x86;arm64;arm;loongarch64 AnyCPU + false false true true false Debug;Release;Checked true + true diff --git a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h index 503e2c42bcf1f1..ce147abc89be60 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h @@ -68,7 +68,7 @@ LWM(GetClassGClayout, DWORDLONG, Agnostic_GetClassGClayout) LWM(GetClassModuleIdForStatics, DWORDLONG, Agnostic_GetClassModuleIdForStatics) LWM(GetClassName, DWORDLONG, DWORD) LWM(GetClassNameFromMetadata, DLD, DD) -LWM(GetTypeInstantiationArgument, DWORDLONG, DWORDLONG) +LWM(GetTypeInstantiationArgument, DLD, DWORDLONG) LWM(GetClassNumInstanceFields, DWORDLONG, DWORD) LWM(GetClassSize, DWORDLONG, DWORD) LWM(GetHeapClassSize, DWORDLONG, DWORD) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index e8a883ed2d606f..fa4441812314d3 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -6449,27 +6449,33 @@ const char* MethodContext::repGetClassNameFromMetadata(CORINFO_CLASS_HANDLE cls, } void MethodContext::recGetTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, - CORINFO_CLASS_HANDLE result, - unsigned index) + unsigned index, + CORINFO_CLASS_HANDLE result) { if (GetTypeInstantiationArgument == nullptr) - GetTypeInstantiationArgument = new LightWeightMap(); + GetTypeInstantiationArgument = new LightWeightMap(); - DWORDLONG key = CastHandle(cls); + DLD key; + ZeroMemory(&key, sizeof(key)); + key.A = CastHandle(cls); + key.B = index; DWORDLONG value = CastHandle(result); GetTypeInstantiationArgument->Add(key, value); DEBUG_REC(dmpGetTypeInstantiationArgument(key, value)); } -void MethodContext::dmpGetTypeInstantiationArgument(DWORDLONG key, DWORDLONG value) +void MethodContext::dmpGetTypeInstantiationArgument(DLD key, DWORDLONG value) { - printf("GetTypeInstantiationArgument key - classNonNull-%llu, value NonNull-%llu", key, value); + printf("GetTypeInstantiationArgument key - classNonNull-%llu, index-%u, value NonNull-%llu", key.A, key.B, value); GetTypeInstantiationArgument->Unlock(); } CORINFO_CLASS_HANDLE MethodContext::repGetTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, unsigned index) { CORINFO_CLASS_HANDLE result = nullptr; - DWORDLONG key = CastHandle(cls); + DLD key; + ZeroMemory(&key, sizeof(key)); + key.A = CastHandle(cls); + key.B = index; int itemIndex = -1; if (GetTypeInstantiationArgument != nullptr) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h index 3fc5247ef66c20..66a3b90e49a601 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h @@ -796,8 +796,8 @@ class MethodContext void dmpGetClassNameFromMetadata(DLD key, DD value); const char* repGetClassNameFromMetadata(CORINFO_CLASS_HANDLE cls, const char** namespaceName); - void recGetTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE result, unsigned index); - void dmpGetTypeInstantiationArgument(DWORDLONG key, DWORDLONG value); + void recGetTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, unsigned index, CORINFO_CLASS_HANDLE result); + void dmpGetTypeInstantiationArgument(DLD key, DWORDLONG value); CORINFO_CLASS_HANDLE repGetTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, unsigned index); void recAppendClassName(int nBufLenIn, diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 5d146d2de2cd77..2943db7a5376f4 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -489,9 +489,9 @@ const char* interceptor_ICJI::getClassNameFromMetadata(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE interceptor_ICJI::getTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, unsigned index) { mc->cr->AddCall("getTypeInstantiationArgument"); - CORINFO_CLASS_HANDLE temp = original_ICorJitInfo->getTypeInstantiationArgument(cls, index); - mc->recGetTypeInstantiationArgument(cls, temp, index); - return temp; + CORINFO_CLASS_HANDLE result = original_ICorJitInfo->getTypeInstantiationArgument(cls, index); + mc->recGetTypeInstantiationArgument(cls, index, result); + return result; } // Append a (possibly truncated) textual representation of the type `cls` to a preallocated buffer. diff --git a/src/coreclr/utilcode/executableallocator.cpp b/src/coreclr/utilcode/executableallocator.cpp index 9536b673f53a51..7872954adc485c 100644 --- a/src/coreclr/utilcode/executableallocator.cpp +++ b/src/coreclr/utilcode/executableallocator.cpp @@ -259,7 +259,7 @@ bool ExecutableAllocator::Initialize() return true; } -//#define ENABLE_CACHED_MAPPINGS +#define ENABLE_CACHED_MAPPINGS void ExecutableAllocator::UpdateCachedMapping(BlockRW* pBlock) { diff --git a/src/coreclr/utilcode/loaderheap.cpp b/src/coreclr/utilcode/loaderheap.cpp index 5c55e6283c16b4..db6489026b2921 100644 --- a/src/coreclr/utilcode/loaderheap.cpp +++ b/src/coreclr/utilcode/loaderheap.cpp @@ -341,6 +341,7 @@ RangeList::RangeListBlock::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) // code:LoaderHeap::UnlockedReservePages adds a range for the entire reserved region, instead // of updating the RangeList when pages are committed. But in that case, the committed region of // memory will be enumerated by the LoaderHeap anyway, so it's OK if this fails + EMEM_OUT(("MEM: RangeListBlock %p - %p\n", range->start, range->end)); DacEnumMemoryRegion(range->start, size, false); } } @@ -1933,8 +1934,6 @@ void UnlockedLoaderHeap::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) { WRAPPER_NO_CONTRACT; - DAC_ENUM_DTHIS(); - PTR_LoaderHeapBlock block = m_pFirstBlock; while (block.IsValid()) { @@ -1946,6 +1945,7 @@ void UnlockedLoaderHeap::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) // but it seems wasteful (eg. makes each AppDomain objects 32 bytes larger on x64). TADDR addr = dac_cast(block->pVirtualAddress); TSIZE_T size = block->dwVirtualSize; + EMEM_OUT(("MEM: UnlockedLoaderHeap %p - %p\n", addr, addr + size)); DacEnumMemoryRegion(addr, size, false); block = block->pNext; diff --git a/src/coreclr/utilcode/util.cpp b/src/coreclr/utilcode/util.cpp index 623ef923516ce5..26f09eea2564c1 100644 --- a/src/coreclr/utilcode/util.cpp +++ b/src/coreclr/utilcode/util.cpp @@ -369,7 +369,7 @@ BYTE * ClrVirtualAllocWithinRange(const BYTE *pMinAddr, { NOTHROW; PRECONDITION(dwSize != 0); - PRECONDITION(flAllocationType == MEM_RESERVE); + PRECONDITION(flAllocationType == MEM_RESERVE); // ORed with MEM_RESERVE_EXECUTABLE on Unix } CONTRACTL_END; @@ -449,7 +449,7 @@ BYTE * ClrVirtualAllocWithinRange(const BYTE *pMinAddr, (mbInfo.RegionSize >= (SIZE_T) dwSize || mbInfo.RegionSize == 0)) { // Try reserving the memory using VirtualAlloc now - pResult = (BYTE*)ClrVirtualAlloc(tryAddr, dwSize, MEM_RESERVE, flProtect); + pResult = (BYTE*)ClrVirtualAlloc(tryAddr, dwSize, flAllocationType, flProtect); // Normally this will be successful // diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 39a02a3ffdd639..924c09165b1013 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -1267,108 +1267,110 @@ void SystemDomain::LoadBaseSystemClasses() ETWOnStartup(LdSysBases_V1, LdSysBasesEnd_V1); - m_pSystemPEAssembly = PEAssembly::OpenSystem(); + EX_TRY + { + m_pSystemPEAssembly = PEAssembly::OpenSystem(); - // Only partially load the system assembly. Other parts of the code will want to access - // the globals in this function before finishing the load. - m_pSystemAssembly = DefaultDomain()->LoadDomainAssembly(NULL, m_pSystemPEAssembly, FILE_LOAD_POST_LOADLIBRARY)->GetAssembly(); + // Only partially load the system assembly. Other parts of the code will want to access + // the globals in this function before finishing the load. + m_pSystemAssembly = DefaultDomain()->LoadDomainAssembly(NULL, m_pSystemPEAssembly, FILE_LOAD_POST_LOADLIBRARY)->GetAssembly(); - // Set up binder for CoreLib - CoreLibBinder::AttachModule(m_pSystemAssembly->GetModule()); + // Set up binder for CoreLib + CoreLibBinder::AttachModule(m_pSystemAssembly->GetModule()); - // Load Object - g_pObjectClass = CoreLibBinder::GetClass(CLASS__OBJECT); + // Load Object + g_pObjectClass = CoreLibBinder::GetClass(CLASS__OBJECT); - // Now that ObjectClass is loaded, we can set up - // the system for finalizers. There is no point in deferring this, since we need - // to know this before we allocate our first object. - g_pObjectFinalizerMD = CoreLibBinder::GetMethod(METHOD__OBJECT__FINALIZE); + // Now that ObjectClass is loaded, we can set up + // the system for finalizers. There is no point in deferring this, since we need + // to know this before we allocate our first object. + g_pObjectFinalizerMD = CoreLibBinder::GetMethod(METHOD__OBJECT__FINALIZE); - g_pCanonMethodTableClass = CoreLibBinder::GetClass(CLASS____CANON); + g_pCanonMethodTableClass = CoreLibBinder::GetClass(CLASS____CANON); - // NOTE: !!!IMPORTANT!!! ValueType and Enum MUST be loaded one immediately after - // the other, because we have coded MethodTable::IsChildValueType - // in such a way that it depends on this behaviour. - // Load the ValueType class - g_pValueTypeClass = CoreLibBinder::GetClass(CLASS__VALUE_TYPE); + // NOTE: !!!IMPORTANT!!! ValueType and Enum MUST be loaded one immediately after + // the other, because we have coded MethodTable::IsChildValueType + // in such a way that it depends on this behaviour. + // Load the ValueType class + g_pValueTypeClass = CoreLibBinder::GetClass(CLASS__VALUE_TYPE); - // Load the enum class - g_pEnumClass = CoreLibBinder::GetClass(CLASS__ENUM); - _ASSERTE(!g_pEnumClass->IsValueType()); + // Load the enum class + g_pEnumClass = CoreLibBinder::GetClass(CLASS__ENUM); + _ASSERTE(!g_pEnumClass->IsValueType()); - // Load System.RuntimeType - g_pRuntimeTypeClass = CoreLibBinder::GetClass(CLASS__CLASS); - _ASSERTE(g_pRuntimeTypeClass->IsFullyLoaded()); + // Load System.RuntimeType + g_pRuntimeTypeClass = CoreLibBinder::GetClass(CLASS__CLASS); + _ASSERTE(g_pRuntimeTypeClass->IsFullyLoaded()); - // Load Array class - g_pArrayClass = CoreLibBinder::GetClass(CLASS__ARRAY); + // Load Array class + g_pArrayClass = CoreLibBinder::GetClass(CLASS__ARRAY); - // Calling a method on IList for an array requires redirection to a method on - // the SZArrayHelper class. Retrieving such methods means calling - // GetActualImplementationForArrayGenericIListMethod, which calls FetchMethod for - // the corresponding method on SZArrayHelper. This basically results in a class - // load due to a method call, which the debugger cannot handle, so we pre-load - // the SZArrayHelper class here. - g_pSZArrayHelperClass = CoreLibBinder::GetClass(CLASS__SZARRAYHELPER); + // Calling a method on IList for an array requires redirection to a method on + // the SZArrayHelper class. Retrieving such methods means calling + // GetActualImplementationForArrayGenericIListMethod, which calls FetchMethod for + // the corresponding method on SZArrayHelper. This basically results in a class + // load due to a method call, which the debugger cannot handle, so we pre-load + // the SZArrayHelper class here. + g_pSZArrayHelperClass = CoreLibBinder::GetClass(CLASS__SZARRAYHELPER); - // Load Nullable class - g_pNullableClass = CoreLibBinder::GetClass(CLASS__NULLABLE); + // Load Nullable class + g_pNullableClass = CoreLibBinder::GetClass(CLASS__NULLABLE); - // Load the Object array class. - g_pPredefinedArrayTypes[ELEMENT_TYPE_OBJECT] = ClassLoader::LoadArrayTypeThrowing(TypeHandle(g_pObjectClass)); + // Load the Object array class. + g_pPredefinedArrayTypes[ELEMENT_TYPE_OBJECT] = ClassLoader::LoadArrayTypeThrowing(TypeHandle(g_pObjectClass)); - // We have delayed allocation of CoreLib's static handles until we load the object class - CoreLibBinder::GetModule()->AllocateRegularStaticHandles(DefaultDomain()); + // We have delayed allocation of CoreLib's static handles until we load the object class + CoreLibBinder::GetModule()->AllocateRegularStaticHandles(DefaultDomain()); - // Boolean has to be loaded first to break cycle in IComparisonOperations and IEqualityOperators - CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_BOOLEAN); + // Boolean has to be loaded first to break cycle in IComparisonOperations and IEqualityOperators + CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_BOOLEAN); - // Int32 has to be loaded next to break cycle in IShiftOperators - CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_I4); + // Int32 has to be loaded next to break cycle in IShiftOperators + CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_I4); - // Make sure all primitive types are loaded - for (int et = ELEMENT_TYPE_VOID; et <= ELEMENT_TYPE_R8; et++) - CoreLibBinder::LoadPrimitiveType((CorElementType)et); + // Make sure all primitive types are loaded + for (int et = ELEMENT_TYPE_VOID; et <= ELEMENT_TYPE_R8; et++) + CoreLibBinder::LoadPrimitiveType((CorElementType)et); - CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_I); - CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_U); + CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_I); + CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_U); - g_TypedReferenceMT = CoreLibBinder::GetClass(CLASS__TYPED_REFERENCE); + g_TypedReferenceMT = CoreLibBinder::GetClass(CLASS__TYPED_REFERENCE); - // unfortunately, the following cannot be delay loaded since the jit - // uses it to compute method attributes within a function that cannot - // handle Complus exception and the following call goes through a path - // where a complus exception can be thrown. It is unfortunate, because - // we know that the delegate class and multidelegate class are always - // guaranteed to be found. - g_pDelegateClass = CoreLibBinder::GetClass(CLASS__DELEGATE); - g_pMulticastDelegateClass = CoreLibBinder::GetClass(CLASS__MULTICAST_DELEGATE); + // unfortunately, the following cannot be delay loaded since the jit + // uses it to compute method attributes within a function that cannot + // handle Complus exception and the following call goes through a path + // where a complus exception can be thrown. It is unfortunate, because + // we know that the delegate class and multidelegate class are always + // guaranteed to be found. + g_pDelegateClass = CoreLibBinder::GetClass(CLASS__DELEGATE); + g_pMulticastDelegateClass = CoreLibBinder::GetClass(CLASS__MULTICAST_DELEGATE); - // further loading of nonprimitive types may need casting support. - // initialize cast cache here. - CastCache::Initialize(); - ECall::PopulateManagedCastHelpers(); + // further loading of nonprimitive types may need casting support. + // initialize cast cache here. + CastCache::Initialize(); + ECall::PopulateManagedCastHelpers(); - // used by IsImplicitInterfaceOfSZArray - CoreLibBinder::GetClass(CLASS__IENUMERABLEGENERIC); - CoreLibBinder::GetClass(CLASS__ICOLLECTIONGENERIC); - CoreLibBinder::GetClass(CLASS__ILISTGENERIC); - CoreLibBinder::GetClass(CLASS__IREADONLYCOLLECTIONGENERIC); - CoreLibBinder::GetClass(CLASS__IREADONLYLISTGENERIC); + // used by IsImplicitInterfaceOfSZArray + CoreLibBinder::GetClass(CLASS__IENUMERABLEGENERIC); + CoreLibBinder::GetClass(CLASS__ICOLLECTIONGENERIC); + CoreLibBinder::GetClass(CLASS__ILISTGENERIC); + CoreLibBinder::GetClass(CLASS__IREADONLYCOLLECTIONGENERIC); + CoreLibBinder::GetClass(CLASS__IREADONLYLISTGENERIC); - // Load String - g_pStringClass = CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_STRING); + // Load String + g_pStringClass = CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_STRING); - ECall::PopulateManagedStringConstructors(); + ECall::PopulateManagedStringConstructors(); - g_pExceptionClass = CoreLibBinder::GetClass(CLASS__EXCEPTION); - g_pOutOfMemoryExceptionClass = CoreLibBinder::GetException(kOutOfMemoryException); - g_pStackOverflowExceptionClass = CoreLibBinder::GetException(kStackOverflowException); - g_pExecutionEngineExceptionClass = CoreLibBinder::GetException(kExecutionEngineException); - g_pThreadAbortExceptionClass = CoreLibBinder::GetException(kThreadAbortException); + g_pExceptionClass = CoreLibBinder::GetClass(CLASS__EXCEPTION); + g_pOutOfMemoryExceptionClass = CoreLibBinder::GetException(kOutOfMemoryException); + g_pStackOverflowExceptionClass = CoreLibBinder::GetException(kStackOverflowException); + g_pExecutionEngineExceptionClass = CoreLibBinder::GetException(kExecutionEngineException); + g_pThreadAbortExceptionClass = CoreLibBinder::GetException(kThreadAbortException); - g_pThreadClass = CoreLibBinder::GetClass(CLASS__THREAD); + g_pThreadClass = CoreLibBinder::GetClass(CLASS__THREAD); #ifdef FEATURE_COMINTEROP if (g_pConfig->IsBuiltInCOMSupported()) @@ -1381,27 +1383,47 @@ void SystemDomain::LoadBaseSystemClasses() } #endif - g_pIDynamicInterfaceCastableInterface = CoreLibBinder::GetClass(CLASS__IDYNAMICINTERFACECASTABLE); + g_pIDynamicInterfaceCastableInterface = CoreLibBinder::GetClass(CLASS__IDYNAMICINTERFACECASTABLE); -#ifdef FEATURE_ICASTABLE - g_pICastableInterface = CoreLibBinder::GetClass(CLASS__ICASTABLE); -#endif // FEATURE_ICASTABLE + #ifdef FEATURE_ICASTABLE + g_pICastableInterface = CoreLibBinder::GetClass(CLASS__ICASTABLE); + #endif // FEATURE_ICASTABLE - // Make sure that FCall mapping for Monitor.Enter is initialized. We need it in case Monitor.Enter is used only as JIT helper. - // For more details, see comment in code:JITutil_MonEnterWorker around "__me = GetEEFuncEntryPointMacro(JIT_MonEnter)". - ECall::GetFCallImpl(CoreLibBinder::GetMethod(METHOD__MONITOR__ENTER)); + // Make sure that FCall mapping for Monitor.Enter is initialized. We need it in case Monitor.Enter is used only as JIT helper. + // For more details, see comment in code:JITutil_MonEnterWorker around "__me = GetEEFuncEntryPointMacro(JIT_MonEnter)". + ECall::GetFCallImpl(CoreLibBinder::GetMethod(METHOD__MONITOR__ENTER)); -#ifdef PROFILING_SUPPORTED - // Note that g_profControlBlock.fBaseSystemClassesLoaded must be set to TRUE only after - // all base system classes are loaded. Profilers are not allowed to call any type-loading - // APIs until g_profControlBlock.fBaseSystemClassesLoaded is TRUE. It is important that - // all base system classes need to be loaded before profilers can trigger the type loading. - g_profControlBlock.fBaseSystemClassesLoaded = TRUE; -#endif // PROFILING_SUPPORTED + #ifdef PROFILING_SUPPORTED + // Note that g_profControlBlock.fBaseSystemClassesLoaded must be set to TRUE only after + // all base system classes are loaded. Profilers are not allowed to call any type-loading + // APIs until g_profControlBlock.fBaseSystemClassesLoaded is TRUE. It is important that + // all base system classes need to be loaded before profilers can trigger the type loading. + g_profControlBlock.fBaseSystemClassesLoaded = TRUE; + #endif // PROFILING_SUPPORTED -#if defined(_DEBUG) - g_CoreLib.Check(); -#endif + // Perform any once-only SafeHandle initialization. + SafeHandle::Init(); + + #if defined(_DEBUG) + g_CoreLib.Check(); + g_CoreLib.CheckExtended(); + #endif // _DEBUG + } + EX_HOOK + { + Exception *ex = GET_EXCEPTION(); + + LogErrorToHost("Failed to load System.Private.CoreLib.dll (error code 0x%08X)", ex->GetHR()); + MAKE_UTF8PTR_FROMWIDE_NOTHROW(filePathUtf8, SystemDomain::System()->BaseLibrary()) + if (filePathUtf8 != NULL) + { + LogErrorToHost("Path: %s", filePathUtf8); + } + SString err; + ex->GetMessage(err); + LogErrorToHost("Error message: %s", err.GetUTF8()); + } + EX_END_HOOK; } #endif // !DACCESS_COMPILE @@ -5086,26 +5108,7 @@ DomainLocalModule::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) } void -BaseDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, - bool enumThis) -{ - SUPPORTS_DAC; - if (enumThis) - { - // This is wrong. Don't do it. - // BaseDomain cannot be instantiated. - // The only thing this code can hope to accomplish is to potentially break - // memory enumeration walking through the derived class if we - // explicitly call the base class enum first. -// DAC_ENUM_VTHIS(); - } - - EMEM_OUT(("MEM: %p BaseDomain\n", dac_cast(this))); -} - -void -AppDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, - bool enumThis) +AppDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, bool enumThis) { SUPPORTS_DAC; @@ -5113,8 +5116,8 @@ AppDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, { //sizeof(AppDomain) == 0xeb0 DAC_ENUM_VTHIS(); + EMEM_OUT(("MEM: %p AppDomain\n", dac_cast(this))); } - BaseDomain::EnumMemoryRegions(flags, false); // We don't need AppDomain name in triage dumps. if (flags != CLRDATA_ENUM_MEM_TRIAGE) @@ -5122,6 +5125,11 @@ AppDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, m_friendlyName.EnumMemoryRegions(flags); } + if (flags == CLRDATA_ENUM_MEM_HEAP2) + { + GetLoaderAllocator()->EnumMemoryRegions(flags); + } + m_Assemblies.EnumMemoryRegions(flags); AssemblyIterator assem = IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoaded | kIncludeExecution)); CollectibleAssemblyHolder pDomainAssembly; @@ -5133,16 +5141,19 @@ AppDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, } void -SystemDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, - bool enumThis) +SystemDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, bool enumThis) { SUPPORTS_DAC; if (enumThis) { DAC_ENUM_VTHIS(); + EMEM_OUT(("MEM: %p SystemAppomain\n", dac_cast(this))); } - BaseDomain::EnumMemoryRegions(flags, false); + if (flags == CLRDATA_ENUM_MEM_HEAP2) + { + GetLoaderAllocator()->EnumMemoryRegions(flags); + } if (m_pSystemPEAssembly.IsValid()) { m_pSystemPEAssembly->EnumMemoryRegions(flags); diff --git a/src/coreclr/vm/appdomain.hpp b/src/coreclr/vm/appdomain.hpp index 5798301810d2d0..e25b2d56cd6573 100644 --- a/src/coreclr/vm/appdomain.hpp +++ b/src/coreclr/vm/appdomain.hpp @@ -1260,8 +1260,7 @@ class BaseDomain #ifdef DACCESS_COMPILE public: - virtual void EnumMemoryRegions(CLRDataEnumMemoryFlags flags, - bool enumThis); + virtual void EnumMemoryRegions(CLRDataEnumMemoryFlags flags, bool enumThis) = 0; #endif }; // class BaseDomain diff --git a/src/coreclr/vm/assembly.cpp b/src/coreclr/vm/assembly.cpp index 97f1d26d1c281e..9bb246a62f49e8 100644 --- a/src/coreclr/vm/assembly.cpp +++ b/src/coreclr/vm/assembly.cpp @@ -2132,21 +2132,28 @@ Assembly::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) DAC_ENUM_DTHIS(); EMEM_OUT(("MEM: %p Assembly\n", dac_cast(this))); - if (m_pDomain.IsValid()) + if (flags == CLRDATA_ENUM_MEM_HEAP2) { - m_pDomain->EnumMemoryRegions(flags, true); + GetLoaderAllocator()->EnumMemoryRegions(flags); } - if (m_pClassLoader.IsValid()) - { - m_pClassLoader->EnumMemoryRegions(flags); - } - if (m_pModule.IsValid()) - { - m_pModule->EnumMemoryRegions(flags, true); - } - if (m_pPEAssembly.IsValid()) + else { - m_pPEAssembly->EnumMemoryRegions(flags); + if (m_pDomain.IsValid()) + { + m_pDomain->EnumMemoryRegions(flags, true); + } + if (m_pClassLoader.IsValid()) + { + m_pClassLoader->EnumMemoryRegions(flags); + } + if (m_pModule.IsValid()) + { + m_pModule->EnumMemoryRegions(flags, true); + } + if (m_pPEAssembly.IsValid()) + { + m_pPEAssembly->EnumMemoryRegions(flags); + } } } diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index 04e65cbfbdbe1c..76b57204df42cf 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -2311,26 +2311,17 @@ ISymUnmanagedReader *Module::GetISymUnmanagedReader(void) "reachable or needs to be reimplemented for CoreCLR!"); } - // We're going to be working with Windows PDB format symbols. Attempt to CoCreate the symbol binder. - // CoreCLR supports not having a symbol reader installed, so CoCreate searches the PATH env var - // and then tries coreclr dll location. - // On desktop, the framework installer is supposed to install diasymreader.dll as well - // and so this shouldn't happen. - hr = FakeCoCreateInstanceEx(CLSID_CorSymBinder_SxS, NATIVE_SYMBOL_READER_DLL, IID_ISymUnmanagedBinder, (void**)&pBinder, NULL); + PathString symbolReaderPath; + hr = GetClrModuleDirectory(symbolReaderPath); if (FAILED(hr)) { - PathString symbolReaderPath; - hr = GetClrModuleDirectory(symbolReaderPath); - if (FAILED(hr)) - { - RETURN (NULL); - } - symbolReaderPath.Append(NATIVE_SYMBOL_READER_DLL); - hr = FakeCoCreateInstanceEx(CLSID_CorSymBinder_SxS, symbolReaderPath.GetUnicode(), IID_ISymUnmanagedBinder, (void**)&pBinder, NULL); - if (FAILED(hr)) - { - RETURN (NULL); - } + RETURN (NULL); + } + symbolReaderPath.Append(NATIVE_SYMBOL_READER_DLL); + hr = FakeCoCreateInstanceEx(CLSID_CorSymBinder_SxS, symbolReaderPath.GetUnicode(), IID_ISymUnmanagedBinder, (void**)&pBinder, NULL); + if (FAILED(hr)) + { + RETURN (NULL); } LOG((LF_CORDB, LL_INFO10, "M::GISUR: Created binder\n")); @@ -5123,7 +5114,6 @@ void Module::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, { m_ModuleID->EnumMemoryRegions(flags); } - if (m_pPEAssembly.IsValid()) { m_pPEAssembly->EnumMemoryRegions(flags); @@ -5136,7 +5126,11 @@ void Module::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, m_TypeRefToMethodTableMap.ListEnumMemoryRegions(flags); m_TypeDefToMethodTableMap.ListEnumMemoryRegions(flags); - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags == CLRDATA_ENUM_MEM_HEAP2) + { + GetLoaderAllocator()->EnumMemoryRegions(flags); + } + else if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) { if (m_pAvailableClasses.IsValid()) { @@ -5224,7 +5218,7 @@ void Module::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, } } - } // !CLRDATA_ENUM_MEM_MINI && !CLRDATA_ENUM_MEM_TRIAGE + } // !CLRDATA_ENUM_MEM_MINI && !CLRDATA_ENUM_MEM_TRIAGE && !CLRDATA_ENUM_MEM_HEAP2 LookupMap::Iterator fileRefIter(&m_FileReferencesMap); @@ -5589,4 +5583,4 @@ void DECLSPEC_NORETURN Module::ThrowTypeLoadExceptionImpl(IMDInternalImport *pIn WRAPPER_NO_CONTRACT; GetAssembly()->ThrowTypeLoadException(pInternalImport, token, NULL, resIDWhy); } -#endif \ No newline at end of file +#endif diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index 4094b4ff28db83..257a16e4144004 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -890,6 +890,11 @@ void EEStartupHelper() // requires write barriers to have been set up on x86, which happens as part // of InitJITHelpers1. hr = g_pGCHeap->Initialize(); + if (FAILED(hr)) + { + LogErrorToHost("GC heap initialization failed with error 0x%08X", hr); + } + IfFailGo(hr); #ifdef FEATURE_PERFTRACING @@ -945,9 +950,6 @@ void EEStartupHelper() StackSampler::Init(); #endif - // Perform any once-only SafeHandle initialization. - SafeHandle::Init(); - #ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS // retrieve configured max size for the mini-metadata buffer (defaults to 64KB) g_MiniMetaDataBuffMaxSize = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_MiniMdBufferCapacity); @@ -961,7 +963,6 @@ void EEStartupHelper() g_MiniMetaDataBuffMaxSize, MEM_COMMIT, PAGE_READWRITE); #endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS - g_fEEStarted = TRUE; g_EEStartupStatus = S_OK; hr = S_OK; @@ -982,9 +983,6 @@ void EEStartupHelper() { SystemDomain::SystemModule()->ExpandAll(); } - - // Perform CoreLib consistency check if requested - g_CoreLib.CheckExtended(); #endif // _DEBUG @@ -992,6 +990,7 @@ ErrExit: ; } EX_CATCH { + hr = GET_EXCEPTION()->GetHR(); } EX_END_CATCH(RethrowTerminalExceptionsWithInitCheck) @@ -1642,8 +1641,10 @@ void InitializeGarbageCollector() g_pFreeObjectMethodTable->SetComponentSize(1); hr = GCHeapUtilities::LoadAndInitialize(); + if (hr != S_OK) { + LogErrorToHost("GC initialization failed with error 0x%08X", hr); ThrowHR(hr); } diff --git a/src/coreclr/vm/ceemain.h b/src/coreclr/vm/ceemain.h index 688f41c6deb1be..1404a5a04237ff 100644 --- a/src/coreclr/vm/ceemain.h +++ b/src/coreclr/vm/ceemain.h @@ -54,5 +54,10 @@ INT32 GetLatchedExitCode (void); // Stronger than IsGCHeapInitialized BOOL IsGarbageCollectorFullyInitialized(); +// Specifies whether coreclr is embedded or standalone +extern bool g_coreclr_embedded; + +// Specifies whether hostpolicy is embedded in executable or standalone +extern bool g_hostpolicy_embedded; #endif diff --git a/src/coreclr/vm/class.cpp b/src/coreclr/vm/class.cpp index 4492c025d71a51..6558f7dec2e187 100644 --- a/src/coreclr/vm/class.cpp +++ b/src/coreclr/vm/class.cpp @@ -162,7 +162,8 @@ void EEClass::Destruct(MethodTable * pOwningMT) } if (pDelegateEEClass->m_pInstRetBuffCallStub) { - pDelegateEEClass->m_pInstRetBuffCallStub->DecRef(); + ExecutableWriterHolder stubWriterHolder(pDelegateEEClass->m_pInstRetBuffCallStub, sizeof(Stub)); + stubWriterHolder.GetRW()->DecRef(); } // While m_pMultiCastInvokeStub is also a member, // it is owned by the m_pMulticastStubCache, not by the class @@ -2994,7 +2995,7 @@ EEClass::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, MethodTable * pMT) if (HasOptionalFields()) DacEnumMemoryRegion(dac_cast(GetOptionalFields()), sizeof(EEClassOptionalFields)); - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2) { PTR_Module pModule = pMT->GetModule(); if (pModule.IsValid()) diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index 1fc909efe9c623..22ef2b9919cc19 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -376,7 +376,9 @@ class EEClassLayoutInfo // The size of the struct is explicitly specified in the meta-data. e_HAS_EXPLICIT_SIZE = 0x08, // The type recursively has a field that is LayoutKind.Auto and not an enum. - e_HAS_AUTO_LAYOUT_FIELD_IN_LAYOUT = 0x10 + e_HAS_AUTO_LAYOUT_FIELD_IN_LAYOUT = 0x10, + // Type type recursively has a field which is an Int128 + e_IS_OR_HAS_INT128_FIELD = 0x20, }; BYTE m_bFlags; @@ -426,6 +428,12 @@ class EEClassLayoutInfo return (m_bFlags & e_HAS_AUTO_LAYOUT_FIELD_IN_LAYOUT) == e_HAS_AUTO_LAYOUT_FIELD_IN_LAYOUT; } + BOOL IsInt128OrHasInt128Fields() const + { + LIMITED_METHOD_CONTRACT; + return (m_bFlags & e_IS_OR_HAS_INT128_FIELD) == e_IS_OR_HAS_INT128_FIELD; + } + BYTE GetPackingSize() const { LIMITED_METHOD_CONTRACT; @@ -467,6 +475,13 @@ class EEClassLayoutInfo m_bFlags = hasAutoLayoutField ? (m_bFlags | e_HAS_AUTO_LAYOUT_FIELD_IN_LAYOUT) : (m_bFlags & ~e_HAS_AUTO_LAYOUT_FIELD_IN_LAYOUT); } + + void SetIsInt128OrHasInt128Fields(BOOL hasInt128Field) + { + LIMITED_METHOD_CONTRACT; + m_bFlags = hasInt128Field ? (m_bFlags | e_IS_OR_HAS_INT128_FIELD) + : (m_bFlags & ~e_IS_OR_HAS_INT128_FIELD); + } }; // @@ -1410,6 +1425,9 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW! BOOL HasExplicitSize(); BOOL IsAutoLayoutOrHasAutoLayoutField(); + + // Only accurate on non-auto layout types + BOOL IsInt128OrHasInt128Fields(); static void GetBestFitMapping(MethodTable * pMT, BOOL *pfBestFitMapping, BOOL *pfThrowOnUnmappableChar); @@ -2105,6 +2123,15 @@ inline BOOL EEClass::IsAutoLayoutOrHasAutoLayoutField() return !HasLayout() || GetLayoutInfo()->HasAutoLayoutField(); } +inline BOOL EEClass::IsInt128OrHasInt128Fields() +{ + // The name of this type is a slight misnomer as it doesn't detect Int128 fields on + // auto layout types, but since we only need this for interop scenarios, it works out. + LIMITED_METHOD_CONTRACT; + // If this type is not auto + return HasLayout() && GetLayoutInfo()->IsInt128OrHasInt128Fields(); +} + //========================================================================== // These routines manage the prestub (a bootstrapping stub that all // FunctionDesc's are initialized with.) diff --git a/src/coreclr/vm/classlayoutinfo.cpp b/src/coreclr/vm/classlayoutinfo.cpp index 34b04dcd6f7ab3..a37d7a06521216 100644 --- a/src/coreclr/vm/classlayoutinfo.cpp +++ b/src/coreclr/vm/classlayoutinfo.cpp @@ -328,6 +328,16 @@ namespace return FALSE; } + BOOL TypeHasInt128Field(CorElementType corElemType, TypeHandle pNestedType) + { + if (corElemType == ELEMENT_TYPE_VALUETYPE) + { + _ASSERTE(!pNestedType.IsNull()); + return pNestedType.GetMethodTable()->IsInt128OrHasInt128Fields(); + } + return FALSE; + } + #ifdef UNIX_AMD64_ABI void SystemVAmd64CheckForPassNativeStructInRegister(MethodTable* pMT, EEClassNativeLayoutInfo* pNativeLayoutInfo) { @@ -454,6 +464,7 @@ namespace const SigTypeContext* pTypeContext, BOOL* fDisqualifyFromManagedSequential, BOOL* fHasAutoLayoutField, + BOOL* fHasInt128Field, LayoutRawFieldInfo* pFieldInfoArrayOut, BOOL* pIsBlittableOut, ULONG* cInstanceFields @@ -532,6 +543,7 @@ namespace pFieldInfoArrayOut->m_placement = GetFieldPlacementInfo(corElemType, typeHandleMaybe); *fDisqualifyFromManagedSequential |= TypeHasGCPointers(corElemType, typeHandleMaybe); *fHasAutoLayoutField |= TypeHasAutoLayoutField(corElemType, typeHandleMaybe); + *fHasInt128Field |= TypeHasInt128Field(corElemType, typeHandleMaybe); if (!IsFieldBlittable(pModule, fd, fsig.GetArgProps(), pTypeContext, nativeTypeFlags)) *pIsBlittableOut = FALSE; @@ -625,6 +637,7 @@ VOID EEClassLayoutInfo::CollectLayoutFieldMetadataThrowing( // function exits. BOOL fDisqualifyFromManagedSequential; BOOL hasAutoLayoutField = FALSE; + BOOL hasInt128Field = FALSE; // Check if this type might be ManagedSequential. Only valuetypes marked Sequential can be // ManagedSequential. Other issues checked below might also disqualify the type. @@ -639,9 +652,12 @@ VOID EEClassLayoutInfo::CollectLayoutFieldMetadataThrowing( fDisqualifyFromManagedSequential = TRUE; } - if (pParentMT && !pParentMT->IsValueTypeClass() && pParentMT->IsAutoLayoutOrHasAutoLayoutField()) + if (pParentMT && !pParentMT->IsValueTypeClass()) { - hasAutoLayoutField = TRUE; + if (pParentMT->IsAutoLayoutOrHasAutoLayoutField()) + hasAutoLayoutField = TRUE; + if (pParentMT->IsInt128OrHasInt128Fields()) + hasInt128Field = TRUE; } @@ -692,6 +708,7 @@ VOID EEClassLayoutInfo::CollectLayoutFieldMetadataThrowing( pTypeContext, &fDisqualifyFromManagedSequential, &hasAutoLayoutField, + &hasInt128Field, pInfoArrayOut, &isBlittable, &cInstanceFields @@ -706,6 +723,8 @@ VOID EEClassLayoutInfo::CollectLayoutFieldMetadataThrowing( pEEClassLayoutInfoOut->SetHasAutoLayoutField(hasAutoLayoutField); + pEEClassLayoutInfoOut->SetIsInt128OrHasInt128Fields(hasInt128Field); + S_UINT32 cbSortArraySize = S_UINT32(cTotalFields) * S_UINT32(sizeof(LayoutRawFieldInfo*)); if (cbSortArraySize.IsOverflow()) { diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index 6c2e15fe7a82fa..d8cdd083b46771 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -1832,6 +1832,10 @@ static bool ValidateJitName(LPCWSTR pwzJitName) CORINFO_OS getClrVmOs(); +#define LogJITInitializationError(...) \ + LOG((LF_JIT, LL_FATALERROR, __VA_ARGS__)); \ + LogErrorToHost(__VA_ARGS__); + // LoadAndInitializeJIT: load the JIT dll into the process, and initialize it (call the UtilCode initialization function, // check the JIT-EE interface GUID, etc.) // @@ -1884,7 +1888,7 @@ static void LoadAndInitializeJIT(LPCWSTR pwzJitName DEBUGARG(LPCWSTR pwzJitPath) if (pwzJitName == nullptr) { pJitLoadData->jld_hr = E_FAIL; - LOG((LF_JIT, LL_FATALERROR, "LoadAndInitializeJIT: pwzJitName is null")); + LogJITInitializationError("LoadAndInitializeJIT: pwzJitName is null"); return; } @@ -1911,10 +1915,13 @@ static void LoadAndInitializeJIT(LPCWSTR pwzJitName DEBUGARG(LPCWSTR pwzJitPath) } else { - LOG((LF_JIT, LL_FATALERROR, "LoadAndInitializeJIT: invalid characters in %S\n", pwzJitName)); + MAKE_UTF8PTR_FROMWIDE_NOTHROW(utf8JitName, pwzJitName); + LogJITInitializationError("LoadAndInitializeJIT: invalid characters in %s", utf8JitName); } } + MAKE_UTF8PTR_FROMWIDE_NOTHROW(utf8JitName, pwzJitName); + if (SUCCEEDED(hr)) { pJitLoadData->jld_status = JIT_LOAD_STATUS_DONE_LOAD; @@ -1967,29 +1974,29 @@ static void LoadAndInitializeJIT(LPCWSTR pwzJitName DEBUGARG(LPCWSTR pwzJitPath) else { // Mismatched version ID. Fail the load. - LOG((LF_JIT, LL_FATALERROR, "LoadAndInitializeJIT: mismatched JIT version identifier in %S\n", pwzJitName)); + LogJITInitializationError("LoadAndInitializeJIT: mismatched JIT version identifier in %s", utf8JitName); } } else { - LOG((LF_JIT, LL_FATALERROR, "LoadAndInitializeJIT: failed to get ICorJitCompiler in %S\n", pwzJitName)); + LogJITInitializationError("LoadAndInitializeJIT: failed to get ICorJitCompiler in %s", utf8JitName); } } else { - LOG((LF_JIT, LL_FATALERROR, "LoadAndInitializeJIT: failed to find 'getJit' entrypoint in %S\n", pwzJitName)); + LogJITInitializationError("LoadAndInitializeJIT: failed to find 'getJit' entrypoint in %s", utf8JitName); } } EX_CATCH { - LOG((LF_JIT, LL_FATALERROR, "LoadAndInitializeJIT: caught an exception trying to initialize %S\n", pwzJitName)); + LogJITInitializationError("LoadAndInitializeJIT: LoadAndInitializeJIT: caught an exception trying to initialize %s", utf8JitName); } EX_END_CATCH(SwallowAllExceptions) } else { pJitLoadData->jld_hr = hr; - LOG((LF_JIT, LL_FATALERROR, "LoadAndInitializeJIT: failed to load %S, hr=0x%08x\n", pwzJitName, hr)); + LogJITInitializationError("LoadAndInitializeJIT: failed to load %s, hr=0x%08X", utf8JitName, hr); } } diff --git a/src/coreclr/vm/comcallablewrapper.cpp b/src/coreclr/vm/comcallablewrapper.cpp index b526383f755ea7..e0c5c063ac26cb 100644 --- a/src/coreclr/vm/comcallablewrapper.cpp +++ b/src/coreclr/vm/comcallablewrapper.cpp @@ -3851,7 +3851,8 @@ void ComMethodTable::SetITypeInfo(ITypeInfo *pNew) } CONTRACTL_END; - if (InterlockedCompareExchangeT(&m_pITypeInfo, pNew, NULL) == NULL) + ExecutableWriterHolder comMTWriterHolder(this, sizeof(ComMethodTable)); + if (InterlockedCompareExchangeT(&comMTWriterHolder.GetRW()->m_pITypeInfo, pNew, NULL) == NULL) { SafeAddRef(pNew); } diff --git a/src/coreclr/vm/common.h b/src/coreclr/vm/common.h index 6430970b87b6e1..074518eab0cc29 100644 --- a/src/coreclr/vm/common.h +++ b/src/coreclr/vm/common.h @@ -417,6 +417,8 @@ extern DummyGlobalContract ___contract; #undef FPO_ON #endif +void LogErrorToHost(const char* format, ...); + #endif // !_common_h_ diff --git a/src/coreclr/vm/corhost.cpp b/src/coreclr/vm/corhost.cpp index e1babc7f6228e4..bd283b7076fda3 100644 --- a/src/coreclr/vm/corhost.cpp +++ b/src/coreclr/vm/corhost.cpp @@ -37,6 +37,8 @@ #include "dwreport.h" #endif // !TARGET_UNIX +#include "nativelibrary.h" + #ifndef DACCESS_COMPILE extern void STDMETHODCALLTYPE EEShutDown(BOOL fIsDllUnloading); @@ -659,6 +661,27 @@ HRESULT CorHost2::CreateAppDomainWithManager( sAppPaths)); } +#if defined(TARGET_UNIX) + if (!g_coreclr_embedded) + { + // Check if the current code is executing in the single file host or in libcoreclr.so. The libSystem.Native is linked + // into the single file host, so we need to check only when this code is in libcoreclr.so. + // Preload the libSystem.Native.so/dylib to detect possible problems with loading it early + EX_TRY + { + NativeLibrary::LoadLibraryByName(W("libSystem.Native"), SystemDomain::SystemAssembly(), FALSE, 0, TRUE); + } + EX_HOOK + { + Exception *ex = GET_EXCEPTION(); + SString err; + ex->GetMessage(err); + LogErrorToHost("Error message: %s", err.GetUTF8()); + } + EX_END_HOOK; + } +#endif // TARGET_UNIX + *pAppDomainID=DefaultADID; m_fAppDomainCreated = TRUE; diff --git a/src/coreclr/vm/dacenumerablehash.inl b/src/coreclr/vm/dacenumerablehash.inl index 02024c229ca7b3..cfee7b8f684be3 100644 --- a/src/coreclr/vm/dacenumerablehash.inl +++ b/src/coreclr/vm/dacenumerablehash.inl @@ -309,8 +309,8 @@ DPTR(VALUE) DacEnumerableHashTable::BaseFindFirstEntryByHash // +2 to skip "length" and "next" slots DWORD dwBucket = iHash % cBuckets + SKIP_SPECIAL_SLOTS; - // Point at the first entry in the bucket chain which would contain any entries with the given hash code. - PTR_VolatileEntry pEntry = curBuckets[dwBucket]; + // Point at the first entry in the bucket chain that stores entries with the given hash code. + PTR_VolatileEntry pEntry = VolatileLoadWithoutBarrier(&curBuckets[dwBucket]); // Walk the bucket chain one entry at a time. while (pEntry) @@ -329,13 +329,13 @@ DPTR(VALUE) DacEnumerableHashTable::BaseFindFirstEntryByHash } // Move to the next entry in the chain. - pEntry = pEntry->m_pNextEntry; + pEntry = VolatileLoadWithoutBarrier(&pEntry->m_pNextEntry); } // in a rare case if resize is in progress, look in the new table as well. // if existing entry is not in the old table, it must be in the new // since we unlink it from old only after linking into the new. - // check for next table must hapen after we looked through the current. + // check for next table must happen after we looked through the current. VolatileLoadBarrier(); curBuckets = GetNext(curBuckets); } while (curBuckets != nullptr); @@ -367,11 +367,9 @@ DPTR(VALUE) DacEnumerableHashTable::BaseFindNextEntryByHash( PTR_VolatileEntry pVolatileEntry = dac_cast(pContext->m_pEntry); iHash = pVolatileEntry->m_iHashValue; - // Iterate over the bucket chain. - while (pVolatileEntry->m_pNextEntry) + // Iterate over the rest ot the bucket chain. + while ((pVolatileEntry = VolatileLoadWithoutBarrier(&pVolatileEntry->m_pNextEntry)) != nullptr) { - // Advance to the next entry. - pVolatileEntry = pVolatileEntry->m_pNextEntry; if (pVolatileEntry->m_iHashValue == iHash) { // Found a match on hash code. Update our find context to indicate where we got to and return @@ -381,7 +379,7 @@ DPTR(VALUE) DacEnumerableHashTable::BaseFindNextEntryByHash( } } - // check for next table must hapen after we looked through the current. + // check for next table must happen after we looked through the current. VolatileLoadBarrier(); // in a case if resize is in progress, look in the new table as well. diff --git a/src/coreclr/vm/dispatchinfo.cpp b/src/coreclr/vm/dispatchinfo.cpp index 8d970bca9afe8d..59aa001f2d4174 100644 --- a/src/coreclr/vm/dispatchinfo.cpp +++ b/src/coreclr/vm/dispatchinfo.cpp @@ -66,7 +66,7 @@ typedef int (__cdecl *UnicodeStringCompareFuncPtr)(const WCHAR *, const WCHAR *) //-------------------------------------------------------------------------------- // The DispatchMemberInfo class implementation. -DispatchMemberInfo::DispatchMemberInfo(DispatchInfo *pDispInfo, DISPID DispID, SString& strName, OBJECTREF MemberInfoObj) +DispatchMemberInfo::DispatchMemberInfo(DispatchInfo *pDispInfo, DISPID DispID, SString& strName) : m_DispID(DispID) , m_hndMemberInfo(NULL) , m_apParamMarshaler(NULL) @@ -82,7 +82,7 @@ DispatchMemberInfo::DispatchMemberInfo(DispatchInfo *pDispInfo, DISPID DispID, S , m_pDispInfo(pDispInfo) , m_bLastParamOleVarArg(FALSE) { - WRAPPER_NO_CONTRACT; // Calls to CreateHandle, above, means not a leaf contract + WRAPPER_NO_CONTRACT; } void DispatchMemberInfo::Neuter() @@ -137,6 +137,9 @@ DispatchMemberInfo::~DispatchMemberInfo() if (m_pParamInOnly) delete [] m_pParamInOnly; + if (m_hndMemberInfo) + m_pDispInfo->GetLoaderAllocator()->FreeHandle(m_hndMemberInfo); + // Clear the name of the member. m_strName.Clear(); } @@ -335,6 +338,11 @@ PTRARRAYREF DispatchMemberInfo::GetParameters() return ParamArray; } +OBJECTREF DispatchMemberInfo::GetMemberInfoObject() +{ + return m_pDispInfo->GetLoaderAllocator()->GetHandleValue(m_hndMemberInfo); +} + void DispatchMemberInfo::MarshalParamNativeToManaged(int iParam, VARIANT *pSrcVar, OBJECTREF *pDestObj) { CONTRACTL @@ -1013,19 +1021,6 @@ void DispatchMemberInfo::SetUpDispParamAttributes(int iParam, MarshalInfo* Info) m_pParamInOnly[iParam] = ( Info->IsIn() && !Info->IsOut() ); } -#ifndef DACCESS_COMPILE -OBJECTREF DispatchMemberInfo::GetMemberInfoObject() -{ - return m_pDispInfo->GetLoaderAllocator()->GetHandleValue(m_hndMemberInfo); -} - -void DispatchMemberInfo::ClearMemberInfoObject() -{ - m_pDispInfo->GetLoaderAllocator()->SetHandleValue(m_hndMemberInfo, NULL); -} -#endif // DACCESS_COMPILE - - //-------------------------------------------------------------------------------- // The DispatchInfo class implementation. @@ -1162,7 +1157,7 @@ DispatchMemberInfo* DispatchInfo::CreateDispatchMemberInfoInstance(DISPID DispID } CONTRACT_END; - DispatchMemberInfo* pInfo = new DispatchMemberInfo(this, DispID, strMemberName, MemberInfoObj); + DispatchMemberInfo* pInfo = new DispatchMemberInfo(this, DispID, strMemberName); pInfo->SetHandle(GetLoaderAllocator()->AllocateHandle(MemberInfoObj)); RETURN pInfo; @@ -3291,8 +3286,7 @@ DispatchMemberInfo* DispatchExInfo::CreateDispatchMemberInfoInstance(DISPID Disp } CONTRACT_END; - DispatchMemberInfo* pInfo = new DispatchMemberInfo(this, DispID, strMemberName, MemberInfoObj); - + DispatchMemberInfo* pInfo = new DispatchMemberInfo(this, DispID, strMemberName); pInfo->SetHandle(GetLoaderAllocator()->AllocateHandle(MemberInfoObj)); RETURN pInfo; diff --git a/src/coreclr/vm/dispatchinfo.h b/src/coreclr/vm/dispatchinfo.h index 1ee7b71f607abb..dfe3d0facc411c 100644 --- a/src/coreclr/vm/dispatchinfo.h +++ b/src/coreclr/vm/dispatchinfo.h @@ -54,7 +54,7 @@ enum CultureAwareStates // This structure represents a dispatch member. struct DispatchMemberInfo { - DispatchMemberInfo(DispatchInfo *pDispInfo, DISPID DispID, SString& strName, OBJECTREF MemberInfoObj); + DispatchMemberInfo(DispatchInfo *pDispInfo, DISPID DispID, SString& strName); ~DispatchMemberInfo(); // Helper method to ensure the entry is initialized. @@ -148,10 +148,7 @@ struct DispatchMemberInfo return m_bRequiresManagedCleanup; } -#ifndef DACCESS_COMPILE OBJECTREF GetMemberInfoObject(); - void ClearMemberInfoObject(); -#endif // DACCESS_COMPILE // Parameter marshaling methods. void MarshalParamNativeToManaged(int iParam, VARIANT *pSrcVar, OBJECTREF *pDestObj); diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index 239480035443d7..b62b041ce50952 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -3326,6 +3326,12 @@ BOOL NDirect::MarshalingRequired( { TypeHandle hndArgType = arg.GetTypeHandleThrowing(pModule, &emptyTypeContext); + if (hndArgType.GetMethodTable()->IsInt128OrHasInt128Fields()) + { + // Int128 cannot be marshalled by value at this time + return TRUE; + } + // When the runtime runtime marshalling system is disabled, we don't support // any types that contain gc pointers, but all "unmanaged" types are treated as blittable // as long as they aren't auto-layout and don't have any auto-layout fields. @@ -4739,11 +4745,25 @@ namespace RemoveILStubCacheEntry(); } + inline bool CreatedTheAssociatedPublishedStubMD() + { + return m_bILStubCreator; + } + inline void GetStubMethodDesc() { WRAPPER_NO_CONTRACT; + // The creator flag represents ownership of the associated stub MD and indicates that the + // stub MD has not been removed from the cache, so the lookup below is guaranteed to return + // this owned published stub MD. +#ifdef _DEBUG + MethodDesc* pPreexistingStubMD = m_pStubMD; + bool createdThePreexistingMD = m_bILStubCreator; +#endif // _DEBUG + m_pStubMD = ::GetStubMethodDesc(m_pTargetMD, m_pParams, m_pHashParams, &m_amTracker, m_bILStubCreator, m_pStubMD); + _ASSERTE(!createdThePreexistingMD || (m_bILStubCreator && (m_pStubMD == pPreexistingStubMD))); } inline void RemoveILStubCacheEntry() @@ -4770,20 +4790,6 @@ namespace m_amTracker.SuppressRelease(); } - DEBUG_NOINLINE static void HolderEnter(ILStubCreatorHelper *pThis) - { - WRAPPER_NO_CONTRACT; - ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; - pThis->GetStubMethodDesc(); - } - - DEBUG_NOINLINE static void HolderLeave(ILStubCreatorHelper *pThis) - { - WRAPPER_NO_CONTRACT; - ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; - pThis->RemoveILStubCacheEntry(); - } - private: MethodDesc* m_pTargetMD; NDirectStubParameters* m_pParams; @@ -4794,8 +4800,6 @@ namespace bool m_bILStubCreator; // Only the creator can remove the ILStub from the Cache }; //ILStubCreatorHelper - typedef Wrapper ILStubCreatorHelperHolder; - MethodDesc* CreateInteropILStub( ILStubState* pss, StubSigDesc* pSigDesc, @@ -4869,8 +4873,8 @@ namespace pSigDesc->m_pMT ); - // The following two ILStubCreatorHelperHolder are to recover the status when an - // exception happen during the generation of the IL stubs. We need to free the + // The following ILStubCreatorHelper is to recover the status when an + // exception happens during the generation of the IL stubs. We need to free the // memory allocated and restore the ILStubCache. // // The following block is logically divided into two phases. The first phase is @@ -4880,7 +4884,7 @@ namespace // // ilStubCreatorHelper contains an instance of AllocMemTracker which tracks the // allocated memory during the creation of MethodDesc so that we are able to remove - // them when releasing the ILStubCreatorHelperHolder or destructing ILStubCreatorHelper + // them when destructing ILStubCreatorHelper // When removing IL Stub from Cache, we have a constraint that only the thread which // creates the stub can remove it. Otherwise, any thread hits cache and gets the stub will @@ -4893,10 +4897,8 @@ namespace ListLockHolder pILStubLock(pLoaderModule->GetDomain()->GetILStubGenLock()); { - // The holder will free the allocated MethodDesc and restore the ILStubCache - // if exception happen. - ILStubCreatorHelperHolder pCreateOrGetStubHolder(&ilStubCreatorHelper); - pStubMD = pCreateOrGetStubHolder->GetStubMD(); + ilStubCreatorHelper.GetStubMethodDesc(); + pStubMD = ilStubCreatorHelper.GetStubMD(); /////////////////////////////// // @@ -4910,16 +4912,11 @@ namespace ListLockEntryLockHolder pEntryLock(pEntry, FALSE); - // We can release the holder for the first phase now - pCreateOrGetStubHolder.SuppressRelease(); - // We have the entry lock we need to use, so we can release the global lock. pILStubLock.Release(); { - // The holder will free the allocated MethodDesc and restore the ILStubCache - // if exception happen. The reason to get the holder again is to - ILStubCreatorHelperHolder pGenILHolder(&ilStubCreatorHelper); + ilStubCreatorHelper.GetStubMethodDesc(); if (!pEntryLock.DeadlockAwareAcquire()) { @@ -4944,11 +4941,11 @@ namespace pILStubLock.Acquire(); // Assure that pStubMD we have now has not been destroyed by other threads - pGenILHolder->GetStubMethodDesc(); + ilStubCreatorHelper.GetStubMethodDesc(); - while (pStubMD != pGenILHolder->GetStubMD()) + while (pStubMD != ilStubCreatorHelper.GetStubMD()) { - pStubMD = pGenILHolder->GetStubMD(); + pStubMD = ilStubCreatorHelper.GetStubMD(); pEntry.Assign(ListLockEntry::Find(pILStubLock, pStubMD, "il stub gen lock")); pEntryLock.Assign(pEntry, FALSE); @@ -4974,7 +4971,7 @@ namespace pILStubLock.Acquire(); - pGenILHolder->GetStubMethodDesc(); + ilStubCreatorHelper.GetStubMethodDesc(); } } @@ -5058,22 +5055,38 @@ namespace sgh.SuppressRelease(); } - if (pGeneratedNewStub) - { - *pGeneratedNewStub = true; - } - pEntry->m_hrResultCode = S_OK; break; } // Link the MethodDesc onto the method table with the lock taken AddMethodDescChunkWithLockTaken(¶ms, pStubMD); - - pGenILHolder.SuppressRelease(); } } } + + // Callers use the new stub indicator to distinguish between 1) the case where a new stub + // MD was generated during this call and 2) the case where this function attached to a stub + // MD that was generated by some other call (either a call that completed earlier or a call + // on a racing thread). In particular, reliably detecting case (1) is crucial because it is + // the only case where this call permanently publishes a new stub MD into the cache, + // meaning it is the only case where the caller cannot safely free any allocations (such as + // a signature buffer) which the stub MD might reference. + // + // Set the indicator if and only if the stub MD that will be imminiently returned to the + // caller was created by the code above (and will therefore become a permanent member of + // the cache when the SuppressRelease occurs below). Note that, in the presence of racing + // threads, the current call may or may not have carried out IL generation for the stub; + // the only important thing is whether the current call was the one that created the stub + // MD earlier on. + if (ilStubCreatorHelper.CreatedTheAssociatedPublishedStubMD()) + { + if (pGeneratedNewStub) + { + *pGeneratedNewStub = true; + } + } + ilStubCreatorHelper.SuppressRelease(); } diff --git a/src/coreclr/vm/domainassembly.cpp b/src/coreclr/vm/domainassembly.cpp index 3e5706e48c817f..fc9821780b4a67 100644 --- a/src/coreclr/vm/domainassembly.cpp +++ b/src/coreclr/vm/domainassembly.cpp @@ -810,6 +810,14 @@ void DomainAssembly::DeliverSyncEvents() GetModule()->NotifyEtwLoadFinished(S_OK); +#ifdef PROFILING_SUPPORTED + if (!IsProfilerNotified()) + { + SetProfilerNotified(); + GetModule()->NotifyProfilerLoadFinished(S_OK); + } +#endif + #ifdef DEBUGGING_SUPPORTED GCX_COOP(); if (!IsDebuggerNotified()) @@ -1067,14 +1075,17 @@ void DomainAssembly::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) m_pPEAssembly->EnumMemoryRegions(flags); } - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE - && m_pDomain.IsValid()) + if (flags == CLRDATA_ENUM_MEM_HEAP2) { - m_pDomain->EnumMemoryRegions(flags, true); + GetLoaderAllocator()->EnumMemoryRegions(flags); } - - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + else if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) { + if (m_pDomain.IsValid()) + { + m_pDomain->EnumMemoryRegions(flags, true); + } + if (m_pAssembly.IsValid()) { m_pAssembly->EnumMemoryRegions(flags); diff --git a/src/coreclr/vm/eepolicy.cpp b/src/coreclr/vm/eepolicy.cpp index ae56debb60f584..25b787e381c2c6 100644 --- a/src/coreclr/vm/eepolicy.cpp +++ b/src/coreclr/vm/eepolicy.cpp @@ -445,10 +445,12 @@ void EEPolicy::LogFatalError(UINT exitCode, UINT_PTR address, LPCWSTR pszMessage failureType = EventReporter::ERT_ManagedFailFast; else if (exitCode == (UINT)COR_E_CODECONTRACTFAILED) failureType = EventReporter::ERT_CodeContractFailed; + else if (exitCode == EXCEPTION_ACCESS_VIOLATION) + failureType = EventReporter::ERT_UnhandledException; EventReporter reporter(failureType); StackSString s(argExceptionString); - if ((exitCode == (UINT)COR_E_FAILFAST) || (exitCode == (UINT)COR_E_CODECONTRACTFAILED) || (exitCode == (UINT)CLR_E_GC_OOM)) + if ((exitCode == (UINT)COR_E_FAILFAST) || (exitCode == (UINT)COR_E_CODECONTRACTFAILED) || (exitCode == (UINT)CLR_E_GC_OOM) || (exitCode == EXCEPTION_ACCESS_VIOLATION)) { if (pszMessage) { @@ -469,7 +471,7 @@ void EEPolicy::LogFatalError(UINT exitCode, UINT_PTR address, LPCWSTR pszMessage InlineSString<80> ssMessage; InlineSString<80> ssErrorFormat; if(!ssErrorFormat.LoadResource(CCompRC::Optional, IDS_ER_UNMANAGEDFAILFASTMSG )) - ssErrorFormat.Set(W("at IP 0x%x (0x%x) with exit code 0x%x.")); + ssErrorFormat.Set(W("at IP 0x%1 (0x%2) with exit code 0x%3.")); SmallStackSString addressString; addressString.Printf(W("%p"), pExceptionInfo? (PVOID)pExceptionInfo->ExceptionRecord->ExceptionAddress : (PVOID)address); diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h index 620fe62f30268e..7b6614e4423c00 100644 --- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h +++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h @@ -1670,6 +1670,15 @@ ep_rt_config_value_get_use_portable_thread_pool (void) return ThreadpoolMgr::UsePortableThreadPool (); } +static +inline +bool +ep_rt_config_value_get_enable_stackwalk (void) +{ + STATIC_CONTRACT_NOTHROW; + return CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EventPipeEnableStackwalk) != 0; +} + /* * EventPipeSampleProfiler. */ diff --git a/src/coreclr/vm/eventpipeinternal.cpp b/src/coreclr/vm/eventpipeinternal.cpp index 454a4bdae04e0e..4ed2b819aee42c 100644 --- a/src/coreclr/vm/eventpipeinternal.cpp +++ b/src/coreclr/vm/eventpipeinternal.cpp @@ -60,7 +60,7 @@ extern "C" void QCALLTYPE EventPipeInternal_Disable(UINT64 sessionID) END_QCALL; } -extern "C" bool QCALLTYPE EventPipeInternal_GetSessionInfo(UINT64 sessionID, EventPipeSessionInfo *pSessionInfo) +extern "C" BOOL QCALLTYPE EventPipeInternal_GetSessionInfo(UINT64 sessionID, EventPipeSessionInfo *pSessionInfo) { QCALL_CONTRACT; @@ -229,7 +229,7 @@ extern "C" void QCALLTYPE EventPipeInternal_WriteEventData( END_QCALL; } -extern "C" bool QCALLTYPE EventPipeInternal_GetNextEvent(UINT64 sessionID, EventPipeEventInstanceData *pInstance) +extern "C" BOOL QCALLTYPE EventPipeInternal_GetNextEvent(UINT64 sessionID, EventPipeEventInstanceData *pInstance) { QCALL_CONTRACT; @@ -255,7 +255,7 @@ extern "C" bool QCALLTYPE EventPipeInternal_GetNextEvent(UINT64 sessionID, Event return pNextInstance != NULL; } -extern "C" bool QCALLTYPE EventPipeInternal_SignalSession(UINT64 sessionID) +extern "C" BOOL QCALLTYPE EventPipeInternal_SignalSession(UINT64 sessionID) { QCALL_CONTRACT; @@ -268,7 +268,7 @@ extern "C" bool QCALLTYPE EventPipeInternal_SignalSession(UINT64 sessionID) return result; } -extern "C" bool QCALLTYPE EventPipeInternal_WaitForSessionSignal(UINT64 sessionID, INT32 timeoutMs) +extern "C" BOOL QCALLTYPE EventPipeInternal_WaitForSessionSignal(UINT64 sessionID, INT32 timeoutMs) { QCALL_CONTRACT; diff --git a/src/coreclr/vm/eventpipeinternal.h b/src/coreclr/vm/eventpipeinternal.h index 7da96b67ea7b56..b18bfe96ff66af 100644 --- a/src/coreclr/vm/eventpipeinternal.h +++ b/src/coreclr/vm/eventpipeinternal.h @@ -51,7 +51,7 @@ extern "C" UINT64 QCALLTYPE EventPipeInternal_Enable( //! extern "C" void QCALLTYPE EventPipeInternal_Disable(UINT64 sessionID); -extern "C" bool QCALLTYPE EventPipeInternal_GetSessionInfo(UINT64 sessionID, EventPipeSessionInfo *pSessionInfo); +extern "C" BOOL QCALLTYPE EventPipeInternal_GetSessionInfo(UINT64 sessionID, EventPipeSessionInfo *pSessionInfo); extern "C" INT_PTR QCALLTYPE EventPipeInternal_CreateProvider( _In_z_ LPCWSTR providerName, @@ -82,14 +82,14 @@ extern "C" void QCALLTYPE EventPipeInternal_WriteEventData( UINT32 eventDataCount, LPCGUID pActivityId, LPCGUID pRelatedActivityId); -extern "C" bool QCALLTYPE EventPipeInternal_GetNextEvent( +extern "C" BOOL QCALLTYPE EventPipeInternal_GetNextEvent( UINT64 sessionID, EventPipeEventInstanceData *pInstance); -extern "C" bool QCALLTYPE EventPipeInternal_SignalSession( +extern "C" BOOL QCALLTYPE EventPipeInternal_SignalSession( UINT64 sessionID); -extern "C" bool QCALLTYPE EventPipeInternal_WaitForSessionSignal( +extern "C" BOOL QCALLTYPE EventPipeInternal_WaitForSessionSignal( UINT64 sessionID, INT32 timeoutMs); diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index c23a4de544ce43..74d9447cab2b0e 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -5637,7 +5637,7 @@ LONG CallOutFilter(PEXCEPTION_POINTERS pExceptionInfo, PVOID pv) { CallOutFilterParam *pParam = static_cast(pv); - _ASSERTE(pParam->OneShot && (pParam->OneShot == TRUE || pParam->OneShot == FALSE)); + _ASSERTE(pParam && (pParam->OneShot == TRUE || pParam->OneShot == FALSE)); if (pParam->OneShot == TRUE) { @@ -6533,24 +6533,6 @@ AdjustContextForJITHelpers( #if defined(USE_FEF) && !defined(TARGET_UNIX) -static void FixContextForFaultingExceptionFrame( - EXCEPTION_POINTERS* ep, - EXCEPTION_RECORD* pOriginalExceptionRecord, - CONTEXT* pOriginalExceptionContext) -{ - WRAPPER_NO_CONTRACT; - - // don't copy param args as have already supplied them on the throw - memcpy((void*) ep->ExceptionRecord, - (void*) pOriginalExceptionRecord, - offsetof(EXCEPTION_RECORD, ExceptionInformation) - ); - - ReplaceExceptionContextRecord(ep->ContextRecord, pOriginalExceptionContext); - - GetThread()->ResetThreadStateNC(Thread::TSNC_DebuggerIsManagedException); -} - struct HandleManagedFaultFilterParam { // It's possible for our filter to be called more than once if some other first-pass @@ -6558,7 +6540,6 @@ struct HandleManagedFaultFilterParam // the first exception we see. This flag takes care of that. BOOL fFilterExecuted; EXCEPTION_RECORD *pOriginalExceptionRecord; - CONTEXT *pOriginalExceptionContext; }; static LONG HandleManagedFaultFilter(EXCEPTION_POINTERS* ep, LPVOID pv) @@ -6569,17 +6550,15 @@ static LONG HandleManagedFaultFilter(EXCEPTION_POINTERS* ep, LPVOID pv) if (!pParam->fFilterExecuted) { - FixContextForFaultingExceptionFrame(ep, pParam->pOriginalExceptionRecord, pParam->pOriginalExceptionContext); + ep->ExceptionRecord->ExceptionAddress = pParam->pOriginalExceptionRecord->ExceptionAddress; + GetThread()->ResetThreadStateNC(Thread::TSNC_DebuggerIsManagedException); pParam->fFilterExecuted = TRUE; } return EXCEPTION_CONTINUE_SEARCH; } -void HandleManagedFault(EXCEPTION_RECORD* pExceptionRecord, - CONTEXT* pContext, - EXCEPTION_REGISTRATION_RECORD* pEstablisherFrame, - Thread* pThread) +void HandleManagedFault(EXCEPTION_RECORD* pExceptionRecord, CONTEXT* pContext) { WRAPPER_NO_CONTRACT; @@ -6594,7 +6573,6 @@ void HandleManagedFault(EXCEPTION_RECORD* pExceptionRecord, HandleManagedFaultFilterParam param; param.fFilterExecuted = FALSE; param.pOriginalExceptionRecord = pExceptionRecord; - param.pOriginalExceptionContext = pContext; PAL_TRY(HandleManagedFaultFilterParam *, pParam, ¶m) { @@ -6602,7 +6580,7 @@ void HandleManagedFault(EXCEPTION_RECORD* pExceptionRecord, EXCEPTION_RECORD *pRecord = pParam->pOriginalExceptionRecord; - RaiseException(pRecord->ExceptionCode, pRecord->ExceptionFlags, + RaiseException(pRecord->ExceptionCode, 0, pRecord->NumberParameters, pRecord->ExceptionInformation); } PAL_EXCEPT_FILTER(HandleManagedFaultFilter) @@ -6726,27 +6704,16 @@ bool ShouldHandleManagedFault( #ifndef TARGET_UNIX -LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo); - -enum VEH_ACTION -{ - VEH_NO_ACTION = 0, - VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION, - VEH_CONTINUE_EXECUTION, - VEH_CONTINUE_SEARCH, - VEH_EXECUTE_HANDLER -}; - - +VEH_ACTION WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo); VEH_ACTION WINAPI CLRVectoredExceptionHandlerPhase3(PEXCEPTION_POINTERS pExceptionInfo); -LONG WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) +VEH_ACTION WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { // It is not safe to execute code inside VM after we shutdown EE. One example is DisablePreemptiveGC // will block forever. if (g_fForbidEnterEE) { - return EXCEPTION_CONTINUE_SEARCH; + return VEH_CONTINUE_SEARCH; } @@ -6836,7 +6803,7 @@ LONG WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) pExceptionInfo->ContextRecord->Rip = hijackArgs.ReturnAddress; } - return EXCEPTION_CONTINUE_EXECUTION; + return VEH_CONTINUE_EXECUTION; } #endif @@ -6860,11 +6827,9 @@ LONG WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) // // Not an Out-of-memory situation, so no need for a forbid fault region here // - return EXCEPTION_CONTINUE_SEARCH; + return VEH_CONTINUE_SEARCH; } - LONG retVal = 0; - // We can't probe here, because we won't return from the CLRVectoredExceptionHandlerPhase2 // on WIN64 // @@ -6875,15 +6840,10 @@ LONG WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) CantAllocHolder caHolder; } - retVal = CLRVectoredExceptionHandlerPhase2(pExceptionInfo); - - // - //END_ENTRYPOINT_VOIDRET; - // - return retVal; + return CLRVectoredExceptionHandlerPhase2(pExceptionInfo); } -LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo) +VEH_ACTION WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo) { // // DO NOT USE CONTRACTS HERE AS THIS ROUTINE MAY NEVER RETURN. You can use @@ -6917,31 +6877,16 @@ LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo action = CLRVectoredExceptionHandlerPhase3(pExceptionInfo); } - if (action == VEH_CONTINUE_EXECUTION) - { - return EXCEPTION_CONTINUE_EXECUTION; - } - - if (action == VEH_CONTINUE_SEARCH) + if ((action == VEH_CONTINUE_EXECUTION) || (action == VEH_CONTINUE_SEARCH) || (action == VEH_EXECUTE_HANDLER)) { - return EXCEPTION_CONTINUE_SEARCH; - } - - if (action == VEH_EXECUTE_HANDLER) - { - return EXCEPTION_EXECUTE_HANDLER; + return action; } #if defined(FEATURE_EH_FUNCLETS) if (action == VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION) { - HandleManagedFault(pExceptionInfo->ExceptionRecord, - pExceptionInfo->ContextRecord, - NULL, // establisher frame (x86 only) - NULL // pThread (x86 only) - ); - return EXCEPTION_CONTINUE_EXECUTION; + return action; } #endif // defined(FEATURE_EH_FUNCLETS) @@ -6961,7 +6906,7 @@ LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo // the choice to break the no-trigger region after taking all necessary precautions. if (IsDebuggerFault(pExceptionRecord, pExceptionInfo->ContextRecord, pExceptionRecord->ExceptionCode, GetThreadNULLOk())) { - return EXCEPTION_CONTINUE_EXECUTION; + return VEH_CONTINUE_EXECUTION; } } @@ -6993,11 +6938,11 @@ LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo { // The breakpoint was not ours. Someone else can handle it. (Or if not, we'll get it again as // an unhandled exception.) - return EXCEPTION_CONTINUE_SEARCH; + return VEH_CONTINUE_SEARCH; } // The breakpoint was from managed or the runtime. Handle it. - return UserBreakpointFilter(pExceptionInfo); + return (VEH_ACTION)UserBreakpointFilter(pExceptionInfo); } #if defined(FEATURE_EH_FUNCLETS) @@ -7015,19 +6960,11 @@ LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo if (fShouldHandleManagedFault) { - // - // HandleManagedFault may never return, so we cannot use a forbid fault region around it. - // - HandleManagedFault(pExceptionInfo->ExceptionRecord, - pExceptionInfo->ContextRecord, - NULL, // establisher frame (x86 only) - NULL // pThread (x86 only) - ); - return EXCEPTION_CONTINUE_EXECUTION; -} + return VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION; + } #endif // defined(FEATURE_EH_FUNCLETS) - return EXCEPTION_EXECUTE_HANDLER; + return VEH_EXECUTE_HANDLER; } /* @@ -7594,13 +7531,34 @@ LONG WINAPI CLRVectoredExceptionHandlerShim(PEXCEPTION_POINTERS pExceptionInfo) if (pThread || fExceptionInEE) { if (!bIsGCMarker) - result = CLRVectoredExceptionHandler(pExceptionInfo); - else - result = EXCEPTION_CONTINUE_EXECUTION; + { + VEH_ACTION action = CLRVectoredExceptionHandler(pExceptionInfo); - if (EXCEPTION_EXECUTE_HANDLER == result) +#ifdef FEATURE_EH_FUNCLETS + if (VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION == action) + { + // + // HandleManagedFault may never return, so we cannot use a forbid fault region around it. + // + HandleManagedFault(pExceptionInfo->ExceptionRecord, pExceptionInfo->ContextRecord); + return EXCEPTION_CONTINUE_EXECUTION; + } +#endif // FEATURE_EH_FUNCLETS + + if (VEH_EXECUTE_HANDLER == action) + { + result = EXCEPTION_CONTINUE_SEARCH; + } + else + { + _ASSERTE((action == VEH_CONTINUE_EXECUTION) || (action == VEH_CONTINUE_SEARCH)); + result = (LONG)action; + } + + } + else { - result = EXCEPTION_CONTINUE_SEARCH; + result = EXCEPTION_CONTINUE_EXECUTION; } #ifdef _DEBUG @@ -8321,7 +8279,7 @@ void SetReversePInvokeEscapingUnhandledExceptionStatus(BOOL fIsUnwinding, #if defined(TARGET_X86) EXCEPTION_REGISTRATION_RECORD * pEstablisherFrame #elif defined(FEATURE_EH_FUNCLETS) - ULONG64 pEstablisherFrame + PVOID pEstablisherFrame #else #error Unsupported platform #endif diff --git a/src/coreclr/vm/excep.h b/src/coreclr/vm/excep.h index eefaaa1ffc48eb..d30cff50f25b9e 100644 --- a/src/coreclr/vm/excep.h +++ b/src/coreclr/vm/excep.h @@ -744,14 +744,11 @@ bool IsGcMarker(T_CONTEXT *pContext, EXCEPTION_RECORD *pExceptionRecord); bool ShouldHandleManagedFault( EXCEPTION_RECORD* pExceptionRecord, - T_CONTEXT* pContext, + T_CONTEXT* pContext, EXCEPTION_REGISTRATION_RECORD* pEstablisherFrame, Thread* pThread); -void HandleManagedFault(EXCEPTION_RECORD* pExceptionRecord, - T_CONTEXT* pContext, - EXCEPTION_REGISTRATION_RECORD* pEstablisherFrame, - Thread* pThread); +void HandleManagedFault(EXCEPTION_RECORD* pExceptionRecord, T_CONTEXT* pContext); LONG WatsonLastChance( Thread *pThread, @@ -778,7 +775,7 @@ void SetReversePInvokeEscapingUnhandledExceptionStatus(BOOL fIsUnwinding, #ifdef TARGET_X86 EXCEPTION_REGISTRATION_RECORD * pEstablisherFrame #elif defined(FEATURE_EH_FUNCLETS) - ULONG64 pEstablisherFrame + PVOID pEstablisherFrame #else #error Unsupported platform #endif diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index 21fe40769384be..441c6a6c6a3715 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -837,12 +837,9 @@ UINT_PTR ExceptionTracker::FinishSecondPass( void CleanUpForSecondPass(Thread* pThread, bool fIsSO, LPVOID MemoryStackFpForFrameChain, LPVOID MemoryStackFp); -// On CoreARM, the MemoryStackFp is ULONG when passed by RtlDispatchException, -// unlike its 64bit counterparts. EXTERN_C EXCEPTION_DISPOSITION -ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG MemoryStackFp), +ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -861,7 +858,7 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord EXCEPTION_DISPOSITION returnDisposition = ExceptionContinueSearch; STRESS_LOG5(LF_EH, LL_INFO10, "Processing exception at establisher=%p, ip=%p disp->cxr: %p, sp: %p, cxr @ exception: %p\n", - MemoryStackFp, pDispatcherContext->ControlPc, + pEstablisherFrame, pDispatcherContext->ControlPc, pDispatcherContext->ContextRecord, GetSP(pDispatcherContext->ContextRecord), pContextRecord); AMD64_ONLY(STRESS_LOG3(LF_EH, LL_INFO10, " rbx=%p, rsi=%p, rdi=%p\n", pContextRecord->Rbx, pContextRecord->Rsi, pContextRecord->Rdi)); @@ -928,7 +925,7 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord } } - StackFrame sf((UINT_PTR)MemoryStackFp); + StackFrame sf((UINT_PTR)pEstablisherFrame); { @@ -954,7 +951,7 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord // { EH_LOG((LL_INFO100, "..................................................................................\n")); - EH_LOG((LL_INFO100, "ProcessCLRException enter, sp = 0x%p, ControlPc = 0x%p\n", MemoryStackFp, pDispatcherContext->ControlPc)); + EH_LOG((LL_INFO100, "ProcessCLRException enter, sp = 0x%p, ControlPc = 0x%p\n", pEstablisherFrame, pDispatcherContext->ControlPc)); DebugLogExceptionRecord(pExceptionRecord); if (STATUS_UNWIND_CONSOLIDATE == pExceptionRecord->ExceptionCode) @@ -1236,7 +1233,7 @@ lExit: ; // Exception is being propagated from a method marked UnmanagedCallersOnlyAttribute into its native caller. // The explicit frame chain needs to be unwound at this boundary. bool fIsSO = pExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW; - CleanUpForSecondPass(pThread, fIsSO, (void*)MemoryStackFp, (void*)MemoryStackFp); + CleanUpForSecondPass(pThread, fIsSO, pEstablisherFrame, pEstablisherFrame); } } } @@ -4453,7 +4450,7 @@ VOID UnwindManagedExceptionPass2(PAL_SEHException& ex, CONTEXT* unwindStartConte // Perform unwinding of the current frame disposition = ProcessCLRException(exceptionRecord, - establisherFrame, + (void*)establisherFrame, currentFrameContext, &dispatcherContext); @@ -4526,6 +4523,8 @@ VOID UnwindManagedExceptionPass2(PAL_SEHException& ex, CONTEXT* unwindStartConte EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); } +extern void* g_hostingApiReturnAddress; + //--------------------------------------------------------------------------------------- // // This functions performs dispatching of a managed exception. @@ -4621,7 +4620,7 @@ VOID DECLSPEC_NORETURN UnwindManagedExceptionPass1(PAL_SEHException& ex, CONTEXT // Find exception handler in the current frame disposition = ProcessCLRException(ex.GetExceptionRecord(), - establisherFrame, + (void*)establisherFrame, ex.GetContextRecord(), &dispatcherContext); @@ -4727,7 +4726,8 @@ VOID DECLSPEC_NORETURN UnwindManagedExceptionPass1(PAL_SEHException& ex, CONTEXT STRESS_LOG2(LF_EH, LL_INFO100, "Processing exception at native frame: IP = %p, SP = %p \n", controlPc, sp); - if (controlPc == 0) + // Consider the exception unhandled if the unwinding cannot proceed further or if it went past the coreclr_initialize or coreclr_execute_assembly + if ((controlPc == 0) || (controlPc == (UINT_PTR)g_hostingApiReturnAddress)) { if (!GetThread()->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException)) { @@ -5497,16 +5497,7 @@ void TrackerAllocator::FreeTrackerMemory(ExceptionTracker* pTracker) // specify pUnwindPersonalityRoutine. For instance the debugger uses this to unwind from ExceptionHijack back // to RaiseException in win32 and specifies an empty personality routine. For more details about this // see the comments in the code below. -// -// -// AMD64 is more "advanced", in that the DISPATCHER_CONTEXT contains a field for the TargetIp. So we don't have -// to use the control PC in pDispatcherContext->ContextRecord to indicate the target IP for the unwind. However, -// this also means that pDispatcherContext->ContextRecord is expected to be consistent. -// -// -// For more information, refer to vctools\crt\crtw32\misc\{ia64|amd64}\chandler.c for __C_specific_handler() and -// nt\base\ntos\rtl\{ia64|amd64}\exdsptch.c for RtlUnwindEx(). -void FixupDispatcherContext(DISPATCHER_CONTEXT* pDispatcherContext, CONTEXT* pContext, LPVOID originalControlPC, PEXCEPTION_ROUTINE pUnwindPersonalityRoutine) +void FixupDispatcherContext(DISPATCHER_CONTEXT* pDispatcherContext, CONTEXT* pContext, PEXCEPTION_ROUTINE pUnwindPersonalityRoutine = NULL) { if (pContext) { @@ -5514,7 +5505,7 @@ void FixupDispatcherContext(DISPATCHER_CONTEXT* pDispatcherContext, CONTEXT* pCo CopyOSContext(pDispatcherContext->ContextRecord, pContext); } - pDispatcherContext->ControlPc = (UINT_PTR) GetIP(pDispatcherContext->ContextRecord); + pDispatcherContext->ControlPc = (UINT_PTR) GetIP(pDispatcherContext->ContextRecord); #if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) // Since this routine is used to fixup contexts for async exceptions, @@ -5634,14 +5625,6 @@ void FixupDispatcherContext(DISPATCHER_CONTEXT* pDispatcherContext, CONTEXT* pCo } -// See the comment above for the overloaded version of this function. -void FixupDispatcherContext(DISPATCHER_CONTEXT* pDispatcherContext, CONTEXT* pContext, CONTEXT* pOriginalContext, PEXCEPTION_ROUTINE pUnwindPersonalityRoutine = NULL) -{ - _ASSERTE(pOriginalContext != NULL); - FixupDispatcherContext(pDispatcherContext, pContext, (LPVOID)::GetIP(pOriginalContext), pUnwindPersonalityRoutine); -} - - BOOL FirstCallToHandler ( DISPATCHER_CONTEXT *pDispatcherContext, CONTEXT **ppContextRecord) @@ -5673,9 +5656,8 @@ BOOL FirstCallToHandler ( EXTERN_C EXCEPTION_DISPOSITION -HijackHandler(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) -NOT_BIT64_ARG(IN ULONG MemoryStackFp), +HijackHandler(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -5716,7 +5698,7 @@ NOT_BIT64_ARG(IN ULONG MemoryStackFp), pThread->SetThrowControlForThread(Thread::InducedThreadStop); } - FixupDispatcherContext(pDispatcherContext, pNewContext, pContextRecord); + FixupDispatcherContext(pDispatcherContext, pNewContext); STRESS_LOG4(LF_EH, LL_INFO10, "HijackHandler: new establisher: %p, disp->cxr: %p, new ip: %p, new sp: %p\n", pDispatcherContext->EstablisherFrame, @@ -5803,7 +5785,7 @@ void CleanUpForSecondPass(Thread* pThread, bool fIsSO, LPVOID MemoryStackFpForFr EH_LOG((LL_INFO100, "Exception is going into unmanaged code, unwinding frame chain to %p\n", MemoryStackFpForFrameChain)); // On AMD64 the establisher pointer is the live stack pointer, but on - // IA64 and ARM it's the caller's stack pointer. It makes no difference, since there + // ARM and ARM64 it's the caller's stack pointer. It makes no difference, since there // is no Frame anywhere in CallDescrWorker's region of stack. // First make sure that unwinding the frame chain does not remove any transition frames @@ -5822,7 +5804,7 @@ void CleanUpForSecondPass(Thread* pThread, bool fIsSO, LPVOID MemoryStackFpForFr // (stack grows up). if (!fIsSO) { - ExceptionTracker::PopTrackerIfEscaping((void*)MemoryStackFp); + ExceptionTracker::PopTrackerIfEscaping(MemoryStackFp); } } @@ -5862,9 +5844,8 @@ UnhandledExceptionHandlerUnix( #else // TARGET_UNIX EXTERN_C EXCEPTION_DISPOSITION -UMThunkUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG MemoryStackFp), +UMThunkUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -5885,7 +5866,7 @@ UMThunkUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord pThread->DisablePreemptiveGC(); } } - CleanUpForSecondPass(pThread, fIsSO, (void*)MemoryStackFp, (void*)MemoryStackFp); + CleanUpForSecondPass(pThread, fIsSO, pEstablisherFrame, pEstablisherFrame); } // The asm stub put us into COOP mode, but we're about to scan unmanaged call frames @@ -5908,16 +5889,15 @@ UMThunkUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord EXTERN_C EXCEPTION_DISPOSITION UMEntryPrestubUnwindFrameChainHandler( - IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG MemoryStackFp), + IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) { EXCEPTION_DISPOSITION disposition = UMThunkUnwindFrameChainHandler( pExceptionRecord, - MemoryStackFp, + pEstablisherFrame, pContextRecord, pDispatcherContext ); @@ -5927,9 +5907,8 @@ UMEntryPrestubUnwindFrameChainHandler( EXTERN_C EXCEPTION_DISPOSITION UMThunkStubUnwindFrameChainHandler( - IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) -NOT_BIT64_ARG(IN ULONG MemoryStackFp), + IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -5944,14 +5923,14 @@ NOT_BIT64_ARG(IN ULONG MemoryStackFp), if (GetThreadNULLOk() != NULL) { SetReversePInvokeEscapingUnhandledExceptionStatus(IS_UNWINDING(pExceptionRecord->ExceptionFlags), - MemoryStackFp + pEstablisherFrame ); } #endif // _DEBUG EXCEPTION_DISPOSITION disposition = UMThunkUnwindFrameChainHandler( pExceptionRecord, - MemoryStackFp, + pEstablisherFrame, pContextRecord, pDispatcherContext ); @@ -5963,9 +5942,8 @@ NOT_BIT64_ARG(IN ULONG MemoryStackFp), // This is the personality routine setup for the assembly helper (CallDescrWorker) that calls into // managed code. EXTERN_C EXCEPTION_DISPOSITION -CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG MemoryStackFp), +CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -5977,7 +5955,7 @@ CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionReco if (IS_UNWINDING(pExceptionRecord->ExceptionFlags)) { GCX_COOP_NO_DTOR(); - CleanUpForSecondPass(pThread, true, (void*)MemoryStackFp, (void*)MemoryStackFp); + CleanUpForSecondPass(pThread, true, pEstablisherFrame, pEstablisherFrame); } InterlockedAnd((LONG*)&pThread->m_fPreemptiveGCDisabled, 0); @@ -5987,7 +5965,7 @@ CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionReco } EXCEPTION_DISPOSITION retVal = ProcessCLRException(pExceptionRecord, - MemoryStackFp, + pEstablisherFrame, pContextRecord, pDispatcherContext); @@ -5996,7 +5974,7 @@ CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionReco if (IS_UNWINDING(pExceptionRecord->ExceptionFlags)) { - CleanUpForSecondPass(pThread, false, (void*)MemoryStackFp, (void*)MemoryStackFp); + CleanUpForSecondPass(pThread, false, pEstablisherFrame, pEstablisherFrame); } // We're scanning out from CallDescr and potentially through the EE and out to unmanaged. @@ -6011,9 +5989,8 @@ CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionReco #ifdef FEATURE_COMINTEROP EXTERN_C EXCEPTION_DISPOSITION -ReverseComUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG MemoryStackFp), +ReverseComUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -6029,9 +6006,8 @@ ReverseComUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord #ifndef TARGET_UNIX EXTERN_C EXCEPTION_DISPOSITION FixRedirectContextHandler( - IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG MemoryStackFp), + IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -6052,7 +6028,7 @@ FixRedirectContextHandler( CONTEXT *pRedirectedContext = GetCONTEXTFromRedirectedStubStackFrame(pDispatcherContext); - FixupDispatcherContext(pDispatcherContext, pRedirectedContext, pContextRecord); + FixupDispatcherContext(pDispatcherContext, pRedirectedContext); // Returning ExceptionCollidedUnwind will cause the OS to take our new context record // and dispatcher context and restart the exception dispatching on this call frame, diff --git a/src/coreclr/vm/exceptionhandling.h b/src/coreclr/vm/exceptionhandling.h index a69740d175f4d1..a9c2086b8636c3 100644 --- a/src/coreclr/vm/exceptionhandling.h +++ b/src/coreclr/vm/exceptionhandling.h @@ -17,9 +17,8 @@ #define INVALID_RESUME_ADDRESS 0x000000000000bad0 EXTERN_C EXCEPTION_DISPOSITION -ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG MemoryStackFp), +ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PT_CONTEXT pContextRecord, IN OUT PT_DISPATCHER_CONTEXT pDispatcherContext); diff --git a/src/coreclr/vm/exceptmacros.h b/src/coreclr/vm/exceptmacros.h index 3b33292044ddd4..1627e3d3d65cc4 100644 --- a/src/coreclr/vm/exceptmacros.h +++ b/src/coreclr/vm/exceptmacros.h @@ -240,7 +240,16 @@ VOID DECLSPEC_NORETURN RealCOMPlusThrowOM(); #endif // !defined(FEATURE_EH_FUNCLETS) -LONG WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); +enum VEH_ACTION +{ + VEH_NO_ACTION = -3, + VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION = -2, + VEH_CONTINUE_EXECUTION = EXCEPTION_CONTINUE_EXECUTION, + VEH_CONTINUE_SEARCH = EXCEPTION_CONTINUE_SEARCH, + VEH_EXECUTE_HANDLER = EXCEPTION_EXECUTE_HANDLER +}; + +VEH_ACTION CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); // Actual UEF worker prototype for use by GCUnhandledExceptionFilter. extern LONG InternalUnhandledExceptionFilter_Worker(PEXCEPTION_POINTERS pExceptionInfo); diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp index 364a08c4bd5407..848e7a32950f81 100644 --- a/src/coreclr/vm/gcenv.ee.cpp +++ b/src/coreclr/vm/gcenv.ee.cpp @@ -1785,3 +1785,8 @@ void GCToEEInterface::DiagAddNewRegion(int generation, uint8_t* rangeStart, uint { ProfilerAddNewRegion(generation, rangeStart, rangeEnd, rangeEndReserved); } + +void GCToEEInterface::LogErrorToHost(const char *message) +{ + ::LogErrorToHost("GC: %s", message); +} diff --git a/src/coreclr/vm/gcenv.ee.h b/src/coreclr/vm/gcenv.ee.h index 6403f2637b817f..c431cb8245a4cc 100644 --- a/src/coreclr/vm/gcenv.ee.h +++ b/src/coreclr/vm/gcenv.ee.h @@ -87,6 +87,8 @@ class GCToEEInterface : public IGCToCLR { uint32_t GetCurrentProcessCpuCount(); void DiagAddNewRegion(int generation, BYTE * rangeStart, BYTE * rangeEnd, BYTE * rangeEndReserved); + + void LogErrorToHost(const char *message); }; } // namespace standalone diff --git a/src/coreclr/vm/gcheaputilities.cpp b/src/coreclr/vm/gcheaputilities.cpp index d3301df9ff51fa..6f354370e2e25e 100644 --- a/src/coreclr/vm/gcheaputilities.cpp +++ b/src/coreclr/vm/gcheaputilities.cpp @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "common.h" +#include "configuration.h" #include "gcheaputilities.h" #include "gcenv.ee.h" #include "appdomain.hpp" @@ -179,7 +180,7 @@ HMODULE LoadStandaloneGc(LPCWSTR libFileName) // // See Documentation/design-docs/standalone-gc-loading.md for details // on the loading protocol in use here. -HRESULT LoadAndInitializeGC(LPWSTR standaloneGcLocation) +HRESULT LoadAndInitializeGC(LPCWSTR standaloneGcLocation) { LIMITED_METHOD_CONTRACT; @@ -213,17 +214,21 @@ HRESULT LoadAndInitializeGC(LPWSTR standaloneGcLocation) } g_gc_load_status = GC_LOAD_STATUS_GET_VERSIONINFO; + g_gc_version_info.MajorVersion = EE_INTERFACE_MAJOR_VERSION; + g_gc_version_info.MinorVersion = 0; + g_gc_version_info.BuildVersion = 0; versionInfo(&g_gc_version_info); g_gc_load_status = GC_LOAD_STATUS_CALL_VERSIONINFO; - if (g_gc_version_info.MajorVersion != GC_INTERFACE_MAJOR_VERSION) + if (g_gc_version_info.MajorVersion < GC_INTERFACE_MAJOR_VERSION) { - LOG((LF_GC, LL_FATALERROR, "Loaded GC has incompatible major version number (expected %d, got %d)\n", + LOG((LF_GC, LL_FATALERROR, "Loaded GC has incompatible major version number (expected at least %d, got %d)\n", GC_INTERFACE_MAJOR_VERSION, g_gc_version_info.MajorVersion)); return E_FAIL; } - if (g_gc_version_info.MinorVersion < GC_INTERFACE_MINOR_VERSION) + if ((g_gc_version_info.MajorVersion == GC_INTERFACE_MAJOR_VERSION) && + (g_gc_version_info.MinorVersion < GC_INTERFACE_MINOR_VERSION)) { LOG((LF_GC, LL_INFO100, "Loaded GC has lower minor version number (%d) than EE was compiled against (%d)\n", g_gc_version_info.MinorVersion, GC_INTERFACE_MINOR_VERSION)); @@ -334,8 +339,7 @@ HRESULT GCHeapUtilities::LoadAndInitialize() assert(g_gc_load_status == GC_LOAD_STATUS_BEFORE_START); g_gc_load_status = GC_LOAD_STATUS_START; - LPWSTR standaloneGcLocation = nullptr; - CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_GCName, &standaloneGcLocation); + LPCWSTR standaloneGcLocation = Configuration::GetKnobStringValue(W("System.GC.Name"), CLRConfig::EXTERNAL_GCName); if (!standaloneGcLocation) { return InitializeDefaultGC(); diff --git a/src/coreclr/vm/gdbjit.cpp b/src/coreclr/vm/gdbjit.cpp index 126193549b47db..bea3035299d9c7 100644 --- a/src/coreclr/vm/gdbjit.cpp +++ b/src/coreclr/vm/gdbjit.cpp @@ -508,7 +508,7 @@ GetDebugInfoFromPDB(MethodDesc* methodDescPtr, return E_FAIL; const Module* mod = methodDescPtr->GetMethodTable()->GetModule(); - SString modName = mod->GetFile()->GetPath(); + SString modName = mod->GetPEAssembly()->GetPath(); if (modName.IsEmpty()) return E_FAIL; @@ -2533,7 +2533,7 @@ void NotifyGdb::OnMethodPrepared(MethodDesc* methodDescPtr) /* Get module name */ const Module* mod = methodDescPtr->GetMethodTable()->GetModule(); - SString modName = mod->GetFile()->GetPath(); + SString modName = mod->GetPEAssembly()->GetPath(); const char* szModName = modName.GetUTF8(); const char* szModuleFile = SplitFilename(szModName); diff --git a/src/coreclr/vm/i386/excepx86.cpp b/src/coreclr/vm/i386/excepx86.cpp index 68c68f7f258a0e..51df72d448705e 100644 --- a/src/coreclr/vm/i386/excepx86.cpp +++ b/src/coreclr/vm/i386/excepx86.cpp @@ -1254,13 +1254,13 @@ CPFH_FirstPassHandler(EXCEPTION_RECORD *pExceptionRecord, // Call to the vectored handler to give other parts of the Runtime a chance to jump in and take over an // exception before we do too much with it. The most important point in the vectored handler is not to toggle // the GC mode. - DWORD filter = CLRVectoredExceptionHandler(&ptrs); + VEH_ACTION filter = CLRVectoredExceptionHandler(&ptrs); - if (filter == (DWORD) EXCEPTION_CONTINUE_EXECUTION) + if (filter == VEH_CONTINUE_EXECUTION) { return ExceptionContinueExecution; } - else if (filter == EXCEPTION_CONTINUE_SEARCH) + else if (filter == VEH_CONTINUE_SEARCH) { return ExceptionContinueSearch; } @@ -1300,22 +1300,16 @@ CPFH_FirstPassHandler(EXCEPTION_RECORD *pExceptionRecord, CPFH_VerifyThreadIsInValidState(pThread, exceptionCode, pEstablisherFrame); - // If we were in cooperative mode when we came in here, then its okay to see if we should do HandleManagedFault + // If we were in cooperative mode when we came in here, then it's okay to see if we should do HandleManagedFault // and push a FaultingExceptionFrame. If we weren't in coop mode coming in here, then it means that there's no - // way the exception could really be from managed code. I might look like it was from managed code, but in - // reality its a rethrow from unmanaged code, either unmanaged user code, or unmanaged EE implementation. + // way the exception could really be from managed code. It might look like it was from managed code, but in + // reality it's a rethrow from unmanaged code, either unmanaged user code, or unmanaged EE implementation. if (disabled && ShouldHandleManagedFault(pExceptionRecord, pContext, pEstablisherFrame, pThread)) { #if defined(USE_FEF) - HandleManagedFault(pExceptionRecord, pContext, pEstablisherFrame, pThread); + HandleManagedFault(pExceptionRecord, pContext); retval = ExceptionContinueExecution; goto exit; -#else // USE_FEF - // Save the context pointer in the Thread's EXInfo, so that a stack crawl can recover the - // register values from the fault. - - //@todo: I haven't yet found any case where we need to do anything here. If there are none, eliminate - // this entire if () {} block. #endif // USE_FEF } diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index e2b23d5c5dfae1..873ebce421cb02 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -4916,6 +4916,9 @@ HCIMPLEND void JIT_Patchpoint(int* counter, int ilOffset) { + // BEGIN_PRESERVE_LAST_ERROR; + DWORD dwLastError = ::GetLastError(); + // This method may not return normally STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_MODE_COOPERATIVE; @@ -4929,6 +4932,8 @@ void JIT_Patchpoint(int* counter, int ilOffset) LoaderAllocator* allocator = pMD->GetLoaderAllocator(); OnStackReplacementManager* manager = allocator->GetOnStackReplacementManager(); PerPatchpointInfo * ppInfo = manager->GetPerPatchpointInfo(ip); + PCODE osrMethodCode = NULL; + bool isNewMethod = false; // In the current prototype, counter is shared by all patchpoints // in a method, so no matter what happens below, we don't want to @@ -4955,12 +4960,12 @@ void JIT_Patchpoint(int* counter, int ilOffset) { LOG((LF_TIEREDCOMPILATION, LL_INFO1000, "Jit_Patchpoint: invalid patchpoint [%d] (0x%p) in Method=0x%pM (%s::%s) at offset %d\n", ppId, ip, pMD, pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName, ilOffset)); - return; + + goto DONE; } // See if we have an OSR method for this patchpoint. - PCODE osrMethodCode = ppInfo->m_osrMethodCode; - bool isNewMethod = false; + osrMethodCode = ppInfo->m_osrMethodCode; if (osrMethodCode == NULL) { @@ -4983,7 +4988,7 @@ void JIT_Patchpoint(int* counter, int ilOffset) { LOG((LF_TIEREDCOMPILATION, LL_INFO10, "Jit_Patchpoint: ignoring patchpoint [%d] (0x%p) in Method=0x%pM (%s::%s) at offset %d\n", ppId, ip, pMD, pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName, ilOffset)); - return; + goto DONE; } #endif @@ -5024,7 +5029,7 @@ void JIT_Patchpoint(int* counter, int ilOffset) // Defer, if we haven't yet reached the limit if (hitCount < hitLimit) { - return; + goto DONE; } // Third, make sure no other thread is trying to create the OSR method. @@ -5032,7 +5037,7 @@ void JIT_Patchpoint(int* counter, int ilOffset) if ((oldFlags & PerPatchpointInfo::patchpoint_triggered) == PerPatchpointInfo::patchpoint_triggered) { LOG((LF_TIEREDCOMPILATION, LL_INFO1000, "Jit_Patchpoint: AWAITING OSR method for patchpoint [%d] (0x%p)\n", ppId, ip)); - return; + goto DONE; } LONG newFlags = oldFlags | PerPatchpointInfo::patchpoint_triggered; @@ -5041,7 +5046,7 @@ void JIT_Patchpoint(int* counter, int ilOffset) if (!triggerTransition) { LOG((LF_TIEREDCOMPILATION, LL_INFO1000, "Jit_Patchpoint: (lost race) AWAITING OSR method for patchpoint [%d] (0x%p)\n", ppId, ip)); - return; + goto DONE; } // Time to create the OSR method. @@ -5071,7 +5076,7 @@ void JIT_Patchpoint(int* counter, int ilOffset) " marking patchpoint invalid for Method=0x%pM il offset %d\n", ip, pMD, ilOffset); InterlockedOr(&ppInfo->m_flags, (LONG)PerPatchpointInfo::patchpoint_invalid); - return; + goto DONE; } // We've successfully created the osr method; make it available. @@ -5083,115 +5088,126 @@ void JIT_Patchpoint(int* counter, int ilOffset) // If we get here, we have code to transition to... _ASSERTE(osrMethodCode != NULL); - Thread *pThread = GetThread(); + { + Thread *pThread = GetThread(); #ifdef FEATURE_HIJACK - // We can't crawl the stack of a thread that currently has a hijack pending - // (since the hijack routine won't be recognized by any code manager). So we - // Undo any hijack, the EE will re-attempt it later. - pThread->UnhijackThread(); + // We can't crawl the stack of a thread that currently has a hijack pending + // (since the hijack routine won't be recognized by any code manager). So we + // Undo any hijack, the EE will re-attempt it later. + pThread->UnhijackThread(); #endif - // Find context for the original method - CONTEXT *pFrameContext = NULL; + // Find context for the original method + CONTEXT *pFrameContext = NULL; #if defined(TARGET_WINDOWS) && defined(TARGET_AMD64) - DWORD contextSize = 0; - ULONG64 xStateCompactionMask = 0; - DWORD contextFlags = CONTEXT_FULL; - if (Thread::AreCetShadowStacksEnabled()) - { - xStateCompactionMask = XSTATE_MASK_CET_U; - contextFlags |= CONTEXT_XSTATE; - } + DWORD contextSize = 0; + ULONG64 xStateCompactionMask = 0; + DWORD contextFlags = CONTEXT_FULL; + if (Thread::AreCetShadowStacksEnabled()) + { + xStateCompactionMask = XSTATE_MASK_CET_U; + contextFlags |= CONTEXT_XSTATE; + } - // The initialize call should fail but return contextSize - BOOL success = g_pfnInitializeContext2 ? - g_pfnInitializeContext2(NULL, contextFlags, NULL, &contextSize, xStateCompactionMask) : - InitializeContext(NULL, contextFlags, NULL, &contextSize); + // The initialize call should fail but return contextSize + BOOL success = g_pfnInitializeContext2 ? + g_pfnInitializeContext2(NULL, contextFlags, NULL, &contextSize, xStateCompactionMask) : + InitializeContext(NULL, contextFlags, NULL, &contextSize); - _ASSERTE(!success && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)); + _ASSERTE(!success && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)); - PVOID pBuffer = _alloca(contextSize); - success = g_pfnInitializeContext2 ? - g_pfnInitializeContext2(pBuffer, contextFlags, &pFrameContext, &contextSize, xStateCompactionMask) : - InitializeContext(pBuffer, contextFlags, &pFrameContext, &contextSize); - _ASSERTE(success); + PVOID pBuffer = _alloca(contextSize); + success = g_pfnInitializeContext2 ? + g_pfnInitializeContext2(pBuffer, contextFlags, &pFrameContext, &contextSize, xStateCompactionMask) : + InitializeContext(pBuffer, contextFlags, &pFrameContext, &contextSize); + _ASSERTE(success); #else // TARGET_WINDOWS && TARGET_AMD64 - CONTEXT frameContext; - frameContext.ContextFlags = CONTEXT_FULL; - pFrameContext = &frameContext; + CONTEXT frameContext; + frameContext.ContextFlags = CONTEXT_FULL; + pFrameContext = &frameContext; #endif // TARGET_WINDOWS && TARGET_AMD64 - // Find context for the original method - RtlCaptureContext(pFrameContext); + // Find context for the original method + RtlCaptureContext(pFrameContext); #if defined(TARGET_WINDOWS) && defined(TARGET_AMD64) - if (Thread::AreCetShadowStacksEnabled()) - { - pFrameContext->ContextFlags |= CONTEXT_XSTATE; - SetXStateFeaturesMask(pFrameContext, xStateCompactionMask); - SetSSP(pFrameContext, _rdsspq()); - } + if (Thread::AreCetShadowStacksEnabled()) + { + pFrameContext->ContextFlags |= CONTEXT_XSTATE; + SetXStateFeaturesMask(pFrameContext, xStateCompactionMask); + SetSSP(pFrameContext, _rdsspq()); + } #endif // TARGET_WINDOWS && TARGET_AMD64 - // Walk back to the original method frame - pThread->VirtualUnwindToFirstManagedCallFrame(pFrameContext); + // Walk back to the original method frame + pThread->VirtualUnwindToFirstManagedCallFrame(pFrameContext); - // Remember original method FP and SP because new method will inherit them. - UINT_PTR currentSP = GetSP(pFrameContext); - UINT_PTR currentFP = GetFP(pFrameContext); + // Remember original method FP and SP because new method will inherit them. + UINT_PTR currentSP = GetSP(pFrameContext); + UINT_PTR currentFP = GetFP(pFrameContext); - // We expect to be back at the right IP - if ((UINT_PTR)ip != GetIP(pFrameContext)) - { - // Should be fatal - STRESS_LOG2(LF_TIEREDCOMPILATION, LL_FATALERROR, "Jit_Patchpoint: patchpoint (0x%p) TRANSITION" - " unexpected context IP 0x%p\n", ip, GetIP(pFrameContext)); - EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); - } + // We expect to be back at the right IP + if ((UINT_PTR)ip != GetIP(pFrameContext)) + { + // Should be fatal + STRESS_LOG2(LF_TIEREDCOMPILATION, LL_FATALERROR, "Jit_Patchpoint: patchpoint (0x%p) TRANSITION" + " unexpected context IP 0x%p\n", ip, GetIP(pFrameContext)); + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); + } - // Now unwind back to the original method caller frame. - EECodeInfo callerCodeInfo(GetIP(pFrameContext)); - ULONG_PTR establisherFrame = 0; - PVOID handlerData = NULL; - RtlVirtualUnwind(UNW_FLAG_NHANDLER, callerCodeInfo.GetModuleBase(), GetIP(pFrameContext), callerCodeInfo.GetFunctionEntry(), - pFrameContext, &handlerData, &establisherFrame, NULL); + // Now unwind back to the original method caller frame. + EECodeInfo callerCodeInfo(GetIP(pFrameContext)); + ULONG_PTR establisherFrame = 0; + PVOID handlerData = NULL; + RtlVirtualUnwind(UNW_FLAG_NHANDLER, callerCodeInfo.GetModuleBase(), GetIP(pFrameContext), callerCodeInfo.GetFunctionEntry(), + pFrameContext, &handlerData, &establisherFrame, NULL); - // Now, set FP and SP back to the values they had just before this helper was called, - // since the new method must have access to the original method frame. - // - // TODO: if we access the patchpointInfo here, we can read out the FP-SP delta from there and - // use that to adjust the stack, likely saving some stack space. + // Now, set FP and SP back to the values they had just before this helper was called, + // since the new method must have access to the original method frame. + // + // TODO: if we access the patchpointInfo here, we can read out the FP-SP delta from there and + // use that to adjust the stack, likely saving some stack space. #if defined(TARGET_AMD64) - // If calls push the return address, we need to simulate that here, so the OSR - // method sees the "expected" SP misalgnment on entry. - _ASSERTE(currentSP % 16 == 0); - currentSP -= 8; + // If calls push the return address, we need to simulate that here, so the OSR + // method sees the "expected" SP misalgnment on entry. + _ASSERTE(currentSP % 16 == 0); + currentSP -= 8; #if defined(TARGET_WINDOWS) - DWORD64 ssp = GetSSP(pFrameContext); - if (ssp != 0) - { - SetSSP(pFrameContext, ssp - 8); - } + DWORD64 ssp = GetSSP(pFrameContext); + if (ssp != 0) + { + SetSSP(pFrameContext, ssp - 8); + } #endif // TARGET_WINDOWS - pFrameContext->Rbp = currentFP; + pFrameContext->Rbp = currentFP; #endif // TARGET_AMD64 - SetSP(pFrameContext, currentSP); + SetSP(pFrameContext, currentSP); - // Note we can get here w/o triggering, if there is an existing OSR method and - // we hit the patchpoint. - const int transitionLogLevel = isNewMethod ? LL_INFO10 : LL_INFO1000; - LOG((LF_TIEREDCOMPILATION, transitionLogLevel, "Jit_Patchpoint: patchpoint [%d] (0x%p) TRANSITION to ip 0x%p\n", ppId, ip, osrMethodCode)); + // Note we can get here w/o triggering, if there is an existing OSR method and + // we hit the patchpoint. + const int transitionLogLevel = isNewMethod ? LL_INFO10 : LL_INFO1000; + LOG((LF_TIEREDCOMPILATION, transitionLogLevel, "Jit_Patchpoint: patchpoint [%d] (0x%p) TRANSITION to ip 0x%p\n", ppId, ip, osrMethodCode)); - // Install new entry point as IP - SetIP(pFrameContext, osrMethodCode); + // Install new entry point as IP + SetIP(pFrameContext, osrMethodCode); - // Transition! - ClrRestoreNonvolatileContext(pFrameContext); + // Restore last error (since call below does not return) + // END_PRESERVE_LAST_ERROR; + ::SetLastError(dwLastError); + + // Transition! + ClrRestoreNonvolatileContext(pFrameContext); + } + + DONE: + + // END_PRESERVE_LAST_ERROR; + ::SetLastError(dwLastError); } // Jit helper invoked at a partial compilation patchpoint. @@ -5205,6 +5221,9 @@ void JIT_Patchpoint(int* counter, int ilOffset) // void JIT_PartialCompilationPatchpoint(int ilOffset) { + // BEGIN_PRESERVE_LAST_ERROR; + DWORD dwLastError = ::GetLastError(); + // This method will not return normally STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_MODE_COOPERATIVE; @@ -5356,6 +5375,10 @@ void JIT_PartialCompilationPatchpoint(int ilOffset) // Install new entry point as IP SetIP(&frameContext, osrMethodCode); + // Restore last error (since call below does not return) + // END_PRESERVE_LAST_ERROR; + ::SetLastError(dwLastError); + // Transition! RtlRestoreContext(&frameContext, NULL); } @@ -5571,7 +5594,7 @@ HCIMPL2(void, JIT_DelegateProfile32, Object *obj, ICorJitInfo::HandleHistogram32 HCIMPLEND // Version of helper above used when the count is 64-bit -HCIMPL3(void, JIT_DelegateProfile64, Object *obj, CORINFO_METHOD_HANDLE baseMethod, ICorJitInfo::HandleHistogram64* methodProfile) +HCIMPL2(void, JIT_DelegateProfile64, Object *obj, ICorJitInfo::HandleHistogram64* methodProfile) { FCALL_CONTRACT; FC_GC_POLL_NOT_NEEDED(); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 3163a593440080..fb46cde8964eb4 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -4882,7 +4882,7 @@ void CEEInfo::getCallInfo( MethodDesc * pTargetMD = pMDAfterConstraintResolution; DWORD dwTargetMethodAttrs = pTargetMD->GetAttrs(); - pResult->exactContextNeedsRuntimeLookup = (!constrainedType.IsNull() && constrainedType.IsCanonicalSubtype()); + pResult->exactContextNeedsRuntimeLookup = (fIsStaticVirtualMethod && !fResolvedConstraint && !constrainedType.IsNull() && constrainedType.IsCanonicalSubtype()); if (pTargetMD->HasMethodInstantiation()) { diff --git a/src/coreclr/vm/loaderallocator.cpp b/src/coreclr/vm/loaderallocator.cpp index b7eebd07afecb5..86a72ff57fac5a 100644 --- a/src/coreclr/vm/loaderallocator.cpp +++ b/src/coreclr/vm/loaderallocator.cpp @@ -1445,6 +1445,7 @@ void LoaderAllocator::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) { SUPPORTS_DAC; DAC_ENUM_DTHIS(); + EMEM_OUT(("MEM: %p LoaderAllocator\n", dac_cast(this))); if (m_pLowFrequencyHeap.IsValid()) { m_pLowFrequencyHeap->EnumMemoryRegions(flags); @@ -1461,9 +1462,27 @@ void LoaderAllocator::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) { m_pPrecodeHeap->EnumMemoryRegions(flags); } - if (m_pPrecodeHeap.IsValid()) + if (m_pExecutableHeap.IsValid()) { - m_pPrecodeHeap->EnumMemoryRegions(flags); + m_pExecutableHeap->EnumMemoryRegions(flags); + } +#ifdef FEATURE_READYTORUN + if (m_pDynamicHelpersHeap.IsValid()) + { + m_pDynamicHelpersHeap->EnumMemoryRegions(flags); + } +#endif + if (m_pFixupPrecodeHeap.IsValid()) + { + m_pFixupPrecodeHeap->EnumMemoryRegions(flags); + } + if (m_pNewStubPrecodeHeap.IsValid()) + { + m_pNewStubPrecodeHeap->EnumMemoryRegions(flags); + } + if (m_pVirtualCallStubManager.IsValid()) + { + m_pVirtualCallStubManager->EnumMemoryRegions(flags); } } #endif //DACCESS_COMPILE diff --git a/src/coreclr/vm/loaderallocator.hpp b/src/coreclr/vm/loaderallocator.hpp index 90311d3a01ca18..cbe6ce460f3fe8 100644 --- a/src/coreclr/vm/loaderallocator.hpp +++ b/src/coreclr/vm/loaderallocator.hpp @@ -240,7 +240,7 @@ class LoaderAllocator FatTokenSet *m_pFatTokenSet; #endif - VirtualCallStubManager *m_pVirtualCallStubManager; + PTR_VirtualCallStubManager m_pVirtualCallStubManager; private: LoaderAllocatorSet m_LoaderAllocatorReferences; @@ -599,7 +599,7 @@ class LoaderAllocator void InitVirtualCallStubManager(BaseDomain *pDomain); void UninitVirtualCallStubManager(); - inline VirtualCallStubManager *GetVirtualCallStubManager() + inline PTR_VirtualCallStubManager GetVirtualCallStubManager() { LIMITED_METHOD_CONTRACT; return m_pVirtualCallStubManager; diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index c4d8156fc939b8..e61162ac553c89 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -7904,7 +7904,7 @@ MethodTable::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) pWriteableData.EnumMem(); } - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2) { DispatchMap * pMap = GetDispatchMap(); if (pMap != NULL) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 515cc554b4f6d8..07792d5fbf36d9 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -1487,6 +1487,9 @@ class MethodTable inline BOOL IsAutoLayoutOrHasAutoLayoutField(); + // Only accurate on types which are not auto layout + inline BOOL IsInt128OrHasInt128Fields(); + UINT32 GetNativeSize(); DWORD GetBaseSize() diff --git a/src/coreclr/vm/methodtable.inl b/src/coreclr/vm/methodtable.inl index adcdbd33469858..8f8f8178e26023 100644 --- a/src/coreclr/vm/methodtable.inl +++ b/src/coreclr/vm/methodtable.inl @@ -948,6 +948,13 @@ inline BOOL MethodTable::IsAutoLayoutOrHasAutoLayoutField() return GetClass()->IsAutoLayoutOrHasAutoLayoutField(); } +//========================================================================================== +inline BOOL MethodTable::IsInt128OrHasInt128Fields() +{ + LIMITED_METHOD_CONTRACT; + return HasLayout() && GetClass()->IsInt128OrHasInt128Fields(); +} + //========================================================================================== inline DWORD MethodTable::GetPerInstInfoSize() { diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index ecd1e9d22916cb..245fcaf55bf2bb 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -8743,29 +8743,30 @@ MethodTableBuilder::HandleExplicitLayout( if (pMT->IsByRefLike()) return CheckByRefLikeValueClassLayout(pMT, pFieldLayout); - // This method assumes there is a GC desc associated with the MethodTable. - _ASSERTE(pMT->ContainsPointers()); - // Build a layout of the value class (vc). Don't know the sizes of all the fields easily, but // do know (a) vc is already consistent so don't need to check it's overlaps and // (b) size and location of all objectrefs. So build it by setting all non-oref - // then fill in the orefs later + // then fill in the orefs later if present. UINT fieldSize = pMT->GetNumInstanceFieldBytes(); CQuickBytes qb; bmtFieldLayoutTag *vcLayout = (bmtFieldLayoutTag*) qb.AllocThrows(fieldSize * sizeof(bmtFieldLayoutTag)); memset((void*)vcLayout, nonoref, fieldSize); - // use pointer series to locate the orefs - CGCDesc* map = CGCDesc::GetCGCDescFromMT(pMT); - CGCDescSeries *pSeries = map->GetLowestSeries(); - - for (SIZE_T j = 0; j < map->GetNumSeries(); j++) + // If the type contains pointers fill it out from the GC data + if (pMT->ContainsPointers()) { - CONSISTENCY_CHECK(pSeries <= map->GetHighestSeries()); + // use pointer series to locate the orefs + CGCDesc* map = CGCDesc::GetCGCDescFromMT(pMT); + CGCDescSeries *pSeries = map->GetLowestSeries(); + + for (SIZE_T j = 0; j < map->GetNumSeries(); j++) + { + CONSISTENCY_CHECK(pSeries <= map->GetHighestSeries()); - memset((void*)&vcLayout[pSeries->GetSeriesOffset() - OBJECT_SIZE], oref, pSeries->GetSeriesSize() + pMT->GetBaseSize()); - pSeries++; + memset((void*)&vcLayout[pSeries->GetSeriesOffset() - OBJECT_SIZE], oref, pSeries->GetSeriesSize() + pMT->GetBaseSize()); + pSeries++; + } } ExplicitClassTrust explicitClassTrust; @@ -9907,21 +9908,6 @@ void MethodTableBuilder::CheckForSystemTypes() return; } -#if defined(UNIX_AMD64_ABI) || defined(TARGET_ARM64) - else if (strcmp(nameSpace, g_SystemNS) == 0) - { - EEClassLayoutInfo* pLayout = pClass->GetLayoutInfo(); - - // These types correspond to fundamental data types in the underlying ABIs: - // * Int128: __int128 - // * UInt128: unsigned __int128 - - if ((strcmp(name, g_Int128Name) == 0) || (strcmp(name, g_UInt128Name) == 0)) - { - pLayout->m_ManagedLargestAlignmentRequirementOfAllMembers = 16; // sizeof(__int128) - } - } -#endif // UNIX_AMD64_ABI || TARGET_ARM64 } if (g_pNullableClass != NULL) @@ -10005,6 +9991,30 @@ void MethodTableBuilder::CheckForSystemTypes() { pMT->SetInternalCorElementType (ELEMENT_TYPE_I); } + else if ((strcmp(name, g_Int128Name) == 0) || (strcmp(name, g_UInt128Name) == 0)) + { + EEClassLayoutInfo* pLayout = pClass->GetLayoutInfo(); + pLayout->SetIsInt128OrHasInt128Fields(TRUE); +#ifdef TARGET_ARM + // No such type exists for the Procedure Call Standard for ARM. We will default + // to the same alignment as __m128, which is supported by the ABI. + + pLayout->m_ManagedLargestAlignmentRequirementOfAllMembers = 8; +#elif defined(TARGET_64BIT) || defined(TARGET_X86) + + // These types correspond to fundamental data types in the underlying ABIs: + // * Int128: __int128 + // * UInt128: unsigned __int128 + // + // This behavior matches the ABI standard on various Unix platforms + // On Windows, no standard for Int128 has been established yet, + // although applying 16 byte alignment is consistent with treatment of 128 bit SSE types + // even on X86 + pLayout->m_ManagedLargestAlignmentRequirementOfAllMembers = 16; // sizeof(__int128) +#else +#error Unknown architecture +#endif // TARGET_64BIT + } } else { diff --git a/src/coreclr/vm/mlinfo.cpp b/src/coreclr/vm/mlinfo.cpp index 2665a46d12f0ab..2ca3596ac9236f 100644 --- a/src/coreclr/vm/mlinfo.cpp +++ b/src/coreclr/vm/mlinfo.cpp @@ -1131,6 +1131,11 @@ namespace *errorResIDOut = IDS_EE_BADMARSHAL_AUTOLAYOUT; return MarshalInfo::MARSHAL_TYPE_UNKNOWN; } + if (pMT->IsInt128OrHasInt128Fields()) + { + *errorResIDOut = IDS_EE_BADMARSHAL_INT128_RESTRICTION; + return MarshalInfo::MARSHAL_TYPE_UNKNOWN; + } *pMTOut = pMT; return MarshalInfo::MARSHAL_TYPE_BLITTABLEVALUECLASS; } @@ -2283,6 +2288,18 @@ MarshalInfo::MarshalInfo(Module* pModule, IfFailGoto(E_FAIL, lFail); } + // * Int128: Represents the 128 bit integer ABI primitive type which requires currently unimplemented handling + // * UInt128: Represents the 128 bit integer ABI primitive type which requires currently unimplemented handling + // The field layout is correct, so field scenarios work, but these should not be passed by value as parameters + if (!IsFieldScenario() && !m_byref) + { + if (m_pMT->IsInt128OrHasInt128Fields()) + { + m_resID = IDS_EE_BADMARSHAL_INT128_RESTRICTION; + IfFailGoto(E_FAIL, lFail); + } + } + if (!m_pMT->HasLayout()) { m_resID = IDS_EE_BADMARSHAL_AUTOLAYOUT; diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index 5a8f1624ac5971..4c731c4a240f4b 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -1153,7 +1153,6 @@ class ReflectFieldObject : public BaseObjectWithCachedData INT32 m_empty2; OBJECTREF m_empty3; OBJECTREF m_empty4; - OBJECTREF m_empty5; FieldDesc * m_pFD; public: diff --git a/src/coreclr/vm/peassembly.cpp b/src/coreclr/vm/peassembly.cpp index 4b1719f032a147..1f73b9122615bf 100644 --- a/src/coreclr/vm/peassembly.cpp +++ b/src/coreclr/vm/peassembly.cpp @@ -19,8 +19,6 @@ #include "strongnameinternal.h" #include "../binder/inc/applicationcontext.hpp" - -#include "assemblybinderutil.h" #include "../binder/inc/assemblybindercommon.hpp" #include "sha1.h" @@ -1114,10 +1112,10 @@ PTR_AssemblyBinder PEAssembly::GetAssemblyBinder() PTR_AssemblyBinder pBinder = NULL; - BINDER_SPACE::Assembly* pHostAssembly = GetHostAssembly(); + PTR_BINDER_SPACE_Assembly pHostAssembly = GetHostAssembly(); if (pHostAssembly) { - pBinder = dac_cast(pHostAssembly->GetBinder()); + pBinder = pHostAssembly->GetBinder(); } else { diff --git a/src/coreclr/vm/peimagelayout.cpp b/src/coreclr/vm/peimagelayout.cpp index 2cf519425da2a9..e5cba8b0c816ad 100644 --- a/src/coreclr/vm/peimagelayout.cpp +++ b/src/coreclr/vm/peimagelayout.cpp @@ -85,16 +85,18 @@ PEImageLayout* PEImageLayout::LoadConverted(PEImage* pOwner) // ConvertedImageLayout may be able to handle them, but the fact that we were unable to // load directly implies that MAPMapPEFile could not consume what crossgen produced. // that is suspicious, one or another might have a bug. - _ASSERTE(!pFlat->HasReadyToRunHeader()); + _ASSERTE(!pOwner->IsFile() || !pFlat->HasReadyToRunHeader()); #endif - if (!pFlat->HasReadyToRunHeader() && !pFlat->HasWriteableSections()) + // ignore R2R if the image is not a file. + if ((pFlat->HasReadyToRunHeader() && pOwner->IsFile()) || + pFlat->HasWriteableSections()) { - // we can use flat layout for this - return pFlat.Extract(); + return new ConvertedImageLayout(pFlat); } - return new ConvertedImageLayout(pFlat); + // we can use flat layout for this + return pFlat.Extract(); } PEImageLayout* PEImageLayout::Load(PEImage* pOwner, HRESULT* loadFailure) @@ -448,7 +450,7 @@ ConvertedImageLayout::ConvertedImageLayout(FlatImageLayout* source) IfFailThrow(Init(loadedImage)); - if (IsNativeMachineFormat() && g_fAllowNativeImages) + if (m_pOwner->IsFile() && IsNativeMachineFormat() && g_fAllowNativeImages) { // Do base relocation and exception hookup, if necessary. // otherwise R2R will be disabled for this image. @@ -773,10 +775,18 @@ void* FlatImageLayout::LoadImageByCopyingParts(SIZE_T* m_imageParts) const } #endif // FEATURE_ENABLE_NO_ADDRESS_SPACE_RANDOMIZATION + DWORD allocationType = MEM_RESERVE | MEM_COMMIT; +#ifdef HOST_UNIX + // Tell PAL to use the executable memory allocator to satisfy this request for virtual memory. + // This is required on MacOS and otherwise will allow us to place native R2R code close to the + // coreclr library and thus improve performance by avoiding jump stubs in managed code. + allocationType |= MEM_RESERVE_EXECUTABLE; +#endif + COUNT_T allocSize = ALIGN_UP(this->GetVirtualSize(), g_SystemInfo.dwAllocationGranularity); - LPVOID base = ClrVirtualAlloc(preferredBase, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + LPVOID base = ClrVirtualAlloc(preferredBase, allocSize, allocationType, PAGE_READWRITE); if (base == NULL && preferredBase != NULL) - base = ClrVirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + base = ClrVirtualAlloc(NULL, allocSize, allocationType, PAGE_READWRITE); if (base == NULL) ThrowLastError(); @@ -817,9 +827,13 @@ void* FlatImageLayout::LoadImageByCopyingParts(SIZE_T* m_imageParts) const // Finally, apply proper protection to copied sections for (section = sectionStart; section < sectionEnd; section++) { + DWORD executableProtection = PAGE_EXECUTE_READ; +#if defined(__APPLE__) && defined(HOST_ARM64) + executableProtection = PAGE_EXECUTE_READWRITE; +#endif // Add appropriate page protection. DWORD newProtection = section->Characteristics & IMAGE_SCN_MEM_EXECUTE ? - PAGE_EXECUTE_READ : + executableProtection : section->Characteristics & IMAGE_SCN_MEM_WRITE ? PAGE_READWRITE : PAGE_READONLY; diff --git a/src/coreclr/vm/perfinfo.cpp b/src/coreclr/vm/perfinfo.cpp index 85e44ac8668dac..201a8988377062 100644 --- a/src/coreclr/vm/perfinfo.cpp +++ b/src/coreclr/vm/perfinfo.cpp @@ -10,19 +10,13 @@ #include "perfinfo.h" #include "pal.h" -PerfInfo::PerfInfo(int pid) +PerfInfo::PerfInfo(int pid, const char* basePath) : m_Stream(nullptr) { LIMITED_METHOD_CONTRACT; - SString tempPath; - if (!WszGetTempPath(tempPath)) - { - return; - } - SString path; - path.Printf("%Sperfinfo-%d.map", tempPath.GetUnicode(), pid); + path.Printf("%s/perfinfo-%d.map", basePath, pid); OpenFile(path); } diff --git a/src/coreclr/vm/perfinfo.h b/src/coreclr/vm/perfinfo.h index 16b06865925c64..c3ff8746886f81 100644 --- a/src/coreclr/vm/perfinfo.h +++ b/src/coreclr/vm/perfinfo.h @@ -20,7 +20,7 @@ */ class PerfInfo { public: - PerfInfo(int pid); + PerfInfo(int pid, const char* basePath); ~PerfInfo(); void LogImage(PEAssembly* pPEAssembly, WCHAR* guid); diff --git a/src/coreclr/vm/perfmap.cpp b/src/coreclr/vm/perfmap.cpp index af4cfcf646c08b..c376534307117c 100644 --- a/src/coreclr/vm/perfmap.cpp +++ b/src/coreclr/vm/perfmap.cpp @@ -12,29 +12,57 @@ #include "perfinfo.h" #include "pal.h" + // The code addresses are actually native image offsets during crossgen. Print // them as 32-bit numbers for consistent output when cross-targeting and to // make the output more compact. #define FMT_CODE_ADDR "%p" +#ifndef __ANDROID__ +#define TEMP_DIRECTORY_PATH "/tmp" +#else +// On Android, "/tmp/" doesn't exist; temporary files should go to +// /data/local/tmp/ +#define TEMP_DIRECTORY_PATH "/data/local/tmp" +#endif + Volatile PerfMap::s_enabled = false; PerfMap * PerfMap::s_Current = nullptr; bool PerfMap::s_ShowOptimizationTiers = false; +unsigned PerfMap::s_StubsMapped = 0; + +enum +{ + DISABLED, + ALL, + JITDUMP, + PERFMAP +}; // Initialize the map for the process - called from EEStartupHelper. void PerfMap::Initialize() { LIMITED_METHOD_CONTRACT; + const DWORD perfMapEnabled = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_PerfMapEnabled); + if (perfMapEnabled == DISABLED) + { + return; + } + + // Build the path to the map file on disk. + char tempPathBuffer[MAX_LONGPATH+1]; + const char* tempPath = InternalConstructPath(tempPathBuffer, sizeof(tempPathBuffer)); + // Only enable the map if requested. - if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_PerfMapEnabled)) + if (perfMapEnabled == ALL || perfMapEnabled == PERFMAP) { // Get the current process id. int currentPid = GetCurrentProcessId(); // Create the map. - s_Current = new PerfMap(currentPid); + s_Current = new PerfMap(currentPid, tempPath); int signalNum = (int) CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_PerfMapIgnoreSignal); @@ -42,30 +70,39 @@ void PerfMap::Initialize() { PAL_IgnoreProfileSignal(signalNum); } + } - if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_PerfMapShowOptimizationTiers) != 0) - { - s_ShowOptimizationTiers = true; - } - - s_enabled = true; + // only enable JitDumps if requested + if (perfMapEnabled == ALL || perfMapEnabled == JITDUMP) + { + PAL_PerfJitDump_Start(tempPath); + } - const char* jitdumpPath; - char jitdumpPathBuffer[4096]; + if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_PerfMapShowOptimizationTiers) != 0) + { + s_ShowOptimizationTiers = true; + } + + s_enabled = true; +} - CLRConfigNoCache value = CLRConfigNoCache::Get("PerfMapJitDumpPath"); - if (value.IsSet()) - { - jitdumpPath = value.AsString(); - } - else - { - GetTempPathA(sizeof(jitdumpPathBuffer) - 1, jitdumpPathBuffer); - jitdumpPath = jitdumpPathBuffer; - } +// InternalConstructPath is guaranteed to return a non-null path +// the function uses the input buffer only whe PerfMapJitDumpPath environment variable is set +const char * PerfMap::InternalConstructPath(char *tmpBuf, int lenBuf) +{ + DWORD len = GetEnvironmentVariableA("DOTNET_PerfMapJitDumpPath", tmpBuf, lenBuf); + if (len == 0) + { + len = GetEnvironmentVariableA("COMPlus_PerfMapJitDumpPath", tmpBuf, lenBuf); + } - PAL_PerfJitDump_Start(jitdumpPath); + if (len == 0 || // GetEnvironmentVariableA returns 0 if the variable is not found, + len >= lenBuf) // or the length of the string not including the null terminator on success. + { + return TEMP_DIRECTORY_PATH; } + + return tmpBuf; } // Destroy the map for the process - called from EEShutdownHelper. @@ -82,29 +119,21 @@ void PerfMap::Destroy() } // Construct a new map for the process. -PerfMap::PerfMap(int pid) +PerfMap::PerfMap(int pid, const char* path) { LIMITED_METHOD_CONTRACT; // Initialize with no failures. m_ErrorEncountered = false; - m_StubsMapped = 0; - // Build the path to the map file on disk. - WCHAR tempPath[MAX_LONGPATH+1]; - if(!GetTempPathW(MAX_LONGPATH, tempPath)) - { - return; - } - - SString path; - path.Printf("%Sperf-%d.map", &tempPath, pid); + SString pathFile; + pathFile.Printf("%s/perf-%d.map", path, pid); // Open the map file for writing. - OpenFile(path); + OpenFile(pathFile); - m_PerfInfo = new PerfInfo(pid); + m_PerfInfo = new PerfInfo(pid, path); } // Construct a new map without a specified file name. @@ -117,8 +146,6 @@ PerfMap::PerfMap() // Initialize with no failures. m_ErrorEncountered = false; - - m_StubsMapped = 0; } // Clean-up resources. @@ -156,6 +183,11 @@ void PerfMap::WriteLine(SString& line) { STANDARD_VM_CONTRACT; + if (m_FileStream == nullptr || m_ErrorEncountered) + { + return; + } + EX_TRY { // Write the line. @@ -176,50 +208,9 @@ void PerfMap::WriteLine(SString& line) EX_CATCH{} EX_END_CATCH(SwallowAllExceptions); } -// Log a method to the map. -void PerfMap::LogMethod(MethodDesc * pMethod, PCODE pCode, size_t codeSize, const char *optimizationTier) -{ - CONTRACTL{ - THROWS; - GC_NOTRIGGER; - MODE_PREEMPTIVE; - PRECONDITION(pMethod != nullptr); - PRECONDITION(pCode != nullptr); - PRECONDITION(codeSize > 0); - } CONTRACTL_END; - - if (m_FileStream == nullptr || m_ErrorEncountered) - { - // A failure occurred, do not log. - return; - } - - // Logging failures should not cause any exceptions to flow upstream. - EX_TRY - { - // Get the full method signature. - SString name; - pMethod->GetFullMethodInfo(name); - - // Build the map file line. - if (optimizationTier != nullptr && s_ShowOptimizationTiers) - { - name.AppendPrintf("[%s]", optimizationTier); - } - SString line; - line.Printf(FMT_CODE_ADDR " %x %s\n", pCode, codeSize, name.GetUTF8()); - - // Write the line. - WriteLine(line); - PAL_PerfJitDump_LogMethod((void*)pCode, codeSize, name.GetUTF8(), nullptr, nullptr); - } - EX_CATCH{} EX_END_CATCH(SwallowAllExceptions); -} - - void PerfMap::LogImageLoad(PEAssembly * pPEAssembly) { - if (s_enabled) + if (s_enabled && s_Current != nullptr) { s_Current->LogImage(pPEAssembly); } @@ -258,6 +249,15 @@ void PerfMap::LogJITCompiledMethod(MethodDesc * pMethod, PCODE pCode, size_t cod { LIMITED_METHOD_CONTRACT; + CONTRACTL{ + THROWS; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + PRECONDITION(pMethod != nullptr); + PRECONDITION(pCode != nullptr); + PRECONDITION(codeSize > 0); + } CONTRACTL_END; + if (!s_enabled) { return; @@ -269,7 +269,31 @@ void PerfMap::LogJITCompiledMethod(MethodDesc * pMethod, PCODE pCode, size_t cod optimizationTier = PrepareCodeConfig::GetJitOptimizationTierStr(pConfig, pMethod); } - s_Current->LogMethod(pMethod, pCode, codeSize, optimizationTier); + // Logging failures should not cause any exceptions to flow upstream. + EX_TRY + { + // Get the full method signature. + SString name; + pMethod->GetFullMethodInfo(name); + + // Build the map file line. + if (optimizationTier != nullptr && s_ShowOptimizationTiers) + { + name.AppendPrintf("[%s]", optimizationTier); + } + + SString line; + line.Printf(FMT_CODE_ADDR " %x %s\n", pCode, codeSize, name.GetUTF8()); + + // Write the line. + if(s_Current != nullptr) + { + s_Current->WriteLine(line); + } + PAL_PerfJitDump_LogMethod((void*)pCode, codeSize, name.GetUTF8(), nullptr, nullptr); + } + EX_CATCH{} EX_END_CATCH(SwallowAllExceptions); + } // Log a pre-compiled method to the perfmap. @@ -326,7 +350,7 @@ void PerfMap::LogStubs(const char* stubType, const char* stubOwner, PCODE pCode, { LIMITED_METHOD_CONTRACT; - if (!s_enabled || s_Current->m_FileStream == nullptr) + if (!s_enabled) { return; } @@ -344,13 +368,15 @@ void PerfMap::LogStubs(const char* stubType, const char* stubOwner, PCODE pCode, } SString name; - // Build the map file line. - name.Printf("stub<%d> %s<%s>", ++(s_Current->m_StubsMapped), stubType, stubOwner); + name.Printf("stub<%d> %s<%s>", ++(s_StubsMapped), stubType, stubOwner); SString line; line.Printf(FMT_CODE_ADDR " %x %s\n", pCode, codeSize, name.GetUTF8()); // Write the line. - s_Current->WriteLine(line); + if(s_Current != nullptr) + { + s_Current->WriteLine(line); + } PAL_PerfJitDump_LogMethod((void*)pCode, codeSize, name.GetUTF8(), nullptr, nullptr); } EX_CATCH{} EX_END_CATCH(SwallowAllExceptions); @@ -407,6 +433,41 @@ NativeImagePerfMap::NativeImagePerfMap(Assembly * pAssembly, BSTR pDestPath) } } +void NativeImagePerfMap::LogMethod(MethodDesc * pMethod, PCODE pCode, size_t codeSize, const char *optimizationTier) +{ + CONTRACTL{ + THROWS; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + PRECONDITION(pMethod != nullptr); + PRECONDITION(pCode != nullptr); + PRECONDITION(codeSize > 0); + } CONTRACTL_END; + + // Logging failures should not cause any exceptions to flow upstream. + EX_TRY + { + // Get the full method signature. + SString name; + pMethod->GetFullMethodInfo(name); + + // Build the map file line. + if (optimizationTier != nullptr && s_ShowOptimizationTiers) + { + name.AppendPrintf("[%s]", optimizationTier); + } + SString line; + line.Printf(FMT_CODE_ADDR " %x %s\n", pCode, codeSize, name.GetUTF8()); + + if (s_Current != nullptr) + { + s_Current->WriteLine(line); + } + PAL_PerfJitDump_LogMethod((void*)pCode, codeSize, name.GetUTF8(), nullptr, nullptr); + } + EX_CATCH{} EX_END_CATCH(SwallowAllExceptions); +} + // Log data to the perfmap for the specified module. void NativeImagePerfMap::LogDataForModule(Module * pModule) { diff --git a/src/coreclr/vm/perfmap.h b/src/coreclr/vm/perfmap.h index 587a776e682760..c46827b80fc074 100644 --- a/src/coreclr/vm/perfmap.h +++ b/src/coreclr/vm/perfmap.h @@ -18,11 +18,8 @@ class PerfMap private: static Volatile s_enabled; - // The one and only PerfMap for the process. - static PerfMap * s_Current; - - // Indicates whether optimization tiers should be shown for methods in perf maps - static bool s_ShowOptimizationTiers; + // Set to true if an error is encountered when writing to the file. + static unsigned s_StubsMapped; // The file stream to write the map to. CFileStream * m_FileStream; @@ -33,16 +30,19 @@ class PerfMap // Set to true if an error is encountered when writing to the file. bool m_ErrorEncountered; - // Set to true if an error is encountered when writing to the file. - unsigned m_StubsMapped; - // Construct a new map for the specified pid. - PerfMap(int pid); + PerfMap(int pid, const char* path); - // Write a line to the map file. - void WriteLine(SString & line); + // Default to /tmp or use DOTNET_PerfMapJitDumpPath if set + static const char* InternalConstructPath(char *tmpBuf, int lenBuf); protected: + // Indicates whether optimization tiers should be shown for methods in perf maps + static bool s_ShowOptimizationTiers; + + // The one and only PerfMap for the process. + static PerfMap * s_Current; + // Construct a new map without a specified file name. // Used for offline creation of NGEN map files. PerfMap(); @@ -53,9 +53,6 @@ class PerfMap // Open the perf map file for write. void OpenFile(SString& path); - // Does the actual work to log a method to the map. - void LogMethod(MethodDesc * pMethod, PCODE pCode, size_t codeSize, const char *optimizationTier); - // Does the actual work to log an image void LogImage(PEAssembly * pPEAssembly); @@ -63,6 +60,9 @@ class PerfMap static void GetNativeImageSignature(PEAssembly * pPEAssembly, WCHAR * pwszSig, unsigned int nSigSize); public: + // Write a line to the map file. + void WriteLine(SString & line); + // Initialize the map for the current process. static void Initialize(); @@ -91,6 +91,9 @@ class NativeImagePerfMap : PerfMap // Specify the address format since it's now possible for 'perf script' to output file offsets or RVAs. bool m_EmitRVAs; + // Does the actual work to log a method to the map. + void LogMethod(MethodDesc * pMethod, PCODE pCode, size_t codeSize, const char *optimizationTier); + // Log a pre-compiled method to the map. void LogPreCompiledMethod(MethodDesc * pMethod, PCODE pCode, PEImageLayout *pLoadedLayout, const char *optimizationTier); diff --git a/src/coreclr/vm/proftoeeinterfaceimpl.cpp b/src/coreclr/vm/proftoeeinterfaceimpl.cpp index 79167f7dc706b5..bedb711b0fccbd 100644 --- a/src/coreclr/vm/proftoeeinterfaceimpl.cpp +++ b/src/coreclr/vm/proftoeeinterfaceimpl.cpp @@ -901,7 +901,7 @@ void GenerationTable::Refresh() // This is the table of generation bounds updated by the gc // and read by the profiler. -static GenerationTable *s_currentGenerationTable; +static GenerationTable *s_currentGenerationTable = nullptr; // This is just so we can assert there's a single writer #ifdef ENABLE_CONTRACTS @@ -931,7 +931,6 @@ void __stdcall UpdateGenerationBounds() // Notify the profiler of start of the collection if (CORProfilerTrackGC() || CORProfilerTrackBasicGC()) { - if (s_currentGenerationTable == nullptr) { EX_TRY @@ -965,7 +964,10 @@ void __stdcall ProfilerAddNewRegion(int generation, uint8_t* rangeStart, uint8_t #ifdef PROFILING_SUPPORTED if (CORProfilerTrackGC() || CORProfilerTrackBasicGC()) { - s_currentGenerationTable->AddRecord(generation, rangeStart, rangeEnd, rangeEndReserved); + if (s_currentGenerationTable != nullptr) + { + s_currentGenerationTable->AddRecord(generation, rangeStart, rangeEnd, rangeEndReserved); + } } #endif // PROFILING_SUPPORTED RETURN; @@ -4330,7 +4332,7 @@ HRESULT ProfToEEInterfaceImpl::GetILFunctionBody(ModuleID moduleId, PEAssembly *pPEAssembly = pModule->GetPEAssembly(); - if (!pPEAssembly->HasLoadedPEImage()) + if (!pPEAssembly->IsLoaded()) return (CORPROF_E_DATAINCOMPLETE); LPCBYTE pbMethod = NULL; @@ -4440,7 +4442,7 @@ HRESULT ProfToEEInterfaceImpl::GetILFunctionBodyAllocator(ModuleID modul Module * pModule = (Module *) moduleId; if (pModule->IsBeingUnloaded() || - !pModule->GetPEAssembly()->HasLoadedPEImage()) + !pModule->GetPEAssembly()->IsLoaded()) { return (CORPROF_E_DATAINCOMPLETE); } diff --git a/src/coreclr/vm/stackwalk.cpp b/src/coreclr/vm/stackwalk.cpp index 08faf72c69e3dd..6b6d8c10f29daf 100644 --- a/src/coreclr/vm/stackwalk.cpp +++ b/src/coreclr/vm/stackwalk.cpp @@ -709,6 +709,8 @@ PCODE Thread::VirtualUnwindNonLeafCallFrame(T_CONTEXT* pContext, KNONVOLATILE_CO return uControlPc; } +extern void* g_hostingApiReturnAddress; + // static UINT_PTR Thread::VirtualUnwindToFirstManagedCallFrame(T_CONTEXT* pContext) { @@ -751,8 +753,9 @@ UINT_PTR Thread::VirtualUnwindToFirstManagedCallFrame(T_CONTEXT* pContext) uControlPc = GetIP(pContext); - if (uControlPc == 0) + if ((uControlPc == 0) || (uControlPc == (PCODE)g_hostingApiReturnAddress)) { + uControlPc = 0; break; } #endif // !TARGET_UNIX diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 91b2f55d9ea33a..c86a6886515042 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -8383,7 +8383,7 @@ Thread::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) WRAPPER_NO_CONTRACT; DAC_ENUM_DTHIS(); - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2) { if (m_pDomain.IsValid()) { @@ -8467,7 +8467,7 @@ Thread::EnumMemoryRegionsWorker(CLRDataEnumMemoryFlags flags) DacGetThreadContext(this, &context); } - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2) { AppDomain::GetCurrentDomain()->EnumMemoryRegions(flags, true); } diff --git a/src/coreclr/vm/tieredcompilation.cpp b/src/coreclr/vm/tieredcompilation.cpp index 4e4da913e86641..cf93435a071603 100644 --- a/src/coreclr/vm/tieredcompilation.cpp +++ b/src/coreclr/vm/tieredcompilation.cpp @@ -593,10 +593,17 @@ bool TieredCompilationManager::TryDeactivateTieringDelay() continue; } + PCODE codeEntryPoint = activeCodeVersion.GetNativeCode(); + if (codeEntryPoint == NULL) + { + // The active IL/native code version has changed since the method was queued, and the currently active version + // doesn't have a code entry point yet + continue; + } + EX_TRY { - bool wasSet = - CallCountingManager::SetCodeEntryPoint(activeCodeVersion, activeCodeVersion.GetNativeCode(), false, nullptr); + bool wasSet = CallCountingManager::SetCodeEntryPoint(activeCodeVersion, codeEntryPoint, false, nullptr); _ASSERTE(wasSet); } EX_CATCH diff --git a/src/installer/pkg/THIRD-PARTY-NOTICES.TXT b/src/installer/pkg/THIRD-PARTY-NOTICES.TXT index d15ea798c9519a..4cb5719fa86113 100644 --- a/src/installer/pkg/THIRD-PARTY-NOTICES.TXT +++ b/src/installer/pkg/THIRD-PARTY-NOTICES.TXT @@ -72,7 +72,7 @@ https://github.com/madler/zlib https://zlib.net/zlib_license.html /* zlib.h -- interface of the 'zlib' general purpose compression library - version 1.2.12, March 27th, 2022 + version 1.2.13, October 13th, 2022 Copyright (C) 1995-2022 Jean-loup Gailly and Mark Adler @@ -1391,30 +1391,6 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -License notice for Newtonsoft.Json -=================================== - -The MIT License (MIT) - -Copyright (c) 2007 James Newton-King - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License notice for NuGet.Client ------------------------------- @@ -1500,3 +1476,165 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +License for Jb Evain +--------------------- + +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +License for sse4-strstr (https://github.com/WojciechMula/sse4-strstr) +-------------------------------------- + + Copyright (c) 2008-2016, Wojciech Muła + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License notice for amd/aocl-libm-ose +------------------------------- + +Copyright (C) 2008-2020 Advanced Micro Devices, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +License notice for code from The Practice of Programming +------------------------------- + +Copyright (C) 1999 Lucent Technologies + +Excerpted from 'The Practice of Programming +by Brian W. Kernighan and Rob Pike + +You may use this code for any purpose, as long as you leave the copyright notice and book citation attached. + +License notice for m-ou-se/floatconv +------------------------------- + +Copyright (c) 2020 Mara Bos +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License notice for MsQuic +-------------------------------------- + +Copyright (c) Microsoft Corporation. +Licensed under the MIT License. + +Available at +https://github.com/microsoft/msquic/blob/main/LICENSE + +--------------------------------------------------------- + +Newtonsoft.Json 13.0.1 - MIT + + +(c) 2008 VeriSign, Inc. +Copyright James Newton-King 2008 +Copyright (c) 2007 James Newton-King +Copyright (c) James Newton-King 2008 + +The MIT License (MIT) + +Copyright (c) 2007 James Newton-King + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Notice for Euclidean Affine Functions and Applications to Calendar +Algorithms +------------------------------- + +Aspects of Date/Time processing based on algorithm described in "Euclidean Affine Functions and Applications to Calendar +Algorithms", Cassio Neri and Lorenz Schneider. https://arxiv.org/pdf/2102.06959.pdf diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props index 0bb1a67b0320fb..07d77161002bcf 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -95,7 +95,6 @@ - @@ -206,13 +205,6 @@ - - - - - - - @@ -220,7 +212,6 @@ - diff --git a/src/installer/prepare-artifacts.proj b/src/installer/prepare-artifacts.proj index 595c8e49d575c1..f2b108bf4217a9 100644 --- a/src/installer/prepare-artifacts.proj +++ b/src/installer/prepare-artifacts.proj @@ -12,7 +12,7 @@ - + $(ArtifactsObjDir)TempWorkingDir\$([System.Guid]::NewGuid())\ - + $(ProductionVersion) $(ProductVersion) @@ -101,14 +101,14 @@ Lines="$(ProductVersionTxtContents)" Overwrite="true" Encoding="ASCII" /> - + - + @@ -133,7 +133,7 @@ - $(InstallersRelativePath)workloads/$(SdkBandVersion)/%(Filename)%(Extension) + $(InstallersRelativePath)workloads/%(Filename)%(Extension) true @@ -222,11 +222,13 @@ Include="$(DownloadDirectory)**\VS.Redist.Common.*.nupkg" Exclude="@(DownloadedSymbolNupkgFile)" /> - + + $(DownloadDirectory)*\workloads-vs\**\*.zip"/> @@ -276,7 +278,7 @@ - + Exe $(TestTargetRid) + + + + net6.0 + diff --git a/src/installer/tests/Assets/TestProjects/StartupHookWithDependency/StartupHookWithDependency.cs b/src/installer/tests/Assets/TestProjects/StartupHookWithDependency/StartupHookWithDependency.cs index 9178e26b84e473..dddc9580c3b747 100644 --- a/src/installer/tests/Assets/TestProjects/StartupHookWithDependency/StartupHookWithDependency.cs +++ b/src/installer/tests/Assets/TestProjects/StartupHookWithDependency/StartupHookWithDependency.cs @@ -13,5 +13,6 @@ public static void Initialize() // A small operation involving NewtonSoft.Json to ensure the assembly is loaded properly var t = typeof(Newtonsoft.Json.JsonReader); + System.Diagnostics.Trace.WriteLine(t); } } diff --git a/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs b/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs index ca97f1f8e01656..1c7e8683c4563e 100644 --- a/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs +++ b/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs @@ -322,7 +322,8 @@ public void AppHost_FrameworkDependent_GlobalLocation_Succeeds(bool useRegistere .Execute() .Should().Pass() .And.HaveStdOutContaining("Hello World") - .And.HaveStdOutContaining(sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion); + .And.HaveStdOutContaining(sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion) + .And.NotHaveStdErr(); // Verify running from within the working directory Command.Create(appExe) @@ -336,7 +337,8 @@ public void AppHost_FrameworkDependent_GlobalLocation_Succeeds(bool useRegistere .Execute() .Should().Pass() .And.HaveStdOutContaining("Hello World") - .And.HaveStdOutContaining(sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion); + .And.HaveStdOutContaining(sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion) + .And.NotHaveStdErr(); } } diff --git a/src/installer/tests/HostActivation.Tests/StartupHooks.cs b/src/installer/tests/HostActivation.Tests/StartupHooks.cs index 7f71bb83baa102..cbe7fd3e07c151 100644 --- a/src/installer/tests/HostActivation.Tests/StartupHooks.cs +++ b/src/installer/tests/HostActivation.Tests/StartupHooks.cs @@ -177,7 +177,8 @@ public void Muxer_activation_of_Empty_StartupHook_Variable_Succeeds() .CaptureStdErr() .Execute() .Should().Pass() - .And.HaveStdOutContaining("Hello World"); + .And.HaveStdOutContaining("Hello World") + .And.NotHaveStdErr(); } // Run the app with a startup hook assembly that depends on assemblies not on the TPA list diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleLegacy.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleLegacy.cs index 10cf437ccb3a48..71720994744c31 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleLegacy.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleLegacy.cs @@ -10,6 +10,7 @@ namespace Microsoft.NET.HostModel.Tests { + [SkipOnPlatform(TestPlatforms.OSX, "Not supported on OSX.")] public class BundleLegacy : IClassFixture { private SharedTestState sharedTestState; diff --git a/src/installer/tests/TestUtils/DotNetCli.cs b/src/installer/tests/TestUtils/DotNetCli.cs index 08e40aaf25e6c1..d7fcdabeb8a17f 100644 --- a/src/installer/tests/TestUtils/DotNetCli.cs +++ b/src/installer/tests/TestUtils/DotNetCli.cs @@ -53,6 +53,7 @@ public Command Exec(string command, params string[] args) newArgs.Insert(0, command); return Command.Create(DotnetExecutablePath, newArgs) + .EnvironmentVariable("DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER", "1") // https://github.com/dotnet/runtime/issues/74328 .EnvironmentVariable("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "1") .EnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0"); // Avoid looking at machine state by default } diff --git a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Ssl.cs b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Ssl.cs index ba9fbd297c93b5..addb8d540e43c1 100644 --- a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Ssl.cs +++ b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Ssl.cs @@ -55,16 +55,18 @@ private static unsafe partial int SSLStreamInitializeImpl( IntPtr managedContextHandle, delegate* unmanaged streamRead, delegate* unmanaged streamWrite, - int appBufferSize); + int appBufferSize, + [MarshalAs(UnmanagedType.LPUTF8Str)] string? peerHost); internal static unsafe void SSLStreamInitialize( SafeSslHandle sslHandle, bool isServer, IntPtr managedContextHandle, delegate* unmanaged streamRead, delegate* unmanaged streamWrite, - int appBufferSize) + int appBufferSize, + string? peerHost) { - int ret = SSLStreamInitializeImpl(sslHandle, isServer, managedContextHandle, streamRead, streamWrite, appBufferSize); + int ret = SSLStreamInitializeImpl(sslHandle, isServer, managedContextHandle, streamRead, streamWrite, appBufferSize, peerHost); if (ret != SUCCESS) throw new SslException(); } diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Libraries.cs b/src/libraries/Common/src/Interop/Browser/Interop.Libraries.cs index b28d723f0bfc9d..df7bdb4b890559 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Libraries.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Libraries.cs @@ -7,6 +7,5 @@ internal static partial class Libraries { // Shims internal const string SystemNative = "libSystem.Native"; - internal const string CryptoNative = "libSystem.Security.Cryptography.Native.Browser"; } } diff --git a/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SubtleCrypto.cs b/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SubtleCrypto.cs deleted file mode 100644 index 1e6ea5c2821faa..00000000000000 --- a/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SubtleCrypto.cs +++ /dev/null @@ -1,67 +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; - -internal static partial class Interop -{ - internal static partial class BrowserCrypto - { - // These values are also defined in the pal_crypto_webworker header file, and utilized in the dotnet-crypto-worker in the wasm runtime. - internal enum SimpleDigest - { - Sha1, - Sha256, - Sha384, - Sha512, - }; - - internal static readonly bool CanUseSubtleCrypto = CanUseSubtleCryptoImpl() == 1; - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_CanUseSubtleCryptoImpl")] - private static partial int CanUseSubtleCryptoImpl(); - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_SimpleDigestHash")] - internal static unsafe partial int SimpleDigestHash( - SimpleDigest hash, - byte* input_buffer, - int input_len, - byte* output_buffer, - int output_len); - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_Sign")] - internal static unsafe partial int Sign( - SimpleDigest hashAlgorithm, - byte* key_buffer, - int key_len, - byte* input_buffer, - int input_len, - byte* output_buffer, - int output_len); - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_EncryptDecrypt")] - internal static unsafe partial int EncryptDecrypt( - int encrypting, - byte* key_buffer, - int key_len, - byte* iv_buffer, - int iv_len, - byte* input_buffer, - int input_len, - byte* output_buffer, - int output_len); - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_DeriveBits")] - internal static unsafe partial int DeriveBits( - byte* password_buffer, - int password_len, - byte* salt_buffer, - int salt_len, - int iterations, - SimpleDigest hashAlgorithm, - byte* output_buffer, - int output_len); - } -} 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 8dd29a54221d03..ee9c2b75971888 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs @@ -19,10 +19,6 @@ internal static partial class @procfs private const string FileDescriptorDirectoryName = "/fd/"; private const string TaskDirectoryName = "/task/"; - internal const string SelfExeFilePath = RootPath + "self" + ExeFileName; - internal const string SelfCmdLineFilePath = RootPath + "self" + CmdLineFileName; - internal const string ProcStatFilePath = RootPath + "stat"; - internal struct ParsedStat { // Commented out fields are available in the stat data file but diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetBootTimeTicks.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetBootTimeTicks.cs new file mode 100644 index 00000000000000..d6c327578c2183 --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetBootTimeTicks.cs @@ -0,0 +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.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetBootTimeTicks")] + [SuppressGCTransition] + internal static partial long GetBootTimeTicks(); + } +} diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetGroupName.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetGroupName.cs index fadbf314e4d51f..f36935ae7f39ae 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetGroupName.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetGroupName.cs @@ -7,19 +7,26 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.IO; +using System.Diagnostics.CodeAnalysis; internal static partial class Interop { internal static partial class Sys { /// - /// Gets the group name associated to the specified group ID. + /// Tries to get the group name associated to the specified group ID. /// /// The group ID. - /// On success, return a string with the group name. On failure, throws an IOException. - internal static string GetGroupName(uint gid) => GetGroupNameInternal(gid) ?? throw GetIOException(GetLastErrorInfo()); + /// When this method returns true, gets the value of the group name associated with the specified id. On failure, it is null. + /// On success, returns true. On failure, returns false. + internal static bool TryGetGroupName(uint gid, [NotNullWhen(returnValue: true)] out string? groupName) + { + groupName = GetGroupName(gid); + return groupName != null; + } [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetGroupName", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] - private static unsafe partial string? GetGroupNameInternal(uint uid); + private static unsafe partial string? GetGroupName(uint uid); } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.UnixFileSystemTypes.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.UnixFileSystemTypes.cs index ce0dffd7372f85..23e733425748a5 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.UnixFileSystemTypes.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.UnixFileSystemTypes.cs @@ -155,7 +155,7 @@ internal static bool TryGetFileSystemType(SafeFileHandle fd, out UnixFileSystemT { uint fstatfsResult = GetFileSystemType(fd); fileSystemType = (UnixFileSystemTypes)fstatfsResult; - Debug.Assert(Enum.IsDefined(fileSystemType) || fstatfsResult == 0 || !OperatingSystem.IsLinux(), $"GetFileSystemType returned {fstatfsResult}"); + Debug.Assert(Enum.IsDefined(fileSystemType) || fstatfsResult == 0, $"GetFileSystemType returned {fstatfsResult}"); return fstatfsResult != 0; } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs index 826d39abc09e1f..510c51741737d9 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 @@ -203,18 +203,20 @@ internal static unsafe SafeSslContextHandle AllocateSslContext(SslAuthentication { if (sslAuthenticationOptions.IsServer) { - Ssl.SslCtxSetCaching(sslCtx, 1, s_cacheSize, null, null); + Span contextId = stackalloc byte[32]; + RandomNumberGenerator.Fill(contextId); + Ssl.SslCtxSetCaching(sslCtx, 1, s_cacheSize, contextId.Length, contextId, null, null); } else { - int result = Ssl.SslCtxSetCaching(sslCtx, 1, s_cacheSize, &NewSessionCallback, &RemoveSessionCallback); + int result = Ssl.SslCtxSetCaching(sslCtx, 1, s_cacheSize, 0, null, &NewSessionCallback, &RemoveSessionCallback); Debug.Assert(result == 1); sslCtx.EnableSessionCache(); } } else { - Ssl.SslCtxSetCaching(sslCtx, 0, -1, null, null); + Ssl.SslCtxSetCaching(sslCtx, 0, -1, 0, null, null, null); } if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0) @@ -304,7 +306,8 @@ internal static SafeSslHandle AllocateSslHandle(SslAuthenticationOptions sslAuth if (!Interop.Ssl.Capabilities.Tls13Supported || string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost) || sslAuthenticationOptions.CertificateContext != null || - sslAuthenticationOptions.CertSelectionDelegate != null) + sslAuthenticationOptions.ClientCertificates?.Count > 0 || + sslAuthenticationOptions.CertSelectionDelegate != null) { cacheSslContext = false; } @@ -394,6 +397,9 @@ internal static SafeSslHandle AllocateSslHandle(SslAuthenticationOptions sslAuth if (cacheSslContext && !string.IsNullOrEmpty(punyCode)) { sslCtxHandle.TrySetSession(sslHandle, punyCode); + bool ignored = false; + sslCtxHandle.DangerousAddRef(ref ignored); + sslHandle.SslContextHandle = sslCtxHandle; } // relevant to TLS 1.3 only: if user supplied a client cert or cert callback, @@ -675,6 +681,11 @@ private static unsafe int AlpnServerSelectCallback(IntPtr ssl, byte** outp, byte *outlen = 0; IntPtr sslData = Ssl.SslGetData(ssl); + if (sslData == IntPtr.Zero) + { + return Ssl.SSL_TLSEXT_ERR_ALERT_FATAL; + } + // reset application data to avoid dangling pointer. Ssl.SslSetData(ssl, IntPtr.Zero); 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 bea9f4625a4ac0..3a1ca5559a2129 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 @@ -335,6 +335,7 @@ internal sealed class SafeSslHandle : SafeDeleteSslContext private bool _handshakeCompleted; public GCHandle AlpnHandle; + public SafeSslContextHandle? SslContextHandle; public bool IsServer { @@ -419,6 +420,7 @@ protected override void Dispose(bool disposing) if (AlpnHandle.IsAllocated) { + Interop.Ssl.SslSetData(handle, IntPtr.Zero); AlpnHandle.Free(); } @@ -432,6 +434,8 @@ protected override bool ReleaseHandle() Disconnect(); } + SslContextHandle?.DangerousRelease(); + IntPtr h = handle; SetHandle(IntPtr.Zero); Interop.Ssl.SslDestroy(h); // will free the handles underlying _readBio and _writeBio diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs index 18a6b27f825e12..4ed5c1881328eb 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs @@ -33,7 +33,7 @@ internal static partial class Ssl internal static unsafe partial void SslCtxSetAlpnSelectCb(SafeSslContextHandle ctx, delegate* unmanaged callback, IntPtr arg); [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetCaching")] - internal static unsafe partial int SslCtxSetCaching(SafeSslContextHandle ctx, int mode, int cacheSize, delegate* unmanaged neewSessionCallback, delegate* unmanaged removeSessionCallback); + internal static unsafe partial int SslCtxSetCaching(SafeSslContextHandle ctx, int mode, int cacheSize, int contextIdLength, Span contextId, delegate* unmanaged neewSessionCallback, delegate* unmanaged removeSessionCallback); internal static bool AddExtraChainCertificates(SafeSslContextHandle ctx, X509Certificate2[] chain) { diff --git a/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.QueryServiceStatusEx.cs b/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.QueryServiceStatusEx.cs new file mode 100644 index 00000000000000..8c38dec4df8eb4 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.QueryServiceStatusEx.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 Microsoft.Win32.SafeHandles; +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Advapi32 + { + [StructLayout(LayoutKind.Sequential)] + internal struct SERVICE_STATUS_PROCESS + { + public int dwServiceType; + public int dwCurrentState; + public int dwControlsAccepted; + public int dwWin32ExitCode; + public int dwServiceSpecificExitCode; + public int dwCheckPoint; + public int dwWaitHint; + public int dwProcessId; + public int dwServiceFlags; + } + + private const int SC_STATUS_PROCESS_INFO = 0; + + [LibraryImport(Libraries.Advapi32, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static unsafe partial bool QueryServiceStatusEx(SafeServiceHandle serviceHandle, int InfoLevel, SERVICE_STATUS_PROCESS* pStatus, int cbBufSize, out int pcbBytesNeeded); + + internal static unsafe bool QueryServiceStatusEx(SafeServiceHandle serviceHandle, SERVICE_STATUS_PROCESS* pStatus) => QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, pStatus, sizeof(SERVICE_STATUS_PROCESS), out _); + } +} diff --git a/src/libraries/Common/src/Interop/Windows/Interop.Errors.cs b/src/libraries/Common/src/Interop/Windows/Interop.Errors.cs index cde3ae0ac197e8..c810603e6300a9 100644 --- a/src/libraries/Common/src/Interop/Windows/Interop.Errors.cs +++ b/src/libraries/Common/src/Interop/Windows/Interop.Errors.cs @@ -64,6 +64,8 @@ internal static partial class Errors internal const int ERROR_IO_PENDING = 0x3E5; internal const int ERROR_NO_TOKEN = 0x3f0; internal const int ERROR_SERVICE_DOES_NOT_EXIST = 0x424; + internal const int ERROR_EXCEPTION_IN_SERVICE = 0x428; + internal const int ERROR_PROCESS_ABORTED = 0x42B; internal const int ERROR_NO_UNICODE_TRANSLATION = 0x459; internal const int ERROR_DLL_INIT_FAILED = 0x45A; internal const int ERROR_COUNTER_TIMEOUT = 0x461; diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Threading.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Threading.cs index b1e9ed9a6be6ad..21ddf78f34ca5f 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Threading.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Threading.cs @@ -73,5 +73,9 @@ internal enum ThreadPriority : int [LibraryImport(Libraries.Kernel32)] [return:MarshalAs(UnmanagedType.Bool)] internal static partial bool SetThreadPriority(SafeWaitHandle hThread, int nPriority); + + [LibraryImport(Libraries.Kernel32, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool GetThreadIOPendingFlag(nint hThread, out BOOL lpIOIsPending); } } diff --git a/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtStatus.cs b/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtStatus.cs index 43508983609a55..ad48e8e74d52a4 100644 --- a/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtStatus.cs +++ b/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtStatus.cs @@ -5,6 +5,10 @@ internal static partial class Interop { internal static class StatusOptions { + // See the NT_SUCCESS macro in the Windows SDK, and + // https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/using-ntstatus-values + internal static bool NT_SUCCESS(uint ntStatus) => (int)ntStatus >= 0; + // Error codes from ntstatus.h internal const uint STATUS_SUCCESS = 0x00000000; internal const uint STATUS_SOME_NOT_MAPPED = 0x00000107; diff --git a/src/libraries/Common/src/Interop/Windows/WinMm/Interop.waveOutGetDevCaps.cs b/src/libraries/Common/src/Interop/Windows/WinMm/Interop.waveOutGetDevCaps.cs index 3c3e831fac98d4..a9ebcba2950c55 100644 --- a/src/libraries/Common/src/Interop/Windows/WinMm/Interop.waveOutGetDevCaps.cs +++ b/src/libraries/Common/src/Interop/Windows/WinMm/Interop.waveOutGetDevCaps.cs @@ -50,7 +50,9 @@ public Native(WAVEOUTCAPS managed) wMid = managed.wMid; wPid = managed.wPid; vDriverVersion = managed.vDriverVersion; - managed.szPname.CopyTo(MemoryMarshal.CreateSpan(ref szPname[0], szPnameLength)); + Span szPnameSpan = MemoryMarshal.CreateSpan(ref szPname[0], szPnameLength); + szPnameSpan.Clear(); + managed.szPname?.CopyTo(szPnameSpan); dwFormats = managed.dwFormats; wChannels = managed.wChannels; wReserved1 = managed.wReserved1; diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs index 03ab7371863e37..6ecbc1b35baaa7 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs @@ -215,7 +215,7 @@ private static ImmutableArray GetMatchingNodes( try { - recurse(compilationUnit, ref localAliases, ref seenNames, ref results, ref attributeTargets); + processCompilationUnit(compilationUnit, ref localAliases, ref seenNames, ref results, ref attributeTargets); if (results.Length == 0) return ImmutableArray.Empty; @@ -229,7 +229,21 @@ private static ImmutableArray GetMatchingNodes( seenNames.Dispose(); } - void recurse( + void processCompilationUnit( + SyntaxNode compilationUnit, + ref Aliases localAliases, + ref ValueListBuilder seenNames, + ref ValueListBuilder results, + ref ValueListBuilder attributeTargets) + { + cancellationToken.ThrowIfCancellationRequested(); + + syntaxHelper.AddAliases(compilationUnit, ref localAliases, global: false); + + processCompilationOrNamespaceMembers(compilationUnit, ref localAliases, ref seenNames, ref results, ref attributeTargets); + } + + void processCompilationOrNamespaceMembers( SyntaxNode node, ref Aliases localAliases, ref ValueListBuilder seenNames, @@ -238,70 +252,109 @@ void recurse( { cancellationToken.ThrowIfCancellationRequested(); - if (node is ICompilationUnitSyntax) + foreach (var child in node.ChildNodesAndTokens()) { - syntaxHelper.AddAliases(node, ref localAliases, global: false); - - recurseChildren(node, ref localAliases, ref seenNames, ref results, ref attributeTargets); + if (child.IsNode) + { + var childNode = child.AsNode()!; + if (syntaxHelper.IsAnyNamespaceBlock(childNode)) + processNamespaceBlock(childNode, ref localAliases, ref seenNames, ref results, ref attributeTargets); + else + processMember(childNode, ref localAliases, ref seenNames, ref results, ref attributeTargets); + } } - else if (syntaxHelper.IsAnyNamespaceBlock(node)) - { - var localAliasCount = localAliases.Length; - syntaxHelper.AddAliases(node, ref localAliases, global: false); + } + + void processNamespaceBlock( + SyntaxNode namespaceBlock, + ref Aliases localAliases, + ref ValueListBuilder seenNames, + ref ValueListBuilder results, + ref ValueListBuilder attributeTargets) + { + cancellationToken.ThrowIfCancellationRequested(); - recurseChildren(node, ref localAliases, ref seenNames, ref results, ref attributeTargets); + var localAliasCount = localAliases.Length; + syntaxHelper.AddAliases(namespaceBlock, ref localAliases, global: false); - // after recursing into this namespace, dump any local aliases we added from this namespace decl itself. - localAliases.Length = localAliasCount; - } - else if (syntaxHelper.IsAttributeList(node)) + processCompilationOrNamespaceMembers( + namespaceBlock, ref localAliases, ref seenNames, ref results, ref attributeTargets); + + // after recursing into this namespace, dump any local aliases we added from this namespace decl itself. + localAliases.Length = localAliasCount; + } + + void processMember( + SyntaxNode member, + ref Aliases localAliases, + ref ValueListBuilder seenNames, + ref ValueListBuilder results, + ref ValueListBuilder attributeTargets) + { + cancellationToken.ThrowIfCancellationRequested(); + + // nodes can be arbitrarily deep. Use an explicit stack over recursion to prevent a stack-overflow. + var nodeStack = new ValueListBuilder(Span.Empty); + nodeStack.Append(member); + + try { - foreach (var attribute in syntaxHelper.GetAttributesOfAttributeList(node)) + while (nodeStack.Length > 0) { - // Have to lookup both with the name in the attribute, as well as adding the 'Attribute' suffix. - // e.g. if there is [X] then we have to lookup with X and with XAttribute. - var simpleAttributeName = syntaxHelper.GetUnqualifiedIdentifierOfName( - syntaxHelper.GetNameOfAttribute(attribute)).ValueText; - if (matchesAttributeName(ref localAliases, ref seenNames, simpleAttributeName, withAttributeSuffix: false) || - matchesAttributeName(ref localAliases, ref seenNames, simpleAttributeName, withAttributeSuffix: true)) + var node = nodeStack.Pop(); + + if (syntaxHelper.IsAttributeList(node)) { - attributeTargets.Length = 0; - syntaxHelper.AddAttributeTargets(node, ref attributeTargets); + foreach (var attribute in syntaxHelper.GetAttributesOfAttributeList(node)) + { + // Have to lookup both with the name in the attribute, as well as adding the 'Attribute' suffix. + // e.g. if there is [X] then we have to lookup with X and with XAttribute. + var simpleAttributeName = syntaxHelper.GetUnqualifiedIdentifierOfName( + syntaxHelper.GetNameOfAttribute(attribute)).ValueText; + if (matchesAttributeName(ref localAliases, ref seenNames, simpleAttributeName, withAttributeSuffix: false) || + matchesAttributeName(ref localAliases, ref seenNames, simpleAttributeName, withAttributeSuffix: true)) + { + attributeTargets.Length = 0; + syntaxHelper.AddAttributeTargets(node, ref attributeTargets); + + foreach (var target in attributeTargets.AsSpan()) + { + if (predicate(target, cancellationToken)) + results.Append(target); + } + + break; + } + } - foreach (var target in attributeTargets.AsSpan()) + // attributes can't have attributes inside of them. so no need to recurse when we're done. + } + else + { + // For any other node, just keep recursing deeper to see if we can find an attribute. Note: we cannot + // terminate the search anywhere as attributes may be found on things like local functions, and that + // means having to dive deep into statements and expressions. + var childNodesAndTokens = node.ChildNodesAndTokens(); + + // Avoid performance issue in ChildSyntaxList when iterating the child list in reverse + // (see https://github.com/dotnet/roslyn/issues/66475) by iterating forward first to + // ensure child nodes are realized. + foreach (var childNode in childNodesAndTokens) { - if (predicate(target, cancellationToken)) - results.Append(target); } - return; + foreach (var child in childNodesAndTokens.Reverse()) + { + if (child.IsNode) + nodeStack.Append(child.AsNode()!); + } } - } - // attributes can't have attributes inside of them. so no need to recurse when we're done. - } - else - { - // For any other node, just keep recursing deeper to see if we can find an attribute. Note: we cannot - // terminate the search anywhere as attributes may be found on things like local functions, and that - // means having to dive deep into statements and expressions. - recurseChildren(node, ref localAliases, ref seenNames, ref results, ref attributeTargets); + } } - - return; - - void recurseChildren( - SyntaxNode node, - ref Aliases localAliases, - ref ValueListBuilder seenNames, - ref ValueListBuilder results, - ref ValueListBuilder attributeTargets) + finally { - foreach (var child in node.ChildNodesAndTokens()) - { - if (child.IsNode) - recurse(child.AsNode()!, ref localAliases, ref seenNames, ref results, ref attributeTargets); - } + nodeStack.Dispose(); } } diff --git a/src/libraries/Common/src/System/IO/PathInternal.Unix.cs b/src/libraries/Common/src/System/IO/PathInternal.Unix.cs index 5bb8ab93fd9b23..4914071a448717 100644 --- a/src/libraries/Common/src/System/IO/PathInternal.Unix.cs +++ b/src/libraries/Common/src/System/IO/PathInternal.Unix.cs @@ -17,6 +17,7 @@ internal static partial class PathInternal internal const string DirectorySeparatorCharAsString = "/"; internal const string ParentDirectoryPrefix = @"../"; internal const string DirectorySeparators = DirectorySeparatorCharAsString; + internal static ReadOnlySpan Utf8DirectorySeparators => "/"u8; internal static int GetRootLength(ReadOnlySpan path) { diff --git a/src/libraries/Common/src/System/IO/PathInternal.Windows.cs b/src/libraries/Common/src/System/IO/PathInternal.Windows.cs index 3bc5b493c37845..e0aff52855590c 100644 --- a/src/libraries/Common/src/System/IO/PathInternal.Windows.cs +++ b/src/libraries/Common/src/System/IO/PathInternal.Windows.cs @@ -55,6 +55,7 @@ internal static partial class PathInternal internal const string DevicePathPrefix = @"\\.\"; internal const string ParentDirectoryPrefix = @"..\"; internal const string DirectorySeparators = @"\/"; + internal static ReadOnlySpan Utf8DirectorySeparators => @"\/"u8; internal const int MaxShortPath = 260; internal const int MaxShortDirectoryPath = 248; diff --git a/src/libraries/Common/src/System/IO/Win32Marshal.cs b/src/libraries/Common/src/System/IO/Win32Marshal.cs index cbc75603c428b6..15e84ed97f5cb6 100644 --- a/src/libraries/Common/src/System/IO/Win32Marshal.cs +++ b/src/libraries/Common/src/System/IO/Win32Marshal.cs @@ -68,7 +68,9 @@ internal static Exception GetExceptionForWin32Error(int errorCode, string? path static string GetPInvokeErrorMessage(int errorCode) { -#if NET7_0_OR_GREATER + // Call Kernel32.GetMessage directly in CoreLib. It eliminates one level of indirection and it is necessary to + // produce correct error messages for CoreCLR Win32 PAL. +#if NET7_0_OR_GREATER && !SYSTEM_PRIVATE_CORELIB return Marshal.GetPInvokeErrorMessage(errorCode); #else return Interop.Kernel32.GetMessage(errorCode); diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Http2/Hpack/HPackDecoder.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Http2/Hpack/HPackDecoder.cs index cefb377f40c094..0f1a7e94d75c47 100644 --- a/src/libraries/Common/src/System/Net/Http/aspnetcore/Http2/Hpack/HPackDecoder.cs +++ b/src/libraries/Common/src/System/Net/Http/aspnetcore/Http2/Hpack/HPackDecoder.cs @@ -187,12 +187,11 @@ private void DecodeInternal(ReadOnlySpan data, IHttpStreamHeadersHandler h // will no longer be valid. if (_headerNameRange != null) { - EnsureStringCapacity(ref _headerNameOctets); + EnsureStringCapacity(ref _headerNameOctets, _headerNameLength); _headerName = _headerNameOctets; ReadOnlySpan headerBytes = data.Slice(_headerNameRange.GetValueOrDefault().start, _headerNameRange.GetValueOrDefault().length); headerBytes.CopyTo(_headerName); - _headerNameLength = headerBytes.Length; _headerNameRange = null; } } @@ -427,6 +426,7 @@ private void ParseHeaderName(ReadOnlySpan data, ref int currentIndex, IHtt { // Fast path. Store the range rather than copying. _headerNameRange = (start: currentIndex, count); + _headerNameLength = _stringLength; currentIndex += count; _state = State.HeaderValueLength; @@ -621,11 +621,12 @@ int Decode(ref byte[] dst) _state = nextState; } - private void EnsureStringCapacity(ref byte[] dst) + private void EnsureStringCapacity(ref byte[] dst, int stringLength = -1) { - if (dst.Length < _stringLength) + stringLength = stringLength >= 0 ? stringLength : _stringLength; + if (dst.Length < stringLength) { - dst = new byte[Math.Max(_stringLength, dst.Length * 2)]; + dst = new byte[Math.Max(stringLength, dst.Length * 2)]; } } diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Http3/QPack/QPackDecoder.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Http3/QPack/QPackDecoder.cs index 394cc1aee72a5f..608a2ae73acfd5 100644 --- a/src/libraries/Common/src/System/Net/Http/aspnetcore/Http3/QPack/QPackDecoder.cs +++ b/src/libraries/Common/src/System/Net/Http/aspnetcore/Http3/QPack/QPackDecoder.cs @@ -243,12 +243,11 @@ private void DecodeInternal(ReadOnlySpan data, IHttpStreamHeadersHandler h // will no longer be valid. if (_headerNameRange != null) { - EnsureStringCapacity(ref _headerNameOctets, _stringLength, existingLength: 0); + EnsureStringCapacity(ref _headerNameOctets, _headerNameLength, existingLength: 0); _headerName = _headerNameOctets; ReadOnlySpan headerBytes = data.Slice(_headerNameRange.GetValueOrDefault().start, _headerNameRange.GetValueOrDefault().length); headerBytes.CopyTo(_headerName); - _headerNameLength = headerBytes.Length; _headerNameRange = null; } } @@ -294,6 +293,7 @@ private void ParseHeaderName(ReadOnlySpan data, ref int currentIndex, IHtt { // Fast path. Store the range rather than copying. _headerNameRange = (start: currentIndex, count); + _headerNameLength = _stringLength; currentIndex += count; _state = State.HeaderValueLength; diff --git a/src/libraries/Common/src/System/Net/NetworkInformation/UnixCommandLinePing.cs b/src/libraries/Common/src/System/Net/NetworkInformation/UnixCommandLinePing.cs index c2fcca169d21dc..edf5372499334b 100644 --- a/src/libraries/Common/src/System/Net/NetworkInformation/UnixCommandLinePing.cs +++ b/src/libraries/Common/src/System/Net/NetworkInformation/UnixCommandLinePing.cs @@ -9,8 +9,8 @@ namespace System.Net.NetworkInformation { internal static class UnixCommandLinePing { - // Ubuntu has ping under /bin, OSX under /sbin, ArchLinux under /usr/bin. - private static readonly string[] s_binFolders = { "/bin/", "/sbin/", "/usr/bin/" }; + // Ubuntu has ping under /bin, OSX under /sbin, ArchLinux under /usr/bin, Android under /system/bin. + private static readonly string[] s_binFolders = { "/bin/", "/sbin/", "/usr/bin/", "/system/bin" }; private const string s_ipv4PingFile = "ping"; private const string s_ipv6PingFile = "ping6"; diff --git a/src/libraries/Common/src/System/Runtime/InteropServices/ComEventsMethod.cs b/src/libraries/Common/src/System/Runtime/InteropServices/ComEventsMethod.cs index 9da0795ec6e033..9ffd267a881a4d 100644 --- a/src/libraries/Common/src/System/Runtime/InteropServices/ComEventsMethod.cs +++ b/src/libraries/Common/src/System/Runtime/InteropServices/ComEventsMethod.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Threading; using System.Reflection; namespace System.Runtime.InteropServices @@ -99,7 +100,7 @@ private void PreProcessSignature() /// Since multicast delegate's built-in chaining supports only chaining instances of the same type, /// we need to complement this design by using an explicit linked list data structure. /// - private readonly List _delegateWrappers = new List(); + private DelegateWrapper[] _delegateWrappers = Array.Empty(); private readonly int _dispid; private ComEventsMethod? _next; @@ -154,48 +155,36 @@ public static ComEventsMethod Add(ComEventsMethod? methods, ComEventsMethod meth public bool Empty { - get - { - lock (_delegateWrappers) - { - return _delegateWrappers.Count == 0; - } - } + get => _delegateWrappers.Length == 0; } public void AddDelegate(Delegate d, bool wrapArgs = false) { - lock (_delegateWrappers) + DelegateWrapper[] wrappers, newWrappers; + do { - // Update an existing delegate wrapper - foreach (DelegateWrapper wrapper in _delegateWrappers) - { - if (wrapper.Delegate.GetType() == d.GetType() && wrapper.WrapArgs == wrapArgs) - { - wrapper.Delegate = Delegate.Combine(wrapper.Delegate, d); - return; - } - } - - var newWrapper = new DelegateWrapper(d, wrapArgs); - _delegateWrappers.Add(newWrapper); - } + wrappers = _delegateWrappers; + newWrappers = new DelegateWrapper[wrappers.Length + 1]; + wrappers.CopyTo(newWrappers, 0); + newWrappers[^1] = new DelegateWrapper(d, wrapArgs); + } while (!PublishNewWrappers(newWrappers, wrappers)); } public void RemoveDelegate(Delegate d, bool wrapArgs = false) { - lock (_delegateWrappers) + DelegateWrapper[] wrappers, newWrappers; + do { + wrappers = _delegateWrappers; + // Find delegate wrapper index int removeIdx = -1; - DelegateWrapper? wrapper = null; - for (int i = 0; i < _delegateWrappers.Count; i++) + for (int i = 0; i < wrappers.Length; i++) { - DelegateWrapper wrapperMaybe = _delegateWrappers[i]; - if (wrapperMaybe.Delegate.GetType() == d.GetType() && wrapperMaybe.WrapArgs == wrapArgs) + DelegateWrapper wrapperMaybe = wrappers[i]; + if (wrapperMaybe.Delegate == d && wrapperMaybe.WrapArgs == wrapArgs) { removeIdx = i; - wrapper = wrapperMaybe; break; } } @@ -206,51 +195,23 @@ public void RemoveDelegate(Delegate d, bool wrapArgs = false) return; } - // Update wrapper or remove from collection - Delegate? newDelegate = Delegate.Remove(wrapper!.Delegate, d); - if (newDelegate != null) - { - wrapper.Delegate = newDelegate; - } - else - { - _delegateWrappers.RemoveAt(removeIdx); - } - } + newWrappers = new DelegateWrapper[wrappers.Length - 1]; + wrappers.AsSpan(0, removeIdx).CopyTo(newWrappers); + wrappers.AsSpan(removeIdx + 1).CopyTo(newWrappers.AsSpan(removeIdx)); + } while (!PublishNewWrappers(newWrappers, wrappers)); } public void RemoveDelegates(Func condition) { - lock (_delegateWrappers) + DelegateWrapper[] wrappers, newWrappers; + do { - // Find delegate wrapper indexes. Iterate in reverse such that the list to remove is sorted by high to low index. - List toRemove = new List(); - for (int i = _delegateWrappers.Count - 1; i >= 0; i--) - { - DelegateWrapper wrapper = _delegateWrappers[i]; - Delegate[] invocationList = wrapper.Delegate.GetInvocationList(); - foreach (Delegate delegateMaybe in invocationList) - { - if (condition(delegateMaybe)) - { - Delegate? newDelegate = Delegate.Remove(wrapper!.Delegate, delegateMaybe); - if (newDelegate != null) - { - wrapper.Delegate = newDelegate; - } - else - { - toRemove.Add(i); - } - } - } - } - - foreach (int idx in toRemove) - { - _delegateWrappers.RemoveAt(idx); - } + wrappers = _delegateWrappers; + List tmp = new(wrappers); + tmp.RemoveAll(w => condition(w.Delegate)); + newWrappers = tmp.ToArray(); } + while (!PublishNewWrappers(newWrappers, wrappers)); } public object? Invoke(object[] args) @@ -258,15 +219,18 @@ public void RemoveDelegates(Func condition) Debug.Assert(!Empty); object? result = null; - lock (_delegateWrappers) + foreach (DelegateWrapper wrapper in _delegateWrappers) { - foreach (DelegateWrapper wrapper in _delegateWrappers) - { - result = wrapper.Invoke(args); - } + result = wrapper.Invoke(args); } return result; } + + // Attempt to update the member wrapper field + private bool PublishNewWrappers(DelegateWrapper[] newWrappers, DelegateWrapper[] currentMaybe) + { + return Interlocked.CompareExchange(ref _delegateWrappers, newWrappers, currentMaybe) == currentMaybe; + } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.manual.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.manual.cs index 85c0697114db3c..b934884323724d 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.manual.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.manual.cs @@ -2,12 +2,23 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Formats.Asn1; +using System.Security.Cryptography.Asn1.Pkcs7; using System.Security.Cryptography.Pkcs; +using Internal.Cryptography; + +#if BUILDING_PKCS +using Helpers = Internal.Cryptography.PkcsHelpers; +#endif namespace System.Security.Cryptography.Asn1.Pkcs12 { internal partial struct PfxAsn { + private const int MaxIterationWork = 300_000; + private static ReadOnlySpan EmptyPassword => ""; // don't use ReadOnlySpan.Empty because it will get confused with default. + private static ReadOnlySpan NullPassword => default; + internal bool VerifyMac( ReadOnlySpan macPassword, ReadOnlySpan authSafeContents) @@ -84,5 +95,257 @@ internal bool VerifyMac( MacData.Value.Mac.Digest.Span); } } + + internal ulong CountTotalIterations() + { + checked + { + ulong count = 0; + + // RFC 7292 section 4.1: + // the contentType field of authSafe shall be of type data + // or signedData. The content field of the authSafe shall, either + // directly (data case) or indirectly (signedData case), contain a BER- + // encoded value of type AuthenticatedSafe. + // We don't support authSafe that is signedData, so enforce that it's just data. + if (AuthSafe.ContentType != Oids.Pkcs7Data) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + ReadOnlyMemory authSafeContents = Helpers.DecodeOctetStringAsMemory(AuthSafe.Content); + AsnValueReader outerAuthSafe = new AsnValueReader(authSafeContents.Span, AsnEncodingRules.BER); // RFC 7292 PDU says BER + AsnValueReader authSafeReader = outerAuthSafe.ReadSequence(); + outerAuthSafe.ThrowIfNotEmpty(); + + bool hasSeenEncryptedInfo = false; + + while (authSafeReader.HasData) + { + ContentInfoAsn.Decode(ref authSafeReader, authSafeContents, out ContentInfoAsn contentInfo); + + ReadOnlyMemory contentData; + ArraySegment? rentedData = null; + + try + { + if (contentInfo.ContentType != Oids.Pkcs7Data) + { + if (contentInfo.ContentType == Oids.Pkcs7Encrypted) + { + if (hasSeenEncryptedInfo) + { + // We will process at most one encryptedData ContentInfo. This is the most typical scenario where + // certificates are stored in an encryptedData ContentInfo, and keys are shrouded in a data ContentInfo. + throw new CryptographicException(SR.Cryptography_X509_PfxWithoutPassword); + } + + ArraySegment content = DecryptContentInfo(contentInfo, out uint iterations); + contentData = content; + rentedData = content; + hasSeenEncryptedInfo = true; + count += iterations; + } + else + { + // Not a common scenario. It's not data or encryptedData, so they need to go through the + // regular PKCS12 loader. + throw new CryptographicException(SR.Cryptography_X509_PfxWithoutPassword); + } + } + else + { + contentData = Helpers.DecodeOctetStringAsMemory(contentInfo.Content); + } + + AsnValueReader outerSafeBag = new AsnValueReader(contentData.Span, AsnEncodingRules.BER); + AsnValueReader safeBagReader = outerSafeBag.ReadSequence(); + outerSafeBag.ThrowIfNotEmpty(); + + while (safeBagReader.HasData) + { + SafeBagAsn.Decode(ref safeBagReader, contentData, out SafeBagAsn bag); + + // We only need to count iterations on PKCS8ShroudedKeyBag. + // * KeyBag is PKCS#8 PrivateKeyInfo and doesn't do iterations. + // * CertBag, either for x509Certificate or sdsiCertificate don't do iterations. + // * CRLBag doesn't do iterations. + // * SecretBag doesn't do iteations. + // * Nested SafeContents _can_ do iterations, but Windows ignores it. So we will ignore it too. + if (bag.BagId == Oids.Pkcs12ShroudedKeyBag) + { + AsnValueReader pkcs8ShroudedKeyReader = new AsnValueReader(bag.BagValue.Span, AsnEncodingRules.BER); + EncryptedPrivateKeyInfoAsn.Decode( + ref pkcs8ShroudedKeyReader, + bag.BagValue, + out EncryptedPrivateKeyInfoAsn epki); + + count += IterationsFromParameters(epki.EncryptionAlgorithm); + } + } + } + finally + { + if (rentedData.HasValue) + { + CryptoPool.Return(rentedData.Value); + } + } + } + + if (MacData.HasValue) + { + if (MacData.Value.IterationCount < 0) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + count += (uint)MacData.Value.IterationCount; + } + + return count; + } + } + + private static ArraySegment DecryptContentInfo(ContentInfoAsn contentInfo, out uint iterations) + { + EncryptedDataAsn encryptedData = EncryptedDataAsn.Decode(contentInfo.Content, AsnEncodingRules.BER); + + if (encryptedData.Version != 0 && encryptedData.Version != 2) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + // The encrypted contentInfo can only wrap a PKCS7 data. + if (encryptedData.EncryptedContentInfo.ContentType != Oids.Pkcs7Data) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + if (!encryptedData.EncryptedContentInfo.EncryptedContent.HasValue) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + iterations = IterationsFromParameters(encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm); + + // This encryptData is encrypted with more rounds than we are willing to process. Bail out of the whole thing. + if (iterations > MaxIterationWork) + { + throw new CryptographicException(SR.Cryptography_X509_PfxWithoutPassword); + } + + int encryptedValueLength = encryptedData.EncryptedContentInfo.EncryptedContent.Value.Length; + byte[] destination = CryptoPool.Rent(encryptedValueLength); + int written = 0; + + try + { + try + { + written = PasswordBasedEncryption.Decrypt( + in encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm, + EmptyPassword, + default, + encryptedData.EncryptedContentInfo.EncryptedContent.Value.Span, + destination); + + // When padding happens to be as expected (false-positive), we can detect gibberish and prevent unexpected failures later + // This extra check makes it so it's very unlikely we'll end up with false positive. + AsnValueReader outerSafeBag = new AsnValueReader(destination.AsSpan(0, written), AsnEncodingRules.BER); + AsnValueReader safeBagReader = outerSafeBag.ReadSequence(); + outerSafeBag.ThrowIfNotEmpty(); + } + catch + { + // If empty password didn't work, try null password. + written = PasswordBasedEncryption.Decrypt( + in encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm, + NullPassword, + default, + encryptedData.EncryptedContentInfo.EncryptedContent.Value.Span, + destination); + + // When padding happens to be as expected (false-positive), we can detect gibberish and prevent unexpected failures later + // This extra check makes it so it's very unlikely we'll end up with false positive. + AsnValueReader outerSafeBag = new AsnValueReader(destination.AsSpan(0, written), AsnEncodingRules.BER); + AsnValueReader safeBagReader = outerSafeBag.ReadSequence(); + outerSafeBag.ThrowIfNotEmpty(); + } + } + finally + { + if (written == 0) + { + // This means the decryption operation failed and destination could contain + // partial data. Clear it to be hygienic. + CryptographicOperations.ZeroMemory(destination); + } + } + + return new ArraySegment(destination, 0, written); + } + + private static uint IterationsFromParameters(in AlgorithmIdentifierAsn algorithmIdentifier) + { + switch (algorithmIdentifier.Algorithm) + { + case Oids.PasswordBasedEncryptionScheme2: + if (!algorithmIdentifier.Parameters.HasValue) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + PBES2Params pbes2Params = PBES2Params.Decode(algorithmIdentifier.Parameters.Value, AsnEncodingRules.BER); + + // PBES2 only defines PKBDF2 for now. See RFC 8018 A.4 + if (pbes2Params.KeyDerivationFunc.Algorithm != Oids.Pbkdf2) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + if (!pbes2Params.KeyDerivationFunc.Parameters.HasValue) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + Pbkdf2Params pbkdf2Params = Pbkdf2Params.Decode(pbes2Params.KeyDerivationFunc.Parameters.Value, AsnEncodingRules.BER); + + if (pbkdf2Params.IterationCount < 0) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + return (uint)pbkdf2Params.IterationCount; + + // PBES1 + case Oids.PbeWithMD5AndDESCBC: + case Oids.PbeWithMD5AndRC2CBC: + case Oids.PbeWithSha1AndDESCBC: + case Oids.PbeWithSha1AndRC2CBC: + case Oids.Pkcs12PbeWithShaAnd3Key3Des: + case Oids.Pkcs12PbeWithShaAnd2Key3Des: + case Oids.Pkcs12PbeWithShaAnd128BitRC2: + case Oids.Pkcs12PbeWithShaAnd40BitRC2: + if (!algorithmIdentifier.Parameters.HasValue) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + PBEParameter pbeParameters = PBEParameter.Decode( + algorithmIdentifier.Parameters.Value, + AsnEncodingRules.BER); + + if (pbeParameters.IterationCount < 0) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + return (uint)pbeParameters.IterationCount; + + default: + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/Helpers.cs b/src/libraries/Common/src/System/Security/Cryptography/Helpers.cs index 685a7e1beed628..b1dc4d1de9a04c 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Helpers.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Helpers.cs @@ -11,7 +11,7 @@ namespace Internal.Cryptography internal static partial class Helpers { [UnsupportedOSPlatformGuard("browser")] - internal static bool HasNonAesSymmetricEncryption => + internal static bool HasSymmetricEncryption { get; } = #if NETCOREAPP !OperatingSystem.IsBrowser(); #else diff --git a/src/libraries/Common/src/System/Security/Cryptography/KdfWorkLimiter.cs b/src/libraries/Common/src/System/Security/Cryptography/KdfWorkLimiter.cs new file mode 100644 index 00000000000000..7500212fe27d9c --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/KdfWorkLimiter.cs @@ -0,0 +1,86 @@ +// 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; + +namespace System.Security.Cryptography +{ + // Places KDF work limits on the current thread. + internal static class KdfWorkLimiter + { + [ThreadStatic] + private static State? t_state; + + // Entry point: sets the iteration limit to a new value. + internal static void SetIterationLimit(ulong workLimit) + { + Debug.Assert(t_state == null, "This method is not intended to be called recursively."); + State state = new State(); + state.RemainingAllowedWork = workLimit; + t_state = state; + } + + internal static bool WasWorkLimitExceeded() + { + Debug.Assert(t_state != null, "This method should only be called within a protected block."); + return t_state.WorkLimitWasExceeded; + } + + // Removes any iteration limit on the current thread. + internal static void ResetIterationLimit() + { + t_state = null; + } + + // Records that we're about to perform some amount of work. + // Overflows if the work count is exceeded. + internal static void RecordIterations(int workCount) + { + RecordIterations((long)workCount); + } + + // Records that we're about to perform some amount of work. + // Overflows if the work count is exceeded. + internal static void RecordIterations(long workCount) + { + State? state = t_state; + if (state == null) + { + return; + } + + bool success = false; + + if (workCount < 0) + { + throw new CryptographicException(); + } + + try + { + if (!state.WorkLimitWasExceeded) + { + state.RemainingAllowedWork = checked(state.RemainingAllowedWork - (ulong)workCount); + success = true; + } + } + finally + { + // If for any reason we failed, mark the thread as "no further work allowed" and + // normalize to CryptographicException. + if (!success) + { + state.RemainingAllowedWork = 0; + state.WorkLimitWasExceeded = true; + throw new CryptographicException(); + } + } + } + + private sealed class State + { + internal ulong RemainingAllowedWork; + internal bool WorkLimitWasExceeded; + } + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs b/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs index 185eb3fcf2b6b7..4bf056ffd5f4f9 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs @@ -74,20 +74,7 @@ internal static unsafe int Decrypt( { Debug.Assert(destination.Length >= encryptedData.Length); - // Don't check that algorithmIdentifier.Parameters is set here. - // Maybe some future PBES3 will have one with a default. - - if (algorithmIdentifier.Algorithm == Oids.PasswordBasedEncryptionScheme2) - { - return Pbes2Decrypt( - algorithmIdentifier.Parameters, - password, - passwordBytes, - encryptedData, - destination); - } - - if (!Helpers.HasNonAesSymmetricEncryption) + if (!Helpers.HasSymmetricEncryption) { throw new CryptographicException( SR.Format( @@ -95,8 +82,11 @@ internal static unsafe int Decrypt( algorithmIdentifier.Algorithm)); } + // Don't check that algorithmIdentifier.Parameters is set here. + // Maybe some future PBES3 will have one with a default. + HashAlgorithmName digestAlgorithmName; - SymmetricAlgorithm cipher; + SymmetricAlgorithm? cipher = null; bool pkcs12 = false; @@ -141,6 +131,13 @@ internal static unsafe int Decrypt( cipher.KeySize = 40; pkcs12 = true; break; + case Oids.PasswordBasedEncryptionScheme2: + return Pbes2Decrypt( + algorithmIdentifier.Parameters, + password, + passwordBytes, + encryptedData, + destination); default: throw new CryptographicException( SR.Format( @@ -149,6 +146,7 @@ internal static unsafe int Decrypt( } Debug.Assert(digestAlgorithmName.Name != null); + Debug.Assert(cipher != null); using (cipher) { @@ -239,6 +237,14 @@ internal static void InitiateEncryption( { Debug.Assert(pbeParameters != null); + if (!Helpers.HasSymmetricEncryption) + { + throw new CryptographicException( + SR.Format( + SR.Cryptography_UnknownAlgorithmIdentifier, + pbeParameters.EncryptionAlgorithm)); + } + isPkcs12 = false; switch (pbeParameters.EncryptionAlgorithm) @@ -258,7 +264,7 @@ internal static void InitiateEncryption( cipher.KeySize = 256; encryptionAlgorithmOid = Oids.Aes256Cbc; break; - case PbeEncryptionAlgorithm.TripleDes3KeyPkcs12 when Helpers.HasNonAesSymmetricEncryption: + case PbeEncryptionAlgorithm.TripleDes3KeyPkcs12: cipher = TripleDES.Create(); cipher.KeySize = 192; encryptionAlgorithmOid = Oids.Pkcs12PbeWithShaAnd3Key3Des; @@ -387,6 +393,7 @@ internal static unsafe int Encrypt( Debug.Assert(pwdTmpBytes!.Length == 0); } + KdfWorkLimiter.RecordIterations(iterationCount); using (var pbkdf2 = new Rfc2898DeriveBytes(pwdTmpBytes, salt.ToArray(), iterationCount, prf)) { derivedKey = pbkdf2.GetBytes(keySizeBytes); @@ -566,6 +573,12 @@ private static SymmetricAlgorithm OpenCipher( { string? algId = encryptionScheme.Algorithm; + if (!Helpers.HasSymmetricEncryption) + { + throw new CryptographicException( + SR.Format(SR.Cryptography_AlgorithmNotSupported, algId)); + } + if (algId == Oids.Aes128Cbc || algId == Oids.Aes192Cbc || algId == Oids.Aes256Cbc) @@ -604,12 +617,6 @@ private static SymmetricAlgorithm OpenCipher( return aes; } - if (!Helpers.HasNonAesSymmetricEncryption) - { - throw new CryptographicException( - SR.Format(SR.Cryptography_AlgorithmNotSupported, algId)); - } - if (algId == Oids.TripleDesCbc) { // https://tools.ietf.org/html/rfc8018#appendix-B.2.2 diff --git a/src/libraries/Common/src/System/Security/Cryptography/Pkcs12Kdf.cs b/src/libraries/Common/src/System/Security/Cryptography/Pkcs12Kdf.cs index 1bd7c53bb2b44a..8e482b931c7646 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Pkcs12Kdf.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Pkcs12Kdf.cs @@ -147,6 +147,7 @@ private static void Derive( I = IRented.AsSpan(0, ILen); } + KdfWorkLimiter.RecordIterations(iterationCount); IncrementalHash hash = IncrementalHash.CreateHash(hashAlgorithm); try diff --git a/src/libraries/System.Formats.Tar/tests/WrappedStream.cs b/src/libraries/Common/tests/System/IO/WrappedStream.cs similarity index 98% rename from src/libraries/System.Formats.Tar/tests/WrappedStream.cs rename to src/libraries/Common/tests/System/IO/WrappedStream.cs index 5697f7c09ba554..71be0b98c9a198 100644 --- a/src/libraries/System.Formats.Tar/tests/WrappedStream.cs +++ b/src/libraries/Common/tests/System/IO/WrappedStream.cs @@ -1,9 +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.IO; - -namespace System.Formats.Tar +namespace System.IO { public class WrappedStream : Stream { diff --git a/src/libraries/Common/tests/System/Net/Capability.Security.Unix.cs b/src/libraries/Common/tests/System/Net/Capability.Security.Unix.cs index 3e08ee08a837ab..f62d68fbd28627 100644 --- a/src/libraries/Common/tests/System/Net/Capability.Security.Unix.cs +++ b/src/libraries/Common/tests/System/Net/Capability.Security.Unix.cs @@ -9,7 +9,7 @@ public static bool IsNtlmInstalled() { // GSS on Linux does not work with OpenSSL 3.0. Fix was submitted to gss-ntlm but it will take a while to make to // all supported distributions. The second part of the check should be removed when it does. - return Interop.NetSecurityNative.IsNtlmInstalled() && (!PlatformDetection.IsOpenSslSupported || PlatformDetection.OpenSslVersion.Major < 3); + return Interop.NetSecurityNative.IsNtlmInstalled() && (!PlatformDetection.IsOpenSslSupported || PlatformDetection.OpenSslVersion.Major < 3) && !PlatformDetection.IsRedHatFamily7; } } } diff --git a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackConnection.cs b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackConnection.cs index 31a0dd7893669a..ec5c7a022c65a6 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackConnection.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackConnection.cs @@ -14,7 +14,7 @@ namespace System.Net.Test.Common { - internal sealed class Http3LoopbackConnection : GenericLoopbackConnection + public sealed class Http3LoopbackConnection : GenericLoopbackConnection { public const long H3_NO_ERROR = 0x100; public const long H3_GENERAL_PROTOCOL_ERROR = 0x101; @@ -188,11 +188,11 @@ public async Task AcceptRequestStreamAsync() return (controlStream, requestStream); } - public async Task EstablishControlStreamAsync() + public async Task EstablishControlStreamAsync(SettingsEntry[] settingsEntries) { _outboundControlStream = await OpenUnidirectionalStreamAsync(); await _outboundControlStream.SendUnidirectionalStreamTypeAsync(Http3LoopbackStream.ControlStream); - await _outboundControlStream.SendSettingsFrameAsync(); + await _outboundControlStream.SendSettingsFrameAsync(settingsEntries); } public override async Task ReadRequestBodyAsync() diff --git a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs index 187490932cfa09..0c0abfcb094b45 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs @@ -66,12 +66,12 @@ public override void Dispose() _cert.Dispose(); } - private async Task EstablishHttp3ConnectionAsync() + private async Task EstablishHttp3ConnectionAsync(params SettingsEntry[] settingsEntries) { QuicConnection con = await _listener.AcceptConnectionAsync().ConfigureAwait(false); Http3LoopbackConnection connection = new Http3LoopbackConnection(con); - await connection.EstablishControlStreamAsync(); + await connection.EstablishControlStreamAsync(settingsEntries); return connection; } @@ -80,6 +80,11 @@ public override async Task EstablishGenericConnection return await EstablishHttp3ConnectionAsync(); } + public Task EstablishConnectionAsync(params SettingsEntry[] settingsEntries) + { + return EstablishHttp3ConnectionAsync(settingsEntries); + } + public override async Task AcceptConnectionAsync(Func funcAsync) { await using Http3LoopbackConnection con = await EstablishHttp3ConnectionAsync().ConfigureAwait(false); diff --git a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs index 17c3d8e8428325..14e4a43e66de18 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs @@ -14,8 +14,7 @@ namespace System.Net.Test.Common { - - internal sealed class Http3LoopbackStream : IAsyncDisposable + public sealed class Http3LoopbackStream : IAsyncDisposable { private const int MaximumVarIntBytes = 8; private const long VarIntMax = (1L << 62) - 1; @@ -58,18 +57,16 @@ public async Task SendUnidirectionalStreamTypeAsync(long streamType) await _stream.WriteAsync(buffer.AsMemory(0, bytesWritten)).ConfigureAwait(false); } - public async Task SendSettingsFrameAsync(ICollection<(long settingId, long settingValue)> settings = null) + public async Task SendSettingsFrameAsync(SettingsEntry[] settingsEntries) { - settings ??= Array.Empty<(long settingId, long settingValue)>(); - - var buffer = new byte[settings.Count * MaximumVarIntBytes * 2]; + var buffer = new byte[settingsEntries.Length * MaximumVarIntBytes * 2]; int bytesWritten = 0; - foreach ((long settingId, long settingValue) in settings) + foreach (SettingsEntry setting in settingsEntries) { - bytesWritten += EncodeHttpInteger(settingId, buffer.AsSpan(bytesWritten)); - bytesWritten += EncodeHttpInteger(settingValue, buffer.AsSpan(bytesWritten)); + bytesWritten += EncodeHttpInteger((int)setting.SettingId, buffer.AsSpan(bytesWritten)); + bytesWritten += EncodeHttpInteger(setting.Value, buffer.AsSpan(bytesWritten)); } await SendFrameAsync(SettingsFrame, buffer.AsMemory(0, bytesWritten)).ConfigureAwait(false); diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index daa55476615b3a..19f059a55158d3 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -984,13 +984,14 @@ await connection.WriteStringAsync( } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - [InlineData(null, false)] + [InlineData(true, true, true)] + [InlineData(true, true, false)] + [InlineData(true, false, false)] + [InlineData(false, true, false)] + [InlineData(false, false, false)] + [InlineData(null, false, false)] [ActiveIssue("https://github.com/dotnet/runtime/issues/65429", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJS))] - public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(bool? chunked, bool enableWasmStreaming) + public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(bool? chunked, bool enableWasmStreaming, bool slowChunks) { if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value) { @@ -1003,6 +1004,13 @@ public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(boo return; } + if (enableWasmStreaming && !PlatformDetection.IsBrowser) + { + // enableWasmStreaming makes only sense on Browser platform + return; + } + + var tcs = new TaskCompletionSource(); await LoopbackServerFactory.CreateClientAndServerAsync(async uri => { var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion }; @@ -1079,11 +1087,21 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => // Various forms of reading var buffer = new byte[1]; + var buffer2 = new byte[2]; if (PlatformDetection.IsBrowser) { #if !NETFRAMEWORK - Assert.Equal('h', await responseStream.ReadByteAsync()); + if(slowChunks) + { + Assert.Equal(1, await responseStream.ReadAsync(new Memory(buffer2))); + Assert.Equal((byte)'h', buffer2[0]); + tcs.SetResult(true); + } + else + { + Assert.Equal('h', await responseStream.ReadByteAsync()); + } Assert.Equal('e', await responseStream.ReadByteAsync()); Assert.Equal(1, await responseStream.ReadAsync(new Memory(buffer))); Assert.Equal((byte)'l', buffer[0]); @@ -1184,7 +1202,18 @@ await server.AcceptConnectionAsync(async connection => { case true: await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Transfer-Encoding", "chunked") }, isFinal: false); - await connection.SendResponseBodyAsync("3\r\nhel\r\n8\r\nlo world\r\n0\r\n\r\n"); + if(PlatformDetection.IsBrowser && slowChunks) + { + await connection.SendResponseBodyAsync("1\r\nh\r\n", false); + await tcs.Task; + await connection.SendResponseBodyAsync("2\r\nel\r\n", false); + await connection.SendResponseBodyAsync("8\r\nlo world\r\n", false); + await connection.SendResponseBodyAsync("0\r\n\r\n", true); + } + else + { + await connection.SendResponseBodyAsync("3\r\nhel\r\n8\r\nlo world\r\n0\r\n\r\n"); + } break; case false: @@ -1295,6 +1324,80 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => server => server.AcceptConnectionSendResponseAndCloseAsync()); } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/65429", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJS))] + public async Task ReadAsStreamAsync_StreamingCancellation() + { + var tcs = new TaskCompletionSource(); + var tcs2 = new TaskCompletionSource(); + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion }; +#if !NETFRAMEWORK + request.Options.Set(new HttpRequestOptionsKey("WebAssemblyEnableStreamingResponse"), true); +#endif + + var cts = new CancellationTokenSource(); + using (var client = new HttpMessageInvoker(CreateHttpClientHandler())) + using (HttpResponseMessage response = await client.SendAsync(TestAsync, request, CancellationToken.None)) + { + using (Stream responseStream = await response.Content.ReadAsStreamAsync(TestAsync)) + { + var buffer = new byte[1]; +#if !NETFRAMEWORK + Assert.Equal(1, await responseStream.ReadAsync(new Memory(buffer))); + Assert.Equal((byte)'h', buffer[0]); + var sizePromise = responseStream.ReadAsync(new Memory(buffer), cts.Token); + await tcs2.Task; // wait for the request and response header to be sent + cts.Cancel(); + await Assert.ThrowsAsync(async () => await sizePromise); + tcs.SetResult(true); +#endif + } + } + }, async server => + { + await server.AcceptConnectionAsync(async connection => + { + await connection.ReadRequestDataAsync(); + await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Transfer-Encoding", "chunked") }, isFinal: false); + await connection.SendResponseBodyAsync("1\r\nh\r\n", false); + tcs2.SetResult(true); + await tcs.Task; + }); + }); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))] + public async Task ReadAsStreamAsync_Cancellation() + { + var tcs = new TaskCompletionSource(); + var tcs2 = new TaskCompletionSource(); + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion }; + var cts = new CancellationTokenSource(); + using (var client = new HttpMessageInvoker(CreateHttpClientHandler())) + { + var responsePromise = client.SendAsync(TestAsync, request, cts.Token); + await tcs2.Task; // wait for the request to be sent + cts.Cancel(); + await Assert.ThrowsAsync(async () => await responsePromise); + tcs.SetResult(true); + } + }, async server => + { + await server.AcceptConnectionAsync(async connection => + { + await connection.ReadRequestDataAsync(); + tcs2.SetResult(true); + await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Transfer-Encoding", "chunked") }, isFinal: false); + await connection.SendResponseBodyAsync("1\r\nh\r\n", false); + await tcs.Task; + }); + }); + } + [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/58812", TestPlatforms.Browser)] public async Task Dispose_DisposingHandlerCancelsActiveOperationsWithoutResponses() diff --git a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs index c559ffe762c008..980c88b6587df4 100644 --- a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs @@ -726,7 +726,7 @@ private async Task> ReadRequestHeaderBytesAsync() public async Task WriteStringAsync(string s) { byte[] bytes = Encoding.ASCII.GetBytes(s); - await _stream.WriteAsync(bytes); + await _stream.WriteAsync(bytes, 0, bytes.Length); } public async Task SendResponseAsync(string response) @@ -736,7 +736,7 @@ public async Task SendResponseAsync(string response) public async Task SendResponseAsync(byte[] response) { - await _stream.WriteAsync(response); + await _stream.WriteAsync(response, 0, response.Length); } public async Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, string additionalHeaders = null, string content = null) diff --git a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs index 8525ed8c1b2974..e9feee002f6ec8 100644 --- a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs +++ b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs @@ -158,7 +158,7 @@ public static X509Certificate2 CreateServerSelfSignedCertificate(string name = " X509Certificate2 cert = req.CreateSelfSigned(start, end); if (PlatformDetection.IsWindows) { - cert = new X509Certificate2(cert.Export(X509ContentType.Pfx)); + cert = new X509Certificate2(cert.Export(X509ContentType.Pfx), (string?)null); } return cert; diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/README.md b/src/libraries/Common/tests/System/Net/Prerequisites/README.md index b273f0c48b902a..a1f766b07fe55d 100644 --- a/src/libraries/Common/tests/System/Net/Prerequisites/README.md +++ b/src/libraries/Common/tests/System/Net/Prerequisites/README.md @@ -38,10 +38,3 @@ This will join all machines to a test Active Directory and enable Windows Remoti Running as the Active Directory Administrator, run .\setup.ps1 from any of the machines within the environment. The script will use WinRM to connect and update all other roles. - -## Deployment Instructions to update the Azure-based environment - -1. Create a _Classic_ Azure WebService role. -2. Create a server certificate and add it to the subscription with the name: `CoreFxNetCertificate` -3. Edit `Servers\CoreFxNetCloudService\CoreFxNetCloudService\ServiceConfiguration.Cloud.cscfg` and ensure that the `CoreFxNetCertificate` `thumbprint` and `thumbprintAlgorithm` are correct. -4. Open the solution in Visual Studio and Run the Azure Publishing wizard to create and deploy the application. diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService.sln b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService.sln deleted file mode 100644 index b905dfaeecf721..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService.sln +++ /dev/null @@ -1,28 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{CC5FD16D-436D-48AD-A40C-5A424C6E3E79}") = "CoreFxNetCloudService", "CoreFxNetCloudService\CoreFxNetCloudService.ccproj", "{57E639CE-BD4D-4CB3-A913-AE51E18CD4A0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebServer", "WebServer\WebServer.csproj", "{6ACFF710-5F63-4E46-B0DA-0D1FE36EF4A7}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {57E639CE-BD4D-4CB3-A913-AE51E18CD4A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {57E639CE-BD4D-4CB3-A913-AE51E18CD4A0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {57E639CE-BD4D-4CB3-A913-AE51E18CD4A0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {57E639CE-BD4D-4CB3-A913-AE51E18CD4A0}.Release|Any CPU.Build.0 = Release|Any CPU - {6ACFF710-5F63-4E46-B0DA-0D1FE36EF4A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6ACFF710-5F63-4E46-B0DA-0D1FE36EF4A7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6ACFF710-5F63-4E46-B0DA-0D1FE36EF4A7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6ACFF710-5F63-4E46-B0DA-0D1FE36EF4A7}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/CoreFxNetCloudService.ccproj b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/CoreFxNetCloudService.ccproj deleted file mode 100644 index 17b2a50c043d31..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/CoreFxNetCloudService.ccproj +++ /dev/null @@ -1,57 +0,0 @@ - - - - Debug - AnyCPU - 2.9 - 57e639ce-bd4d-4cb3-a913-ae51e18cd4a0 - Library - Properties - CoreFxNetCloudService - CoreFxNetCloudService - True - CoreFxNetCloudService - False - - - true - full - false - bin\Debug\ - DEBUG;TRACE - - - pdbonly - true - bin\Release\ - TRACE - - - - - - - - - - WebServer - {6acff710-5f63-4e46-b0da-0d1fe36ef4a7} - True - Web - WebServer - True - - - - - - - - - - - 10.0 - $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Windows Azure Tools\2.9\ - - - diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/ServiceConfiguration.Cloud.cscfg b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/ServiceConfiguration.Cloud.cscfg deleted file mode 100644 index 410b90b4dcc491..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/ServiceConfiguration.Cloud.cscfg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/ServiceConfiguration.Local.cscfg b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/ServiceConfiguration.Local.cscfg deleted file mode 100644 index 473d8b0ce2c985..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/ServiceConfiguration.Local.cscfg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/ServiceDefinition.csdef b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/ServiceDefinition.csdef deleted file mode 100644 index 66e00f2fd7803d..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/ServiceDefinition.csdef +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/WebServerContent/diagnostics.wadcfgx b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/WebServerContent/diagnostics.wadcfgx deleted file mode 100644 index 8ca93efc6ebdda..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/WebServerContent/diagnostics.wadcfgx +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - \ No newline at end of file diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/ecf/WebServerContent/diagnostics.wadcfgx b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/ecf/WebServerContent/diagnostics.wadcfgx deleted file mode 100644 index df40d84e9c3b58..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/CoreFxNetCloudService/ecf/WebServerContent/diagnostics.wadcfgx +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - devstoreaccount1 - - - - - true - \ No newline at end of file diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/AuthenticationHelper.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/AuthenticationHelper.cs deleted file mode 100644 index e6ce64cbebb3f7..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/AuthenticationHelper.cs +++ /dev/null @@ -1,121 +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.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web; - -namespace WebServer -{ - public static class AuthenticationHelper - { - public static bool HandleAuthentication(HttpContext context) - { - string authType = context.Request.QueryString["auth"]; - string user = context.Request.QueryString["user"]; - string password = context.Request.QueryString["password"]; - string domain = context.Request.QueryString["domain"]; - - if (string.Equals("basic", authType, StringComparison.OrdinalIgnoreCase)) - { - if (!HandleBasicAuthentication(context, user, password, domain)) - { - context.Response.End(); - return false; - } - } - else if (string.Equals("Negotiate", authType, StringComparison.OrdinalIgnoreCase) || - string.Equals("NTLM", authType, StringComparison.OrdinalIgnoreCase)) - { - if (!HandleChallengeResponseAuthentication(context, authType, user, password, domain)) - { - context.Response.End(); - return false; - } - } - else if (authType != null) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Unsupported auth type: " + authType; - context.Response.End(); - return false; - } - - return true; - } - - private static bool HandleBasicAuthentication(HttpContext context, string user, string password, string domain) - { - const string WwwAuthenticateHeaderValue = "Basic realm=\"corefx-networking\""; - - string authHeader = context.Request.Headers["Authorization"]; - if (authHeader == null) - { - context.Response.StatusCode = 401; - context.Response.Headers.Add("WWW-Authenticate", WwwAuthenticateHeaderValue); - return false; - } - - string[] split = authHeader.Split(new char[] { ' ' }); - if (split.Length < 2) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Invalid Authorization header: " + authHeader; - return false; - } - - if (!string.Equals("basic", split[0], StringComparison.OrdinalIgnoreCase)) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Unsupported auth type: " + split[0]; - return false; - } - - // Decode base64 username:password. - byte[] bytes = Convert.FromBase64String(split[1]); - string credential = Encoding.ASCII.GetString(bytes); - string[] pair = credential.Split(new char[] { ':' }); - - // Prefix "domain\" to username if domain is specified. - if (domain != null) - { - user = domain + "\\" + user; - } - - if (pair.Length != 2 || pair[0] != user || pair[1] != password) - { - context.Response.StatusCode = 401; - context.Response.Headers.Add("WWW-Authenticate", WwwAuthenticateHeaderValue); - return false; - } - - // Success. - return true; - } - private static bool HandleChallengeResponseAuthentication( - HttpContext context, - string authType, - string user, - string password, - string domain) - { - string authHeader = context.Request.Headers["Authorization"]; - if (authHeader == null) - { - context.Response.StatusCode = 401; - context.Response.Headers.Add("WWW-Authenticate", authType); - return false; - } - - // We don't fully support this authentication method. - context.Response.StatusCode = 501; - context.Response.StatusDescription = string.Format( - "Attempt to use unsupported challenge/response auth type. {0}: {1}", - authType, - authHeader); - return false; - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/ContentHelper.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/ContentHelper.cs deleted file mode 100644 index ba64f862628f34..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/ContentHelper.cs +++ /dev/null @@ -1,53 +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.IO; -using System.IO.Compression; -using System.Security.Cryptography; -using System.Text; - -namespace WebServer -{ - public static class ContentHelper - { - public static byte[] GetDeflateBytes(string data) - { - byte[] bytes = Encoding.UTF8.GetBytes(data); - var compressedStream = new MemoryStream(); - - using (var compressor = new DeflateStream(compressedStream, CompressionMode.Compress, true)) - { - compressor.Write(bytes, 0, bytes.Length); - } - - return compressedStream.ToArray(); - } - - public static byte[] GetGZipBytes(string data) - { - byte[] bytes = Encoding.UTF8.GetBytes(data); - var compressedStream = new MemoryStream(); - - using (var compressor = new GZipStream(compressedStream, CompressionMode.Compress, true)) - { - compressor.Write(bytes, 0, bytes.Length); - } - - return compressedStream.ToArray(); - } - - public static byte[] ComputeMD5Hash(string data) - { - return ComputeMD5Hash(Encoding.UTF8.GetBytes(data)); - } - - public static byte[] ComputeMD5Hash(byte[] data) - { - using (MD5 md5 = MD5.Create()) - { - return md5.ComputeHash(data); - } - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Deflate.ashx b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Deflate.ashx deleted file mode 100644 index 2a039a03b134d1..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Deflate.ashx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebHandler Language="C#" CodeBehind="Deflate.ashx.cs" Class="WebServer.Deflate" %> diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Deflate.ashx.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Deflate.ashx.cs deleted file mode 100644 index ec90095d7bfe60..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Deflate.ashx.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; -using System.Web; - -namespace WebServer -{ - /// - /// Summary description for Deflate - /// - public class Deflate : IHttpHandler - { - public void ProcessRequest(HttpContext context) - { - string responseBody = "Sending DEFLATE compressed"; - - context.Response.Headers.Add("Content-MD5", Convert.ToBase64String(ContentHelper.ComputeMD5Hash(responseBody))); - context.Response.Headers.Add("Content-Encoding", "deflate"); - - context.Response.ContentType = "text/plain"; - - byte[] bytes = ContentHelper.GetDeflateBytes(responseBody); - context.Response.BinaryWrite(bytes); - } - - public bool IsReusable - { - get - { - return true; - } - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Echo.ashx b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Echo.ashx deleted file mode 100644 index 05b60371b0c1c9..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Echo.ashx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebHandler Language="C#" CodeBehind="Echo.ashx.cs" Class="WebServer.Echo" %> diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Echo.ashx.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Echo.ashx.cs deleted file mode 100644 index 406193a506f5cd..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Echo.ashx.cs +++ /dev/null @@ -1,53 +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.Security.Cryptography; -using System.Text; -using System.Web; - -namespace WebServer -{ - public class Echo : IHttpHandler - { - public void ProcessRequest(HttpContext context) - { - RequestHelper.AddResponseCookies(context); - - if (!AuthenticationHelper.HandleAuthentication(context)) - { - context.Response.End(); - return; - } - - // Add original request method verb as a custom response header. - context.Response.Headers.Add("X-HttpRequest-Method", context.Request.HttpMethod); - - // Echo back JSON encoded payload. - RequestInformation info = RequestInformation.Create(context.Request); - string echoJson = info.SerializeToJson(); - - // Compute MD5 hash to clients can verify the received data. - using (MD5 md5 = MD5.Create()) - { - byte[] bytes = Encoding.UTF8.GetBytes(echoJson); - byte[] hash = md5.ComputeHash(bytes); - string encodedHash = Convert.ToBase64String(hash); - - context.Response.Headers.Add("Content-MD5", encodedHash); - context.Response.ContentType = "application/json"; - context.Response.Write(echoJson); - } - - context.Response.End(); - } - - public bool IsReusable - { - get - { - return true; - } - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/EmptyContent.ashx b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/EmptyContent.ashx deleted file mode 100644 index a647c9a5da6b3b..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/EmptyContent.ashx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebHandler Language="C#" CodeBehind="EmptyContent.ashx.cs" Class="WebServer.EmptyContent" %> diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/EmptyContent.ashx.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/EmptyContent.ashx.cs deleted file mode 100644 index 66c09205cd574d..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/EmptyContent.ashx.cs +++ /dev/null @@ -1,30 +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.Collections.Generic; -using System.Linq; -using System.Web; - -namespace WebServer -{ - /// - /// Summary description for EmptyContent - /// - public class EmptyContent : IHttpHandler - { - public void ProcessRequest(HttpContext context) - { - // By default, this empty method sends back a 200 status code with 'Content-Length: 0' response header. - // There are no other entity-body related (i.e. 'Content-Type') headers returned. - } - - public bool IsReusable - { - get - { - return true; - } - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/GZip.ashx b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/GZip.ashx deleted file mode 100644 index b6ee5e1f5d8fca..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/GZip.ashx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebHandler Language="C#" CodeBehind="GZip.ashx.cs" Class="WebServer.GZip" %> diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/GZip.ashx.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/GZip.ashx.cs deleted file mode 100644 index 64b334b32655b9..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/GZip.ashx.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; -using System.Web; - -namespace WebServer -{ - /// - /// Summary description for Gzip - /// - public class GZip : IHttpHandler - { - public void ProcessRequest(HttpContext context) - { - string responseBody = "Sending GZIP compressed"; - - context.Response.Headers.Add("Content-MD5", Convert.ToBase64String(ContentHelper.ComputeMD5Hash(responseBody))); - context.Response.Headers.Add("Content-Encoding", "gzip"); - - context.Response.ContentType = "text/plain"; - - byte[] bytes = ContentHelper.GetGZipBytes(responseBody); - context.Response.BinaryWrite(bytes); - } - - public bool IsReusable - { - get - { - return false; - } - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/NameValueCollectionConverter.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/NameValueCollectionConverter.cs deleted file mode 100644 index d0d78a366bdd25..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/NameValueCollectionConverter.cs +++ /dev/null @@ -1,76 +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.Collections.Specialized; - -using Newtonsoft.Json; - -namespace WebServer -{ - public class NameValueCollectionConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var collection = value as NameValueCollection; - if (collection == null) - { - return; - } - - writer.Formatting = Formatting.Indented; - writer.WriteStartObject(); - foreach (var key in collection.AllKeys) - { - writer.WritePropertyName(key); - writer.WriteValue(collection.Get(key)); - } - writer.WriteEndObject(); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var nameValueCollection = new NameValueCollection(); - var key = ""; - while (reader.Read()) - { - if (reader.TokenType == JsonToken.StartObject) - { - nameValueCollection = new NameValueCollection(); - } - if (reader.TokenType == JsonToken.EndObject) - { - return nameValueCollection; - } - if (reader.TokenType == JsonToken.PropertyName) - { - key = reader.Value.ToString(); - } - if (reader.TokenType == JsonToken.String) - nameValueCollection.Add(key, reader.Value.ToString()); - } - return nameValueCollection; - } - - public override bool CanConvert(Type objectType) - { - return IsTypeDerivedFromType(objectType, typeof(NameValueCollection)); - } - - private bool IsTypeDerivedFromType(Type childType, Type parentType) - { - Type testType = childType; - while (testType != null) - { - if (testType == parentType) - { - return true; - } - - testType = testType.BaseType; - } - - return false; - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Properties/AssemblyInfo.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Properties/AssemblyInfo.cs deleted file mode 100644 index a32dd585a9ff69..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,30 +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.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("WebServer")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("WebServer")] -[assembly: AssemblyCopyright("Copyright \u00A9 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Properties/PublishProfiles/IIS_PublishToLocalPath_CHK.pubxml b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Properties/PublishProfiles/IIS_PublishToLocalPath_CHK.pubxml deleted file mode 100644 index 885092287ea9a5..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Properties/PublishProfiles/IIS_PublishToLocalPath_CHK.pubxml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - FileSystem - Debug - Any CPU - - True - False - .\PublishToIIS - False - - \ No newline at end of file diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Properties/PublishProfiles/IIS_PublishToLocalPath_RET.pubxml b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Properties/PublishProfiles/IIS_PublishToLocalPath_RET.pubxml deleted file mode 100644 index 15494e57a62e89..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Properties/PublishProfiles/IIS_PublishToLocalPath_RET.pubxml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - FileSystem - Release - Any CPU - - True - False - .\PublishToIIS - False - - \ No newline at end of file diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Redirect.ashx b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Redirect.ashx deleted file mode 100644 index 819e88ec0d60b3..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Redirect.ashx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebHandler Language="C#" CodeBehind="Redirect.ashx.cs" Class="WebServer.Redirect" %> diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Redirect.ashx.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Redirect.ashx.cs deleted file mode 100644 index 4c6ca5c342d1a0..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Redirect.ashx.cs +++ /dev/null @@ -1,88 +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.Web; - -namespace WebServer -{ - /// - /// Summary description for Redirect - /// - public class Redirect : IHttpHandler - { - public void ProcessRequest(HttpContext context) - { - int statusCode = 302; - string statusCodeString = context.Request.QueryString["statuscode"]; - if (!string.IsNullOrEmpty(statusCodeString)) - { - try - { - statusCode = int.Parse(statusCodeString); - if (statusCode < 300 || statusCode > 307) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Invalid redirect statuscode: " + statusCodeString; - return; - } - } - catch (Exception) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Error parsing statuscode: " + statusCodeString; - return; - } - } - - string redirectUri = context.Request.QueryString["uri"]; - if (string.IsNullOrEmpty(redirectUri)) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Missing redirection uri"; - return; - } - - string hopsString = context.Request.QueryString["hops"]; - int hops = 1; - if (!string.IsNullOrEmpty(hopsString)) - { - try - { - hops = int.Parse(hopsString); - } - catch (Exception) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Error parsing hops: " + hopsString; - return; - } - } - - RequestHelper.AddResponseCookies(context); - - if (hops <= 1) - { - context.Response.Headers.Add("Location", redirectUri); - } - else - { - context.Response.Headers.Add( - "Location", - string.Format("/Redirect.ashx?uri={0}&hops={1}", - redirectUri, - hops - 1)); - } - - context.Response.StatusCode = statusCode; - } - - public bool IsReusable - { - get - { - return true; - } - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/RequestHelper.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/RequestHelper.cs deleted file mode 100644 index 39d5d3cf967ece..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/RequestHelper.cs +++ /dev/null @@ -1,43 +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.Net; -using System.Web; - -namespace WebServer -{ - public static class RequestHelper - { - public static void AddResponseCookies(HttpContext context) - { - // Turn all 'X-SetCookie' request headers into 'Set-Cookie' response headers. - string headerName; - string headerValue; - for (int i = 0; i < context.Request.Headers.Count; i++) - { - headerName = context.Request.Headers.Keys[i]; - headerValue = context.Request.Headers[i]; - - if (string.Equals(headerName, "X-SetCookie", StringComparison.OrdinalIgnoreCase)) - { - context.Response.Headers.Add("Set-Cookie", headerValue); - } - } - } - - public static CookieCollection GetRequestCookies(HttpRequest request) - { - var cookieCollection = new CookieCollection(); - HttpCookieCollection cookies = request.Cookies; - - for (int i = 0; i < cookies.Count; i++) - { - var cookie = new Cookie(cookies[i].Name, cookies[i].Value); - cookieCollection.Add(cookie); - } - - return cookieCollection; - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/RequestInformation.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/RequestInformation.cs deleted file mode 100644 index 795f1a18e3527d..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/RequestInformation.cs +++ /dev/null @@ -1,86 +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.Collections.Specialized; -using System.IO; -using System.Net; -using System.Web; - -using Newtonsoft.Json; - -namespace WebServer -{ - public class RequestInformation - { - public string Method { get; private set; } - - public string Url { get; private set; } - - public NameValueCollection Headers { get; private set; } - - public NameValueCollection Cookies { get; private set; } - - public string BodyContent { get; private set; } - - public int BodyLength { get; private set; } - - public bool SecureConnection { get; private set; } - - public bool ClientCertificatePresent { get; private set; } - - public HttpClientCertificate ClientCertificate { get; private set; } - - public static RequestInformation Create(HttpRequest request) - { - var info = new RequestInformation(); - info.Method = request.HttpMethod; - info.Url = request.RawUrl; - info.Headers = request.Headers; - - var cookies = new NameValueCollection(); - CookieCollection cookieCollection = RequestHelper.GetRequestCookies(request); - foreach (Cookie cookie in cookieCollection) - { - cookies.Add(cookie.Name, cookie.Value); - } - info.Cookies = cookies; - - Stream stream = request.GetBufferedInputStream(); - using (var reader = new StreamReader(stream)) - { - string body = reader.ReadToEnd(); - info.BodyContent = body; - info.BodyLength = body.Length; - } - - info.SecureConnection = request.IsSecureConnection; - - var cs = request.ClientCertificate; - info.ClientCertificatePresent = cs.IsPresent; - if (cs.IsPresent) - { - info.ClientCertificate = request.ClientCertificate; - } - - return info; - } - - public static RequestInformation DeSerializeFromJson(string json) - { - return (RequestInformation)JsonConvert.DeserializeObject( - json, - typeof(RequestInformation), - new NameValueCollectionConverter()); - } - - public string SerializeToJson() - { - return JsonConvert.SerializeObject(this, new NameValueCollectionConverter()); - } - - private RequestInformation() - { - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/StatusCode.ashx b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/StatusCode.ashx deleted file mode 100644 index ea27244f48d5f2..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/StatusCode.ashx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebHandler Language="C#" CodeBehind="StatusCode.ashx.cs" Class="WebServer.StatusCode" %> diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/StatusCode.ashx.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/StatusCode.ashx.cs deleted file mode 100644 index 02e3165759bfb1..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/StatusCode.ashx.cs +++ /dev/null @@ -1,39 +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.Web; - -namespace WebServer -{ - public class StatusCode : IHttpHandler - { - public void ProcessRequest(HttpContext context) - { - string statusCodeString = context.Request.QueryString["statuscode"]; - string statusDescription = context.Request.QueryString["statusdescription"]; - try - { - int statusCode = int.Parse(statusCodeString); - context.Response.StatusCode = statusCode; - if (statusDescription != null) - { - context.Response.StatusDescription = statusDescription; - } - } - catch (Exception) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Error parsing statuscode: " + statusCodeString; - } - } - - public bool IsReusable - { - get - { - return true; - } - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Test.ashx b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Test.ashx deleted file mode 100644 index 07bfc94e625d6d..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Test.ashx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebHandler Language="C#" CodeBehind="Test.ashx.cs" Class="WebServer.Test" %> diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Test.ashx.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Test.ashx.cs deleted file mode 100644 index ad698047e5d92e..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Test.ashx.cs +++ /dev/null @@ -1,45 +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.Collections.Generic; -using System.Collections.Specialized; -using System.Net; -using System.Net.Http; -using System.Security.Cryptography; -using System.Text; -using System.Web; - -using Newtonsoft.Json; - -namespace WebServer -{ - public class Test : IHttpHandler - { - public void ProcessRequest(HttpContext context) - { - RequestInformation info = RequestInformation.Create(context.Request); - - string echoJson = info.SerializeToJson(); - - // Compute MD5 hash to clients can verify the received data. - MD5 md5 = MD5.Create(); - byte[] bytes = Encoding.ASCII.GetBytes(echoJson); - var hash = md5.ComputeHash(bytes); - string encodedHash = Convert.ToBase64String(hash); - context.Response.Headers.Add("Content-MD5", encodedHash); - - RequestInformation newEcho = RequestInformation.DeSerializeFromJson(echoJson); - context.Response.ContentType = "text/plain"; //"application/json"; - context.Response.Write(echoJson); - } - - public bool IsReusable - { - get - { - return false; - } - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/VerifyUpload.ashx b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/VerifyUpload.ashx deleted file mode 100644 index c50104d4f49e24..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/VerifyUpload.ashx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebHandler Language="C#" CodeBehind="VerifyUpload.ashx.cs" Class="WebServer.VerifyUpload" %> diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/VerifyUpload.ashx.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/VerifyUpload.ashx.cs deleted file mode 100644 index 67fd31989d836b..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/VerifyUpload.ashx.cs +++ /dev/null @@ -1,82 +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.IO; -using System.Security.Cryptography; -using System.Web; - -namespace WebServer -{ - public class VerifyUpload : IHttpHandler - { - public void ProcessRequest(HttpContext context) - { - // Report back original request method verb. - context.Response.Headers.Add("X-HttpRequest-Method", context.Request.HttpMethod); - - // Report back original entity-body related request headers. - string contentLength = context.Request.Headers["Content-Length"]; - if (!string.IsNullOrEmpty(contentLength)) - { - context.Response.Headers.Add("X-HttpRequest-Headers-ContentLength", contentLength); - } - - string transferEncoding = context.Request.Headers["Transfer-Encoding"]; - if (!string.IsNullOrEmpty(transferEncoding)) - { - context.Response.Headers.Add("X-HttpRequest-Headers-TransferEncoding", transferEncoding); - } - - // Get expected MD5 hash of request body. - string expectedHash = context.Request.Headers["Content-MD5"]; - if (string.IsNullOrEmpty(expectedHash)) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Missing 'Content-MD5' request header"; - return; - } - - // Compute MD5 hash of received request body. - string actualHash; - using (MD5 md5 = MD5.Create()) - { - byte[] hash = md5.ComputeHash(ReadAllRequestBytes(context)); - actualHash = Convert.ToBase64String(hash); - } - - if (expectedHash == actualHash) - { - context.Response.StatusCode = 200; - } - else - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Request body not verfied"; - } - } - - public bool IsReusable - { - get - { - return true; - } - } - - private static byte[] ReadAllRequestBytes(HttpContext context) - { - Stream requestStream = context.Request.GetBufferedInputStream(); - byte[] buffer = new byte[16 * 1024]; - using (MemoryStream ms = new MemoryStream()) - { - int read; - while ((read = requestStream.Read(buffer, 0, buffer.Length)) > 0) - { - ms.Write(buffer, 0, read); - } - return ms.ToArray(); - } - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Web.Debug.config b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Web.Debug.config deleted file mode 100644 index 392bd6ae1652ba..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Web.Debug.config +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Web.Release.config b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Web.Release.config deleted file mode 100644 index eac2ffb30d72b5..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Web.Release.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Web.config b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Web.config deleted file mode 100644 index 1474319672d816..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/Web.config +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebRole.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebRole.cs deleted file mode 100644 index faf1a3d4e147cb..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebRole.cs +++ /dev/null @@ -1,23 +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.Collections.Generic; -using System.Linq; -using Microsoft.WindowsAzure; -using Microsoft.WindowsAzure.Diagnostics; -using Microsoft.WindowsAzure.ServiceRuntime; - -namespace WebServer -{ - public class WebRole : RoleEntryPoint - { - public override bool OnStart() - { - // For information on handling configuration changes - // see the MSDN topic at https://go.microsoft.com/fwlink/?LinkId=166357. - - return base.OnStart(); - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebServer.csproj b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebServer.csproj deleted file mode 100644 index 65dc1c23c46ba5..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebServer.csproj +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - Debug - AnyCPU - - - 2.0 - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - WebServer - WebServer - v4.5.1 - true - - - - - - - - bin\ - - - - - ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - True - - - - ..\packages\Microsoft.Data.Edm.5.8.4\lib\net40\Microsoft.Data.Edm.dll - True - - - ..\packages\Microsoft.Data.OData.5.8.4\lib\net40\Microsoft.Data.OData.dll - True - - - ..\packages\Microsoft.Data.Services.Client.5.8.4\lib\net40\Microsoft.Data.Services.Client.dll - True - - - ..\packages\Microsoft.WindowsAzure.ConfigurationManager.2.0.3\lib\net40\Microsoft.WindowsAzure.Configuration.dll - True - - - True - - - False - - - ..\packages\WindowsAzure.Storage.4.3.0\lib\net40\Microsoft.WindowsAzure.Storage.dll - True - - - ..\packages\Newtonsoft.Json.5.0.8\lib\net45\Newtonsoft.Json.dll - True - - - - - - - - - - - ..\packages\System.Spatial.5.8.4\lib\net40\System.Spatial.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - - Web.config - - - Web.config - - - - - - - Designer - - - - - - - Deflate.ashx - - - Echo.ashx - - - EmptyContent.ashx - - - GZip.ashx - - - - - Redirect.ashx - - - - - StatusCode.ashx - - - Test.ashx - - - VerifyUpload.ashx - - - - EchoWebSocket.ashx - - - EchoWebSocketHeaders.ashx - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - True - True - 42127 - / - http://localhost:42127/ - False - False - - - False - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/Default.htm b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/Default.htm deleted file mode 100644 index 105c4b5fee7d78..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/Default.htm +++ /dev/null @@ -1,14 +0,0 @@ - - - - - -

Redirecting to the websocket.org test client in 5 seconds...

- -

Use the following URIs to test the local endpoint:

-
-    ws://testserver.contoso.com/WebSocket/EchoWebSocket.ashx
-    wss://testserver.contoso.com/WebSocket/EchoWebSocket.ashx
-    
- - diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/EchoWebSocket.ashx b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/EchoWebSocket.ashx deleted file mode 100644 index 411704bca698ac..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/EchoWebSocket.ashx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebHandler Language="C#" CodeBehind="EchoWebSocket.ashx.cs" Class="WebServer.EchoWebSocket" %> diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/EchoWebSocket.ashx.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/EchoWebSocket.ashx.cs deleted file mode 100644 index 95ea6119f16a2a..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/EchoWebSocket.ashx.cs +++ /dev/null @@ -1,167 +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.Net.WebSockets; -using System.Web; -using System.Web.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace WebServer -{ - public class EchoWebSocket : IHttpHandler - { - private const int MaxBufferSize = 128 * 1024; - private bool _replyWithPartialMessages = false; - - public void ProcessRequest(HttpContext context) - { - _replyWithPartialMessages = context.Request.Url.Query.Contains("replyWithPartialMessages"); - - string subProtocol = context.Request.QueryString["subprotocol"]; - - if (context.Request.Url.Query.Contains("delay10sec")) - { - Thread.Sleep(10000); - } - - try - { - if (!context.IsWebSocketRequest) - { - context.Response.StatusCode = 200; - context.Response.ContentType = "text/plain"; - context.Response.Write("Not a websocket request"); - - return; - } - - if (!string.IsNullOrEmpty(subProtocol)) - { - var wsOptions = new AspNetWebSocketOptions(); - wsOptions.SubProtocol = subProtocol; - - context.AcceptWebSocketRequest(ProcessWebSocketRequest, wsOptions); - } - else - { - context.AcceptWebSocketRequest(ProcessWebSocketRequest); - } - } - catch (Exception ex) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = ex.Message; - } - } - - public bool IsReusable - { - get - { - return false; - } - } - - private async Task ProcessWebSocketRequest(WebSocketContext wsContext) - { - WebSocket socket = wsContext.WebSocket; - var receiveBuffer = new byte[MaxBufferSize]; - var throwAwayBuffer = new byte[MaxBufferSize]; - - // Stay in loop while websocket is open - while (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseSent) - { - var receiveResult = await socket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); - if (receiveResult.MessageType == WebSocketMessageType.Close) - { - if (receiveResult.CloseStatus == WebSocketCloseStatus.Empty) - { - await socket.CloseAsync(WebSocketCloseStatus.Empty, null, CancellationToken.None); - } - else - { - await socket.CloseAsync( - receiveResult.CloseStatus.GetValueOrDefault(), - receiveResult.CloseStatusDescription, - CancellationToken.None); - } - - continue; - } - - // Keep reading until we get an entire message. - int offset = receiveResult.Count; - while (receiveResult.EndOfMessage == false) - { - if (offset < MaxBufferSize) - { - receiveResult = await socket.ReceiveAsync( - new ArraySegment(receiveBuffer, offset, MaxBufferSize - offset), - CancellationToken.None); - } - else - { - receiveResult = await socket.ReceiveAsync( - new ArraySegment(throwAwayBuffer), - CancellationToken.None); - } - - offset += receiveResult.Count; - } - - // Close socket if the message was too big. - if (offset > MaxBufferSize) - { - await socket.CloseAsync( - WebSocketCloseStatus.MessageTooBig, - string.Format("{0}: {1} > {2}", WebSocketCloseStatus.MessageTooBig.ToString(), offset, MaxBufferSize), - CancellationToken.None); - - continue; - } - - bool sendMessage = false; - if (receiveResult.MessageType == WebSocketMessageType.Text) - { - string receivedMessage = Encoding.UTF8.GetString(receiveBuffer, 0, offset); - if (receivedMessage == ".close") - { - await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, receivedMessage, CancellationToken.None); - } - if (receivedMessage == ".shutdown") - { - await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, receivedMessage, CancellationToken.None); - } - else if (receivedMessage == ".abort") - { - socket.Abort(); - } - else if (receivedMessage == ".delay5sec") - { - await Task.Delay(5000); - } - else if (socket.State == WebSocketState.Open) - { - sendMessage = true; - } - } - else - { - sendMessage = true; - } - - if (sendMessage) - { - await socket.SendAsync( - new ArraySegment(receiveBuffer, 0, offset), - receiveResult.MessageType, - !_replyWithPartialMessages, - CancellationToken.None); - } - } - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/EchoWebSocketHeaders.ashx b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/EchoWebSocketHeaders.ashx deleted file mode 100644 index 72e742e93e1695..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/EchoWebSocketHeaders.ashx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebHandler Language="C#" CodeBehind="EchoWebSocketHeaders.ashx.cs" Class="WebServer.EchoWebSocketHeaders" %> diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/EchoWebSocketHeaders.ashx.cs b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/EchoWebSocketHeaders.ashx.cs deleted file mode 100644 index 28abef11fbdddf..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/WebSocket/EchoWebSocketHeaders.ashx.cs +++ /dev/null @@ -1,89 +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.Web; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace WebServer -{ - public class EchoWebSocketHeaders : IHttpHandler - { - private const int MaxBufferSize = 1024; - - public void ProcessRequest(HttpContext context) - { - try - { - if (!context.IsWebSocketRequest) - { - context.Response.StatusCode = 200; - context.Response.ContentType = "text/plain"; - context.Response.Write("Not a websocket request"); - - return; - } - - context.AcceptWebSocketRequest(ProcessWebSocketRequest); - } - catch (Exception ex) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = ex.Message; - } - } - - public bool IsReusable - { - get - { - return false; - } - } - - private async Task ProcessWebSocketRequest(WebSocketContext wsContext) - { - WebSocket socket = wsContext.WebSocket; - var receiveBuffer = new byte[MaxBufferSize]; - - // Reflect all headers and cookies - var sb = new StringBuilder(); - sb.AppendLine("Headers:"); - - foreach (string header in wsContext.Headers.AllKeys) - { - sb.Append(header); - sb.Append(":"); - sb.AppendLine(wsContext.Headers[header]); - } - - byte[] sendBuffer = Encoding.UTF8.GetBytes(sb.ToString()); - await socket.SendAsync(new ArraySegment(sendBuffer), WebSocketMessageType.Text, true, new CancellationToken()); - - // Stay in loop while websocket is open - while (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseSent) - { - var receiveResult = await socket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); - if (receiveResult.MessageType == WebSocketMessageType.Close) - { - if (receiveResult.CloseStatus == WebSocketCloseStatus.Empty) - { - await socket.CloseAsync(WebSocketCloseStatus.Empty, null, CancellationToken.None); - } - else - { - await socket.CloseAsync( - receiveResult.CloseStatus.GetValueOrDefault(), - receiveResult.CloseStatusDescription, - CancellationToken.None); - } - - continue; - } - } - } - } -} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/index.html b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/index.html deleted file mode 100644 index 6deebf937f1651..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Networking Test Server - - - -

NetworkingTestServer

-

Networking test server in Azure. Used by dotnet/corefx repo.

- - diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/packages.config b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/packages.config deleted file mode 100644 index d0a39047efb507..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/CoreFxNetCloudService/WebServer/packages.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/buildAndPackage.ps1 b/src/libraries/Common/tests/System/Net/Prerequisites/Servers/buildAndPackage.ps1 deleted file mode 100644 index bb3828e448338d..00000000000000 --- a/src/libraries/Common/tests/System/Net/Prerequisites/Servers/buildAndPackage.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -# Licensed to the .NET Foundation under one or more agreements. -# The .NET Foundation licenses this file to you under the MIT license. - -# Requires Visual Studio Command Prompt -# Requires Azure SDK and .NET Framework SDK installed on the build machine. - -$cdir = pwd -$folderName = "CoreFxNetCloudService" -$src = Join-Path $cdir $folderName -$tmp = Join-Path $Env:TEMP $folderName -$tmpOut = Join-Path $tmp "WebServer\PublishToIIS" -$dst = Join-Path $cdir "..\Deployment\IISApplications" - -$nugetSrc = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" - -if (Test-Path $tmp) -{ - rm -Recurse $tmp -} - -copy -Recurse $src $Env:TEMP -Start-BitsTransfer -Source $nugetSrc -Destination $tmp - -cd $tmp - -.\nuget restore -msbuild /p:DeployOnBuild=true /p:PublishProfile=IIS_PublishToLocalPath_RET - -copy -Recurse $tmpOut $dst - -cd $cdir -rm -Recurse $tmp diff --git a/src/libraries/Common/tests/System/Net/QuicLoad.cs b/src/libraries/Common/tests/System/Net/QuicLoad.cs new file mode 100644 index 00000000000000..24de3c8e9d6044 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/QuicLoad.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Net.Quic; +using System.Runtime.CompilerServices; + +namespace System.Net.Test.Common +{ + public static class QuicLoad + { + [ModuleInitializer] + internal static void InitializeQuic() + { + // This will load Quic (if supported) to avoid interference with RemoteExecutor + // See https://github.com/dotnet/runtime/pull/75424 for more details + // IsSupported currently does not unload lttng. If it does in the future, + // we may need to call some real Quic API here to get everything loaded properly + _ = OperatingSystem.IsLinux() && QuicConnection.IsSupported; + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Security/Kerberos/KerberosExecutor.cs b/src/libraries/Common/tests/System/Net/Security/Kerberos/KerberosExecutor.cs index 6cb36e463da1f6..e034e82bd864a8 100644 --- a/src/libraries/Common/tests/System/Net/Security/Kerberos/KerberosExecutor.cs +++ b/src/libraries/Common/tests/System/Net/Security/Kerberos/KerberosExecutor.cs @@ -29,7 +29,9 @@ public class KerberosExecutor : IDisposable private readonly ITestOutputHelper _testOutputHelper; public static bool IsSupported { get; } = - RemoteExecutor.IsSupported && (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()); + RemoteExecutor.IsSupported && + !PlatformDetection.IsLinuxBionic && + (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()); public const string DefaultAdminPassword = "PLACEHOLDERadmin."; public const string DefaultUserPassword = "PLACEHOLDERcorrect20"; diff --git a/src/libraries/Common/tests/System/Net/Security/Kerberos/System.Net.Security.Kerberos.Shared.projitems b/src/libraries/Common/tests/System/Net/Security/Kerberos/System.Net.Security.Kerberos.Shared.projitems index 6371a6a6b6fb70..013923faa3d662 100644 --- a/src/libraries/Common/tests/System/Net/Security/Kerberos/System.Net.Security.Kerberos.Shared.projitems +++ b/src/libraries/Common/tests/System/Net/Security/Kerberos/System.Net.Security.Kerberos.Shared.projitems @@ -36,6 +36,6 @@ - + diff --git a/src/libraries/Common/tests/System/Runtime/Serialization/DataContractSerializerHelper.cs b/src/libraries/Common/tests/System/Runtime/Serialization/DataContractSerializerHelper.cs index 164015006109b4..eeebcc9dcbb28d 100644 --- a/src/libraries/Common/tests/System/Runtime/Serialization/DataContractSerializerHelper.cs +++ b/src/libraries/Common/tests/System/Runtime/Serialization/DataContractSerializerHelper.cs @@ -4,14 +4,14 @@ using System.IO; using System.Reflection; using System.Runtime.Serialization.Formatters.Binary; - +using System.Xml; using Xunit; namespace System.Runtime.Serialization.Tests { public static class DataContractSerializerHelper { - internal static T SerializeAndDeserialize(T value, string baseline, DataContractSerializerSettings settings = null, Func serializerFactory = null, bool skipStringCompare = false) + internal static T SerializeAndDeserialize(T value, string baseline, DataContractSerializerSettings settings = null, Func serializerFactory = null, bool skipStringCompare = false, bool verifyBinaryRoundTrip = true) { DataContractSerializer dcs; if (serializerFactory != null) @@ -23,6 +23,7 @@ internal static T SerializeAndDeserialize(T value, string baseline, DataContr dcs = (settings != null) ? new DataContractSerializer(typeof(T), settings) : new DataContractSerializer(typeof(T)); } + T deserialized; using (MemoryStream ms = new MemoryStream()) { dcs.WriteObject(ms, value); @@ -38,9 +39,37 @@ internal static T SerializeAndDeserialize(T value, string baseline, DataContr } ms.Position = 0; - T deserialized = (T)dcs.ReadObject(ms); + deserialized = (T)dcs.ReadObject(ms); + } + + if (verifyBinaryRoundTrip) + { + RoundTripBinarySerialization(value, settings, serializerFactory); + } - return deserialized; + return deserialized; + } + + internal static T RoundTripBinarySerialization(T value, DataContractSerializerSettings settings = null, Func serializerFactory = null) + { + DataContractSerializer dcs; + if (serializerFactory != null) + { + dcs = serializerFactory(); + } + else + { + dcs = (settings != null) ? new DataContractSerializer(typeof(T), settings) : new DataContractSerializer(typeof(T)); + } + + using (MemoryStream ms = new MemoryStream()) + using (XmlDictionaryWriter binWriter = XmlDictionaryWriter.CreateBinaryWriter(ms)) + { + dcs.WriteObject(binWriter, value); + binWriter.Flush(); + ms.Position = 0; + XmlDictionaryReader binReader = XmlDictionaryReader.CreateBinaryReader(ms, XmlDictionaryReaderQuotas.Max); + return (T)dcs.ReadObject(binReader); } } } 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 ec1a58e1ada7c3..c489db5197333f 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 @@ -9,6 +9,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests { + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public class AesCipherOneShotTests : SymmetricOneShotBase { protected override byte[] Key => @@ -414,251 +415,519 @@ public static IEnumerable TestCases CipherMode.CBC, }; - if (PlatformDetection.IsNotBrowser) + // ECB test cases + // plaintext requires no padding + yield return new object[] { - // ECB test cases - // plaintext requires no padding - yield return new object[] + // plaintext + new byte[] { - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, - // ciphertext - new byte[] - { - 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, - 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, - 0x6D, 0xE5, 0xF6, 0x07, 0xAB, 0x7E, 0xB8, 0x20, - 0x2F, 0x39, 0x57, 0x70, 0x3B, 0x04, 0xE8, 0xB5, - }, + // ciphertext + new byte[] + { + 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, + 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, + 0x6D, 0xE5, 0xF6, 0x07, 0xAB, 0x7E, 0xB8, 0x20, + 0x2F, 0x39, 0x57, 0x70, 0x3B, 0x04, 0xE8, 0xB5, + }, - PaddingMode.PKCS7, - CipherMode.ECB, - }; + PaddingMode.PKCS7, + CipherMode.ECB, + }; - yield return new object[] + yield return new object[] + { + // plaintext + new byte[] { - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, - // ciphertext - new byte[] - { - 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, - 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, - }, + // ciphertext + new byte[] + { + 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, + 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, + }, - PaddingMode.None, - CipherMode.ECB, - }; + PaddingMode.None, + CipherMode.ECB, + }; - yield return new object[] + yield return new object[] + { + // plaintext + new byte[] { - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, - // ciphertext - new byte[] - { - 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, - 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, - }, + // ciphertext + new byte[] + { + 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, + 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, + }, - PaddingMode.Zeros, - CipherMode.ECB, - }; + PaddingMode.Zeros, + CipherMode.ECB, + }; - yield return new object[] + yield return new object[] + { + // plaintext + new byte[] { - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, - // ciphertext - new byte[] - { - 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, - 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, - 0xC1, 0xCA, 0x44, 0xE8, 0x05, 0xFF, 0xCB, 0x6F, - 0x4D, 0x7F, 0xE9, 0x17, 0x12, 0xFE, 0xBB, 0xAC, - }, + // ciphertext + new byte[] + { + 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, + 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, + 0xC1, 0xCA, 0x44, 0xE8, 0x05, 0xFF, 0xCB, 0x6F, + 0x4D, 0x7F, 0xE9, 0x17, 0x12, 0xFE, 0xBB, 0xAC, + }, - PaddingMode.ANSIX923, - CipherMode.ECB, - }; + PaddingMode.ANSIX923, + 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[] + { + 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, + 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, + 0xD3, 0xAA, 0x33, 0x5B, 0x93, 0xC2, 0x3D, 0x96, + 0xFD, 0x89, 0xB1, 0x8C, 0x47, 0x75, 0x65, 0xA8, + }, + + PaddingMode.ISO10126, + CipherMode.ECB, + }; + + // plaintext requires padding + 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[] + { + 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, + 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, + 0x82, 0x8D, 0x60, 0xDC, 0x44, 0x26, 0xCF, 0xDE, + 0xC9, 0x54, 0x33, 0x47, 0xE2, 0x9E, 0xF0, 0x8C, + }, + + PaddingMode.PKCS7, + CipherMode.ECB, + }; + + 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[] + { + 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, + 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, + 0x49, 0x39, 0x1B, 0x69, 0xA1, 0xF3, 0x66, 0xE4, + 0x3E, 0x40, 0x51, 0xB8, 0x05, 0x60, 0xDC, 0xFD, + }, + + PaddingMode.Zeros, + CipherMode.ECB, + }; + + 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[] + { + 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, + 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, + 0xCD, 0x0D, 0xCD, 0xEA, 0xA2, 0x1F, 0xC1, 0xC3, + 0x81, 0xEE, 0x8A, 0x63, 0x94, 0x5F, 0x85, 0x43, + }, + + PaddingMode.ANSIX923, + CipherMode.ECB, + }; + + 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[] + { + 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, + 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, + 0x9C, 0xE4, 0x0D, 0x2F, 0xCD, 0x82, 0x25, 0x0E, + 0x13, 0xAB, 0x4B, 0x6B, 0xC0, 0x9A, 0x21, 0x2E, + }, + + PaddingMode.ISO10126, + CipherMode.ECB, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.Zeros, + CipherMode.ECB, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.None, + CipherMode.ECB, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + new byte[] + { + 0x6D, 0xE5, 0xF6, 0x07, 0xAB, 0x7E, 0xB8, 0x20, + 0x2F, 0x39, 0x57, 0x70, 0x3B, 0x04, 0xE8, 0xB5, + }, + + 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, + }, - yield return new object[] + // ciphertext + new byte[] { - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, 0x97, + }, - // ciphertext - new byte[] - { - 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, - 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, - 0xD3, 0xAA, 0x33, 0x5B, 0x93, 0xC2, 0x3D, 0x96, - 0xFD, 0x89, 0xB1, 0x8C, 0x47, 0x75, 0x65, 0xA8, - }, + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; - PaddingMode.ISO10126, - CipherMode.ECB, - }; + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, - // plaintext requires padding - yield return new object[] + // ciphertext + new byte[] { - // plaintext - new byte[] - { - 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, - 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, - 0x59, - }, + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, + }, - // ciphertext - new byte[] - { - 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, - 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, - 0x82, 0x8D, 0x60, 0xDC, 0x44, 0x26, 0xCF, 0xDE, - 0xC9, 0x54, 0x33, 0x47, 0xE2, 0x9E, 0xF0, 0x8C, - }, + PaddingMode.None, + CipherMode.CFB, + 8, + }; - PaddingMode.PKCS7, - CipherMode.ECB, - }; + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, - yield return new object[] + // ciphertext + new byte[] { - // plaintext - new byte[] - { - 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, - 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, - 0x59, - }, + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, + }, - // ciphertext - new byte[] - { - 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, - 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, - 0x49, 0x39, 0x1B, 0x69, 0xA1, 0xF3, 0x66, 0xE4, - 0x3E, 0x40, 0x51, 0xB8, 0x05, 0x60, 0xDC, 0xFD, - }, + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; - PaddingMode.Zeros, - CipherMode.ECB, - }; + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, - yield return new object[] + // ciphertext + new byte[] { - // plaintext - new byte[] - { - 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, - 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, - 0x59, - }, + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, 0x97, + }, - // ciphertext - new byte[] - { - 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, - 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, - 0xCD, 0x0D, 0xCD, 0xEA, 0xA2, 0x1F, 0xC1, 0xC3, - 0x81, 0xEE, 0x8A, 0x63, 0x94, 0x5F, 0x85, 0x43, - }, + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; - PaddingMode.ANSIX923, - CipherMode.ECB, - }; + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, - yield return new object[] + // ciphertext + new byte[] { - // plaintext - new byte[] - { - 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, - 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, - 0x59, - }, + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, 0x97, + }, - // ciphertext - new byte[] - { - 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, - 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, - 0x9C, 0xE4, 0x0D, 0x2F, 0xCD, 0x82, 0x25, 0x0E, - 0x13, 0xAB, 0x4B, 0x6B, 0xC0, 0x9A, 0x21, 0x2E, - }, + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; - PaddingMode.ISO10126, - CipherMode.ECB, - }; + yield return new object[] + { + // plaintext + Array.Empty(), - yield return new object[] - { - // plaintext - Array.Empty(), + // ciphertext + Array.Empty(), - // ciphertext - Array.Empty(), + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; - PaddingMode.Zeros, - CipherMode.ECB, - }; + yield return new object[] + { + // plaintext + Array.Empty(), - yield return new object[] - { - // plaintext - Array.Empty(), + // ciphertext + Array.Empty(), - // ciphertext - Array.Empty(), + PaddingMode.None, + CipherMode.CFB, + 8, + }; - PaddingMode.None, - CipherMode.ECB, - }; + yield return new object[] + { + // plaintext + Array.Empty(), - yield return new object[] + // ciphertext + new byte[] { - // plaintext - Array.Empty(), - - // ciphertext - new byte[] - { - 0x6D, 0xE5, 0xF6, 0x07, 0xAB, 0x7E, 0xB8, 0x20, - 0x2F, 0x39, 0x57, 0x70, 0x3B, 0x04, 0xE8, 0xB5, - }, + 0x02, + }, - PaddingMode.PKCS7, - CipherMode.ECB, - }; + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + // CFB128 is not supported on Windows 7. + if (PlatformDetection.IsNotWindows7) + { yield return new object[] { + // plaintext new byte[] { @@ -669,18 +938,20 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, - 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, - 0xD2, + 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, - 8, + 128, }; yield return new object[] { + // plaintext new byte[] { @@ -691,13 +962,13 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, - 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, }, PaddingMode.None, CipherMode.CFB, - 8, + 128, }; yield return new object[] @@ -713,17 +984,18 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, - 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, }, PaddingMode.Zeros, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext new byte[] { @@ -734,18 +1006,20 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, - 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, - 0xD2, + 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, - 8, + 128, }; yield return new object[] { + // plaintext new byte[] { @@ -756,18 +1030,20 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, - 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, - 0xD2, + 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, - 8, + 128, }; yield return new object[] { + // plaintext new byte[] { @@ -779,18 +1055,20 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, - 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, - 0xF9, 0x97, + 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, - 8, + 128, }; yield return new object[] { + // plaintext new byte[] { @@ -802,18 +1080,20 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, - 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, - 0xF9, + 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.None, + PaddingMode.Zeros, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext new byte[] { @@ -825,18 +1105,20 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, - 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, - 0xF9, + 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.Zeros, + PaddingMode.ANSIX923, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext new byte[] { @@ -848,41 +1130,38 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, - 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, - 0xF9, 0x97, + 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.ANSIX923, + PaddingMode.ISO10126, CipherMode.CFB, - 8, + 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, - }, + Array.Empty(), // ciphertext new byte[] { - 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, - 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, - 0xF9, 0x97, + 0x13, 0x47, 0x4B, 0xA9, 0x1C, 0x31, 0xE1, 0xFE, + 0x23, 0x69, 0x61, 0xE6, 0x27, 0x01, 0xBE, 0xAA, }, - PaddingMode.ISO10126, + PaddingMode.PKCS7, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext Array.Empty(), @@ -891,11 +1170,12 @@ public static IEnumerable TestCases PaddingMode.Zeros, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext Array.Empty(), @@ -904,290 +1184,8 @@ public static IEnumerable TestCases PaddingMode.None, CipherMode.CFB, - 8, + 128, }; - - 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 95a8e740763174..a6206fc3e632b2 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 @@ -11,6 +11,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests { using Aes = System.Security.Cryptography.Aes; + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public partial class AesCipherTests { [Fact] @@ -34,7 +35,6 @@ public static void RandomKeyRoundtrip_128() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "AES-192 is not supported on Browser")] public static void RandomKeyRoundtrip_192() { using (Aes aes = AesFactory.Create()) @@ -79,7 +79,6 @@ public static void DecryptKnownCBC256() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void DecryptKnownCFB128_256() { byte[] encryptedBytes = new byte[] @@ -102,7 +101,6 @@ public static void DecryptKnownCFB128_256() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void DecryptKnownECB192() { byte[] encryptedBytes = new byte[] @@ -125,7 +123,6 @@ public static void DecryptKnownECB192() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void DecryptKnownCFB128_192() { byte[] encryptedBytes = new byte[] @@ -148,7 +145,6 @@ public static void DecryptKnownCFB128_192() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void DecryptKnownCFB128_128() { byte[] encryptedBytes = new byte[] @@ -276,7 +272,6 @@ public static void VerifyInPlaceDecryption() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void VerifyKnownTransform_ECB128_NoPadding() { TestAesTransformDirectKey( @@ -289,7 +284,6 @@ public static void VerifyKnownTransform_ECB128_NoPadding() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void VerifyKnownTransform_ECB256_NoPadding() { TestAesTransformDirectKey( @@ -302,7 +296,6 @@ public static void VerifyKnownTransform_ECB256_NoPadding() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void VerifyKnownTransform_ECB128_NoPadding_2() { TestAesTransformDirectKey( @@ -315,7 +308,6 @@ public static void VerifyKnownTransform_ECB128_NoPadding_2() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void VerifyKnownTransform_ECB128_NoPadding_3() { TestAesTransformDirectKey( @@ -328,7 +320,6 @@ public static void VerifyKnownTransform_ECB128_NoPadding_3() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void VerifyKnownTransform_ECB192_NoPadding() { TestAesTransformDirectKey( @@ -341,7 +332,6 @@ public static void VerifyKnownTransform_ECB192_NoPadding() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void VerifyKnownTransform_ECB192_NoPadding_2() { TestAesTransformDirectKey( @@ -354,7 +344,6 @@ public static void VerifyKnownTransform_ECB192_NoPadding_2() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_8_NoPadding() { TestAesTransformDirectKey( @@ -368,7 +357,6 @@ public static void VerifyKnownTransform_CFB128_8_NoPadding() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "PaddingMode.None is not supported on Browser")] public static void VerifyKnownTransform_CFB128_128_NoPadding() { TestAesTransformDirectKey( @@ -406,7 +394,6 @@ public static void VerifyKnownTransform_CBC256_NoPadding() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_256_NoPadding() { TestAesTransformDirectKey( @@ -420,7 +407,6 @@ public static void VerifyKnownTransform_CFB128_256_NoPadding() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_256_NoPadding() { TestAesTransformDirectKey( @@ -446,7 +432,6 @@ public static void VerifyKnownTransform_CBC128_NoPadding_2() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_128_NoPadding_2() { TestAesTransformDirectKey( @@ -472,7 +457,6 @@ public static void VerifyKnownTransform_CBC128_NoPadding_3() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_128_NoPadding_3() { TestAesTransformDirectKey( @@ -486,7 +470,6 @@ public static void VerifyKnownTransform_CFB128_128_NoPadding_3() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "AES-192 is not supported on Browser")] public static void VerifyKnownTransform_CBC192_NoPadding() { TestAesTransformDirectKey( @@ -499,7 +482,6 @@ public static void VerifyKnownTransform_CBC192_NoPadding() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_192_NoPadding() { TestAesTransformDirectKey( @@ -513,7 +495,6 @@ public static void VerifyKnownTransform_CFB128_192_NoPadding() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_192_NoPadding() { TestAesTransformDirectKey( @@ -527,7 +508,6 @@ public static void VerifyKnownTransform_CFB8_192_NoPadding() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "AES-192 is not supported on Browser")] public static void VerifyKnownTransform_CBC192_NoPadding_2() { TestAesTransformDirectKey( @@ -540,7 +520,6 @@ public static void VerifyKnownTransform_CBC192_NoPadding_2() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_192_NoPadding_2() { TestAesTransformDirectKey( @@ -554,7 +533,6 @@ public static void VerifyKnownTransform_CFB128_192_NoPadding_2() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void WrongKeyFailDecrypt() { // The test: @@ -601,7 +579,6 @@ public static void WrongKeyFailDecrypt() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void WrongKeyFailDecrypt_2() { // The test: @@ -652,7 +629,6 @@ public static void WrongKeyFailDecrypt_2() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_128_NoPadding_4() { // NIST CAVP AESMMT.ZIP CFB8MMT128.rsp, [ENCRYPT] COUNT=4 @@ -668,7 +644,6 @@ public static void VerifyKnownTransform_CFB8_128_NoPadding_4() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_128_NoPadding_4_Fails() { Assert.Throws(() => @@ -684,7 +659,6 @@ public static void VerifyKnownTransform_CFB128_128_NoPadding_4_Fails() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_128_PKCS7_4() { TestAesTransformDirectKey( @@ -698,7 +672,6 @@ public static void VerifyKnownTransform_CFB128_128_PKCS7_4() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_128_PKCS7_4() { TestAesTransformDirectKey( @@ -714,7 +687,6 @@ public static void VerifyKnownTransform_CFB8_128_PKCS7_4() [Theory] [InlineData(PaddingMode.None)] [InlineData(PaddingMode.Zeros)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_128_NoOrZeroPadding_0_Extended(PaddingMode paddingMode) { // NIST CAVP AESMMT.ZIP CFB8MMT128.rsp, [ENCRYPT] COUNT=0 @@ -733,7 +705,6 @@ public static void VerifyKnownTransform_CFB8_128_NoOrZeroPadding_0_Extended(Padd [Theory] [InlineData(PaddingMode.None)] [InlineData(PaddingMode.Zeros)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_128_NoOrZeroPadding_9_Extended(PaddingMode paddingMode) { // NIST CAVP AESMMT.ZIP CFB8MMT128.rsp, [ENCRYPT] COUNT=9 @@ -752,7 +723,6 @@ public static void VerifyKnownTransform_CFB8_128_NoOrZeroPadding_9_Extended(Padd [Theory] [InlineData(PaddingMode.None)] [InlineData(PaddingMode.Zeros)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_192_NoOrZeroPadding_0_Extended(PaddingMode paddingMode) { // NIST CAVP AESMMT.ZIP CFB8MMT192.rsp, [ENCRYPT] COUNT=0 @@ -771,7 +741,6 @@ public static void VerifyKnownTransform_CFB8_192_NoOrZeroPadding_0_Extended(Padd [Theory] [InlineData(PaddingMode.None)] [InlineData(PaddingMode.Zeros)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_192_NoOrZeroPadding_9_Extended(PaddingMode paddingMode) { // NIST CAVP AESMMT.ZIP CFB8MMT192.rsp, [ENCRYPT] COUNT=9 @@ -790,7 +759,6 @@ public static void VerifyKnownTransform_CFB8_192_NoOrZeroPadding_9_Extended(Padd [Theory] [InlineData(PaddingMode.None)] [InlineData(PaddingMode.Zeros)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_256_NoOrZeroPadding_0_Extended(PaddingMode paddingMode) { // NIST CAVP AESMMT.ZIP CFB8MMT256.rsp, [ENCRYPT] COUNT=0 @@ -809,7 +777,6 @@ public static void VerifyKnownTransform_CFB8_256_NoOrZeroPadding_0_Extended(Padd [Theory] [InlineData(PaddingMode.None)] [InlineData(PaddingMode.Zeros)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_256_NoOrZeroPadding_9_Extended(PaddingMode paddingMode) { // NIST CAVP AESMMT.ZIP CFB8MMT256.rsp, [ENCRYPT] COUNT=9 @@ -826,7 +793,6 @@ public static void VerifyKnownTransform_CFB8_256_NoOrZeroPadding_9_Extended(Padd } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_128_NoPadding_0() { // NIST CAVP AESMMT.ZIP CFB128MMT128.rsp, [ENCRYPT] COUNT=0 @@ -841,7 +807,6 @@ public static void VerifyKnownTransform_CFB128_128_NoPadding_0() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_128_NoPadding_1_Extended() { // NIST CAVP AESMMT.ZIP CFB128MMT128.rsp, [ENCRYPT] COUNT=1 @@ -856,7 +821,6 @@ public static void VerifyKnownTransform_CFB128_128_NoPadding_1_Extended() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_192_NoPadding_0_Extended() { // NIST CAVP AESMMT.ZIP CFB128MMT192.rsp, [ENCRYPT] COUNT=0 @@ -871,7 +835,6 @@ public static void VerifyKnownTransform_CFB128_192_NoPadding_0_Extended() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_192_NoPadding_1_Extended() { // NIST CAVP AESMMT.ZIP CFB128MMT192.rsp, [ENCRYPT] COUNT=1 @@ -885,23 +848,11 @@ public static void VerifyKnownTransform_CFB128_192_NoPadding_1_Extended() feedbackSize: 128); } - public static IEnumerable EncryptorReuse_LeadsToSameResultsData - { - get - { - yield return new object[] { CipherMode.CBC, 0 }; - - if (PlatformDetection.IsNotBrowser) - { - yield return new object[] { CipherMode.CFB, 128 }; - yield return new object[] { CipherMode.CFB, 8 }; - yield return new object[] { CipherMode.ECB, 0 }; - } - } - } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [MemberData(nameof(EncryptorReuse_LeadsToSameResultsData))] + [InlineData(CipherMode.CBC, 0)] + [InlineData(CipherMode.CFB, 128)] + [InlineData(CipherMode.CFB, 8)] + [InlineData(CipherMode.ECB, 0)] public static void EncryptorReuse_LeadsToSameResults(CipherMode cipherMode, int feedbackSize) { // AppleCCCryptor does not allow calling Reset on CFB cipher. @@ -928,7 +879,10 @@ public static void EncryptorReuse_LeadsToSameResults(CipherMode cipherMode, int } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [MemberData(nameof(EncryptorReuse_LeadsToSameResultsData))] + [InlineData(CipherMode.CBC, 0)] + [InlineData(CipherMode.CFB, 128)] + [InlineData(CipherMode.CFB, 8)] + [InlineData(CipherMode.ECB, 0)] public static void DecryptorReuse_LeadsToSameResults(CipherMode cipherMode, int feedbackSize) { // AppleCCCryptor does not allow calling Reset on CFB cipher. @@ -960,7 +914,6 @@ public static void DecryptorReuse_LeadsToSameResults(CipherMode cipherMode, int } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_256_NoPadding_0_Extended() { // NIST CAVP AESMMT.ZIP CFB128MMT256.rsp, [ENCRYPT] COUNT=0 @@ -975,7 +928,6 @@ public static void VerifyKnownTransform_CFB128_256_NoPadding_0_Extended() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_256_NoPadding_1_Extended() { // NIST CAVP AESMMT.ZIP CFB128MMT256.rsp, [ENCRYPT] COUNT=1 @@ -995,9 +947,9 @@ public static IEnumerable AesZeroPadData { yield return new object[] { CipherMode.CBC }; - if (PlatformDetection.IsNotBrowser && !PlatformDetection.IsWindows7) + if (!PlatformDetection.IsWindows7) { - // Browser and Windows 7 do not support CFB128. + // Windows 7 does not support CFB128. yield return new object[] { CipherMode.CFB }; } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesContractTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesContractTests.cs index 9de09ff120325c..1b4f6032248356 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesContractTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesContractTests.cs @@ -8,6 +8,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests { using Aes = System.Security.Cryptography.Aes; + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public class AesContractTests { [Fact] @@ -55,10 +56,7 @@ public static void LegalKeySizes() Assert.Equal(128, keySizeLimits.MinSize); Assert.Equal(256, keySizeLimits.MaxSize); - - // Browser's SubtleCrypto doesn't support AES-192 - int expectedKeySkipSize = PlatformDetection.IsBrowser ? 128 : 64; - Assert.Equal(expectedKeySkipSize, keySizeLimits.SkipSize); + Assert.Equal(64, keySizeLimits.SkipSize); } } @@ -109,7 +107,6 @@ public static void InvalidKeySizes(int invalidKeySize, bool skipOnNetfx) [InlineData(64, false)] [InlineData(256, true)] [InlineData(127, true)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void InvalidCFBFeedbackSizes(int feedbackSize, bool discoverableInSetter) { using (Aes aes = AesFactory.Create()) @@ -142,7 +139,6 @@ public static void InvalidCFBFeedbackSizes(int feedbackSize, bool discoverableIn [Theory] [InlineData(8)] [InlineData(128)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void ValidCFBFeedbackSizes(int feedbackSize) { // Windows 7 only supports CFB8. @@ -217,7 +213,6 @@ public static void VerifyKeyGeneration_128() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "AES-192 is not supported on Browser")] public static void VerifyKeyGeneration_192() { using (Aes aes = AesFactory.Create()) @@ -309,28 +304,25 @@ public static void CreateTransformExceptions() Assert.Throws(() => aes.CreateDecryptor(key, null)); } - if (PlatformDetection.IsNotBrowser) + using (Aes aes = AesFactory.Create()) { - using (Aes aes = AesFactory.Create()) - { - aes.Mode = CipherMode.ECB; + aes.Mode = CipherMode.ECB; - Assert.Throws(() => aes.CreateEncryptor(null, iv)); - Assert.Throws(() => aes.CreateEncryptor(null, null)); + Assert.Throws(() => aes.CreateEncryptor(null, iv)); + Assert.Throws(() => aes.CreateEncryptor(null, null)); - Assert.Throws(() => aes.CreateDecryptor(null, iv)); - Assert.Throws(() => aes.CreateDecryptor(null, null)); + Assert.Throws(() => aes.CreateDecryptor(null, iv)); + Assert.Throws(() => aes.CreateDecryptor(null, null)); - // ECB will accept an IV (but ignore it), and doesn't require it. - using (ICryptoTransform didNotThrow = aes.CreateEncryptor(key, null)) - { - Assert.NotNull(didNotThrow); - } + // ECB will accept an IV (but ignore it), and doesn't require it. + using (ICryptoTransform didNotThrow = aes.CreateEncryptor(key, null)) + { + Assert.NotNull(didNotThrow); + } - using (ICryptoTransform didNotThrow = aes.CreateDecryptor(key, null)) - { - Assert.NotNull(didNotThrow); - } + using (ICryptoTransform didNotThrow = aes.CreateDecryptor(key, null)) + { + Assert.NotNull(didNotThrow); } } } @@ -392,7 +384,6 @@ public static void ValidateOffsetAndCount() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void Cfb8ModeCanDepadCfb128Padding() { using (Aes aes = AesFactory.Create()) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCornerTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCornerTests.cs index 94b7a9c7210f99..ed21b358b0987f 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCornerTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCornerTests.cs @@ -12,6 +12,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests { using Aes = System.Security.Cryptography.Aes; + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public static class AesCornerTests { [Fact] diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesModeTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesModeTests.cs index 154ca9897156ba..1a496a505c6660 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesModeTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesModeTests.cs @@ -7,6 +7,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests { using Aes = System.Security.Cryptography.Aes; + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public class AesModeTests { [Fact] @@ -16,28 +17,24 @@ public static void SupportsCBC() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void SupportsECB() { SupportsMode(CipherMode.ECB); } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void SupportsCFB8() { SupportsMode(CipherMode.CFB, feedbackSize: 8); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void SupportsCFB128() { SupportsMode(CipherMode.CFB, feedbackSize: 128); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void Windows7DoesNotSupportCFB128() { DoesNotSupportMode(CipherMode.CFB, feedbackSize: 128); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/DecryptorReusability.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/DecryptorReusability.cs index 5665547eab4b74..f0761570a476bb 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/DecryptorReusability.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/DecryptorReusability.cs @@ -7,6 +7,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests { using Aes = System.Security.Cryptography.Aes; + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public static class DecryptorReusability { // See https://github.com/dotnet/runtime/issues/21354 for details 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 080f1888473391..7b56a7017c4bab 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 @@ -410,7 +410,6 @@ public void DerivedTypesDefineTest() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public void DecryptOneShot_Cfb8_ToleratesExtraPadding() { using (SymmetricAlgorithm alg = CreateAlgorithm()) @@ -470,7 +469,6 @@ public void DecryptOneShot_Cbc_InvalidPadding_DoesNotContainPlaintext(PaddingMod [InlineData(PaddingMode.PKCS7, 2048)] [InlineData(PaddingMode.ANSIX923, 2048)] [InlineData(PaddingMode.ISO10126, 2048)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public void DecryptOneShot_Ecb_InvalidPadding_DoesNotContainPlaintext(PaddingMode paddingMode, int ciphertextSize) { using (SymmetricAlgorithm alg = CreateAlgorithm()) @@ -497,7 +495,6 @@ public void DecryptOneShot_Ecb_InvalidPadding_DoesNotContainPlaintext(PaddingMod [InlineData(PaddingMode.PKCS7, 2048)] [InlineData(PaddingMode.ANSIX923, 2048)] [InlineData(PaddingMode.ISO10126, 2048)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public void DecryptOneShot_Cfb_InvalidPadding_DoesNotContainPlaintext(PaddingMode paddingMode, int ciphertextSize) { using (SymmetricAlgorithm alg = CreateAlgorithm()) @@ -550,7 +547,6 @@ public void DecryptOneShot_Cbc_TooShortDoesNotContainPlaintext(PaddingMode paddi [InlineData(PaddingMode.PKCS7)] [InlineData(PaddingMode.ANSIX923)] [InlineData(PaddingMode.ISO10126)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public void DecryptOneShot_Cfb8_TooShortDoesNotContainPlaintext(PaddingMode paddingMode) { using (SymmetricAlgorithm alg = CreateAlgorithm()) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateAuthority.cs b/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateAuthority.cs index e85100aeb71e28..184d8a62e99366 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateAuthority.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateAuthority.cs @@ -566,9 +566,18 @@ singleExtensions [1] EXPLICIT Extensions OPTIONAL } } else if (status == CertStatus.Revoked) { - // Android does not support all precisions for seconds - just omit fractional seconds for testing on Android writer.PushSequence(s_context1); - writer.WriteGeneralizedTime(revokedTime, omitFractionalSeconds: OperatingSystem.IsAndroid()); + + // Fracational seconds "MUST NOT" be used here. Android and macOS 13+ enforce this and + // reject GeneralizedTime's with fractional seconds, so omit them. + // RFC 6960: 4.2.2.1: + // The format for GeneralizedTime is as specified in Section 4.1.2.5.2 of [RFC5280]. + // RFC 5280 4.1.2.5.2: + // For the purposes of this profile, GeneralizedTime values MUST be + // expressed in Greenwich Mean Time (Zulu) and MUST include seconds + // (i.e., times are YYYYMMDDHHMMSSZ), even where the number of seconds + // is zero. GeneralizedTime values MUST NOT include fractional seconds. + writer.WriteGeneralizedTime(revokedTime, omitFractionalSeconds: true); writer.PopSequence(s_context1); } else @@ -845,8 +854,8 @@ internal static void BuildPrivatePki( rootAuthority = new CertificateAuthority( rootCert, rootDistributionViaHttp ? certUrl : null, - issuerRevocationViaCrl ? cdpUrl : null, - issuerRevocationViaOcsp ? ocspUrl : null); + issuerRevocationViaCrl || (endEntityRevocationViaCrl && intermediateAuthorityCount == 0) ? cdpUrl : null, + issuerRevocationViaOcsp || (endEntityRevocationViaOcsp && intermediateAuthorityCount == 0) ? ocspUrl : null); CertificateAuthority issuingAuthority = rootAuthority; intermediateAuthorities = new CertificateAuthority[intermediateAuthorityCount]; diff --git a/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/RevocationResponder.cs b/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/RevocationResponder.cs index 4e9e5e20612a94..b08655e6f1d10b 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/RevocationResponder.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/RevocationResponder.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using System.Web; +using Xunit; namespace System.Security.Cryptography.X509Certificates.Tests.Common { @@ -29,6 +30,7 @@ private readonly Dictionary _crlPaths public string UriPrefix { get; } public bool RespondEmpty { get; set; } + public AiaResponseKind AiaResponseKind { get; set; } public TimeSpan ResponseDelay { get; set; } public DelayedActionsFlag DelayedActions { get; set; } @@ -181,13 +183,13 @@ private void HandleRequest(HttpListenerContext context, ref bool responded) Thread.Sleep(ResponseDelay); } - byte[] certData = RespondEmpty ? Array.Empty() : authority.GetCertData(); + byte[] certData = RespondEmpty ? Array.Empty() : GetCertDataForAiaResponseKind(AiaResponseKind, authority); responded = true; context.Response.StatusCode = 200; - context.Response.ContentType = "application/pkix-cert"; + context.Response.ContentType = AiaResponseKindToContentType(AiaResponseKind); context.Response.Close(certData, willBlock: true); - Trace($"Responded with {certData.Length}-byte certificate from {authority.SubjectName}."); + Trace($"Responded with {certData.Length}-byte {AiaResponseKind} from {authority.SubjectName}."); return; } @@ -295,6 +297,41 @@ private static HttpListener OpenListener(out string uriPrefix) } } + private static string AiaResponseKindToContentType(AiaResponseKind kind) + { + if (kind == AiaResponseKind.Cert) + { + return "application/pkix-cert"; + } + else if (kind == AiaResponseKind.Pkcs12) + { + return "application/x-pkcs12"; + } + else + { + Assert.True(false, $"Unknown value AiaResponseKind.`{kind}`."); + return null; + } + } + + private static byte[] GetCertDataForAiaResponseKind(AiaResponseKind kind, CertificateAuthority authority) + { + if (kind == AiaResponseKind.Cert) + { + return authority.GetCertData(); + } + else if (kind == AiaResponseKind.Pkcs12) + { + using X509Certificate2 cert = new X509Certificate2(authority.GetCertData()); + return cert.Export(X509ContentType.Pkcs12); + } + else + { + Assert.True(false, $"Unknown value AiaResponseKind.`{kind}`."); + return null; + } + } + private static bool TryGetOcspRequestBytes(HttpListenerRequest request, string prefix, out byte[] requestBytes) { requestBytes = null; @@ -425,4 +462,10 @@ public enum DelayedActionsFlag : byte Aia = 0b100, All = 0b11111111 } + + public enum AiaResponseKind + { + Cert = 0, + Pkcs12 = 1, + } } diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs index 33c4f5f9304230..256a3075b41295 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs @@ -14,7 +14,7 @@ public static partial class PlatformDetection // do it in a way that failures don't cascade. // - private static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + public static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); public static bool IsOpenSUSE => IsDistroAndVersion("opensuse"); public static bool IsUbuntu => IsDistroAndVersion("ubuntu"); public static bool IsDebian => IsDistroAndVersion("debian"); diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 449f9900c33d3a..de9db9d8018fff 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -60,7 +60,9 @@ public static partial class PlatformDetection public static bool IsNotArm64Process => !IsArm64Process; public static bool IsArmOrArm64Process => IsArmProcess || IsArm64Process; public static bool IsNotArmNorArm64Process => !IsArmOrArm64Process; + public static bool IsS390xProcess => (int)RuntimeInformation.ProcessArchitecture == 5; // Architecture.S390x public static bool IsArmv6Process => (int)RuntimeInformation.ProcessArchitecture == 7; // Architecture.Armv6 + public static bool IsPpc64leProcess => (int)RuntimeInformation.ProcessArchitecture == 8; // Architecture.Ppc64le public static bool IsX64Process => RuntimeInformation.ProcessArchitecture == Architecture.X64; public static bool IsX86Process => RuntimeInformation.ProcessArchitecture == Architecture.X86; public static bool IsNotX86Process => !IsX86Process; diff --git a/src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http2/HPackDecoderTest.cs b/src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http2/HPackDecoderTest.cs index e5f3c3b698e21d..5f4390db033d24 100644 --- a/src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http2/HPackDecoderTest.cs +++ b/src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http2/HPackDecoderTest.cs @@ -46,8 +46,13 @@ public class HPackDecoderTests private const string _headerNameString = "new-header"; + // On purpose longer than 4096 (DefaultStringOctetsSize from HPackDecoder) to trigger https://github.com/dotnet/runtime/issues/78516 + private static readonly string _literalHeaderNameString = string.Concat(Enumerable.Range(0, 4100).Select(c => (char)('a' + (c % 26)))); + private static readonly byte[] _headerNameBytes = Encoding.ASCII.GetBytes(_headerNameString); + private static readonly byte[] _literalHeaderNameBytes = Encoding.ASCII.GetBytes(_literalHeaderNameString); + // n e w - h e a d e r * // 10101000 10111110 00010110 10011100 10100011 10010000 10110110 01111111 private static readonly byte[] _headerNameHuffmanBytes = new byte[] { 0xa8, 0xbe, 0x16, 0x9c, 0xa3, 0x90, 0xb6, 0x7f }; @@ -64,6 +69,12 @@ public class HPackDecoderTests .Concat(_headerNameBytes) .ToArray(); + // size = 4096 ==> 0x7f, 0x81, 0x1f (7+) prefixed integer + // size = 4100 ==> 0x7f, 0x85, 0x1f (7+) prefixed integer + private static readonly byte[] _literalHeaderName = new byte[] { 0x7f, 0x85, 0x1f } // 4100 + .Concat(_literalHeaderNameBytes) + .ToArray(); + private static readonly byte[] _headerNameHuffman = new byte[] { (byte)(0x80 | _headerNameHuffmanBytes.Length) } .Concat(_headerNameHuffmanBytes) .ToArray(); @@ -392,6 +403,101 @@ public void DecodesLiteralHeaderFieldNeverIndexed_IndexedName_OutOfRange_Error() Assert.Empty(_handler.DecodedHeaders); } + [Fact] + public void DecodesLiteralHeaderFieldNeverIndexed_NewName_SingleBuffer() + { + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName + .Concat(_literalHeaderName) + .Concat(_headerValue) + .ToArray(); + + _decoder.Decode(encoded, endHeaders: true, handler: _handler); + + Assert.Equal(1, _handler.DecodedHeaders.Count); + Assert.True(_handler.DecodedHeaders.ContainsKey(_literalHeaderNameString)); + Assert.Equal(_headerValueString, _handler.DecodedHeaders[_literalHeaderNameString]); + } + + [Fact] + public void DecodesLiteralHeaderFieldNeverIndexed_NewName_NameLengthBrokenIntoSeparateBuffers() + { + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName + .Concat(_literalHeaderName) + .Concat(_headerValue) + .ToArray(); + + _decoder.Decode(encoded[..1], endHeaders: false, handler: _handler); + _decoder.Decode(encoded[1..], endHeaders: true, handler: _handler); + + Assert.Equal(1, _handler.DecodedHeaders.Count); + Assert.True(_handler.DecodedHeaders.ContainsKey(_literalHeaderNameString)); + Assert.Equal(_headerValueString, _handler.DecodedHeaders[_literalHeaderNameString]); + } + + [Fact] + public void DecodesLiteralHeaderFieldNeverIndexed_NewName_NameBrokenIntoSeparateBuffers() + { + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName + .Concat(_literalHeaderName) + .Concat(_headerValue) + .ToArray(); + + _decoder.Decode(encoded[..(_literalHeaderNameString.Length / 2)], endHeaders: false, handler: _handler); + _decoder.Decode(encoded[(_literalHeaderNameString.Length / 2)..], endHeaders: true, handler: _handler); + + Assert.Equal(1, _handler.DecodedHeaders.Count); + Assert.True(_handler.DecodedHeaders.ContainsKey(_literalHeaderNameString)); + Assert.Equal(_headerValueString, _handler.DecodedHeaders[_literalHeaderNameString]); + } + + [Fact] + public void DecodesLiteralHeaderFieldNeverIndexed_NewName_NameAndValueBrokenIntoSeparateBuffers() + { + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName + .Concat(_literalHeaderName) + .Concat(_headerValue) + .ToArray(); + + _decoder.Decode(encoded[..^_headerValue.Length], endHeaders: false, handler: _handler); + _decoder.Decode(encoded[^_headerValue.Length..], endHeaders: true, handler: _handler); + + Assert.Equal(1, _handler.DecodedHeaders.Count); + Assert.True(_handler.DecodedHeaders.ContainsKey(_literalHeaderNameString)); + Assert.Equal(_headerValueString, _handler.DecodedHeaders[_literalHeaderNameString]); + } + + [Fact] + public void DecodesLiteralHeaderFieldNeverIndexed_NewName_ValueLengthBrokenIntoSeparateBuffers() + { + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName + .Concat(_literalHeaderName) + .Concat(_headerValue) + .ToArray(); + + _decoder.Decode(encoded[..^(_headerValue.Length - 1)], endHeaders: false, handler: _handler); + _decoder.Decode(encoded[^(_headerValue.Length - 1)..], endHeaders: true, handler: _handler); + + Assert.Equal(1, _handler.DecodedHeaders.Count); + Assert.True(_handler.DecodedHeaders.ContainsKey(_literalHeaderNameString)); + Assert.Equal(_headerValueString, _handler.DecodedHeaders[_literalHeaderNameString]); + } + + [Fact] + public void DecodesLiteralHeaderFieldNeverIndexed_NewName_ValueBrokenIntoSeparateBuffers() + { + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName + .Concat(_literalHeaderName) + .Concat(_headerValue) + .ToArray(); + + _decoder.Decode(encoded[..^(_headerValueString.Length / 2)], endHeaders: false, handler: _handler); + _decoder.Decode(encoded[^(_headerValueString.Length / 2)..], endHeaders: true, handler: _handler); + + Assert.Equal(1, _handler.DecodedHeaders.Count); + Assert.True(_handler.DecodedHeaders.ContainsKey(_literalHeaderNameString)); + Assert.Equal(_headerValueString, _handler.DecodedHeaders[_literalHeaderNameString]); + } + [Fact] public void DecodesDynamicTableSizeUpdate() { diff --git a/src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http3/QPackDecoderTest.cs b/src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http3/QPackDecoderTest.cs index 475fddeab09bdf..8db70d84ee05e1 100644 --- a/src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http3/QPackDecoderTest.cs +++ b/src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http3/QPackDecoderTest.cs @@ -25,11 +25,11 @@ public class QPackDecoderTests // 4.5.4 - Literal Header Field With Name Reference - Static Table - Index 44 (content-type) private static readonly byte[] _literalHeaderFieldWithNameReferenceStatic = new byte[] { 0x5f, 0x1d }; - // 4.5.6 - Literal Field Line With Literal Name - (translate) - private static readonly byte[] _literalFieldLineWithLiteralName = new byte[] { 0x37, 0x02, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65 }; + // 4.5.6 - Literal Field Line With Literal Name - (literal-header-field) + private static readonly byte[] _literalFieldLineWithLiteralName = new byte[] { 0x37, 0x0d, 0x6c, 0x69, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x2d, 0x66, 0x69, 0x65, 0x6c, 0x64 }; private const string _contentTypeString = "content-type"; - private const string _translateString = "translate"; + private const string _literalHeaderFieldString = "literal-header-field"; // n e w - h e a d e r * // 10101000 10111110 00010110 10011100 10100011 10010000 10110110 01111111 @@ -97,7 +97,7 @@ public void DecodesLiteralFieldLineWithLiteralName_Value() .Concat(_headerValue) .ToArray(); - TestDecodeWithoutIndexing(encoded, _translateString, _headerValueString); + TestDecodeWithoutIndexing(encoded, _literalHeaderFieldString, _headerValueString); } [Fact] @@ -140,7 +140,7 @@ public void DecodesLiteralFieldLineWithLiteralName_HuffmanEncodedValue() .Concat(_headerValueHuffman) .ToArray(); - TestDecodeWithoutIndexing(encoded, _translateString, _headerValueString); + TestDecodeWithoutIndexing(encoded, _literalHeaderFieldString, _headerValueString); } [Fact] @@ -173,6 +173,101 @@ public void DecodesLiteralFieldLineWithLiteralName_LargeValues() }); } + [Fact] + public void LiteralFieldWithoutNameReference_SingleBuffer() + { + byte[] encoded = _literalFieldLineWithLiteralName + .Concat(_headerValue) + .ToArray(); + + _decoder.Decode(new byte[] { 0x00, 0x00 }, endHeaders: false, handler: _handler); + _decoder.Decode(encoded, endHeaders: true, handler: _handler); + + Assert.Equal(1, _handler.DecodedHeaders.Count); + Assert.True(_handler.DecodedHeaders.ContainsKey(_literalHeaderFieldString)); + Assert.Equal(_headerValueString, _handler.DecodedHeaders[_literalHeaderFieldString]); + } + + [Fact] + public void LiteralFieldWithoutNameReference_NameLengthBrokenIntoSeparateBuffers() + { + byte[] encoded = _literalFieldLineWithLiteralName + .Concat(_headerValue) + .ToArray(); + + _decoder.Decode(new byte[] { 0x00, 0x00 }, endHeaders: false, handler: _handler); + _decoder.Decode(encoded[..1], endHeaders: false, handler: _handler); + _decoder.Decode(encoded[1..], endHeaders: true, handler: _handler); + + Assert.Equal(1, _handler.DecodedHeaders.Count); + Assert.True(_handler.DecodedHeaders.ContainsKey(_literalHeaderFieldString)); + Assert.Equal(_headerValueString, _handler.DecodedHeaders[_literalHeaderFieldString]); + } + + [Fact] + public void LiteralFieldWithoutNameReference_NameBrokenIntoSeparateBuffers() + { + byte[] encoded = _literalFieldLineWithLiteralName + .Concat(_headerValue) + .ToArray(); + + _decoder.Decode(new byte[] { 0x00, 0x00 }, endHeaders: false, handler: _handler); + _decoder.Decode(encoded[..(_literalHeaderFieldString.Length / 2)], endHeaders: false, handler: _handler); + _decoder.Decode(encoded[(_literalHeaderFieldString.Length / 2)..], endHeaders: true, handler: _handler); + + Assert.Equal(1, _handler.DecodedHeaders.Count); + Assert.True(_handler.DecodedHeaders.ContainsKey(_literalHeaderFieldString)); + Assert.Equal(_headerValueString, _handler.DecodedHeaders[_literalHeaderFieldString]); + } + + [Fact] + public void LiteralFieldWithoutNameReference_NameAndValueBrokenIntoSeparateBuffers() + { + byte[] encoded = _literalFieldLineWithLiteralName + .Concat(_headerValue) + .ToArray(); + + _decoder.Decode(new byte[] { 0x00, 0x00 }, endHeaders: false, handler: _handler); + _decoder.Decode(encoded[..^_headerValue.Length], endHeaders: false, handler: _handler); + _decoder.Decode(encoded[^_headerValue.Length..], endHeaders: true, handler: _handler); + + Assert.Equal(1, _handler.DecodedHeaders.Count); + Assert.True(_handler.DecodedHeaders.ContainsKey(_literalHeaderFieldString)); + Assert.Equal(_headerValueString, _handler.DecodedHeaders[_literalHeaderFieldString]); + } + + [Fact] + public void LiteralFieldWithoutNameReference_ValueLengthBrokenIntoSeparateBuffers() + { + byte[] encoded = _literalFieldLineWithLiteralName + .Concat(_headerValue) + .ToArray(); + + _decoder.Decode(new byte[] { 0x00, 0x00 }, endHeaders: false, handler: _handler); + _decoder.Decode(encoded[..^(_headerValue.Length - 1)], endHeaders: false, handler: _handler); + _decoder.Decode(encoded[^(_headerValue.Length - 1)..], endHeaders: true, handler: _handler); + + Assert.Equal(1, _handler.DecodedHeaders.Count); + Assert.True(_handler.DecodedHeaders.ContainsKey(_literalHeaderFieldString)); + Assert.Equal(_headerValueString, _handler.DecodedHeaders[_literalHeaderFieldString]); + } + + [Fact] + public void LiteralFieldWithoutNameReference_ValueBrokenIntoSeparateBuffers() + { + byte[] encoded = _literalFieldLineWithLiteralName + .Concat(_headerValue) + .ToArray(); + + _decoder.Decode(new byte[] { 0x00, 0x00 }, endHeaders: false, handler: _handler); + _decoder.Decode(encoded[..^(_headerValueString.Length / 2)], endHeaders: false, handler: _handler); + _decoder.Decode(encoded[^(_headerValueString.Length / 2)..], endHeaders: true, handler: _handler); + + Assert.Equal(1, _handler.DecodedHeaders.Count); + Assert.True(_handler.DecodedHeaders.ContainsKey(_literalHeaderFieldString)); + Assert.Equal(_headerValueString, _handler.DecodedHeaders[_literalHeaderFieldString]); + } + public static readonly TheoryData _incompleteHeaderBlockData = new TheoryData { // Incomplete header diff --git a/src/libraries/Common/tests/Tests/System/StringTests.cs b/src/libraries/Common/tests/Tests/System/StringTests.cs index 7d2558a9593243..c16a31a1f60140 100644 --- a/src/libraries/Common/tests/Tests/System/StringTests.cs +++ b/src/libraries/Common/tests/Tests/System/StringTests.cs @@ -4697,14 +4697,22 @@ public static void Remove_Invalid() [InlineData("Aaaaaaaa", 'A', 'a', "aaaaaaaa")] // Single iteration of vectorised path; no remainders through non-vectorised path // Three leading 'a's before a match (copyLength > 0), Single iteration of vectorised path; no remainders through non-vectorised path [InlineData("aaaAaaaaaaa", 'A', 'a', "aaaaaaaaaaa")] - // Single iteration of vectorised path; 3 remainders through non-vectorised path + // Single iteration of vectorised path; 3 remainders handled by vectorized path [InlineData("AaaaaaaaaAa", 'A', 'a', "aaaaaaaaaaa")] + // Single iteration of vectorized path; 0 remainders handled by vectorized path + [InlineData("aaaaaaaaaAa", 'A', 'a', "aaaaaaaaaaa")] + // Eight chars before a match (copyLength > 0), single iteration of vectorized path for the remainder + [InlineData("12345678AAAAAAA", 'A', 'a', "12345678aaaaaaa")] // ------------------------- For Vector.Count == 16 (AVX2) ------------------------- [InlineData("AaaaaaaaAaaaaaaa", 'A', 'a', "aaaaaaaaaaaaaaaa")] // Single iteration of vectorised path; no remainders through non-vectorised path // Three leading 'a's before a match (copyLength > 0), Single iteration of vectorised path; no remainders through non-vectorised path [InlineData("aaaAaaaaaaaAaaaaaaa", 'A', 'a', "aaaaaaaaaaaaaaaaaaa")] - // Single iteration of vectorised path; 3 remainders through non-vectorised path + // Single iteration of vectorised path; 3 remainders handled by vectorized path [InlineData("AaaaaaaaAaaaaaaaaAa", 'A', 'a', "aaaaaaaaaaaaaaaaaaa")] + // Single iteration of vectorized path; 0 remainders handled by vectorized path + [InlineData("aaaaaaaaaaaaaaaaaAa", 'A', 'a', "aaaaaaaaaaaaaaaaaaa")] + // Sixteen chars before a match (copyLength > 0), single iteration of vectorized path for the remainder + [InlineData("1234567890123456AAAAAAAAAAAAAAA", 'A', 'a', "1234567890123456aaaaaaaaaaaaaaa")] // ----------------------------------- General test data ----------------------------------- [InlineData("Hello", 'l', '!', "He!!o")] // 2 match, non-vectorised path [InlineData("Hello", 'e', 'e', "Hello")] // oldChar and newChar are same; nothing to replace diff --git a/src/libraries/Directory.Build.props b/src/libraries/Directory.Build.props index 2e3613356868ee..2a5efe383b46c4 100644 --- a/src/libraries/Directory.Build.props +++ b/src/libraries/Directory.Build.props @@ -22,10 +22,12 @@ true true - true + true + true + true - false + false @@ -36,14 +38,14 @@ '$(IsReferenceAssemblyProject)' != 'true' and '$(IsGeneratorProject)' != 'true' and '$(IsTestProject)' != 'true' and - '$(IsTrimmingTestProject)' != 'true' and + '$(IsPublishedAppTestProject)' != 'true' and '$(IsTestSupportProject)' != 'true' and '$(UsingMicrosoftNoTargetsSdk)' != 'true' and '$(UsingMicrosoftTraversalSdk)' != 'true'">true - + $(NoWarn);SYSLIB0011 @@ -131,13 +133,13 @@ - $(ArtifactsBinDir)sdk-no-workload\ + $(ArtifactsBinDir)dotnet-none\ $([MSBuild]::NormalizeDirectory($(SdkWithNoWorkloadForTestingPath))) $(SdkWithNoWorkloadForTestingPath)version-$(SdkVersionForWorkloadTesting).stamp $(SdkWithNoWorkloadForTestingPath)workload.stamp - $(ArtifactsBinDir)dotnet-workload\ + $(ArtifactsBinDir)dotnet-net7\ $([MSBuild]::NormalizeDirectory($(SdkWithWorkloadForTestingPath))) $(SdkWithWorkloadForTestingPath)version-$(SdkVersionForWorkloadTesting).stamp diff --git a/src/libraries/Directory.Build.targets b/src/libraries/Directory.Build.targets index 8f9b52adb2805d..46cc84f810c209 100644 --- a/src/libraries/Directory.Build.targets +++ b/src/libraries/Directory.Build.targets @@ -127,7 +127,7 @@ - + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs index a26b2fe68e7190..d06473e9c8a264 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs @@ -262,7 +262,10 @@ private static void BindProperty(PropertyInfo property, object instance, IConfig config.GetSection(GetPropertyName(property)), options); - if (propertyBindingPoint.HasNewValue) + // For property binding, there are some cases when HasNewValue is not set in BindingPoint while a non-null Value inside that object can be retrieved from the property getter. + // As example, when binding a property which not having a configuration entry matching this property and the getter can initialize the Value. + // It is important to call the property setter as the setters can have a logic adjusting the Value. + if (!propertyBindingPoint.IsReadOnly && propertyBindingPoint.Value is not null) { property.SetValue(instance, propertyBindingPoint.Value); } @@ -299,43 +302,44 @@ private static void BindInstance( if (config != null && config.GetChildren().Any()) { - // for arrays, collections, and read-only list-like interfaces, we concatenate on to what is already there - if (type.IsArray || IsArrayCompatibleInterface(type)) + // for arrays and read-only list-like interfaces, we concatenate on to what is already there, if we can + if (type.IsArray || IsImmutableArrayCompatibleInterface(type)) { if (!bindingPoint.IsReadOnly) { bindingPoint.SetValue(BindArray(type, (IEnumerable?)bindingPoint.Value, config, options)); } + + // for getter-only collection properties that we can't add to, nothing more we can do return; } - // for sets and read-only set interfaces, we clone what's there into a new collection. + // for sets and read-only set interfaces, we clone what's there into a new collection, if we can if (TypeIsASetInterface(type)) { - if (!bindingPoint.IsReadOnly) + if (!bindingPoint.IsReadOnly || bindingPoint.Value is not null) { object? newValue = BindSet(type, (IEnumerable?)bindingPoint.Value, config, options); - if (newValue != null) + if (!bindingPoint.IsReadOnly && newValue != null) { bindingPoint.SetValue(newValue); } } + return; } // For other mutable interfaces like ICollection<>, IDictionary<,> and ISet<>, we prefer copying values and setting them // on a new instance of the interface over populating the existing instance implementing the interface. // This has already been done, so there's not need to check again. - if (TypeIsADictionaryInterface(type)) + if (TypeIsADictionaryInterface(type) && !bindingPoint.IsReadOnly) { - if (!bindingPoint.IsReadOnly) + object? newValue = BindDictionaryInterface(bindingPoint.Value, type, config, options); + if (newValue != null) { - object? newValue = BindDictionaryInterface(bindingPoint.Value, type, config, options); - if (newValue != null) - { - bindingPoint.SetValue(newValue); - } + bindingPoint.SetValue(newValue); } + return; } @@ -348,12 +352,19 @@ private static void BindInstance( return; } - // For other mutable interfaces like ICollection<> and ISet<>, we prefer copying values and setting them - // on a new instance of the interface over populating the existing instance implementing the interface. - // This has already been done, so there's not need to check again. For dictionaries, we fill the existing - // instance if there is one (which hasn't happened yet), and only create a new instance if necessary. + Type? interfaceGenericType = type.IsInterface && type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : null; - bindingPoint.SetValue(CreateInstance(type, config, options)); + if (interfaceGenericType is not null && + (interfaceGenericType == typeof(ICollection<>) || interfaceGenericType == typeof(IList<>))) + { + // For ICollection and IList we bind them to mutable List type. + Type genericType = typeof(List<>).MakeGenericType(type.GenericTypeArguments[0]); + bindingPoint.SetValue(Activator.CreateInstance(genericType)); + } + else + { + bindingPoint.SetValue(CreateInstance(type, config, options)); + } } // At this point we know that we have a non-null bindingPoint.Value, we just have to populate the items @@ -362,7 +373,7 @@ private static void BindInstance( if (dictionaryInterface != null) { - BindConcreteDictionary(bindingPoint.Value!, dictionaryInterface, config, options); + BindDictionary(bindingPoint.Value!, dictionaryInterface, config, options); } else { @@ -520,49 +531,60 @@ private static bool CanBindToTheseConstructorParameters(ParameterInfo[] construc return null; } - Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); - MethodInfo addMethod = genericType.GetMethod("Add", DeclaredOnlyLookup)!; - - Type kvpType = typeof(KeyValuePair<,>).MakeGenericType(keyType, valueType); - PropertyInfo keyMethod = kvpType.GetProperty("Key", DeclaredOnlyLookup)!; - PropertyInfo valueMethod = kvpType.GetProperty("Value", DeclaredOnlyLookup)!; - - object dictionary = Activator.CreateInstance(genericType)!; - - var orig = source as IEnumerable; - object?[] arguments = new object?[2]; - - if (orig != null) + // addMethod can only be null if dictionaryType is IReadOnlyDictionary rather than IDictionary. + MethodInfo? addMethod = dictionaryType.GetMethod("Add", DeclaredOnlyLookup); + if (addMethod is null || source is null) { - foreach (object? item in orig) + dictionaryType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); + var dictionary = Activator.CreateInstance(dictionaryType); + addMethod = dictionaryType.GetMethod("Add", DeclaredOnlyLookup); + + var orig = source as IEnumerable; + if (orig is not null) { - object? k = keyMethod.GetMethod!.Invoke(item, null); - object? v = valueMethod.GetMethod!.Invoke(item, null); - arguments[0] = k; - arguments[1] = v; - addMethod.Invoke(dictionary, arguments); + Type kvpType = typeof(KeyValuePair<,>).MakeGenericType(keyType, valueType); + PropertyInfo keyMethod = kvpType.GetProperty("Key", DeclaredOnlyLookup)!; + PropertyInfo valueMethod = kvpType.GetProperty("Value", DeclaredOnlyLookup)!; + object?[] arguments = new object?[2]; + + foreach (object? item in orig) + { + object? k = keyMethod.GetMethod!.Invoke(item, null); + object? v = valueMethod.GetMethod!.Invoke(item, null); + arguments[0] = k; + arguments[1] = v; + addMethod!.Invoke(dictionary, arguments); + } } + + source = dictionary; } - BindConcreteDictionary(dictionary, dictionaryType, config, options); + Debug.Assert(source is not null); + Debug.Assert(addMethod is not null); + + BindDictionary(source, dictionaryType, config, options); - return dictionary; + return source; } - // Binds and potentially overwrites a concrete dictionary. + // Binds and potentially overwrites a dictionary object. // This differs from BindDictionaryInterface because this method doesn't clone // the dictionary; it sets and/or overwrites values directly. - // When a user specifies a concrete dictionary in their config class, then that - // value is used as-us. When a user specifies an interface (instantiated) in their config class, - // then it is cloned to a new dictionary, the same way as other collections. + // When a user specifies a concrete dictionary or a concrete class implementing IDictionary<,> + // in their config class, then that value is used as-is. When a user specifies an interface (instantiated) + // in their config class, then it is cloned to a new dictionary, the same way as other collections. [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode("Cannot statically analyze what the element type is of the value objects in the dictionary so its members may be trimmed.")] - private static void BindConcreteDictionary( - object? dictionary, + private static void BindDictionary( + object dictionary, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type dictionaryType, IConfiguration config, BinderOptions options) { + Debug.Assert(dictionaryType.IsGenericType && + (dictionaryType.GetGenericTypeDefinition() == typeof(IDictionary<,>) || dictionaryType.GetGenericTypeDefinition() == typeof(Dictionary<,>))); + Type keyType = dictionaryType.GenericTypeArguments[0]; Type valueType = dictionaryType.GenericTypeArguments[1]; bool keyTypeIsEnum = keyType.IsEnum; @@ -582,10 +604,17 @@ private static void BindConcreteDictionary( return; } - Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); + Debug.Assert(dictionary is not null); + + MethodInfo tryGetValue = dictionaryType.GetMethod("TryGetValue", DeclaredOnlyLookup)!; + PropertyInfo? indexerProperty = dictionaryType.GetProperty("Item", DeclaredOnlyLookup); + + if (indexerProperty is null || !indexerProperty.CanWrite) + { + // Cannot set any item on the dictionary object. + return; + } - MethodInfo tryGetValue = dictionaryType.GetMethod("TryGetValue")!; - PropertyInfo setter = genericType.GetProperty("Item", DeclaredOnlyLookup)!; foreach (IConfigurationSection child in config.GetChildren()) { try @@ -608,7 +637,7 @@ private static void BindConcreteDictionary( options: options); if (valueBindingPoint.HasNewValue) { - setter.SetValue(dictionary, valueBindingPoint.Value, new object[] { key }); + indexerProperty.SetValue(dictionary, valueBindingPoint.Value, new object[] { key }); } } catch @@ -709,32 +738,38 @@ private static Array BindArray(Type type, IEnumerable? source, IConfiguration co { Type elementType = type.GetGenericArguments()[0]; - Type keyType = type.GenericTypeArguments[0]; - - bool keyTypeIsEnum = keyType.IsEnum; + bool keyTypeIsEnum = elementType.IsEnum; - if (keyType != typeof(string) && !keyTypeIsEnum) + if (elementType != typeof(string) && !keyTypeIsEnum) { // We only support string and enum keys return null; } - Type genericType = typeof(HashSet<>).MakeGenericType(keyType); - object instance = Activator.CreateInstance(genericType)!; - - MethodInfo addMethod = genericType.GetMethod("Add", DeclaredOnlyLookup)!; - object?[] arguments = new object?[1]; - - if (source != null) + // addMethod can only be null if type is IReadOnlySet rather than ISet. + MethodInfo? addMethod = type.GetMethod("Add", DeclaredOnlyLookup); + if (addMethod is null || source is null) { - foreach (object? item in source) + Type genericType = typeof(HashSet<>).MakeGenericType(elementType); + object instance = Activator.CreateInstance(genericType)!; + addMethod = genericType.GetMethod("Add", DeclaredOnlyLookup); + + if (source != null) { - arguments[0] = item; - addMethod.Invoke(instance, arguments); + foreach (object? item in source) + { + arguments[0] = item; + addMethod!.Invoke(instance, arguments); + } } + + source = (IEnumerable)instance; } + Debug.Assert(source is not null); + Debug.Assert(addMethod is not null); + foreach (IConfigurationSection section in config.GetChildren()) { var itemBindingPoint = new BindingPoint(); @@ -749,7 +784,7 @@ private static Array BindArray(Type type, IEnumerable? source, IConfiguration co { arguments[0] = itemBindingPoint.Value; - addMethod.Invoke(instance, arguments); + addMethod.Invoke(source, arguments); } } catch @@ -757,7 +792,7 @@ private static Array BindArray(Type type, IEnumerable? source, IConfiguration co } } - return instance; + return source; } [RequiresUnreferencedCode(TrimmingWarningMessage)] @@ -836,14 +871,12 @@ private static bool TypeIsADictionaryInterface(Type type) || genericTypeDefinition == typeof(IReadOnlyDictionary<,>); } - private static bool IsArrayCompatibleInterface(Type type) + private static bool IsImmutableArrayCompatibleInterface(Type type) { if (!type.IsInterface || !type.IsConstructedGenericType) { return false; } Type genericTypeDefinition = type.GetGenericTypeDefinition(); return genericTypeDefinition == typeof(IEnumerable<>) - || genericTypeDefinition == typeof(ICollection<>) - || genericTypeDefinition == typeof(IList<>) || genericTypeDefinition == typeof(IReadOnlyCollection<>) || genericTypeDefinition == typeof(IReadOnlyList<>); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Microsoft.Extensions.Configuration.Binder.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Microsoft.Extensions.Configuration.Binder.csproj index f5517f57f23c64..102bef02ec1c2c 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Microsoft.Extensions.Configuration.Binder.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Microsoft.Extensions.Configuration.Binder.csproj @@ -5,6 +5,8 @@ true true true + 4 + false Functionality to bind an object to data in configuration providers for Microsoft.Extensions.Configuration. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs index 9eae194f02cf99..7c2792780ea263 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Test; using System; using System.Collections; using System.Collections.Generic; @@ -72,6 +74,8 @@ public string ReadOnly public ISet InstantiatedISet { get; set; } = new HashSet(); + public ISet ISetNoSetter { get; } = new HashSet(); + public HashSet InstantiatedHashSetWithSomeValues { get; set; } = new HashSet(new[] {"existing1", "existing2"}); @@ -662,6 +666,27 @@ public void CanBindNonInstantiatedISet() Assert.Equal("Yo2", options.NonInstantiatedISet.ElementAt(1)); } + [Fact] + public void CanBindISetNoSetter() + { + var dic = new Dictionary + { + {"ISetNoSetter:0", "Yo1"}, + {"ISetNoSetter:1", "Yo2"}, + {"ISetNoSetter:2", "Yo2"}, + }; + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + + var config = configurationBuilder.Build(); + + var options = config.Get()!; + + Assert.Equal(2, options.ISetNoSetter.Count); + Assert.Equal("Yo1", options.ISetNoSetter.ElementAt(0)); + Assert.Equal("Yo2", options.ISetNoSetter.ElementAt(1)); + } + #if NETCOREAPP [Fact] public void CanBindInstantiatedIReadOnlySet() @@ -2555,6 +2580,61 @@ public void CanBindPrivatePropertiesFromBaseClass() Assert.Equal("a", test.ExposePrivatePropertyValue()); } + [Fact] + public void EnsureCallingThePropertySetter() + { + var json = @"{ + ""IPFiltering"": { + ""HttpStatusCode"": 401, + ""Blacklist"": [ ""192.168.0.10-192.168.10.20"", ""fe80::/10"" ] + } + }"; + + var configuration = new ConfigurationBuilder() + .AddJsonStream(TestStreamHelpers.StringToStream(json)) + .Build(); + + OptionWithCollectionProperties options = configuration.GetSection("IPFiltering").Get(); + + Assert.NotNull(options); + Assert.Equal(2, options.Blacklist.Count); + Assert.Equal("192.168.0.10-192.168.10.20", options.Blacklist.ElementAt(0)); + Assert.Equal("fe80::/10", options.Blacklist.ElementAt(1)); + + Assert.Equal(2, options.ParsedBlacklist.Count); // should be initialized when calling the options.Blacklist setter. + + Assert.Equal(401, options.HttpStatusCode); // exists in configuration and properly sets the property + Assert.Equal(2, options.OtherCode); // doesn't exist in configuration. the setter sets default value '2' + } + + public class OptionWithCollectionProperties + { + private int _otherCode; + private ICollection blacklist = new HashSet(); + + public ICollection Blacklist + { + get => this.blacklist; + set + { + this.blacklist = value ?? new HashSet(); + this.ParsedBlacklist = this.blacklist.Select(b => b).ToList(); + } + } + + public int HttpStatusCode { get; set; } = 0; + + // ParsedBlacklist initialized using the setter of Blacklist. + public ICollection ParsedBlacklist { get; private set; } = new HashSet(); + + // This property not having any match in the configuration. Still the setter need to be called during the binding. + public int OtherCode + { + get => _otherCode; + set => _otherCode = value == 0 ? 2 : value; + } + } + private interface ISomeInterface { } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationCollectionBindingTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationCollectionBindingTests.cs index 7367d0664cf356..7f3bb05cc40ac8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationCollectionBindingTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationCollectionBindingTests.cs @@ -393,6 +393,11 @@ public void AlreadyInitializedListInterfaceBinding() Assert.Equal("val1", list[2]); Assert.Equal("val2", list[3]); Assert.Equal("valx", list[4]); + + // Ensure expandability of the returned list + options.AlreadyInitializedListInterface.Add("ExtraItem"); + Assert.Equal(6, options.AlreadyInitializedListInterface.Count); + Assert.Equal("ExtraItem", options.AlreadyInitializedListInterface[5]); } [Fact] @@ -579,7 +584,10 @@ public void AlreadyInitializedStringDictionaryBinding() { {"AlreadyInitializedStringDictionaryInterface:abc", "val_1"}, {"AlreadyInitializedStringDictionaryInterface:def", "val_2"}, - {"AlreadyInitializedStringDictionaryInterface:ghi", "val_3"} + {"AlreadyInitializedStringDictionaryInterface:ghi", "val_3"}, + + {"IDictionaryNoSetter:Key1", "Value1"}, + {"IDictionaryNoSetter:Key2", "Value2"}, }; var configurationBuilder = new ConfigurationBuilder(); @@ -596,6 +604,10 @@ public void AlreadyInitializedStringDictionaryBinding() Assert.Equal("val_1", options.AlreadyInitializedStringDictionaryInterface["abc"]); Assert.Equal("val_2", options.AlreadyInitializedStringDictionaryInterface["def"]); Assert.Equal("val_3", options.AlreadyInitializedStringDictionaryInterface["ghi"]); + + Assert.Equal(2, options.IDictionaryNoSetter.Count); + Assert.Equal("Value1", options.IDictionaryNoSetter["Key1"]); + Assert.Equal("Value2", options.IDictionaryNoSetter["Key2"]); } [Fact] @@ -1059,7 +1071,10 @@ public void CanBindInitializedIEnumerableAndTheOriginalItemsAreNotMutated() {"AlreadyInitializedIEnumerableInterface:0", "val0"}, {"AlreadyInitializedIEnumerableInterface:1", "val1"}, {"AlreadyInitializedIEnumerableInterface:2", "val2"}, - {"AlreadyInitializedIEnumerableInterface:x", "valx"} + {"AlreadyInitializedIEnumerableInterface:x", "valx"}, + + {"ICollectionNoSetter:0", "val0"}, + {"ICollectionNoSetter:1", "val1"}, }; var configurationBuilder = new ConfigurationBuilder(); @@ -1084,6 +1099,15 @@ public void CanBindInitializedIEnumerableAndTheOriginalItemsAreNotMutated() Assert.Equal(2, options.ListUsedInIEnumerableFieldAndShouldNotBeTouched.Count); Assert.Equal("This was here too", options.ListUsedInIEnumerableFieldAndShouldNotBeTouched.ElementAt(0)); Assert.Equal("Don't touch me!", options.ListUsedInIEnumerableFieldAndShouldNotBeTouched.ElementAt(1)); + + Assert.Equal(2, options.ICollectionNoSetter.Count); + Assert.Equal("val0", options.ICollectionNoSetter.ElementAt(0)); + Assert.Equal("val1", options.ICollectionNoSetter.ElementAt(1)); + + // Ensure expandability of the returned collection + options.ICollectionNoSetter.Add("ExtraItem"); + Assert.Equal(3, options.ICollectionNoSetter.Count); + Assert.Equal("ExtraItem", options.ICollectionNoSetter.ElementAt(2)); } [Fact] @@ -1147,7 +1171,7 @@ public void CanBindInitializedCustomIndirectlyDerivedIEnumerableList() } [Fact] - public void CanBindInitializedIReadOnlyDictionaryAndDoesNotMofifyTheOriginal() + public void CanBindInitializedIReadOnlyDictionaryAndDoesNotModifyTheOriginal() { // A field declared as IEnumerable that is instantiated with a class // that indirectly implements IEnumerable is still bound, but with @@ -1204,6 +1228,11 @@ public void CanBindUninitializedICollection() Assert.Equal("val1", array[1]); Assert.Equal("val2", array[2]); Assert.Equal("valx", array[3]); + + // Ensure expandability of the returned collection + options.ICollection.Add("ExtraItem"); + Assert.Equal(5, options.ICollection.Count); + Assert.Equal("ExtraItem", options.ICollection.ElementAt(4)); } [Fact] @@ -1232,6 +1261,11 @@ public void CanBindUninitializedIList() Assert.Equal("val1", list[1]); Assert.Equal("val2", list[2]); Assert.Equal("valx", list[3]); + + // Ensure expandability of the returned list + options.IList.Add("ExtraItem"); + Assert.Equal(5, options.IList.Count); + Assert.Equal("ExtraItem", options.IList[4]); } [Fact] @@ -1424,6 +1458,8 @@ public InitializedCollectionsOptions() new CustomListIndirectlyDerivedFromIEnumerable(); public IReadOnlyDictionary AlreadyInitializedDictionary { get; set; } + + public ICollection ICollectionNoSetter { get; } = new List(); } private class CustomList : List @@ -1564,6 +1600,8 @@ public OptionsWithDictionary() public Dictionary StringDictionary { get; set; } + public IDictionary IDictionaryNoSetter { get; } = new Dictionary(); + public Dictionary ObjectDictionary { get; set; } public Dictionary> ISetDictionary { get; set; } @@ -1584,5 +1622,245 @@ private class OptionsWithInterdependentProperties public IEnumerable FilteredConfigValues => ConfigValues.Where(p => p > 10); public IEnumerable ConfigValues { get; set; } } + + [Fact] + public void DifferentDictionaryBindingCasesTest() + { + var dic = new Dictionary() { { "key", "value" } }; + var config = new ConfigurationBuilder() + .AddInMemoryCollection(dic) + .Build(); + + Assert.Single(config.Get>()); + Assert.Single(config.Get>()); + Assert.Single(config.Get>()); + Assert.Single(config.Get>()); + } + + public class ImplementerOfIDictionaryClass : IDictionary + { + private Dictionary _dict = new(); + + public TValue this[TKey key] { get => _dict[key]; set => _dict[key] = value; } + + public ICollection Keys => _dict.Keys; + + public ICollection Values => _dict.Values; + + public int Count => _dict.Count; + + public bool IsReadOnly => false; + + public void Add(TKey key, TValue value) => _dict.Add(key, value); + + public void Add(KeyValuePair item) => _dict.Add(item.Key, item.Value); + + public void Clear() => _dict.Clear(); + + public bool Contains(KeyValuePair item) => _dict.Contains(item); + + public bool ContainsKey(TKey key) => _dict.ContainsKey(key); + + public void CopyTo(KeyValuePair[] array, int arrayIndex) => throw new NotImplementedException(); + + public IEnumerator> GetEnumerator() => _dict.GetEnumerator(); + + public bool Remove(TKey key) => _dict.Remove(key); + + public bool Remove(KeyValuePair item) => _dict.Remove(item.Key); + + public bool TryGetValue(TKey key, out TValue value) => _dict.TryGetValue(key, out value); + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => _dict.GetEnumerator(); + + // The following are members which have the same names as the IDictionary<,> members. + // The following members test that there's no System.Reflection.AmbiguousMatchException when binding to the dictionary. + private string? v; + public string? this[string key] { get => v; set => v = value; } + public bool TryGetValue() { return true; } + + } + + public class ExtendedDictionary : Dictionary + { + + } + + private class OptionsWithDifferentCollectionInterfaces + { + private static IEnumerable s_instantiatedIEnumerable = new List { "value1", "value2" }; + public bool IsSameInstantiatedIEnumerable() => object.ReferenceEquals(s_instantiatedIEnumerable, InstantiatedIEnumerable); + public IEnumerable InstantiatedIEnumerable { get; set; } = s_instantiatedIEnumerable; + + private static IList s_instantiatedIList = new List { "value1", "value2" }; + public bool IsSameInstantiatedIList() => object.ReferenceEquals(s_instantiatedIList, InstantiatedIList); + public IList InstantiatedIList { get; set; } = s_instantiatedIList; + + private static IReadOnlyList s_instantiatedIReadOnlyList = new List { "value1", "value2" }; + public bool IsSameInstantiatedIReadOnlyList() => object.ReferenceEquals(s_instantiatedIReadOnlyList, InstantiatedIReadOnlyList); + public IReadOnlyList InstantiatedIReadOnlyList { get; set; } = s_instantiatedIReadOnlyList; + + private static IDictionary s_instantiatedIDictionary = new Dictionary { ["Key1"] = "value1", ["Key2"] = "value2" }; + public IDictionary InstantiatedIDictionary { get; set; } = s_instantiatedIDictionary; + public bool IsSameInstantiatedIDictionary() => object.ReferenceEquals(s_instantiatedIDictionary, InstantiatedIDictionary); + + private static IReadOnlyDictionary s_instantiatedIReadOnlyDictionary = new Dictionary { ["Key1"] = "value1", ["Key2"] = "value2" }; + public IReadOnlyDictionary InstantiatedIReadOnlyDictionary { get; set; } = s_instantiatedIReadOnlyDictionary; + public bool IsSameInstantiatedIReadOnlyDictionary() => object.ReferenceEquals(s_instantiatedIReadOnlyDictionary, InstantiatedIReadOnlyDictionary); + + private static ISet s_instantiatedISet = new HashSet(StringComparer.OrdinalIgnoreCase) { "a", "A", "b" }; + public ISet InstantiatedISet { get; set; } = s_instantiatedISet; + public bool IsSameInstantiatedISet() => object.ReferenceEquals(s_instantiatedISet, InstantiatedISet); + +#if NETCOREAPP + private static IReadOnlySet s_instantiatedIReadOnlySet = new HashSet(StringComparer.OrdinalIgnoreCase) { "a", "A", "b" }; + public IReadOnlySet InstantiatedIReadOnlySet { get; set; } = s_instantiatedIReadOnlySet; + public bool IsSameInstantiatedIReadOnlySet() => object.ReferenceEquals(s_instantiatedIReadOnlySet, InstantiatedIReadOnlySet); + + public IReadOnlySet UnInstantiatedIReadOnlySet { get; set; } +#endif + private static ICollection s_instantiatedICollection = new List { "a", "b", "c" }; + public ICollection InstantiatedICollection { get; set; } = s_instantiatedICollection; + public bool IsSameInstantiatedICollection() => object.ReferenceEquals(s_instantiatedICollection, InstantiatedICollection); + + private static IReadOnlyCollection s_instantiatedIReadOnlyCollection = new List { "a", "b", "c" }; + public IReadOnlyCollection InstantiatedIReadOnlyCollection { get; set; } = s_instantiatedIReadOnlyCollection; + public bool IsSameInstantiatedIReadOnlyCollection() => object.ReferenceEquals(s_instantiatedIReadOnlyCollection, InstantiatedIReadOnlyCollection); + + public IReadOnlyCollection UnInstantiatedIReadOnlyCollection { get; set; } + public ICollection UnInstantiatedICollection { get; set; } + public ISet UnInstantiatedISet { get; set; } + public IReadOnlyDictionary UnInstantiatedIReadOnlyDictionary { get; set; } + public IEnumerable UnInstantiatedIEnumerable { get; set; } + public IList UnInstantiatedIList { get; set; } + public IReadOnlyList UnInstantiatedIReadOnlyList { get; set; } + } + [Fact] + public void TestOptionsWithDifferentCollectionInterfaces() + { + var input = new Dictionary + { + {"InstantiatedIEnumerable:0", "value3"}, + {"UnInstantiatedIEnumerable:0", "value1"}, + {"InstantiatedIList:0", "value3"}, + {"InstantiatedIReadOnlyList:0", "value3"}, + {"UnInstantiatedIReadOnlyList:0", "value"}, + {"UnInstantiatedIList:0", "value"}, + {"InstantiatedIDictionary:Key3", "value3"}, + {"InstantiatedIReadOnlyDictionary:Key3", "value3"}, + {"UnInstantiatedIReadOnlyDictionary:Key", "value"}, + {"InstantiatedISet:0", "B"}, + {"InstantiatedISet:1", "C"}, + {"UnInstantiatedISet:0", "a"}, + {"UnInstantiatedISet:1", "A"}, + {"UnInstantiatedISet:2", "B"}, + {"InstantiatedIReadOnlySet:0", "Z"}, + {"UnInstantiatedIReadOnlySet:0", "y"}, + {"UnInstantiatedIReadOnlySet:1", "z"}, + {"InstantiatedICollection:0", "d"}, + {"UnInstantiatedICollection:0", "t"}, + {"UnInstantiatedICollection:1", "a"}, + {"InstantiatedIReadOnlyCollection:0", "d"}, + {"UnInstantiatedIReadOnlyCollection:0", "r"}, + {"UnInstantiatedIReadOnlyCollection:1", "e"}, + }; + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(input); + var config = configurationBuilder.Build(); + + var options = new OptionsWithDifferentCollectionInterfaces(); + config.Bind(options); + + Assert.True(3 == options.InstantiatedIEnumerable.Count(), $"InstantiatedIEnumerable count is {options.InstantiatedIEnumerable.Count()} .. {options.InstantiatedIEnumerable.ElementAt(options.InstantiatedIEnumerable.Count() - 1)}"); + Assert.Equal("value1", options.InstantiatedIEnumerable.ElementAt(0)); + Assert.Equal("value2", options.InstantiatedIEnumerable.ElementAt(1)); + Assert.Equal("value3", options.InstantiatedIEnumerable.ElementAt(2)); + Assert.False(options.IsSameInstantiatedIEnumerable()); + + Assert.Equal(1, options.UnInstantiatedIEnumerable.Count()); + Assert.Equal("value1", options.UnInstantiatedIEnumerable.ElementAt(0)); + + Assert.True(3 == options.InstantiatedIList.Count(), $"InstantiatedIList count is {options.InstantiatedIList.Count()} .. {options.InstantiatedIList[options.InstantiatedIList.Count() - 1]}"); + Assert.Equal("value1", options.InstantiatedIList[0]); + Assert.Equal("value2", options.InstantiatedIList[1]); + Assert.Equal("value3", options.InstantiatedIList[2]); + Assert.True(options.IsSameInstantiatedIList()); + + Assert.Equal(1, options.UnInstantiatedIList.Count()); + Assert.Equal("value", options.UnInstantiatedIList[0]); + + Assert.True(3 == options.InstantiatedIReadOnlyList.Count(), $"InstantiatedIReadOnlyList count is {options.InstantiatedIReadOnlyList.Count()} .. {options.InstantiatedIReadOnlyList[options.InstantiatedIReadOnlyList.Count() - 1]}"); + Assert.Equal("value1", options.InstantiatedIReadOnlyList[0]); + Assert.Equal("value2", options.InstantiatedIReadOnlyList[1]); + Assert.Equal("value3", options.InstantiatedIReadOnlyList[2]); + Assert.False(options.IsSameInstantiatedIReadOnlyList()); + + Assert.Equal(1, options.UnInstantiatedIReadOnlyList.Count()); + Assert.Equal("value", options.UnInstantiatedIReadOnlyList[0]); + + Assert.True(3 == options.InstantiatedIReadOnlyList.Count(), $"InstantiatedIReadOnlyList count is {options.InstantiatedIReadOnlyList.Count()} .. {options.InstantiatedIReadOnlyList[options.InstantiatedIReadOnlyList.Count() - 1]}"); + Assert.Equal(new string[] { "Key1", "Key2", "Key3" }, options.InstantiatedIDictionary.Keys); + Assert.Equal(new string[] { "value1", "value2", "value3" }, options.InstantiatedIDictionary.Values); + Assert.True(options.IsSameInstantiatedIDictionary()); + + Assert.True(3 == options.InstantiatedIReadOnlyDictionary.Count(), $"InstantiatedIReadOnlyDictionary count is {options.InstantiatedIReadOnlyDictionary.Count()} .. {options.InstantiatedIReadOnlyDictionary.ElementAt(options.InstantiatedIReadOnlyDictionary.Count() - 1)}"); + Assert.Equal(new string[] { "Key1", "Key2", "Key3" }, options.InstantiatedIReadOnlyDictionary.Keys); + Assert.Equal(new string[] { "value1", "value2", "value3" }, options.InstantiatedIReadOnlyDictionary.Values); + Assert.False(options.IsSameInstantiatedIReadOnlyDictionary()); + + Assert.Equal(1, options.UnInstantiatedIReadOnlyDictionary.Count()); + Assert.Equal(new string[] { "Key" }, options.UnInstantiatedIReadOnlyDictionary.Keys); + Assert.Equal(new string[] { "value" }, options.UnInstantiatedIReadOnlyDictionary.Values); + + Assert.True(3 == options.InstantiatedISet.Count(), $"InstantiatedISet count is {options.InstantiatedISet.Count()} .. {string.Join(", ", options.InstantiatedISet)} .. {options.IsSameInstantiatedISet()}"); + Assert.Equal(new string[] { "a", "b", "C" }, options.InstantiatedISet); + Assert.True(options.IsSameInstantiatedISet()); + + Assert.True(3 == options.UnInstantiatedISet.Count(), $"UnInstantiatedISet count is {options.UnInstantiatedISet.Count()} .. {options.UnInstantiatedISet.ElementAt(options.UnInstantiatedISet.Count() - 1)}"); + Assert.Equal(new string[] { "a", "A", "B" }, options.UnInstantiatedISet); + +#if NETCOREAPP + Assert.True(3 == options.InstantiatedIReadOnlySet.Count(), $"InstantiatedIReadOnlySet count is {options.InstantiatedIReadOnlySet.Count()} .. {options.InstantiatedIReadOnlySet.ElementAt(options.InstantiatedIReadOnlySet.Count() - 1)}"); + Assert.Equal(new string[] { "a", "b", "Z" }, options.InstantiatedIReadOnlySet); + Assert.False(options.IsSameInstantiatedIReadOnlySet()); + + Assert.Equal(2, options.UnInstantiatedIReadOnlySet.Count()); + Assert.Equal(new string[] { "y", "z" }, options.UnInstantiatedIReadOnlySet); +#endif + Assert.Equal(4, options.InstantiatedICollection.Count()); + Assert.Equal(new string[] { "a", "b", "c", "d" }, options.InstantiatedICollection); + Assert.True(options.IsSameInstantiatedICollection()); + + Assert.Equal(2, options.UnInstantiatedICollection.Count()); + Assert.Equal(new string[] { "t", "a" }, options.UnInstantiatedICollection); + + Assert.Equal(4, options.InstantiatedIReadOnlyCollection.Count()); + Assert.Equal(new string[] { "a", "b", "c", "d" }, options.InstantiatedIReadOnlyCollection); + Assert.False(options.IsSameInstantiatedIReadOnlyCollection()); + + Assert.Equal(2, options.UnInstantiatedIReadOnlyCollection.Count()); + Assert.Equal(new string[] { "r", "e" }, options.UnInstantiatedIReadOnlyCollection); + } + + [Fact] + public void TestMutatingDictionaryValues() + { + IConfiguration config = new ConfigurationBuilder() + .AddInMemoryCollection() + .Build(); + + config["Key:0"] = "NewValue"; + var dict = new Dictionary() { { "Key", new[] { "InitialValue" } } }; + + Assert.Equal(1, dict["Key"].Length); + Assert.Equal("InitialValue", dict["Key"][0]); + + // Binding will accumulate to the values inside the dictionary. + config.Bind(dict); + Assert.Equal(2, dict["Key"].Length); + Assert.Equal("InitialValue", dict["Key"][0]); + Assert.Equal("NewValue", dict["Key"][1]); + } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ILLink.Descriptors.xml b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ILLink.Descriptors.xml index 5b9621080c97b0..034e7f3ea5c734 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ILLink.Descriptors.xml +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ILLink.Descriptors.xml @@ -15,4 +15,8 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Microsoft.Extensions.Configuration.Binder.Tests.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Microsoft.Extensions.Configuration.Binder.Tests.csproj index a408e921ec89e6..198d6cbfae86b3 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Microsoft.Extensions.Configuration.Binder.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Microsoft.Extensions.Configuration.Binder.Tests.csproj @@ -10,13 +10,14 @@ Link="Common\ConfigurationProviderExtensions.cs" /> - + + diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs index 0cddf30f2a719c..d1af024a3e38e1 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs @@ -187,12 +187,20 @@ static async ValueTask Await(int i, ValueTask vt, List toDispose) // No further changes to _state.Disposables, are allowed. _disposed = true; - // ResolvedServices is never cleared for singletons because there might be a compilation running in background - // trying to get a cached singleton service. If it doesn't find it - // it will try to create a new one which will result in an ObjectDisposedException. + } - return _disposables; + if (IsRootScope && !RootProvider.IsDisposed()) + { + // If this ServiceProviderEngineScope instance is a root scope, disposing this instance will need to dispose the RootProvider too. + // Otherwise the RootProvider will never get disposed and will leak. + // Note, if the RootProvider get disposed first, it will automatically dispose all attached ServiceProviderEngineScope objects. + RootProvider.Dispose(); } + + // ResolvedServices is never cleared for singletons because there might be a compilation running in background + // trying to get a cached singleton service. If it doesn't find it + // it will try to create a new one which will result in an ObjectDisposedException. + return _disposables; } } } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs index f66f36b3cf6ed2..dd9b1af11a55a0 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs @@ -89,6 +89,8 @@ internal ServiceProvider(ICollection serviceDescriptors, Serv /// The service that was produced. public object? GetService(Type serviceType) => GetService(serviceType, Root); + internal bool IsDisposed() => _disposed; + /// public void Dispose() { diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderEngineScopeTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderEngineScopeTests.cs index d43752db21eba7..e801497236f0b9 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderEngineScopeTests.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderEngineScopeTests.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; using Microsoft.Extensions.DependencyInjection.Specification.Fakes; using Xunit; @@ -17,5 +18,16 @@ public void DoubleDisposeWorks() serviceProviderEngineScope.Dispose(); serviceProviderEngineScope.Dispose(); } + + [Fact] + public void RootEngineScopeDisposeTest() + { + var services = new ServiceCollection(); + ServiceProvider sp = services.BuildServiceProvider(); + var s = sp.GetRequiredService(); + ((IDisposable)s).Dispose(); + + Assert.Throws(() => sp.GetRequiredService()); + } } } diff --git a/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/Microsoft.Extensions.Hosting.WindowsServices.csproj b/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/Microsoft.Extensions.Hosting.WindowsServices.csproj index 86e6da3a39b766..1b4717c639e8b1 100644 --- a/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/Microsoft.Extensions.Hosting.WindowsServices.csproj +++ b/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/Microsoft.Extensions.Hosting.WindowsServices.csproj @@ -7,6 +7,8 @@ true .NET hosting infrastructure for Windows Services. true + false + 1 @@ -27,6 +29,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/WindowsServiceLifetime.cs b/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/WindowsServiceLifetime.cs index 164e60670fb674..642f770591d39d 100644 --- a/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/WindowsServiceLifetime.cs +++ b/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/WindowsServiceLifetime.cs @@ -15,8 +15,10 @@ namespace Microsoft.Extensions.Hosting.WindowsServices public class WindowsServiceLifetime : ServiceBase, IHostLifetime { private readonly TaskCompletionSource _delayStart = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + private readonly TaskCompletionSource _serviceDispatcherStopped = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); private readonly ManualResetEventSlim _delayStop = new ManualResetEventSlim(); private readonly HostOptions _hostOptions; + private bool _serviceStopRequested; public WindowsServiceLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor) : this(environment, applicationLifetime, loggerFactory, optionsAccessor, Options.Options.Create(new WindowsServiceLifetimeOptions())) @@ -69,19 +71,30 @@ private void Run() { Run(this); // This blocks until the service is stopped. _delayStart.TrySetException(new InvalidOperationException("Stopped without starting")); + _serviceDispatcherStopped.TrySetResult(null); } catch (Exception ex) { _delayStart.TrySetException(ex); + _serviceDispatcherStopped.TrySetException(ex); } } - public Task StopAsync(CancellationToken cancellationToken) + /// + /// Called from to stop the service if not already stopped, and wait for the service dispatcher to exit. + /// Once this method returns the service is stopped and the process can be terminated at any time. + /// + public async Task StopAsync(CancellationToken cancellationToken) { - // Avoid deadlock where host waits for StopAsync before firing ApplicationStopped, - // and Stop waits for ApplicationStopped. - Task.Run(Stop, CancellationToken.None); - return Task.CompletedTask; + cancellationToken.ThrowIfCancellationRequested(); + + if (!_serviceStopRequested) + { + await Task.Run(Stop, cancellationToken).ConfigureAwait(false); + } + + // When the underlying service is stopped this will cause the ServiceBase.Run method to complete and return, which completes _serviceDispatcherStopped. + await _serviceDispatcherStopped.Task.ConfigureAwait(false); } // Called by base.Run when the service is ready to start. @@ -91,18 +104,28 @@ protected override void OnStart(string[] args) base.OnStart(args); } - // Called by base.Stop. This may be called multiple times by service Stop, ApplicationStopping, and StopAsync. - // That's OK because StopApplication uses a CancellationTokenSource and prevents any recursion. + /// + /// Executes when a Stop command is sent to the service by the Service Control Manager (SCM). + /// Triggers and waits for . + /// Shortly after this method returns, the Service will be marked as stopped in SCM and the process may exit at any point. + /// protected override void OnStop() { + _serviceStopRequested = true; ApplicationLifetime.StopApplication(); // Wait for the host to shutdown before marking service as stopped. _delayStop.Wait(_hostOptions.ShutdownTimeout); base.OnStop(); } + /// + /// Executes when a Shutdown command is sent to the service by the Service Control Manager (SCM). + /// Triggers and waits for . + /// Shortly after this method returns, the Service will be marked as stopped in SCM and the process may exit at any point. + /// protected override void OnShutdown() { + _serviceStopRequested = true; ApplicationLifetime.StopApplication(); // Wait for the host to shutdown before marking service as stopped. _delayStop.Wait(_hostOptions.ShutdownTimeout); diff --git a/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/tests/Microsoft.Extensions.Hosting.WindowsServices.Tests.csproj b/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/tests/Microsoft.Extensions.Hosting.WindowsServices.Tests.csproj index 93be9b87c967b5..ee433d9207d1d2 100644 --- a/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/tests/Microsoft.Extensions.Hosting.WindowsServices.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/tests/Microsoft.Extensions.Hosting.WindowsServices.Tests.csproj @@ -4,12 +4,45 @@ $(NetCoreAppCurrent)-windows;$(NetFrameworkMinimum) true + true + true + true + + + + + + + + + + + + + + + + diff --git a/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/tests/UseWindowsServiceTests.cs b/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/tests/UseWindowsServiceTests.cs index 1fb2ade8a94079..4f1657ee2daa35 100644 --- a/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/tests/UseWindowsServiceTests.cs +++ b/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/tests/UseWindowsServiceTests.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.IO; using System.Reflection; using System.ServiceProcess; +using Microsoft.DotNet.RemoteExecutor; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting.Internal; using Microsoft.Extensions.Hosting.WindowsServices; @@ -17,6 +17,8 @@ namespace Microsoft.Extensions.Hosting { public class UseWindowsServiceTests { + private static bool IsRemoteExecutorSupportedAndPrivilegedProcess => RemoteExecutor.IsSupported && AdminHelpers.IsProcessElevated(); + private static MethodInfo? _addWindowsServiceLifetimeMethod = null; [Fact] @@ -30,6 +32,26 @@ public void DefaultsToOffOutsideOfService() Assert.IsType(lifetime); } + [ConditionalFact(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] + public void CanCreateService() + { + using var serviceTester = WindowsServiceTester.Create(() => + { + using IHost host = new HostBuilder() + .UseWindowsService() + .Build(); + host.Run(); + }); + + serviceTester.Start(); + serviceTester.WaitForStatus(ServiceControllerStatus.Running); + serviceTester.Stop(); + serviceTester.WaitForStatus(ServiceControllerStatus.Stopped); + + var status = serviceTester.QueryServiceStatus(); + Assert.Equal(0, status.win32ExitCode); + } + [Fact] public void ServiceCollectionExtensionMethodDefaultsToOffOutsideOfService() { @@ -66,7 +88,7 @@ public void ServiceCollectionExtensionMethodSetsEventLogSourceNameToApplicationN var builder = new HostApplicationBuilder(new HostApplicationBuilderSettings { ApplicationName = appName, - }); + }); // Emulate calling builder.Services.AddWindowsService() from inside a Windows service. AddWindowsServiceLifetime(builder.Services); @@ -82,7 +104,7 @@ public void ServiceCollectionExtensionMethodSetsEventLogSourceNameToApplicationN [Fact] public void ServiceCollectionExtensionMethodCanBeCalledOnDefaultConfiguration() { - var builder = new HostApplicationBuilder(); + var builder = new HostApplicationBuilder(); // Emulate calling builder.Services.AddWindowsService() from inside a Windows service. AddWindowsServiceLifetime(builder.Services); diff --git a/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/tests/WindowsServiceLifetimeTests.cs b/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/tests/WindowsServiceLifetimeTests.cs new file mode 100644 index 00000000000000..c060648b818e83 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/tests/WindowsServiceLifetimeTests.cs @@ -0,0 +1,352 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.IO; +using System.ServiceProcess; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting.Internal; +using Microsoft.Extensions.Hosting.WindowsServices; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.Extensions.Hosting +{ + public class WindowsServiceLifetimeTests + { + private static bool IsRemoteExecutorSupportedAndPrivilegedProcess => RemoteExecutor.IsSupported && AdminHelpers.IsProcessElevated(); + + [ConditionalFact(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] + public void ServiceStops() + { + using var serviceTester = WindowsServiceTester.Create(async () => + { + var applicationLifetime = new ApplicationLifetime(NullLogger.Instance); + using var lifetime = new WindowsServiceLifetime( + new HostingEnvironment(), + applicationLifetime, + NullLoggerFactory.Instance, + new OptionsWrapper(new HostOptions())); + + await lifetime.WaitForStartAsync(CancellationToken.None); + + // would normally occur here, but WindowsServiceLifetime does not depend on it. + // applicationLifetime.NotifyStarted(); + + // will be signaled by WindowsServiceLifetime when SCM stops the service. + applicationLifetime.ApplicationStopping.WaitHandle.WaitOne(); + + // required by WindowsServiceLifetime to identify that app has stopped. + applicationLifetime.NotifyStopped(); + + await lifetime.StopAsync(CancellationToken.None); + }); + + serviceTester.Start(); + serviceTester.WaitForStatus(ServiceControllerStatus.Running); + + var statusEx = serviceTester.QueryServiceStatusEx(); + var serviceProcess = Process.GetProcessById(statusEx.dwProcessId); + + serviceTester.Stop(); + serviceTester.WaitForStatus(ServiceControllerStatus.Stopped); + + serviceProcess.WaitForExit(); + + var status = serviceTester.QueryServiceStatus(); + Assert.Equal(0, status.win32ExitCode); + } + + + [ConditionalFact(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, ".NET Framework is missing the fix from https://github.com/dotnet/corefx/commit/3e68d791066ad0fdc6e0b81828afbd9df00dd7f8")] + public void ExceptionOnStartIsPropagated() + { + using var serviceTester = WindowsServiceTester.Create(async () => + { + using (var lifetime = ThrowingWindowsServiceLifetime.Create(throwOnStart: new Exception("Should be thrown"))) + { + Assert.Equal(lifetime.ThrowOnStart, + await Assert.ThrowsAsync(async () => + await lifetime.WaitForStartAsync(CancellationToken.None))); + } + }); + + serviceTester.Start(); + + serviceTester.WaitForStatus(ServiceControllerStatus.Stopped); + var status = serviceTester.QueryServiceStatus(); + Assert.Equal(Interop.Errors.ERROR_EXCEPTION_IN_SERVICE, status.win32ExitCode); + } + + [ConditionalFact(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] + public void ExceptionOnStopIsPropagated() + { + using var serviceTester = WindowsServiceTester.Create(async () => + { + using (var lifetime = ThrowingWindowsServiceLifetime.Create(throwOnStop: new Exception("Should be thrown"))) + { + await lifetime.WaitForStartAsync(CancellationToken.None); + lifetime.ApplicationLifetime.NotifyStopped(); + Assert.Equal(lifetime.ThrowOnStop, + await Assert.ThrowsAsync(async () => + await lifetime.StopAsync(CancellationToken.None))); + } + }); + + serviceTester.Start(); + + serviceTester.WaitForStatus(ServiceControllerStatus.Stopped); + var status = serviceTester.QueryServiceStatus(); + Assert.Equal(Interop.Errors.ERROR_PROCESS_ABORTED, status.win32ExitCode); + } + + [ConditionalFact(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] + public void CancelStopAsync() + { + using var serviceTester = WindowsServiceTester.Create(async () => + { + var applicationLifetime = new ApplicationLifetime(NullLogger.Instance); + using var lifetime = new WindowsServiceLifetime( + new HostingEnvironment(), + applicationLifetime, + NullLoggerFactory.Instance, + new OptionsWrapper(new HostOptions())); + await lifetime.WaitForStartAsync(CancellationToken.None); + + await Assert.ThrowsAsync(async () => await lifetime.StopAsync(new CancellationToken(true))); + }); + + serviceTester.Start(); + + serviceTester.WaitForStatus(ServiceControllerStatus.Stopped); + var status = serviceTester.QueryServiceStatus(); + Assert.Equal(Interop.Errors.ERROR_PROCESS_ABORTED, status.win32ExitCode); + } + + + [ConditionalFact(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] + public void ServiceCanStopItself() + { + using (var serviceTester = WindowsServiceTester.Create(async () => + { + FileLogger.InitializeForTestCase(nameof(ServiceCanStopItself)); + using IHost host = new HostBuilder() + .ConfigureServices(services => + { + services.AddHostedService(); + services.AddSingleton(); + }) + .Build(); + + var applicationLifetime = host.Services.GetRequiredService(); + applicationLifetime.ApplicationStarted.Register(() => FileLogger.Log($"lifetime started")); + applicationLifetime.ApplicationStopping.Register(() => FileLogger.Log($"lifetime stopping")); + applicationLifetime.ApplicationStopped.Register(() => FileLogger.Log($"lifetime stopped")); + + FileLogger.Log("host.Start()"); + host.Start(); + + using (ServiceController selfController = new(nameof(ServiceCanStopItself))) + { + selfController.WaitForStatus(ServiceControllerStatus.Running, WindowsServiceTester.WaitForStatusTimeout); + Assert.Equal(ServiceControllerStatus.Running, selfController.Status); + + FileLogger.Log("host.Stop()"); + await host.StopAsync(); + FileLogger.Log("host.Stop() complete"); + + selfController.WaitForStatus(ServiceControllerStatus.Stopped, WindowsServiceTester.WaitForStatusTimeout); + Assert.Equal(ServiceControllerStatus.Stopped, selfController.Status); + } + })) + { + FileLogger.DeleteLog(nameof(ServiceCanStopItself)); + + // service should start cleanly + serviceTester.Start(); + + // service will proceed to stopped without any error + serviceTester.WaitForStatus(ServiceControllerStatus.Stopped); + + var status = serviceTester.QueryServiceStatus(); + Assert.Equal(0, status.win32ExitCode); + + } + + var logText = FileLogger.ReadLog(nameof(ServiceCanStopItself)); + Assert.Equal(""" + host.Start() + WindowsServiceLifetime.OnStart + BackgroundService.StartAsync + lifetime started + host.Stop() + lifetime stopping + BackgroundService.StopAsync + lifetime stopped + WindowsServiceLifetime.OnStop + host.Stop() complete + + """, logText); + } + + [ConditionalFact(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] + public void ServiceSequenceIsCorrect() + { + using (var serviceTester = WindowsServiceTester.Create(() => + { + FileLogger.InitializeForTestCase(nameof(ServiceSequenceIsCorrect)); + using IHost host = new HostBuilder() + .ConfigureServices(services => + { + services.AddHostedService(); + services.AddSingleton(); + }) + .Build(); + + var applicationLifetime = host.Services.GetRequiredService(); + applicationLifetime.ApplicationStarted.Register(() => FileLogger.Log($"lifetime started")); + applicationLifetime.ApplicationStopping.Register(() => FileLogger.Log($"lifetime stopping")); + applicationLifetime.ApplicationStopped.Register(() => FileLogger.Log($"lifetime stopped")); + + FileLogger.Log("host.Run()"); + host.Run(); + FileLogger.Log("host.Run() complete"); + })) + { + + FileLogger.DeleteLog(nameof(ServiceSequenceIsCorrect)); + + serviceTester.Start(); + serviceTester.WaitForStatus(ServiceControllerStatus.Running); + + var statusEx = serviceTester.QueryServiceStatusEx(); + var serviceProcess = Process.GetProcessById(statusEx.dwProcessId); + + // Give a chance for all asynchronous "started" events to be raised, these happen after the service status changes to started + Thread.Sleep(1000); + + serviceTester.Stop(); + serviceTester.WaitForStatus(ServiceControllerStatus.Stopped); + + var status = serviceTester.QueryServiceStatus(); + Assert.Equal(0, status.win32ExitCode); + + } + + var logText = FileLogger.ReadLog(nameof(ServiceSequenceIsCorrect)); + Assert.Equal(""" + host.Run() + WindowsServiceLifetime.OnStart + BackgroundService.StartAsync + lifetime started + WindowsServiceLifetime.OnStop + lifetime stopping + BackgroundService.StopAsync + lifetime stopped + host.Run() complete + + """, logText); + + } + + public class LoggingWindowsServiceLifetime : WindowsServiceLifetime + { + public LoggingWindowsServiceLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor) : + base(environment, applicationLifetime, loggerFactory, optionsAccessor) + { } + + protected override void OnStart(string[] args) + { + FileLogger.Log("WindowsServiceLifetime.OnStart"); + base.OnStart(args); + } + + protected override void OnStop() + { + FileLogger.Log("WindowsServiceLifetime.OnStop"); + base.OnStop(); + } + } + + public class ThrowingWindowsServiceLifetime : WindowsServiceLifetime + { + public static ThrowingWindowsServiceLifetime Create(Exception throwOnStart = null, Exception throwOnStop = null) => + new ThrowingWindowsServiceLifetime( + new HostingEnvironment(), + new ApplicationLifetime(NullLogger.Instance), + NullLoggerFactory.Instance, + new OptionsWrapper(new HostOptions())) + { + ThrowOnStart = throwOnStart, + ThrowOnStop = throwOnStop + }; + + public ThrowingWindowsServiceLifetime(IHostEnvironment environment, ApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor) : + base(environment, applicationLifetime, loggerFactory, optionsAccessor) + { + ApplicationLifetime = applicationLifetime; + } + + public ApplicationLifetime ApplicationLifetime { get; } + + public Exception ThrowOnStart { get; set; } + protected override void OnStart(string[] args) + { + if (ThrowOnStart != null) + { + throw ThrowOnStart; + } + base.OnStart(args); + } + + public Exception ThrowOnStop { get; set; } + protected override void OnStop() + { + if (ThrowOnStop != null) + { + throw ThrowOnStop; + } + base.OnStop(); + } + } + + public class LoggingBackgroundService : BackgroundService + { +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + protected override async Task ExecuteAsync(CancellationToken stoppingToken) => FileLogger.Log("BackgroundService.ExecuteAsync"); + public override async Task StartAsync(CancellationToken stoppingToken) => FileLogger.Log("BackgroundService.StartAsync"); + public override async Task StopAsync(CancellationToken stoppingToken) => FileLogger.Log("BackgroundService.StopAsync"); +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + } + + static class FileLogger + { + static string _fileName; + + public static void InitializeForTestCase(string testCaseName) + { + Assert.Null(_fileName); + _fileName = GetLogForTestCase(testCaseName); + } + + private static string GetLogForTestCase(string testCaseName) => Path.Combine(AppContext.BaseDirectory, $"{testCaseName}.log"); + public static void DeleteLog(string testCaseName) => File.Delete(GetLogForTestCase(testCaseName)); + public static string ReadLog(string testCaseName) => File.ReadAllText(GetLogForTestCase(testCaseName)); + public static void Log(string message) + { + Assert.NotNull(_fileName); + lock (_fileName) + { + File.AppendAllText(_fileName, message + Environment.NewLine); + } + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/tests/WindowsServiceTester.cs b/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/tests/WindowsServiceTester.cs new file mode 100644 index 00000000000000..880fae3e0465a4 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/tests/WindowsServiceTester.cs @@ -0,0 +1,158 @@ +// 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.ComponentModel; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.ServiceProcess; +using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; +using Microsoft.Win32.SafeHandles; +using Xunit; + +namespace Microsoft.Extensions.Hosting +{ + public class WindowsServiceTester : ServiceController + { + private WindowsServiceTester(SafeServiceHandle serviceHandle, RemoteInvokeHandle remoteInvokeHandle, string serviceName) : base(serviceName) + { + _serviceHandle = serviceHandle; + _remoteInvokeHandle = remoteInvokeHandle; + } + + private SafeServiceHandle _serviceHandle; + private RemoteInvokeHandle _remoteInvokeHandle; + + public new void Start() + { + Start(Array.Empty()); + } + + public new void Start(string[] args) + { + base.Start(args); + + // get the process + _remoteInvokeHandle.Process.Dispose(); + _remoteInvokeHandle.Process = null; + + var statusEx = QueryServiceStatusEx(); + try + { + _remoteInvokeHandle.Process = Process.GetProcessById(statusEx.dwProcessId); + // fetch the process handle so that we can get the exit code later. + var _ = _remoteInvokeHandle.Process.SafeHandle; + } + catch (ArgumentException) + { } + } + + public static TimeSpan WaitForStatusTimeout { get; set; } = TimeSpan.FromSeconds(30); + + public new void WaitForStatus(ServiceControllerStatus desiredStatus) => + WaitForStatus(desiredStatus, WaitForStatusTimeout); + + public new void WaitForStatus(ServiceControllerStatus desiredStatus, TimeSpan timeout) + { + base.WaitForStatus(desiredStatus, timeout); + + Assert.Equal(Status, desiredStatus); + } + + // the following overloads are necessary to ensure the compiler will produce the correct signature from a lambda. + public static WindowsServiceTester Create(Func serviceMain, [CallerMemberName] string serviceName = null) => Create(RemoteExecutor.Invoke(serviceMain, remoteInvokeOptions), serviceName); + + public static WindowsServiceTester Create(Func> serviceMain, [CallerMemberName] string serviceName = null) => Create(RemoteExecutor.Invoke(serviceMain, remoteInvokeOptions), serviceName); + + public static WindowsServiceTester Create(Func serviceMain, [CallerMemberName] string serviceName = null) => Create(RemoteExecutor.Invoke(serviceMain, remoteInvokeOptions), serviceName); + + public static WindowsServiceTester Create(Action serviceMain, [CallerMemberName] string serviceName = null) => Create(RemoteExecutor.Invoke(serviceMain, remoteInvokeOptions), serviceName); + + private static RemoteInvokeOptions remoteInvokeOptions = new RemoteInvokeOptions() { Start = false }; + + private static WindowsServiceTester Create(RemoteInvokeHandle remoteInvokeHandle, string serviceName) + { + // create remote executor commandline arguments + var startInfo = remoteInvokeHandle.Process.StartInfo; + string commandLine = startInfo.FileName + " " + startInfo.Arguments; + + // install the service + using (var serviceManagerHandle = new SafeServiceHandle(Interop.Advapi32.OpenSCManager(null, null, Interop.Advapi32.ServiceControllerOptions.SC_MANAGER_ALL))) + { + if (serviceManagerHandle.IsInvalid) + { + throw new InvalidOperationException(); + } + + // delete existing service if it exists + using (var existingServiceHandle = new SafeServiceHandle(Interop.Advapi32.OpenService(serviceManagerHandle, serviceName, Interop.Advapi32.ServiceAccessOptions.ACCESS_TYPE_ALL))) + { + if (!existingServiceHandle.IsInvalid) + { + Interop.Advapi32.DeleteService(existingServiceHandle); + } + } + + var serviceHandle = new SafeServiceHandle( + Interop.Advapi32.CreateService(serviceManagerHandle, + serviceName, + $"{nameof(WindowsServiceTester)} {serviceName} test service", + Interop.Advapi32.ServiceAccessOptions.ACCESS_TYPE_ALL, + Interop.Advapi32.ServiceTypeOptions.SERVICE_WIN32_OWN_PROCESS, + (int)ServiceStartMode.Manual, + Interop.Advapi32.ServiceStartErrorModes.ERROR_CONTROL_NORMAL, + commandLine, + loadOrderGroup: null, + pTagId: IntPtr.Zero, + dependencies: null, + servicesStartName: null, + password: null)); + + if (serviceHandle.IsInvalid) + { + throw new Win32Exception(); + } + + return new WindowsServiceTester(serviceHandle, remoteInvokeHandle, serviceName); + } + } + + internal unsafe Interop.Advapi32.SERVICE_STATUS QueryServiceStatus() + { + Interop.Advapi32.SERVICE_STATUS status = default; + bool success = Interop.Advapi32.QueryServiceStatus(_serviceHandle, &status); + if (!success) + { + throw new Win32Exception(); + } + return status; + } + + internal unsafe Interop.Advapi32.SERVICE_STATUS_PROCESS QueryServiceStatusEx() + { + Interop.Advapi32.SERVICE_STATUS_PROCESS status = default; + bool success = Interop.Advapi32.QueryServiceStatusEx(_serviceHandle, &status); + if (!success) + { + throw new Win32Exception(); + } + return status; + } + + protected override void Dispose(bool disposing) + { + if (_remoteInvokeHandle != null) + { + _remoteInvokeHandle.Dispose(); + } + + if (!_serviceHandle.IsInvalid) + { + // delete the temporary test service + Interop.Advapi32.DeleteService(_serviceHandle); + _serviceHandle.Close(); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/HostApplicationBuilder.cs b/src/libraries/Microsoft.Extensions.Hosting/src/HostApplicationBuilder.cs index c26fdad6b74734..9962638c49d6aa 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/HostApplicationBuilder.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/src/HostApplicationBuilder.cs @@ -88,7 +88,12 @@ public HostApplicationBuilder(HostApplicationBuilderSettings? settings) if (!settings.DisableDefaults) { - HostingHostBuilderExtensions.ApplyDefaultHostConfiguration(Configuration, settings.Args); + if (settings.ContentRootPath is null && Configuration[HostDefaults.ContentRootKey] is null) + { + HostingHostBuilderExtensions.SetDefaultContentRoot(Configuration); + } + + HostingHostBuilderExtensions.AddDefaultHostConfigurationSources(Configuration, settings.Args); } // HostApplicationBuilderSettings override all other config sources. diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs b/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs index a285d3e487ef7f..9c864e489ab139 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs @@ -188,8 +188,6 @@ internal static DiagnosticListener LogHostBuilding(HostApplicationBuilder hostAp return diagnosticListener; } - [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", - Justification = "DiagnosticSource is used here to pass objects in-memory to code using HostFactoryResolver. This won't require creating new generic types.")] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", Justification = "The values being passed into Write are being consumed by the application already.")] private static void Write( diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs b/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs index b8e8c207f650a3..5663e5fbb3c466 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs @@ -198,7 +198,13 @@ public static IHostBuilder ConfigureDefaults(this IHostBuilder builder, string[] .UseServiceProviderFactory(context => new DefaultServiceProviderFactory(CreateDefaultServiceProviderOptions(context))); } - internal static void ApplyDefaultHostConfiguration(IConfigurationBuilder hostConfigBuilder, string[]? args) + private static void ApplyDefaultHostConfiguration(IConfigurationBuilder hostConfigBuilder, string[]? args) + { + SetDefaultContentRoot(hostConfigBuilder); + AddDefaultHostConfigurationSources(hostConfigBuilder, args); + } + + internal static void SetDefaultContentRoot(IConfigurationBuilder hostConfigBuilder) { // If we're running anywhere other than C:\Windows\system32, we default to using the CWD for the ContentRoot. // However, since many things like Windows services and MSIX installers have C:\Windows\system32 as there CWD which is not likely @@ -216,7 +222,10 @@ internal static void ApplyDefaultHostConfiguration(IConfigurationBuilder hostCon new KeyValuePair(HostDefaults.ContentRootKey, cwd), }); } + } + internal static void AddDefaultHostConfigurationSources(IConfigurationBuilder hostConfigBuilder, string[]? args) + { hostConfigBuilder.AddEnvironmentVariables(prefix: "DOTNET_"); if (args is { Length: > 0 }) { diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/Microsoft.Extensions.Hosting.csproj b/src/libraries/Microsoft.Extensions.Hosting/src/Microsoft.Extensions.Hosting.csproj index 945760c9aa144c..c903877734fa0a 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/Microsoft.Extensions.Hosting.csproj +++ b/src/libraries/Microsoft.Extensions.Hosting/src/Microsoft.Extensions.Hosting.csproj @@ -3,9 +3,11 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.1;netstandard2.0;$(NetFrameworkMinimum) true - Hosting and startup infrastructures for applications. true true + Hosting and startup infrastructures for applications. + false + 1 @@ -51,6 +53,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostApplicationBuilderTests.cs b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostApplicationBuilderTests.cs index 458bf37eb7d005..d5ba003153769e 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostApplicationBuilderTests.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostApplicationBuilderTests.cs @@ -229,66 +229,128 @@ public void DisableDefaultIHostEnvironmentValues() Assert.IsAssignableFrom(env.ContentRootFileProvider); } - [Fact] - public void ConfigurationSettingCanInfluenceEnvironment() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ConfigurationSettingCanInfluenceEnvironment(bool disableDefaults) { - using var config = new ConfigurationManager(); + var tempPath = CreateTempSubdirectory(); - config.AddInMemoryCollection(new KeyValuePair[] + try { - new(HostDefaults.ApplicationKey, "AppA" ), - new(HostDefaults.EnvironmentKey, "EnvA" ), - }); + using var config = new ConfigurationManager(); - var builder = new HostApplicationBuilder(new HostApplicationBuilderSettings - { - DisableDefaults = true, - Configuration = config, - }); + config.AddInMemoryCollection(new KeyValuePair[] + { + new(HostDefaults.ApplicationKey, "AppA" ), + new(HostDefaults.EnvironmentKey, "EnvA" ), + new(HostDefaults.ContentRootKey, tempPath) + }); + + var builder = new HostApplicationBuilder(new HostApplicationBuilderSettings + { + DisableDefaults = disableDefaults, + Configuration = config, + }); - Assert.Equal("AppA", builder.Configuration[HostDefaults.ApplicationKey]); - Assert.Equal("EnvA", builder.Configuration[HostDefaults.EnvironmentKey]); + Assert.Equal("AppA", builder.Configuration[HostDefaults.ApplicationKey]); + Assert.Equal("EnvA", builder.Configuration[HostDefaults.EnvironmentKey]); + Assert.Equal(tempPath, builder.Configuration[HostDefaults.ContentRootKey]); - Assert.Equal("AppA", builder.Environment.ApplicationName); - Assert.Equal("EnvA", builder.Environment.EnvironmentName); + Assert.Equal("AppA", builder.Environment.ApplicationName); + Assert.Equal("EnvA", builder.Environment.EnvironmentName); + Assert.Equal(tempPath, builder.Environment.ContentRootPath); + var fileProviderFromBuilder = Assert.IsType(builder.Environment.ContentRootFileProvider); + Assert.Equal(tempPath, fileProviderFromBuilder.Root); - using IHost host = builder.Build(); + using IHost host = builder.Build(); - var hostEnvironmentFromServices = host.Services.GetRequiredService(); - Assert.Equal("AppA", hostEnvironmentFromServices.ApplicationName); - Assert.Equal("EnvA", hostEnvironmentFromServices.EnvironmentName); + var hostEnvironmentFromServices = host.Services.GetRequiredService(); + Assert.Equal("AppA", hostEnvironmentFromServices.ApplicationName); + Assert.Equal("EnvA", hostEnvironmentFromServices.EnvironmentName); + Assert.Equal(tempPath, hostEnvironmentFromServices.ContentRootPath); + var fileProviderFromServices = Assert.IsType(hostEnvironmentFromServices.ContentRootFileProvider); + Assert.Equal(tempPath, fileProviderFromServices.Root); + } + finally + { + Directory.Delete(tempPath); + } } - [Fact] - public void DirectSettingsOverrideConfigurationSetting() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void DirectSettingsOverrideConfigurationSetting(bool disableDefaults) { - using var config = new ConfigurationManager(); + var tempPath = CreateTempSubdirectory(); - config.AddInMemoryCollection(new KeyValuePair[] + try { - new(HostDefaults.ApplicationKey, "AppA" ), - new(HostDefaults.EnvironmentKey, "EnvA" ), - }); + using var config = new ConfigurationManager(); - var builder = new HostApplicationBuilder(new HostApplicationBuilderSettings - { - DisableDefaults = true, - Configuration = config, - ApplicationName = "AppB", - EnvironmentName = "EnvB", - }); + config.AddInMemoryCollection(new KeyValuePair[] + { + new(HostDefaults.ApplicationKey, "AppA" ), + new(HostDefaults.EnvironmentKey, "EnvA" ), + }); - Assert.Equal("AppB", builder.Configuration[HostDefaults.ApplicationKey]); - Assert.Equal("EnvB", builder.Configuration[HostDefaults.EnvironmentKey]); + var builder = new HostApplicationBuilder(new HostApplicationBuilderSettings + { + DisableDefaults = disableDefaults, + Configuration = config, + ApplicationName = "AppB", + EnvironmentName = "EnvB", + ContentRootPath = tempPath, + }); - Assert.Equal("AppB", builder.Environment.ApplicationName); - Assert.Equal("EnvB", builder.Environment.EnvironmentName); + Assert.Equal("AppB", builder.Configuration[HostDefaults.ApplicationKey]); + Assert.Equal("EnvB", builder.Configuration[HostDefaults.EnvironmentKey]); + Assert.Equal(tempPath, builder.Configuration[HostDefaults.ContentRootKey]); - using IHost host = builder.Build(); + Assert.Equal("AppB", builder.Environment.ApplicationName); + Assert.Equal("EnvB", builder.Environment.EnvironmentName); + Assert.Equal(tempPath, builder.Environment.ContentRootPath); + var fileProviderFromBuilder = Assert.IsType(builder.Environment.ContentRootFileProvider); + Assert.Equal(tempPath, fileProviderFromBuilder.Root); - var hostEnvironmentFromServices = host.Services.GetRequiredService(); - Assert.Equal("AppB", hostEnvironmentFromServices.ApplicationName); - Assert.Equal("EnvB", hostEnvironmentFromServices.EnvironmentName); + using IHost host = builder.Build(); + + var hostEnvironmentFromServices = host.Services.GetRequiredService(); + Assert.Equal("AppB", hostEnvironmentFromServices.ApplicationName); + Assert.Equal("EnvB", hostEnvironmentFromServices.EnvironmentName); + Assert.Equal(tempPath, hostEnvironmentFromServices.ContentRootPath); + var fileProviderFromServices = Assert.IsType(hostEnvironmentFromServices.ContentRootFileProvider); + Assert.Equal(tempPath, fileProviderFromServices.Root); + } + finally + { + Directory.Delete(tempPath); + } + } + + private static string CreateTempSubdirectory() + { +#if NETCOREAPP + DirectoryInfo directoryInfo = Directory.CreateTempSubdirectory(); +#else + DirectoryInfo directoryInfo = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())); + directoryInfo.Create(); +#endif + + // PhysicalFileProvider will always ensure the path has a trailing slash + return EnsureTrailingSlash(directoryInfo.FullName); + } + + private static string EnsureTrailingSlash(string path) + { + if (!string.IsNullOrEmpty(path) && + path[path.Length - 1] != Path.DirectorySeparatorChar) + { + return path + Path.DirectorySeparatorChar; + } + + return path; } [Fact] diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Microsoft.Extensions.Hosting.Unit.Tests.csproj b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Microsoft.Extensions.Hosting.Unit.Tests.csproj index b5c760d44ca83e..0743d679b15548 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Microsoft.Extensions.Hosting.Unit.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Microsoft.Extensions.Hosting.Unit.Tests.csproj @@ -5,6 +5,7 @@ true true true + true diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Roslyn4.0.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Roslyn4.0.cs index ab342384270807..7dd80ba2926bf1 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Roslyn4.0.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Roslyn4.0.cs @@ -7,7 +7,9 @@ using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +#if !ROSLYN4_4_OR_GREATER using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; +#endif using Microsoft.CodeAnalysis.Text; [assembly: System.Resources.NeutralResourcesLanguage("en-us")] @@ -21,7 +23,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { IncrementalValuesProvider classDeclarations = context.SyntaxProvider .ForAttributeWithMetadataName( +#if !ROSLYN4_4_OR_GREATER context, +#endif Parser.LoggerMessageAttribute, (node, _) => node is MethodDeclarationSyntax, (context, _) => context.TargetNode.Parent as ClassDeclarationSyntax) diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Microsoft.Extensions.Logging.Generators.Roslyn4.4.csproj b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Microsoft.Extensions.Logging.Generators.Roslyn4.4.csproj new file mode 100644 index 00000000000000..34e7c24c7a921a --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Microsoft.Extensions.Logging.Generators.Roslyn4.4.csproj @@ -0,0 +1,20 @@ + + + + 4.4 + $(MicrosoftCodeAnalysisVersion_4_4) + $(DefineConstants);ROSLYN4_0_OR_GREATER;ROSLYN4_4_OR_GREATER + + + + + + + + + + + + + + diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj index 09e2e23824b20e..97878e68088c15 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj @@ -6,6 +6,8 @@ true true true + false + 1 Logging abstractions for Microsoft.Extensions.Logging. Commonly Used Types: @@ -45,6 +47,9 @@ Microsoft.Extensions.Logging.Abstractions.NullLogger + diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs index 4bb4271e2655c9..e948d02f7269be 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs @@ -767,6 +767,40 @@ partial class C Assert.Equal(21, generatedSources[0].SourceText.Lines.Count); } + [Fact] + public static void SyntaxListWithManyItems() + { + const int nItems = 200000; + var builder = new System.Text.StringBuilder(); + builder.AppendLine( + """ + using Microsoft.Extensions.Logging; + class Program + { + [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = "M1")] + static partial void M1(ILogger logger) + { + """); + builder.AppendLine(" int[] values = new[] { "); + for (int i = 0; i < nItems; i++) + { + builder.Append("0, "); + } + builder.AppendLine("};"); + builder.AppendLine("}"); + builder.AppendLine("}"); + + string source = builder.ToString(); + Compilation compilation = CompilationHelper.CreateCompilation(source); + LoggerMessageGenerator generator = new LoggerMessageGenerator(); + + (ImmutableArray diagnostics, _) = + RoslynTestUtils.RunGenerator(compilation, generator); + + Assert.Single(diagnostics); + Assert.Equal(DiagnosticDescriptors.LoggingMethodHasBody.Id, diagnostics[0].Id); + } + private static async Task> RunGenerator( string code, bool wrap = true, diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj index c1f8f924a4b2ee..af4ed2dbf2c208 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj @@ -5,6 +5,11 @@ true true true + + true diff --git a/src/libraries/Microsoft.Extensions.Logging.EventSource/tests/Microsoft.Extensions.Logging.EventSource.Tests.csproj b/src/libraries/Microsoft.Extensions.Logging.EventSource/tests/Microsoft.Extensions.Logging.EventSource.Tests.csproj index 02fc06ade5edae..cc24d8f4a83ea0 100644 --- a/src/libraries/Microsoft.Extensions.Logging.EventSource/tests/Microsoft.Extensions.Logging.EventSource.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.EventSource/tests/Microsoft.Extensions.Logging.EventSource.Tests.csproj @@ -3,6 +3,7 @@ $(NetCoreAppCurrent);$(NetFrameworkMinimum) true + true diff --git a/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj b/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj index 39bfd89a00736f..b0ebc87c25e20f 100644 --- a/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj +++ b/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj @@ -6,6 +6,8 @@ true true Provides a strongly typed way of specifying and accessing settings using dependency injection. + 1 + false diff --git a/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs b/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs index 9be845ca4e8b71..51de2bbe4fc27d 100644 --- a/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs +++ b/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs @@ -65,7 +65,7 @@ internal TOptions GetOrAdd(string? name, Func crea #if NET || NETSTANDARD2_1 return _cache.GetOrAdd( name ?? Options.DefaultName, - static (name, arg) => new Lazy(arg.createOptions(name, arg.factoryArgument)), (createOptions, factoryArgument)).Value; + static (name, arg) => new Lazy(() => arg.createOptions(name, arg.factoryArgument)), (createOptions, factoryArgument)).Value; #endif } diff --git a/src/libraries/Microsoft.Extensions.Options/tests/Microsoft.Extensions.Options.Tests/OptionsMonitorTest.cs b/src/libraries/Microsoft.Extensions.Options/tests/Microsoft.Extensions.Options.Tests/OptionsMonitorTest.cs index 06fb1d9476350b..c04f48af0e8f9d 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/Microsoft.Extensions.Options.Tests/OptionsMonitorTest.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/Microsoft.Extensions.Options.Tests/OptionsMonitorTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Primitives; @@ -485,5 +486,56 @@ public void TestCurrentValueDoesNotAllocateOnceValueIsCached() Assert.Equal(0, GC.GetAllocatedBytesForCurrentThread() - initialBytes); } #endif + + /// + /// Replicates https://github.com/dotnet/runtime/issues/79529 + /// + [Fact] + [SkipOnPlatform(TestPlatforms.Browser, "Synchronous wait is not supported on browser")] + public void InstantiatesOnlyOneOptionsInstance() + { + using AutoResetEvent @event = new(initialState: false); + + OptionsMonitor monitor = new( + // WaitHandleConfigureOptions makes instance configuration slow enough to force a race condition + new OptionsFactory(new[] { new WaitHandleConfigureOptions(@event) }, Enumerable.Empty>()), + Enumerable.Empty>(), + new OptionsCache()); + + using Barrier barrier = new(participantCount: 2); + Task[] instanceTasks = Enumerable.Range(0, 2) + .Select(_ => Task.Factory.StartNew( + () => + { + barrier.SignalAndWait(); + return monitor.Get("someName"); + }, + CancellationToken.None, + TaskCreationOptions.LongRunning, + TaskScheduler.Default) + ) + .ToArray(); + + // No tasks can finish yet; but give them a chance to run and get blocked on the WaitHandle + Assert.Equal(-1, Task.WaitAny(instanceTasks, TimeSpan.FromSeconds(0.01))); + + // 1 release should be sufficient to complete both tasks + @event.Set(); + Assert.True(Task.WaitAll(instanceTasks, TimeSpan.FromSeconds(30))); + Assert.Equal(1, instanceTasks.Select(t => t.Result).Distinct().Count()); + } + + private class WaitHandleConfigureOptions : IConfigureNamedOptions + { + private readonly WaitHandle _waitHandle; + + public WaitHandleConfigureOptions(WaitHandle waitHandle) + { + _waitHandle = waitHandle; + } + + void IConfigureNamedOptions.Configure(string? name, FakeOptions options) => _waitHandle.WaitOne(); + void IConfigureOptions.Configure(FakeOptions options) => _waitHandle.WaitOne(); + } } } diff --git a/src/libraries/Microsoft.Internal.Runtime.AspNetCore.Transport/src/Microsoft.Internal.Runtime.AspNetCore.Transport.proj b/src/libraries/Microsoft.Internal.Runtime.AspNetCore.Transport/src/Microsoft.Internal.Runtime.AspNetCore.Transport.proj index cd8628c81afd08..e80ea55a40d45a 100644 --- a/src/libraries/Microsoft.Internal.Runtime.AspNetCore.Transport/src/Microsoft.Internal.Runtime.AspNetCore.Transport.proj +++ b/src/libraries/Microsoft.Internal.Runtime.AspNetCore.Transport/src/Microsoft.Internal.Runtime.AspNetCore.Transport.proj @@ -13,6 +13,12 @@ $(NoWarn);NU5131 + + + true + $(PatchVersion) + + - - + diff --git a/src/libraries/Microsoft.Internal.Runtime.WindowsDesktop.Transport/src/Microsoft.Internal.Runtime.WindowsDesktop.Transport.proj b/src/libraries/Microsoft.Internal.Runtime.WindowsDesktop.Transport/src/Microsoft.Internal.Runtime.WindowsDesktop.Transport.proj index 4fd7866e89e358..6d7a255a94c9de 100644 --- a/src/libraries/Microsoft.Internal.Runtime.WindowsDesktop.Transport/src/Microsoft.Internal.Runtime.WindowsDesktop.Transport.proj +++ b/src/libraries/Microsoft.Internal.Runtime.WindowsDesktop.Transport/src/Microsoft.Internal.Runtime.WindowsDesktop.Transport.proj @@ -13,6 +13,12 @@ $(NoWarn);NU5131 + + + true + $(PatchVersion) + + diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/Microsoft.NETCore.Platforms.csproj b/src/libraries/Microsoft.NETCore.Platforms/src/Microsoft.NETCore.Platforms.csproj index 482e0b70e47e70..3f5e05d8f9745b 100644 --- a/src/libraries/Microsoft.NETCore.Platforms/src/Microsoft.NETCore.Platforms.csproj +++ b/src/libraries/Microsoft.NETCore.Platforms/src/Microsoft.NETCore.Platforms.csproj @@ -17,13 +17,15 @@ true $(MSBuildProjectName) - GenerateRuntimeJson;UpdateRuntimeJson;$(BeforePack) - + UpdateRuntimeJson;$(BeforePack) + <_generateRuntimeGraphTargetFramework Condition="'$(MSBuildRuntimeType)' == 'core'">$(NetCoreAppToolCurrent) <_generateRuntimeGraphTargetFramework Condition="'$(MSBuildRuntimeType)' != 'core'">net472 <_generateRuntimeGraphTask>$([MSBuild]::NormalizePath('$(BaseOutputPath)', $(Configuration), '$(_generateRuntimeGraphTargetFramework)', '$(AssemblyName).dll')) $(AdditionalRuntimeIdentifiers);$(OutputRID) + 4 + false @@ -44,7 +46,7 @@ - + @@ -53,17 +55,17 @@ - + - + - + diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/RID.cs b/src/libraries/Microsoft.NETCore.Platforms/src/RID.cs index 6457fd29cadf0e..b464d5f0f54e37 100644 --- a/src/libraries/Microsoft.NETCore.Platforms/src/RID.cs +++ b/src/libraries/Microsoft.NETCore.Platforms/src/RID.cs @@ -56,7 +56,7 @@ private enum RIDPart : int Max = Qualifier } - public static RID Parse(string runtimeIdentifier) + public static RID Parse(string runtimeIdentifier, bool noQualifier) { string[] parts = new string[(int)RIDPart.Max + 1]; bool omitVersionDelimiter = true; @@ -90,6 +90,15 @@ public static RID Parse(string runtimeIdentifier) // version might be omitted else if (current == ArchitectureDelimiter) { + // The qualifier delimiter and architecture delimiter are the same. + // When there is no qualifier, there will be one delimiter past the base part + // for the architecture. + // So if we see another delimiter later in the string (for the architecture), + // extend the base part instead of starting the architecture part. + if (noQualifier && runtimeIdentifier.IndexOf(ArchitectureDelimiter, i + 1) != -1) + { + break; + } // ensure there's no version later in the string if (runtimeIdentifier.IndexOf(VersionDelimiter, i) != -1) { diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeGroupCollection.cs b/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeGroupCollection.cs index 447aa7239a844f..d233ec4a89c0bb 100644 --- a/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeGroupCollection.cs +++ b/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeGroupCollection.cs @@ -34,7 +34,8 @@ public RuntimeGroupCollection(ICollection runtimeGroups) /// public void AddRuntimeIdentifier(string runtimeIdentifier, string parent) { - RID rid = RID.Parse(runtimeIdentifier); + // don't parse qualifier since we don't use them and they are ambiguous with `-` in base RID + RID rid = RID.Parse(runtimeIdentifier, noQualifier: true); AddRuntimeIdentifier(rid, parent); } diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/runtime.compatibility.json b/src/libraries/Microsoft.NETCore.Platforms/src/runtime.compatibility.json index 15c8c0a74777fd..a87f79674ab61c 100644 --- a/src/libraries/Microsoft.NETCore.Platforms/src/runtime.compatibility.json +++ b/src/libraries/Microsoft.NETCore.Platforms/src/runtime.compatibility.json @@ -31,6 +31,42 @@ "any", "base" ], + "alpine-armv6": [ + "alpine-armv6", + "alpine", + "linux-musl-armv6", + "linux-musl", + "linux-armv6", + "linux", + "unix-armv6", + "unix", + "any", + "base" + ], + "alpine-ppc64le": [ + "alpine-ppc64le", + "alpine", + "linux-musl-ppc64le", + "linux-musl", + "linux-ppc64le", + "linux", + "unix-ppc64le", + "unix", + "any", + "base" + ], + "alpine-s390x": [ + "alpine-s390x", + "alpine", + "linux-musl-s390x", + "linux-musl", + "linux-s390x", + "linux", + "unix-s390x", + "unix", + "any", + "base" + ], "alpine-x64": [ "alpine-x64", "alpine", @@ -43,6 +79,18 @@ "any", "base" ], + "alpine-x86": [ + "alpine-x86", + "alpine", + "linux-musl-x86", + "linux-musl", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], "alpine.3.10": [ "alpine.3.10", "alpine.3.9", @@ -100,6 +148,72 @@ "any", "base" ], + "alpine.3.10-armv6": [ + "alpine.3.10-armv6", + "alpine.3.10", + "alpine.3.9-armv6", + "alpine.3.9", + "alpine.3.8-armv6", + "alpine.3.8", + "alpine.3.7-armv6", + "alpine.3.7", + "alpine.3.6-armv6", + "alpine.3.6", + "alpine-armv6", + "alpine", + "linux-musl-armv6", + "linux-musl", + "linux-armv6", + "linux", + "unix-armv6", + "unix", + "any", + "base" + ], + "alpine.3.10-ppc64le": [ + "alpine.3.10-ppc64le", + "alpine.3.10", + "alpine.3.9-ppc64le", + "alpine.3.9", + "alpine.3.8-ppc64le", + "alpine.3.8", + "alpine.3.7-ppc64le", + "alpine.3.7", + "alpine.3.6-ppc64le", + "alpine.3.6", + "alpine-ppc64le", + "alpine", + "linux-musl-ppc64le", + "linux-musl", + "linux-ppc64le", + "linux", + "unix-ppc64le", + "unix", + "any", + "base" + ], + "alpine.3.10-s390x": [ + "alpine.3.10-s390x", + "alpine.3.10", + "alpine.3.9-s390x", + "alpine.3.9", + "alpine.3.8-s390x", + "alpine.3.8", + "alpine.3.7-s390x", + "alpine.3.7", + "alpine.3.6-s390x", + "alpine.3.6", + "alpine-s390x", + "alpine", + "linux-musl-s390x", + "linux-musl", + "linux-s390x", + "linux", + "unix-s390x", + "unix", + "any", + "base" + ], "alpine.3.10-x64": [ "alpine.3.10-x64", "alpine.3.10", @@ -122,6 +236,28 @@ "any", "base" ], + "alpine.3.10-x86": [ + "alpine.3.10-x86", + "alpine.3.10", + "alpine.3.9-x86", + "alpine.3.9", + "alpine.3.8-x86", + "alpine.3.8", + "alpine.3.7-x86", + "alpine.3.7", + "alpine.3.6-x86", + "alpine.3.6", + "alpine-x86", + "alpine", + "linux-musl-x86", + "linux-musl", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], "alpine.3.11": [ "alpine.3.11", "alpine.3.10", @@ -184,6 +320,78 @@ "any", "base" ], + "alpine.3.11-armv6": [ + "alpine.3.11-armv6", + "alpine.3.11", + "alpine.3.10-armv6", + "alpine.3.10", + "alpine.3.9-armv6", + "alpine.3.9", + "alpine.3.8-armv6", + "alpine.3.8", + "alpine.3.7-armv6", + "alpine.3.7", + "alpine.3.6-armv6", + "alpine.3.6", + "alpine-armv6", + "alpine", + "linux-musl-armv6", + "linux-musl", + "linux-armv6", + "linux", + "unix-armv6", + "unix", + "any", + "base" + ], + "alpine.3.11-ppc64le": [ + "alpine.3.11-ppc64le", + "alpine.3.11", + "alpine.3.10-ppc64le", + "alpine.3.10", + "alpine.3.9-ppc64le", + "alpine.3.9", + "alpine.3.8-ppc64le", + "alpine.3.8", + "alpine.3.7-ppc64le", + "alpine.3.7", + "alpine.3.6-ppc64le", + "alpine.3.6", + "alpine-ppc64le", + "alpine", + "linux-musl-ppc64le", + "linux-musl", + "linux-ppc64le", + "linux", + "unix-ppc64le", + "unix", + "any", + "base" + ], + "alpine.3.11-s390x": [ + "alpine.3.11-s390x", + "alpine.3.11", + "alpine.3.10-s390x", + "alpine.3.10", + "alpine.3.9-s390x", + "alpine.3.9", + "alpine.3.8-s390x", + "alpine.3.8", + "alpine.3.7-s390x", + "alpine.3.7", + "alpine.3.6-s390x", + "alpine.3.6", + "alpine-s390x", + "alpine", + "linux-musl-s390x", + "linux-musl", + "linux-s390x", + "linux", + "unix-s390x", + "unix", + "any", + "base" + ], "alpine.3.11-x64": [ "alpine.3.11-x64", "alpine.3.11", @@ -208,6 +416,30 @@ "any", "base" ], + "alpine.3.11-x86": [ + "alpine.3.11-x86", + "alpine.3.11", + "alpine.3.10-x86", + "alpine.3.10", + "alpine.3.9-x86", + "alpine.3.9", + "alpine.3.8-x86", + "alpine.3.8", + "alpine.3.7-x86", + "alpine.3.7", + "alpine.3.6-x86", + "alpine.3.6", + "alpine-x86", + "alpine", + "linux-musl-x86", + "linux-musl", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], "alpine.3.12": [ "alpine.3.12", "alpine.3.11", @@ -275,134 +507,137 @@ "any", "base" ], - "alpine.3.12-x64": [ - "alpine.3.12-x64", + "alpine.3.12-armv6": [ + "alpine.3.12-armv6", "alpine.3.12", - "alpine.3.11-x64", + "alpine.3.11-armv6", "alpine.3.11", - "alpine.3.10-x64", + "alpine.3.10-armv6", "alpine.3.10", - "alpine.3.9-x64", + "alpine.3.9-armv6", "alpine.3.9", - "alpine.3.8-x64", + "alpine.3.8-armv6", "alpine.3.8", - "alpine.3.7-x64", + "alpine.3.7-armv6", "alpine.3.7", - "alpine.3.6-x64", + "alpine.3.6-armv6", "alpine.3.6", - "alpine-x64", + "alpine-armv6", "alpine", - "linux-musl-x64", + "linux-musl-armv6", "linux-musl", - "linux-x64", + "linux-armv6", "linux", - "unix-x64", + "unix-armv6", "unix", "any", "base" ], - "alpine.3.13": [ - "alpine.3.13", + "alpine.3.12-ppc64le": [ + "alpine.3.12-ppc64le", "alpine.3.12", + "alpine.3.11-ppc64le", "alpine.3.11", + "alpine.3.10-ppc64le", "alpine.3.10", + "alpine.3.9-ppc64le", "alpine.3.9", + "alpine.3.8-ppc64le", "alpine.3.8", + "alpine.3.7-ppc64le", "alpine.3.7", + "alpine.3.6-ppc64le", "alpine.3.6", + "alpine-ppc64le", "alpine", + "linux-musl-ppc64le", "linux-musl", + "linux-ppc64le", "linux", + "unix-ppc64le", "unix", "any", "base" ], - "alpine.3.13-arm": [ - "alpine.3.13-arm", - "alpine.3.13", - "alpine.3.12-arm", + "alpine.3.12-s390x": [ + "alpine.3.12-s390x", "alpine.3.12", - "alpine.3.11-arm", + "alpine.3.11-s390x", "alpine.3.11", - "alpine.3.10-arm", + "alpine.3.10-s390x", "alpine.3.10", - "alpine.3.9-arm", + "alpine.3.9-s390x", "alpine.3.9", - "alpine.3.8-arm", + "alpine.3.8-s390x", "alpine.3.8", - "alpine.3.7-arm", + "alpine.3.7-s390x", "alpine.3.7", - "alpine.3.6-arm", + "alpine.3.6-s390x", "alpine.3.6", - "alpine-arm", + "alpine-s390x", "alpine", - "linux-musl-arm", + "linux-musl-s390x", "linux-musl", - "linux-arm", + "linux-s390x", "linux", - "unix-arm", + "unix-s390x", "unix", "any", "base" ], - "alpine.3.13-arm64": [ - "alpine.3.13-arm64", - "alpine.3.13", - "alpine.3.12-arm64", + "alpine.3.12-x64": [ + "alpine.3.12-x64", "alpine.3.12", - "alpine.3.11-arm64", + "alpine.3.11-x64", "alpine.3.11", - "alpine.3.10-arm64", + "alpine.3.10-x64", "alpine.3.10", - "alpine.3.9-arm64", + "alpine.3.9-x64", "alpine.3.9", - "alpine.3.8-arm64", + "alpine.3.8-x64", "alpine.3.8", - "alpine.3.7-arm64", + "alpine.3.7-x64", "alpine.3.7", - "alpine.3.6-arm64", + "alpine.3.6-x64", "alpine.3.6", - "alpine-arm64", + "alpine-x64", "alpine", - "linux-musl-arm64", + "linux-musl-x64", "linux-musl", - "linux-arm64", + "linux-x64", "linux", - "unix-arm64", + "unix-x64", "unix", "any", "base" ], - "alpine.3.13-x64": [ - "alpine.3.13-x64", - "alpine.3.13", - "alpine.3.12-x64", + "alpine.3.12-x86": [ + "alpine.3.12-x86", "alpine.3.12", - "alpine.3.11-x64", + "alpine.3.11-x86", "alpine.3.11", - "alpine.3.10-x64", + "alpine.3.10-x86", "alpine.3.10", - "alpine.3.9-x64", + "alpine.3.9-x86", "alpine.3.9", - "alpine.3.8-x64", + "alpine.3.8-x86", "alpine.3.8", - "alpine.3.7-x64", + "alpine.3.7-x86", "alpine.3.7", - "alpine.3.6-x64", + "alpine.3.6-x86", "alpine.3.6", - "alpine-x64", + "alpine-x86", "alpine", - "linux-musl-x64", + "linux-musl-x86", "linux-musl", - "linux-x64", + "linux-x86", "linux", - "unix-x64", + "unix-x86", "unix", "any", "base" ], - "alpine.3.14": [ - "alpine.3.14", + "alpine.3.13": [ "alpine.3.13", "alpine.3.12", "alpine.3.11", @@ -418,9 +653,7 @@ "any", "base" ], - "alpine.3.14-arm": [ - "alpine.3.14-arm", - "alpine.3.14", + "alpine.3.13-arm": [ "alpine.3.13-arm", "alpine.3.13", "alpine.3.12-arm", @@ -448,9 +681,7 @@ "any", "base" ], - "alpine.3.14-arm64": [ - "alpine.3.14-arm64", - "alpine.3.14", + "alpine.3.13-arm64": [ "alpine.3.13-arm64", "alpine.3.13", "alpine.3.12-arm64", @@ -478,12 +709,94 @@ "any", "base" ], - "alpine.3.14-x64": [ - "alpine.3.14-x64", - "alpine.3.14", - "alpine.3.13-x64", + "alpine.3.13-armv6": [ + "alpine.3.13-armv6", "alpine.3.13", - "alpine.3.12-x64", + "alpine.3.12-armv6", + "alpine.3.12", + "alpine.3.11-armv6", + "alpine.3.11", + "alpine.3.10-armv6", + "alpine.3.10", + "alpine.3.9-armv6", + "alpine.3.9", + "alpine.3.8-armv6", + "alpine.3.8", + "alpine.3.7-armv6", + "alpine.3.7", + "alpine.3.6-armv6", + "alpine.3.6", + "alpine-armv6", + "alpine", + "linux-musl-armv6", + "linux-musl", + "linux-armv6", + "linux", + "unix-armv6", + "unix", + "any", + "base" + ], + "alpine.3.13-ppc64le": [ + "alpine.3.13-ppc64le", + "alpine.3.13", + "alpine.3.12-ppc64le", + "alpine.3.12", + "alpine.3.11-ppc64le", + "alpine.3.11", + "alpine.3.10-ppc64le", + "alpine.3.10", + "alpine.3.9-ppc64le", + "alpine.3.9", + "alpine.3.8-ppc64le", + "alpine.3.8", + "alpine.3.7-ppc64le", + "alpine.3.7", + "alpine.3.6-ppc64le", + "alpine.3.6", + "alpine-ppc64le", + "alpine", + "linux-musl-ppc64le", + "linux-musl", + "linux-ppc64le", + "linux", + "unix-ppc64le", + "unix", + "any", + "base" + ], + "alpine.3.13-s390x": [ + "alpine.3.13-s390x", + "alpine.3.13", + "alpine.3.12-s390x", + "alpine.3.12", + "alpine.3.11-s390x", + "alpine.3.11", + "alpine.3.10-s390x", + "alpine.3.10", + "alpine.3.9-s390x", + "alpine.3.9", + "alpine.3.8-s390x", + "alpine.3.8", + "alpine.3.7-s390x", + "alpine.3.7", + "alpine.3.6-s390x", + "alpine.3.6", + "alpine-s390x", + "alpine", + "linux-musl-s390x", + "linux-musl", + "linux-s390x", + "linux", + "unix-s390x", + "unix", + "any", + "base" + ], + "alpine.3.13-x64": [ + "alpine.3.13-x64", + "alpine.3.13", + "alpine.3.12-x64", "alpine.3.12", "alpine.3.11-x64", "alpine.3.11", @@ -508,8 +821,35 @@ "any", "base" ], - "alpine.3.15": [ - "alpine.3.15", + "alpine.3.13-x86": [ + "alpine.3.13-x86", + "alpine.3.13", + "alpine.3.12-x86", + "alpine.3.12", + "alpine.3.11-x86", + "alpine.3.11", + "alpine.3.10-x86", + "alpine.3.10", + "alpine.3.9-x86", + "alpine.3.9", + "alpine.3.8-x86", + "alpine.3.8", + "alpine.3.7-x86", + "alpine.3.7", + "alpine.3.6-x86", + "alpine.3.6", + "alpine-x86", + "alpine", + "linux-musl-x86", + "linux-musl", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], + "alpine.3.14": [ "alpine.3.14", "alpine.3.13", "alpine.3.12", @@ -526,9 +866,7 @@ "any", "base" ], - "alpine.3.15-arm": [ - "alpine.3.15-arm", - "alpine.3.15", + "alpine.3.14-arm": [ "alpine.3.14-arm", "alpine.3.14", "alpine.3.13-arm", @@ -558,9 +896,7 @@ "any", "base" ], - "alpine.3.15-arm64": [ - "alpine.3.15-arm64", - "alpine.3.15", + "alpine.3.14-arm64": [ "alpine.3.14-arm64", "alpine.3.14", "alpine.3.13-arm64", @@ -590,9 +926,97 @@ "any", "base" ], - "alpine.3.15-x64": [ - "alpine.3.15-x64", - "alpine.3.15", + "alpine.3.14-armv6": [ + "alpine.3.14-armv6", + "alpine.3.14", + "alpine.3.13-armv6", + "alpine.3.13", + "alpine.3.12-armv6", + "alpine.3.12", + "alpine.3.11-armv6", + "alpine.3.11", + "alpine.3.10-armv6", + "alpine.3.10", + "alpine.3.9-armv6", + "alpine.3.9", + "alpine.3.8-armv6", + "alpine.3.8", + "alpine.3.7-armv6", + "alpine.3.7", + "alpine.3.6-armv6", + "alpine.3.6", + "alpine-armv6", + "alpine", + "linux-musl-armv6", + "linux-musl", + "linux-armv6", + "linux", + "unix-armv6", + "unix", + "any", + "base" + ], + "alpine.3.14-ppc64le": [ + "alpine.3.14-ppc64le", + "alpine.3.14", + "alpine.3.13-ppc64le", + "alpine.3.13", + "alpine.3.12-ppc64le", + "alpine.3.12", + "alpine.3.11-ppc64le", + "alpine.3.11", + "alpine.3.10-ppc64le", + "alpine.3.10", + "alpine.3.9-ppc64le", + "alpine.3.9", + "alpine.3.8-ppc64le", + "alpine.3.8", + "alpine.3.7-ppc64le", + "alpine.3.7", + "alpine.3.6-ppc64le", + "alpine.3.6", + "alpine-ppc64le", + "alpine", + "linux-musl-ppc64le", + "linux-musl", + "linux-ppc64le", + "linux", + "unix-ppc64le", + "unix", + "any", + "base" + ], + "alpine.3.14-s390x": [ + "alpine.3.14-s390x", + "alpine.3.14", + "alpine.3.13-s390x", + "alpine.3.13", + "alpine.3.12-s390x", + "alpine.3.12", + "alpine.3.11-s390x", + "alpine.3.11", + "alpine.3.10-s390x", + "alpine.3.10", + "alpine.3.9-s390x", + "alpine.3.9", + "alpine.3.8-s390x", + "alpine.3.8", + "alpine.3.7-s390x", + "alpine.3.7", + "alpine.3.6-s390x", + "alpine.3.6", + "alpine-s390x", + "alpine", + "linux-musl-s390x", + "linux-musl", + "linux-s390x", + "linux", + "unix-s390x", + "unix", + "any", + "base" + ], + "alpine.3.14-x64": [ "alpine.3.14-x64", "alpine.3.14", "alpine.3.13-x64", @@ -622,8 +1046,37 @@ "any", "base" ], - "alpine.3.16": [ - "alpine.3.16", + "alpine.3.14-x86": [ + "alpine.3.14-x86", + "alpine.3.14", + "alpine.3.13-x86", + "alpine.3.13", + "alpine.3.12-x86", + "alpine.3.12", + "alpine.3.11-x86", + "alpine.3.11", + "alpine.3.10-x86", + "alpine.3.10", + "alpine.3.9-x86", + "alpine.3.9", + "alpine.3.8-x86", + "alpine.3.8", + "alpine.3.7-x86", + "alpine.3.7", + "alpine.3.6-x86", + "alpine.3.6", + "alpine-x86", + "alpine", + "linux-musl-x86", + "linux-musl", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], + "alpine.3.15": [ "alpine.3.15", "alpine.3.14", "alpine.3.13", @@ -641,9 +1094,7 @@ "any", "base" ], - "alpine.3.16-arm": [ - "alpine.3.16-arm", - "alpine.3.16", + "alpine.3.15-arm": [ "alpine.3.15-arm", "alpine.3.15", "alpine.3.14-arm", @@ -675,9 +1126,7 @@ "any", "base" ], - "alpine.3.16-arm64": [ - "alpine.3.16-arm64", - "alpine.3.16", + "alpine.3.15-arm64": [ "alpine.3.15-arm64", "alpine.3.15", "alpine.3.14-arm64", @@ -709,78 +1158,1062 @@ "any", "base" ], - "alpine.3.16-x64": [ - "alpine.3.16-x64", - "alpine.3.16", - "alpine.3.15-x64", + "alpine.3.15-armv6": [ + "alpine.3.15-armv6", "alpine.3.15", - "alpine.3.14-x64", + "alpine.3.14-armv6", "alpine.3.14", - "alpine.3.13-x64", + "alpine.3.13-armv6", "alpine.3.13", - "alpine.3.12-x64", + "alpine.3.12-armv6", "alpine.3.12", - "alpine.3.11-x64", + "alpine.3.11-armv6", "alpine.3.11", - "alpine.3.10-x64", + "alpine.3.10-armv6", "alpine.3.10", - "alpine.3.9-x64", + "alpine.3.9-armv6", "alpine.3.9", - "alpine.3.8-x64", + "alpine.3.8-armv6", "alpine.3.8", - "alpine.3.7-x64", + "alpine.3.7-armv6", "alpine.3.7", - "alpine.3.6-x64", + "alpine.3.6-armv6", "alpine.3.6", - "alpine-x64", + "alpine-armv6", "alpine", - "linux-musl-x64", + "linux-musl-armv6", "linux-musl", - "linux-x64", + "linux-armv6", "linux", - "unix-x64", + "unix-armv6", "unix", "any", "base" ], - "alpine.3.6": [ + "alpine.3.15-ppc64le": [ + "alpine.3.15-ppc64le", + "alpine.3.15", + "alpine.3.14-ppc64le", + "alpine.3.14", + "alpine.3.13-ppc64le", + "alpine.3.13", + "alpine.3.12-ppc64le", + "alpine.3.12", + "alpine.3.11-ppc64le", + "alpine.3.11", + "alpine.3.10-ppc64le", + "alpine.3.10", + "alpine.3.9-ppc64le", + "alpine.3.9", + "alpine.3.8-ppc64le", + "alpine.3.8", + "alpine.3.7-ppc64le", + "alpine.3.7", + "alpine.3.6-ppc64le", "alpine.3.6", + "alpine-ppc64le", "alpine", + "linux-musl-ppc64le", "linux-musl", + "linux-ppc64le", "linux", + "unix-ppc64le", "unix", "any", "base" ], - "alpine.3.6-arm": [ - "alpine.3.6-arm", + "alpine.3.15-s390x": [ + "alpine.3.15-s390x", + "alpine.3.15", + "alpine.3.14-s390x", + "alpine.3.14", + "alpine.3.13-s390x", + "alpine.3.13", + "alpine.3.12-s390x", + "alpine.3.12", + "alpine.3.11-s390x", + "alpine.3.11", + "alpine.3.10-s390x", + "alpine.3.10", + "alpine.3.9-s390x", + "alpine.3.9", + "alpine.3.8-s390x", + "alpine.3.8", + "alpine.3.7-s390x", + "alpine.3.7", + "alpine.3.6-s390x", "alpine.3.6", - "alpine-arm", + "alpine-s390x", "alpine", - "linux-musl-arm", + "linux-musl-s390x", "linux-musl", - "linux-arm", + "linux-s390x", "linux", - "unix-arm", + "unix-s390x", "unix", "any", "base" ], - "alpine.3.6-arm64": [ - "alpine.3.6-arm64", + "alpine.3.15-x64": [ + "alpine.3.15-x64", + "alpine.3.15", + "alpine.3.14-x64", + "alpine.3.14", + "alpine.3.13-x64", + "alpine.3.13", + "alpine.3.12-x64", + "alpine.3.12", + "alpine.3.11-x64", + "alpine.3.11", + "alpine.3.10-x64", + "alpine.3.10", + "alpine.3.9-x64", + "alpine.3.9", + "alpine.3.8-x64", + "alpine.3.8", + "alpine.3.7-x64", + "alpine.3.7", + "alpine.3.6-x64", "alpine.3.6", - "alpine-arm64", + "alpine-x64", "alpine", - "linux-musl-arm64", + "linux-musl-x64", "linux-musl", - "linux-arm64", + "linux-x64", "linux", - "unix-arm64", + "unix-x64", "unix", "any", "base" ], - "alpine.3.6-x64": [ + "alpine.3.15-x86": [ + "alpine.3.15-x86", + "alpine.3.15", + "alpine.3.14-x86", + "alpine.3.14", + "alpine.3.13-x86", + "alpine.3.13", + "alpine.3.12-x86", + "alpine.3.12", + "alpine.3.11-x86", + "alpine.3.11", + "alpine.3.10-x86", + "alpine.3.10", + "alpine.3.9-x86", + "alpine.3.9", + "alpine.3.8-x86", + "alpine.3.8", + "alpine.3.7-x86", + "alpine.3.7", + "alpine.3.6-x86", + "alpine.3.6", + "alpine-x86", + "alpine", + "linux-musl-x86", + "linux-musl", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], + "alpine.3.16": [ + "alpine.3.16", + "alpine.3.15", + "alpine.3.14", + "alpine.3.13", + "alpine.3.12", + "alpine.3.11", + "alpine.3.10", + "alpine.3.9", + "alpine.3.8", + "alpine.3.7", + "alpine.3.6", + "alpine", + "linux-musl", + "linux", + "unix", + "any", + "base" + ], + "alpine.3.16-arm": [ + "alpine.3.16-arm", + "alpine.3.16", + "alpine.3.15-arm", + "alpine.3.15", + "alpine.3.14-arm", + "alpine.3.14", + "alpine.3.13-arm", + "alpine.3.13", + "alpine.3.12-arm", + "alpine.3.12", + "alpine.3.11-arm", + "alpine.3.11", + "alpine.3.10-arm", + "alpine.3.10", + "alpine.3.9-arm", + "alpine.3.9", + "alpine.3.8-arm", + "alpine.3.8", + "alpine.3.7-arm", + "alpine.3.7", + "alpine.3.6-arm", + "alpine.3.6", + "alpine-arm", + "alpine", + "linux-musl-arm", + "linux-musl", + "linux-arm", + "linux", + "unix-arm", + "unix", + "any", + "base" + ], + "alpine.3.16-arm64": [ + "alpine.3.16-arm64", + "alpine.3.16", + "alpine.3.15-arm64", + "alpine.3.15", + "alpine.3.14-arm64", + "alpine.3.14", + "alpine.3.13-arm64", + "alpine.3.13", + "alpine.3.12-arm64", + "alpine.3.12", + "alpine.3.11-arm64", + "alpine.3.11", + "alpine.3.10-arm64", + "alpine.3.10", + "alpine.3.9-arm64", + "alpine.3.9", + "alpine.3.8-arm64", + "alpine.3.8", + "alpine.3.7-arm64", + "alpine.3.7", + "alpine.3.6-arm64", + "alpine.3.6", + "alpine-arm64", + "alpine", + "linux-musl-arm64", + "linux-musl", + "linux-arm64", + "linux", + "unix-arm64", + "unix", + "any", + "base" + ], + "alpine.3.16-armv6": [ + "alpine.3.16-armv6", + "alpine.3.16", + "alpine.3.15-armv6", + "alpine.3.15", + "alpine.3.14-armv6", + "alpine.3.14", + "alpine.3.13-armv6", + "alpine.3.13", + "alpine.3.12-armv6", + "alpine.3.12", + "alpine.3.11-armv6", + "alpine.3.11", + "alpine.3.10-armv6", + "alpine.3.10", + "alpine.3.9-armv6", + "alpine.3.9", + "alpine.3.8-armv6", + "alpine.3.8", + "alpine.3.7-armv6", + "alpine.3.7", + "alpine.3.6-armv6", + "alpine.3.6", + "alpine-armv6", + "alpine", + "linux-musl-armv6", + "linux-musl", + "linux-armv6", + "linux", + "unix-armv6", + "unix", + "any", + "base" + ], + "alpine.3.16-ppc64le": [ + "alpine.3.16-ppc64le", + "alpine.3.16", + "alpine.3.15-ppc64le", + "alpine.3.15", + "alpine.3.14-ppc64le", + "alpine.3.14", + "alpine.3.13-ppc64le", + "alpine.3.13", + "alpine.3.12-ppc64le", + "alpine.3.12", + "alpine.3.11-ppc64le", + "alpine.3.11", + "alpine.3.10-ppc64le", + "alpine.3.10", + "alpine.3.9-ppc64le", + "alpine.3.9", + "alpine.3.8-ppc64le", + "alpine.3.8", + "alpine.3.7-ppc64le", + "alpine.3.7", + "alpine.3.6-ppc64le", + "alpine.3.6", + "alpine-ppc64le", + "alpine", + "linux-musl-ppc64le", + "linux-musl", + "linux-ppc64le", + "linux", + "unix-ppc64le", + "unix", + "any", + "base" + ], + "alpine.3.16-s390x": [ + "alpine.3.16-s390x", + "alpine.3.16", + "alpine.3.15-s390x", + "alpine.3.15", + "alpine.3.14-s390x", + "alpine.3.14", + "alpine.3.13-s390x", + "alpine.3.13", + "alpine.3.12-s390x", + "alpine.3.12", + "alpine.3.11-s390x", + "alpine.3.11", + "alpine.3.10-s390x", + "alpine.3.10", + "alpine.3.9-s390x", + "alpine.3.9", + "alpine.3.8-s390x", + "alpine.3.8", + "alpine.3.7-s390x", + "alpine.3.7", + "alpine.3.6-s390x", + "alpine.3.6", + "alpine-s390x", + "alpine", + "linux-musl-s390x", + "linux-musl", + "linux-s390x", + "linux", + "unix-s390x", + "unix", + "any", + "base" + ], + "alpine.3.16-x64": [ + "alpine.3.16-x64", + "alpine.3.16", + "alpine.3.15-x64", + "alpine.3.15", + "alpine.3.14-x64", + "alpine.3.14", + "alpine.3.13-x64", + "alpine.3.13", + "alpine.3.12-x64", + "alpine.3.12", + "alpine.3.11-x64", + "alpine.3.11", + "alpine.3.10-x64", + "alpine.3.10", + "alpine.3.9-x64", + "alpine.3.9", + "alpine.3.8-x64", + "alpine.3.8", + "alpine.3.7-x64", + "alpine.3.7", + "alpine.3.6-x64", + "alpine.3.6", + "alpine-x64", + "alpine", + "linux-musl-x64", + "linux-musl", + "linux-x64", + "linux", + "unix-x64", + "unix", + "any", + "base" + ], + "alpine.3.16-x86": [ + "alpine.3.16-x86", + "alpine.3.16", + "alpine.3.15-x86", + "alpine.3.15", + "alpine.3.14-x86", + "alpine.3.14", + "alpine.3.13-x86", + "alpine.3.13", + "alpine.3.12-x86", + "alpine.3.12", + "alpine.3.11-x86", + "alpine.3.11", + "alpine.3.10-x86", + "alpine.3.10", + "alpine.3.9-x86", + "alpine.3.9", + "alpine.3.8-x86", + "alpine.3.8", + "alpine.3.7-x86", + "alpine.3.7", + "alpine.3.6-x86", + "alpine.3.6", + "alpine-x86", + "alpine", + "linux-musl-x86", + "linux-musl", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], + "alpine.3.17": [ + "alpine.3.17", + "alpine.3.16", + "alpine.3.15", + "alpine.3.14", + "alpine.3.13", + "alpine.3.12", + "alpine.3.11", + "alpine.3.10", + "alpine.3.9", + "alpine.3.8", + "alpine.3.7", + "alpine.3.6", + "alpine", + "linux-musl", + "linux", + "unix", + "any", + "base" + ], + "alpine.3.17-arm": [ + "alpine.3.17-arm", + "alpine.3.17", + "alpine.3.16-arm", + "alpine.3.16", + "alpine.3.15-arm", + "alpine.3.15", + "alpine.3.14-arm", + "alpine.3.14", + "alpine.3.13-arm", + "alpine.3.13", + "alpine.3.12-arm", + "alpine.3.12", + "alpine.3.11-arm", + "alpine.3.11", + "alpine.3.10-arm", + "alpine.3.10", + "alpine.3.9-arm", + "alpine.3.9", + "alpine.3.8-arm", + "alpine.3.8", + "alpine.3.7-arm", + "alpine.3.7", + "alpine.3.6-arm", + "alpine.3.6", + "alpine-arm", + "alpine", + "linux-musl-arm", + "linux-musl", + "linux-arm", + "linux", + "unix-arm", + "unix", + "any", + "base" + ], + "alpine.3.17-arm64": [ + "alpine.3.17-arm64", + "alpine.3.17", + "alpine.3.16-arm64", + "alpine.3.16", + "alpine.3.15-arm64", + "alpine.3.15", + "alpine.3.14-arm64", + "alpine.3.14", + "alpine.3.13-arm64", + "alpine.3.13", + "alpine.3.12-arm64", + "alpine.3.12", + "alpine.3.11-arm64", + "alpine.3.11", + "alpine.3.10-arm64", + "alpine.3.10", + "alpine.3.9-arm64", + "alpine.3.9", + "alpine.3.8-arm64", + "alpine.3.8", + "alpine.3.7-arm64", + "alpine.3.7", + "alpine.3.6-arm64", + "alpine.3.6", + "alpine-arm64", + "alpine", + "linux-musl-arm64", + "linux-musl", + "linux-arm64", + "linux", + "unix-arm64", + "unix", + "any", + "base" + ], + "alpine.3.17-armv6": [ + "alpine.3.17-armv6", + "alpine.3.17", + "alpine.3.16-armv6", + "alpine.3.16", + "alpine.3.15-armv6", + "alpine.3.15", + "alpine.3.14-armv6", + "alpine.3.14", + "alpine.3.13-armv6", + "alpine.3.13", + "alpine.3.12-armv6", + "alpine.3.12", + "alpine.3.11-armv6", + "alpine.3.11", + "alpine.3.10-armv6", + "alpine.3.10", + "alpine.3.9-armv6", + "alpine.3.9", + "alpine.3.8-armv6", + "alpine.3.8", + "alpine.3.7-armv6", + "alpine.3.7", + "alpine.3.6-armv6", + "alpine.3.6", + "alpine-armv6", + "alpine", + "linux-musl-armv6", + "linux-musl", + "linux-armv6", + "linux", + "unix-armv6", + "unix", + "any", + "base" + ], + "alpine.3.17-ppc64le": [ + "alpine.3.17-ppc64le", + "alpine.3.17", + "alpine.3.16-ppc64le", + "alpine.3.16", + "alpine.3.15-ppc64le", + "alpine.3.15", + "alpine.3.14-ppc64le", + "alpine.3.14", + "alpine.3.13-ppc64le", + "alpine.3.13", + "alpine.3.12-ppc64le", + "alpine.3.12", + "alpine.3.11-ppc64le", + "alpine.3.11", + "alpine.3.10-ppc64le", + "alpine.3.10", + "alpine.3.9-ppc64le", + "alpine.3.9", + "alpine.3.8-ppc64le", + "alpine.3.8", + "alpine.3.7-ppc64le", + "alpine.3.7", + "alpine.3.6-ppc64le", + "alpine.3.6", + "alpine-ppc64le", + "alpine", + "linux-musl-ppc64le", + "linux-musl", + "linux-ppc64le", + "linux", + "unix-ppc64le", + "unix", + "any", + "base" + ], + "alpine.3.17-s390x": [ + "alpine.3.17-s390x", + "alpine.3.17", + "alpine.3.16-s390x", + "alpine.3.16", + "alpine.3.15-s390x", + "alpine.3.15", + "alpine.3.14-s390x", + "alpine.3.14", + "alpine.3.13-s390x", + "alpine.3.13", + "alpine.3.12-s390x", + "alpine.3.12", + "alpine.3.11-s390x", + "alpine.3.11", + "alpine.3.10-s390x", + "alpine.3.10", + "alpine.3.9-s390x", + "alpine.3.9", + "alpine.3.8-s390x", + "alpine.3.8", + "alpine.3.7-s390x", + "alpine.3.7", + "alpine.3.6-s390x", + "alpine.3.6", + "alpine-s390x", + "alpine", + "linux-musl-s390x", + "linux-musl", + "linux-s390x", + "linux", + "unix-s390x", + "unix", + "any", + "base" + ], + "alpine.3.17-x64": [ + "alpine.3.17-x64", + "alpine.3.17", + "alpine.3.16-x64", + "alpine.3.16", + "alpine.3.15-x64", + "alpine.3.15", + "alpine.3.14-x64", + "alpine.3.14", + "alpine.3.13-x64", + "alpine.3.13", + "alpine.3.12-x64", + "alpine.3.12", + "alpine.3.11-x64", + "alpine.3.11", + "alpine.3.10-x64", + "alpine.3.10", + "alpine.3.9-x64", + "alpine.3.9", + "alpine.3.8-x64", + "alpine.3.8", + "alpine.3.7-x64", + "alpine.3.7", + "alpine.3.6-x64", + "alpine.3.6", + "alpine-x64", + "alpine", + "linux-musl-x64", + "linux-musl", + "linux-x64", + "linux", + "unix-x64", + "unix", + "any", + "base" + ], + "alpine.3.17-x86": [ + "alpine.3.17-x86", + "alpine.3.17", + "alpine.3.16-x86", + "alpine.3.16", + "alpine.3.15-x86", + "alpine.3.15", + "alpine.3.14-x86", + "alpine.3.14", + "alpine.3.13-x86", + "alpine.3.13", + "alpine.3.12-x86", + "alpine.3.12", + "alpine.3.11-x86", + "alpine.3.11", + "alpine.3.10-x86", + "alpine.3.10", + "alpine.3.9-x86", + "alpine.3.9", + "alpine.3.8-x86", + "alpine.3.8", + "alpine.3.7-x86", + "alpine.3.7", + "alpine.3.6-x86", + "alpine.3.6", + "alpine-x86", + "alpine", + "linux-musl-x86", + "linux-musl", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], + "alpine.3.18": [ + "alpine.3.18", + "alpine.3.17", + "alpine.3.16", + "alpine.3.15", + "alpine.3.14", + "alpine.3.13", + "alpine.3.12", + "alpine.3.11", + "alpine.3.10", + "alpine.3.9", + "alpine.3.8", + "alpine.3.7", + "alpine.3.6", + "alpine", + "linux-musl", + "linux", + "unix", + "any", + "base" + ], + "alpine.3.18-arm": [ + "alpine.3.18-arm", + "alpine.3.18", + "alpine.3.17-arm", + "alpine.3.17", + "alpine.3.16-arm", + "alpine.3.16", + "alpine.3.15-arm", + "alpine.3.15", + "alpine.3.14-arm", + "alpine.3.14", + "alpine.3.13-arm", + "alpine.3.13", + "alpine.3.12-arm", + "alpine.3.12", + "alpine.3.11-arm", + "alpine.3.11", + "alpine.3.10-arm", + "alpine.3.10", + "alpine.3.9-arm", + "alpine.3.9", + "alpine.3.8-arm", + "alpine.3.8", + "alpine.3.7-arm", + "alpine.3.7", + "alpine.3.6-arm", + "alpine.3.6", + "alpine-arm", + "alpine", + "linux-musl-arm", + "linux-musl", + "linux-arm", + "linux", + "unix-arm", + "unix", + "any", + "base" + ], + "alpine.3.18-arm64": [ + "alpine.3.18-arm64", + "alpine.3.18", + "alpine.3.17-arm64", + "alpine.3.17", + "alpine.3.16-arm64", + "alpine.3.16", + "alpine.3.15-arm64", + "alpine.3.15", + "alpine.3.14-arm64", + "alpine.3.14", + "alpine.3.13-arm64", + "alpine.3.13", + "alpine.3.12-arm64", + "alpine.3.12", + "alpine.3.11-arm64", + "alpine.3.11", + "alpine.3.10-arm64", + "alpine.3.10", + "alpine.3.9-arm64", + "alpine.3.9", + "alpine.3.8-arm64", + "alpine.3.8", + "alpine.3.7-arm64", + "alpine.3.7", + "alpine.3.6-arm64", + "alpine.3.6", + "alpine-arm64", + "alpine", + "linux-musl-arm64", + "linux-musl", + "linux-arm64", + "linux", + "unix-arm64", + "unix", + "any", + "base" + ], + "alpine.3.18-armv6": [ + "alpine.3.18-armv6", + "alpine.3.18", + "alpine.3.17-armv6", + "alpine.3.17", + "alpine.3.16-armv6", + "alpine.3.16", + "alpine.3.15-armv6", + "alpine.3.15", + "alpine.3.14-armv6", + "alpine.3.14", + "alpine.3.13-armv6", + "alpine.3.13", + "alpine.3.12-armv6", + "alpine.3.12", + "alpine.3.11-armv6", + "alpine.3.11", + "alpine.3.10-armv6", + "alpine.3.10", + "alpine.3.9-armv6", + "alpine.3.9", + "alpine.3.8-armv6", + "alpine.3.8", + "alpine.3.7-armv6", + "alpine.3.7", + "alpine.3.6-armv6", + "alpine.3.6", + "alpine-armv6", + "alpine", + "linux-musl-armv6", + "linux-musl", + "linux-armv6", + "linux", + "unix-armv6", + "unix", + "any", + "base" + ], + "alpine.3.18-ppc64le": [ + "alpine.3.18-ppc64le", + "alpine.3.18", + "alpine.3.17-ppc64le", + "alpine.3.17", + "alpine.3.16-ppc64le", + "alpine.3.16", + "alpine.3.15-ppc64le", + "alpine.3.15", + "alpine.3.14-ppc64le", + "alpine.3.14", + "alpine.3.13-ppc64le", + "alpine.3.13", + "alpine.3.12-ppc64le", + "alpine.3.12", + "alpine.3.11-ppc64le", + "alpine.3.11", + "alpine.3.10-ppc64le", + "alpine.3.10", + "alpine.3.9-ppc64le", + "alpine.3.9", + "alpine.3.8-ppc64le", + "alpine.3.8", + "alpine.3.7-ppc64le", + "alpine.3.7", + "alpine.3.6-ppc64le", + "alpine.3.6", + "alpine-ppc64le", + "alpine", + "linux-musl-ppc64le", + "linux-musl", + "linux-ppc64le", + "linux", + "unix-ppc64le", + "unix", + "any", + "base" + ], + "alpine.3.18-s390x": [ + "alpine.3.18-s390x", + "alpine.3.18", + "alpine.3.17-s390x", + "alpine.3.17", + "alpine.3.16-s390x", + "alpine.3.16", + "alpine.3.15-s390x", + "alpine.3.15", + "alpine.3.14-s390x", + "alpine.3.14", + "alpine.3.13-s390x", + "alpine.3.13", + "alpine.3.12-s390x", + "alpine.3.12", + "alpine.3.11-s390x", + "alpine.3.11", + "alpine.3.10-s390x", + "alpine.3.10", + "alpine.3.9-s390x", + "alpine.3.9", + "alpine.3.8-s390x", + "alpine.3.8", + "alpine.3.7-s390x", + "alpine.3.7", + "alpine.3.6-s390x", + "alpine.3.6", + "alpine-s390x", + "alpine", + "linux-musl-s390x", + "linux-musl", + "linux-s390x", + "linux", + "unix-s390x", + "unix", + "any", + "base" + ], + "alpine.3.18-x64": [ + "alpine.3.18-x64", + "alpine.3.18", + "alpine.3.17-x64", + "alpine.3.17", + "alpine.3.16-x64", + "alpine.3.16", + "alpine.3.15-x64", + "alpine.3.15", + "alpine.3.14-x64", + "alpine.3.14", + "alpine.3.13-x64", + "alpine.3.13", + "alpine.3.12-x64", + "alpine.3.12", + "alpine.3.11-x64", + "alpine.3.11", + "alpine.3.10-x64", + "alpine.3.10", + "alpine.3.9-x64", + "alpine.3.9", + "alpine.3.8-x64", + "alpine.3.8", + "alpine.3.7-x64", + "alpine.3.7", + "alpine.3.6-x64", + "alpine.3.6", + "alpine-x64", + "alpine", + "linux-musl-x64", + "linux-musl", + "linux-x64", + "linux", + "unix-x64", + "unix", + "any", + "base" + ], + "alpine.3.18-x86": [ + "alpine.3.18-x86", + "alpine.3.18", + "alpine.3.17-x86", + "alpine.3.17", + "alpine.3.16-x86", + "alpine.3.16", + "alpine.3.15-x86", + "alpine.3.15", + "alpine.3.14-x86", + "alpine.3.14", + "alpine.3.13-x86", + "alpine.3.13", + "alpine.3.12-x86", + "alpine.3.12", + "alpine.3.11-x86", + "alpine.3.11", + "alpine.3.10-x86", + "alpine.3.10", + "alpine.3.9-x86", + "alpine.3.9", + "alpine.3.8-x86", + "alpine.3.8", + "alpine.3.7-x86", + "alpine.3.7", + "alpine.3.6-x86", + "alpine.3.6", + "alpine-x86", + "alpine", + "linux-musl-x86", + "linux-musl", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], + "alpine.3.6": [ + "alpine.3.6", + "alpine", + "linux-musl", + "linux", + "unix", + "any", + "base" + ], + "alpine.3.6-arm": [ + "alpine.3.6-arm", + "alpine.3.6", + "alpine-arm", + "alpine", + "linux-musl-arm", + "linux-musl", + "linux-arm", + "linux", + "unix-arm", + "unix", + "any", + "base" + ], + "alpine.3.6-arm64": [ + "alpine.3.6-arm64", + "alpine.3.6", + "alpine-arm64", + "alpine", + "linux-musl-arm64", + "linux-musl", + "linux-arm64", + "linux", + "unix-arm64", + "unix", + "any", + "base" + ], + "alpine.3.6-armv6": [ + "alpine.3.6-armv6", + "alpine.3.6", + "alpine-armv6", + "alpine", + "linux-musl-armv6", + "linux-musl", + "linux-armv6", + "linux", + "unix-armv6", + "unix", + "any", + "base" + ], + "alpine.3.6-ppc64le": [ + "alpine.3.6-ppc64le", + "alpine.3.6", + "alpine-ppc64le", + "alpine", + "linux-musl-ppc64le", + "linux-musl", + "linux-ppc64le", + "linux", + "unix-ppc64le", + "unix", + "any", + "base" + ], + "alpine.3.6-s390x": [ + "alpine.3.6-s390x", + "alpine.3.6", + "alpine-s390x", + "alpine", + "linux-musl-s390x", + "linux-musl", + "linux-s390x", + "linux", + "unix-s390x", + "unix", + "any", + "base" + ], + "alpine.3.6-x64": [ "alpine.3.6-x64", "alpine.3.6", "alpine-x64", @@ -794,6 +2227,20 @@ "any", "base" ], + "alpine.3.6-x86": [ + "alpine.3.6-x86", + "alpine.3.6", + "alpine-x86", + "alpine", + "linux-musl-x86", + "linux-musl", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], "alpine.3.7": [ "alpine.3.7", "alpine.3.6", @@ -836,6 +2283,54 @@ "any", "base" ], + "alpine.3.7-armv6": [ + "alpine.3.7-armv6", + "alpine.3.7", + "alpine.3.6-armv6", + "alpine.3.6", + "alpine-armv6", + "alpine", + "linux-musl-armv6", + "linux-musl", + "linux-armv6", + "linux", + "unix-armv6", + "unix", + "any", + "base" + ], + "alpine.3.7-ppc64le": [ + "alpine.3.7-ppc64le", + "alpine.3.7", + "alpine.3.6-ppc64le", + "alpine.3.6", + "alpine-ppc64le", + "alpine", + "linux-musl-ppc64le", + "linux-musl", + "linux-ppc64le", + "linux", + "unix-ppc64le", + "unix", + "any", + "base" + ], + "alpine.3.7-s390x": [ + "alpine.3.7-s390x", + "alpine.3.7", + "alpine.3.6-s390x", + "alpine.3.6", + "alpine-s390x", + "alpine", + "linux-musl-s390x", + "linux-musl", + "linux-s390x", + "linux", + "unix-s390x", + "unix", + "any", + "base" + ], "alpine.3.7-x64": [ "alpine.3.7-x64", "alpine.3.7", @@ -843,16 +2338,170 @@ "alpine.3.6", "alpine-x64", "alpine", - "linux-musl-x64", + "linux-musl-x64", + "linux-musl", + "linux-x64", + "linux", + "unix-x64", + "unix", + "any", + "base" + ], + "alpine.3.7-x86": [ + "alpine.3.7-x86", + "alpine.3.7", + "alpine.3.6-x86", + "alpine.3.6", + "alpine-x86", + "alpine", + "linux-musl-x86", + "linux-musl", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], + "alpine.3.8": [ + "alpine.3.8", + "alpine.3.7", + "alpine.3.6", + "alpine", + "linux-musl", + "linux", + "unix", + "any", + "base" + ], + "alpine.3.8-arm": [ + "alpine.3.8-arm", + "alpine.3.8", + "alpine.3.7-arm", + "alpine.3.7", + "alpine.3.6-arm", + "alpine.3.6", + "alpine-arm", + "alpine", + "linux-musl-arm", + "linux-musl", + "linux-arm", + "linux", + "unix-arm", + "unix", + "any", + "base" + ], + "alpine.3.8-arm64": [ + "alpine.3.8-arm64", + "alpine.3.8", + "alpine.3.7-arm64", + "alpine.3.7", + "alpine.3.6-arm64", + "alpine.3.6", + "alpine-arm64", + "alpine", + "linux-musl-arm64", + "linux-musl", + "linux-arm64", + "linux", + "unix-arm64", + "unix", + "any", + "base" + ], + "alpine.3.8-armv6": [ + "alpine.3.8-armv6", + "alpine.3.8", + "alpine.3.7-armv6", + "alpine.3.7", + "alpine.3.6-armv6", + "alpine.3.6", + "alpine-armv6", + "alpine", + "linux-musl-armv6", + "linux-musl", + "linux-armv6", + "linux", + "unix-armv6", + "unix", + "any", + "base" + ], + "alpine.3.8-ppc64le": [ + "alpine.3.8-ppc64le", + "alpine.3.8", + "alpine.3.7-ppc64le", + "alpine.3.7", + "alpine.3.6-ppc64le", + "alpine.3.6", + "alpine-ppc64le", + "alpine", + "linux-musl-ppc64le", + "linux-musl", + "linux-ppc64le", + "linux", + "unix-ppc64le", + "unix", + "any", + "base" + ], + "alpine.3.8-s390x": [ + "alpine.3.8-s390x", + "alpine.3.8", + "alpine.3.7-s390x", + "alpine.3.7", + "alpine.3.6-s390x", + "alpine.3.6", + "alpine-s390x", + "alpine", + "linux-musl-s390x", + "linux-musl", + "linux-s390x", + "linux", + "unix-s390x", + "unix", + "any", + "base" + ], + "alpine.3.8-x64": [ + "alpine.3.8-x64", + "alpine.3.8", + "alpine.3.7-x64", + "alpine.3.7", + "alpine.3.6-x64", + "alpine.3.6", + "alpine-x64", + "alpine", + "linux-musl-x64", + "linux-musl", + "linux-x64", + "linux", + "unix-x64", + "unix", + "any", + "base" + ], + "alpine.3.8-x86": [ + "alpine.3.8-x86", + "alpine.3.8", + "alpine.3.7-x86", + "alpine.3.7", + "alpine.3.6-x86", + "alpine.3.6", + "alpine-x86", + "alpine", + "linux-musl-x86", "linux-musl", - "linux-x64", + "linux-x86", "linux", - "unix-x64", + "unix-x86", "unix", "any", "base" ], - "alpine.3.8": [ + "alpine.3.9": [ + "alpine.3.9", "alpine.3.8", "alpine.3.7", "alpine.3.6", @@ -863,7 +2512,9 @@ "any", "base" ], - "alpine.3.8-arm": [ + "alpine.3.9-arm": [ + "alpine.3.9-arm", + "alpine.3.9", "alpine.3.8-arm", "alpine.3.8", "alpine.3.7-arm", @@ -881,7 +2532,9 @@ "any", "base" ], - "alpine.3.8-arm64": [ + "alpine.3.9-arm64": [ + "alpine.3.9-arm64", + "alpine.3.9", "alpine.3.8-arm64", "alpine.3.8", "alpine.3.7-arm64", @@ -899,92 +2552,102 @@ "any", "base" ], - "alpine.3.8-x64": [ - "alpine.3.8-x64", + "alpine.3.9-armv6": [ + "alpine.3.9-armv6", + "alpine.3.9", + "alpine.3.8-armv6", "alpine.3.8", - "alpine.3.7-x64", + "alpine.3.7-armv6", "alpine.3.7", - "alpine.3.6-x64", + "alpine.3.6-armv6", "alpine.3.6", - "alpine-x64", + "alpine-armv6", "alpine", - "linux-musl-x64", + "linux-musl-armv6", "linux-musl", - "linux-x64", + "linux-armv6", "linux", - "unix-x64", + "unix-armv6", "unix", "any", "base" ], - "alpine.3.9": [ + "alpine.3.9-ppc64le": [ + "alpine.3.9-ppc64le", "alpine.3.9", + "alpine.3.8-ppc64le", "alpine.3.8", + "alpine.3.7-ppc64le", "alpine.3.7", + "alpine.3.6-ppc64le", "alpine.3.6", + "alpine-ppc64le", "alpine", + "linux-musl-ppc64le", "linux-musl", + "linux-ppc64le", "linux", + "unix-ppc64le", "unix", "any", "base" ], - "alpine.3.9-arm": [ - "alpine.3.9-arm", + "alpine.3.9-s390x": [ + "alpine.3.9-s390x", "alpine.3.9", - "alpine.3.8-arm", + "alpine.3.8-s390x", "alpine.3.8", - "alpine.3.7-arm", + "alpine.3.7-s390x", "alpine.3.7", - "alpine.3.6-arm", + "alpine.3.6-s390x", "alpine.3.6", - "alpine-arm", + "alpine-s390x", "alpine", - "linux-musl-arm", + "linux-musl-s390x", "linux-musl", - "linux-arm", + "linux-s390x", "linux", - "unix-arm", + "unix-s390x", "unix", "any", "base" ], - "alpine.3.9-arm64": [ - "alpine.3.9-arm64", + "alpine.3.9-x64": [ + "alpine.3.9-x64", "alpine.3.9", - "alpine.3.8-arm64", + "alpine.3.8-x64", "alpine.3.8", - "alpine.3.7-arm64", + "alpine.3.7-x64", "alpine.3.7", - "alpine.3.6-arm64", + "alpine.3.6-x64", "alpine.3.6", - "alpine-arm64", + "alpine-x64", "alpine", - "linux-musl-arm64", + "linux-musl-x64", "linux-musl", - "linux-arm64", + "linux-x64", "linux", - "unix-arm64", + "unix-x64", "unix", "any", "base" ], - "alpine.3.9-x64": [ - "alpine.3.9-x64", + "alpine.3.9-x86": [ + "alpine.3.9-x86", "alpine.3.9", - "alpine.3.8-x64", + "alpine.3.8-x86", "alpine.3.8", - "alpine.3.7-x64", + "alpine.3.7-x86", "alpine.3.7", - "alpine.3.6-x64", + "alpine.3.6-x86", "alpine.3.6", - "alpine-x64", + "alpine-x86", "alpine", - "linux-musl-x64", + "linux-musl-x86", "linux-musl", - "linux-x64", + "linux-x86", "linux", - "unix-x64", + "unix-x86", "unix", "any", "base" @@ -2794,6 +4457,74 @@ "any", "base" ], + "debian.12": [ + "debian.12", + "debian", + "linux", + "unix", + "any", + "base" + ], + "debian.12-arm": [ + "debian.12-arm", + "debian.12", + "debian-arm", + "debian", + "linux-arm", + "linux", + "unix-arm", + "unix", + "any", + "base" + ], + "debian.12-arm64": [ + "debian.12-arm64", + "debian.12", + "debian-arm64", + "debian", + "linux-arm64", + "linux", + "unix-arm64", + "unix", + "any", + "base" + ], + "debian.12-armel": [ + "debian.12-armel", + "debian.12", + "debian-armel", + "debian", + "linux-armel", + "linux", + "unix-armel", + "unix", + "any", + "base" + ], + "debian.12-x64": [ + "debian.12-x64", + "debian.12", + "debian-x64", + "debian", + "linux-x64", + "linux", + "unix-x64", + "unix", + "any", + "base" + ], + "debian.12-x86": [ + "debian.12-x86", + "debian.12", + "debian-x86", + "debian", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], "debian.8": [ "debian.8", "debian", @@ -3454,6 +5185,70 @@ "any", "base" ], + "fedora.38": [ + "fedora.38", + "fedora", + "linux", + "unix", + "any", + "base" + ], + "fedora.38-arm64": [ + "fedora.38-arm64", + "fedora.38", + "fedora-arm64", + "fedora", + "linux-arm64", + "linux", + "unix-arm64", + "unix", + "any", + "base" + ], + "fedora.38-x64": [ + "fedora.38-x64", + "fedora.38", + "fedora-x64", + "fedora", + "linux-x64", + "linux", + "unix-x64", + "unix", + "any", + "base" + ], + "fedora.39": [ + "fedora.39", + "fedora", + "linux", + "unix", + "any", + "base" + ], + "fedora.39-arm64": [ + "fedora.39-arm64", + "fedora.39", + "fedora-arm64", + "fedora", + "linux-arm64", + "linux", + "unix-arm64", + "unix", + "any", + "base" + ], + "fedora.39-x64": [ + "fedora.39-x64", + "fedora.39", + "fedora-x64", + "fedora", + "linux-x64", + "linux", + "unix-x64", + "unix", + "any", + "base" + ], "freebsd": [ "freebsd", "unix", @@ -4285,17 +6080,17 @@ "any", "base" ], - "linux-musl-s390x": [ - "linux-musl-s390x", + "linux-musl-armv6": [ + "linux-musl-armv6", "linux-musl", - "linux-s390x", + "linux-armv6", "linux", - "unix-s390x", + "unix-armv6", "unix", "any", "base" ], - "linux-musl-ppc64le": [ + "linux-musl-ppc64le": [ "linux-musl-ppc64le", "linux-musl", "linux-ppc64le", @@ -4305,6 +6100,16 @@ "any", "base" ], + "linux-musl-s390x": [ + "linux-musl-s390x", + "linux-musl", + "linux-s390x", + "linux", + "unix-s390x", + "unix", + "any", + "base" + ], "linux-musl-x64": [ "linux-musl-x64", "linux-musl", @@ -4325,18 +6130,18 @@ "any", "base" ], - "linux-s390x": [ - "linux-s390x", + "linux-ppc64le": [ + "linux-ppc64le", "linux", - "unix-s390x", + "unix-ppc64le", "unix", "any", "base" ], - "linux-ppc64le": [ - "linux-ppc64le", + "linux-s390x": [ + "linux-s390x", "linux", - "unix-ppc64le", + "unix-s390x", "unix", "any", "base" @@ -5822,7 +7627,65 @@ "any", "base" ], - "osx.10.16": [ + "osx.10.16": [ + "osx.10.16", + "osx.10.15", + "osx.10.14", + "osx.10.13", + "osx.10.12", + "osx.10.11", + "osx.10.10", + "osx", + "unix", + "any", + "base" + ], + "osx.10.16-arm64": [ + "osx.10.16-arm64", + "osx.10.16", + "osx.10.15-arm64", + "osx.10.15", + "osx.10.14-arm64", + "osx.10.14", + "osx.10.13-arm64", + "osx.10.13", + "osx.10.12-arm64", + "osx.10.12", + "osx.10.11-arm64", + "osx.10.11", + "osx.10.10-arm64", + "osx.10.10", + "osx-arm64", + "osx", + "unix-arm64", + "unix", + "any", + "base" + ], + "osx.10.16-x64": [ + "osx.10.16-x64", + "osx.10.16", + "osx.10.15-x64", + "osx.10.15", + "osx.10.14-x64", + "osx.10.14", + "osx.10.13-x64", + "osx.10.13", + "osx.10.12-x64", + "osx.10.12", + "osx.10.11-x64", + "osx.10.11", + "osx.10.10-x64", + "osx.10.10", + "osx-x64", + "osx", + "unix-x64", + "unix", + "any", + "base" + ], + "osx.11.0": [ + "osx.11.0", "osx.10.16", "osx.10.15", "osx.10.14", @@ -5835,7 +7698,9 @@ "any", "base" ], - "osx.10.16-arm64": [ + "osx.11.0-arm64": [ + "osx.11.0-arm64", + "osx.11.0", "osx.10.16-arm64", "osx.10.16", "osx.10.15-arm64", @@ -5857,7 +7722,9 @@ "any", "base" ], - "osx.10.16-x64": [ + "osx.11.0-x64": [ + "osx.11.0-x64", + "osx.11.0", "osx.10.16-x64", "osx.10.16", "osx.10.15-x64", @@ -5879,7 +7746,8 @@ "any", "base" ], - "osx.11.0": [ + "osx.12": [ + "osx.12", "osx.11.0", "osx.10.16", "osx.10.15", @@ -5893,7 +7761,9 @@ "any", "base" ], - "osx.11.0-arm64": [ + "osx.12-arm64": [ + "osx.12-arm64", + "osx.12", "osx.11.0-arm64", "osx.11.0", "osx.10.16-arm64", @@ -5917,7 +7787,9 @@ "any", "base" ], - "osx.11.0-x64": [ + "osx.12-x64": [ + "osx.12-x64", + "osx.12", "osx.11.0-x64", "osx.11.0", "osx.10.16-x64", @@ -5941,7 +7813,8 @@ "any", "base" ], - "osx.12": [ + "osx.13": [ + "osx.13", "osx.12", "osx.11.0", "osx.10.16", @@ -5956,7 +7829,9 @@ "any", "base" ], - "osx.12-arm64": [ + "osx.13-arm64": [ + "osx.13-arm64", + "osx.13", "osx.12-arm64", "osx.12", "osx.11.0-arm64", @@ -5982,7 +7857,9 @@ "any", "base" ], - "osx.12-x64": [ + "osx.13-x64": [ + "osx.13-x64", + "osx.13", "osx.12-x64", "osx.12", "osx.11.0-x64", @@ -7196,6 +9073,85 @@ "any", "base" ], + "tizen.7.0.0": [ + "tizen.7.0.0", + "tizen.6.5.0", + "tizen.6.0.0", + "tizen.5.5.0", + "tizen.5.0.0", + "tizen.4.0.0", + "tizen", + "linux", + "unix", + "any", + "base" + ], + "tizen.7.0.0-arm64": [ + "tizen.7.0.0-arm64", + "tizen.7.0.0", + "tizen.6.5.0-arm64", + "tizen.6.5.0", + "tizen.6.0.0-arm64", + "tizen.6.0.0", + "tizen.5.5.0-arm64", + "tizen.5.5.0", + "tizen.5.0.0-arm64", + "tizen.5.0.0", + "tizen.4.0.0-arm64", + "tizen.4.0.0", + "tizen-arm64", + "tizen", + "linux-arm64", + "linux", + "unix-arm64", + "unix", + "any", + "base" + ], + "tizen.7.0.0-armel": [ + "tizen.7.0.0-armel", + "tizen.7.0.0", + "tizen.6.5.0-armel", + "tizen.6.5.0", + "tizen.6.0.0-armel", + "tizen.6.0.0", + "tizen.5.5.0-armel", + "tizen.5.5.0", + "tizen.5.0.0-armel", + "tizen.5.0.0", + "tizen.4.0.0-armel", + "tizen.4.0.0", + "tizen-armel", + "tizen", + "linux-armel", + "linux", + "unix-armel", + "unix", + "any", + "base" + ], + "tizen.7.0.0-x86": [ + "tizen.7.0.0-x86", + "tizen.7.0.0", + "tizen.6.5.0-x86", + "tizen.6.5.0", + "tizen.6.0.0-x86", + "tizen.6.0.0", + "tizen.5.5.0-x86", + "tizen.5.5.0", + "tizen.5.0.0-x86", + "tizen.5.0.0", + "tizen.4.0.0-x86", + "tizen.4.0.0", + "tizen-x86", + "tizen", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], "tvos": [ "tvos", "unix", @@ -8854,6 +10810,201 @@ "any", "base" ], + "ubuntu.22.10": [ + "ubuntu.22.10", + "ubuntu", + "debian", + "linux", + "unix", + "any", + "base" + ], + "ubuntu.22.10-arm": [ + "ubuntu.22.10-arm", + "ubuntu.22.10", + "ubuntu-arm", + "ubuntu", + "debian-arm", + "debian", + "linux-arm", + "linux", + "unix-arm", + "unix", + "any", + "base" + ], + "ubuntu.22.10-arm64": [ + "ubuntu.22.10-arm64", + "ubuntu.22.10", + "ubuntu-arm64", + "ubuntu", + "debian-arm64", + "debian", + "linux-arm64", + "linux", + "unix-arm64", + "unix", + "any", + "base" + ], + "ubuntu.22.10-x64": [ + "ubuntu.22.10-x64", + "ubuntu.22.10", + "ubuntu-x64", + "ubuntu", + "debian-x64", + "debian", + "linux-x64", + "linux", + "unix-x64", + "unix", + "any", + "base" + ], + "ubuntu.22.10-x86": [ + "ubuntu.22.10-x86", + "ubuntu.22.10", + "ubuntu-x86", + "ubuntu", + "debian-x86", + "debian", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], + "ubuntu.23.04": [ + "ubuntu.23.04", + "ubuntu", + "debian", + "linux", + "unix", + "any", + "base" + ], + "ubuntu.23.04-arm": [ + "ubuntu.23.04-arm", + "ubuntu.23.04", + "ubuntu-arm", + "ubuntu", + "debian-arm", + "debian", + "linux-arm", + "linux", + "unix-arm", + "unix", + "any", + "base" + ], + "ubuntu.23.04-arm64": [ + "ubuntu.23.04-arm64", + "ubuntu.23.04", + "ubuntu-arm64", + "ubuntu", + "debian-arm64", + "debian", + "linux-arm64", + "linux", + "unix-arm64", + "unix", + "any", + "base" + ], + "ubuntu.23.04-x64": [ + "ubuntu.23.04-x64", + "ubuntu.23.04", + "ubuntu-x64", + "ubuntu", + "debian-x64", + "debian", + "linux-x64", + "linux", + "unix-x64", + "unix", + "any", + "base" + ], + "ubuntu.23.04-x86": [ + "ubuntu.23.04-x86", + "ubuntu.23.04", + "ubuntu-x86", + "ubuntu", + "debian-x86", + "debian", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], + "ubuntu.23.10": [ + "ubuntu.23.10", + "ubuntu", + "debian", + "linux", + "unix", + "any", + "base" + ], + "ubuntu.23.10-arm": [ + "ubuntu.23.10-arm", + "ubuntu.23.10", + "ubuntu-arm", + "ubuntu", + "debian-arm", + "debian", + "linux-arm", + "linux", + "unix-arm", + "unix", + "any", + "base" + ], + "ubuntu.23.10-arm64": [ + "ubuntu.23.10-arm64", + "ubuntu.23.10", + "ubuntu-arm64", + "ubuntu", + "debian-arm64", + "debian", + "linux-arm64", + "linux", + "unix-arm64", + "unix", + "any", + "base" + ], + "ubuntu.23.10-x64": [ + "ubuntu.23.10-x64", + "ubuntu.23.10", + "ubuntu-x64", + "ubuntu", + "debian-x64", + "debian", + "linux-x64", + "linux", + "unix-x64", + "unix", + "any", + "base" + ], + "ubuntu.23.10-x86": [ + "ubuntu.23.10-x86", + "ubuntu.23.10", + "ubuntu-x86", + "ubuntu", + "debian-x86", + "debian", + "linux-x86", + "linux", + "unix-x86", + "unix", + "any", + "base" + ], "unix": [ "unix", "any", @@ -8895,14 +11046,14 @@ "any", "base" ], - "unix-s390x": [ - "unix-s390x", + "unix-ppc64le": [ + "unix-ppc64le", "unix", "any", "base" ], - "unix-ppc64le": [ - "unix-ppc64le", + "unix-s390x": [ + "unix-s390x", "unix", "any", "base" @@ -9549,4 +11700,4 @@ "any", "base" ] -} +} \ No newline at end of file diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/runtime.json b/src/libraries/Microsoft.NETCore.Platforms/src/runtime.json index 050ce1e4e8ce36..21e2222f725860 100644 --- a/src/libraries/Microsoft.NETCore.Platforms/src/runtime.json +++ b/src/libraries/Microsoft.NETCore.Platforms/src/runtime.json @@ -17,12 +17,36 @@ "linux-musl-arm64" ] }, + "alpine-armv6": { + "#import": [ + "alpine", + "linux-musl-armv6" + ] + }, + "alpine-ppc64le": { + "#import": [ + "alpine", + "linux-musl-ppc64le" + ] + }, + "alpine-s390x": { + "#import": [ + "alpine", + "linux-musl-s390x" + ] + }, "alpine-x64": { "#import": [ "alpine", "linux-musl-x64" ] }, + "alpine-x86": { + "#import": [ + "alpine", + "linux-musl-x86" + ] + }, "alpine.3.10": { "#import": [ "alpine.3.9" @@ -40,12 +64,36 @@ "alpine.3.9-arm64" ] }, + "alpine.3.10-armv6": { + "#import": [ + "alpine.3.10", + "alpine.3.9-armv6" + ] + }, + "alpine.3.10-ppc64le": { + "#import": [ + "alpine.3.10", + "alpine.3.9-ppc64le" + ] + }, + "alpine.3.10-s390x": { + "#import": [ + "alpine.3.10", + "alpine.3.9-s390x" + ] + }, "alpine.3.10-x64": { "#import": [ "alpine.3.10", "alpine.3.9-x64" ] }, + "alpine.3.10-x86": { + "#import": [ + "alpine.3.10", + "alpine.3.9-x86" + ] + }, "alpine.3.11": { "#import": [ "alpine.3.10" @@ -63,12 +111,36 @@ "alpine.3.10-arm64" ] }, + "alpine.3.11-armv6": { + "#import": [ + "alpine.3.11", + "alpine.3.10-armv6" + ] + }, + "alpine.3.11-ppc64le": { + "#import": [ + "alpine.3.11", + "alpine.3.10-ppc64le" + ] + }, + "alpine.3.11-s390x": { + "#import": [ + "alpine.3.11", + "alpine.3.10-s390x" + ] + }, "alpine.3.11-x64": { "#import": [ "alpine.3.11", "alpine.3.10-x64" ] }, + "alpine.3.11-x86": { + "#import": [ + "alpine.3.11", + "alpine.3.10-x86" + ] + }, "alpine.3.12": { "#import": [ "alpine.3.11" @@ -86,12 +158,36 @@ "alpine.3.11-arm64" ] }, + "alpine.3.12-armv6": { + "#import": [ + "alpine.3.12", + "alpine.3.11-armv6" + ] + }, + "alpine.3.12-ppc64le": { + "#import": [ + "alpine.3.12", + "alpine.3.11-ppc64le" + ] + }, + "alpine.3.12-s390x": { + "#import": [ + "alpine.3.12", + "alpine.3.11-s390x" + ] + }, "alpine.3.12-x64": { "#import": [ "alpine.3.12", "alpine.3.11-x64" ] }, + "alpine.3.12-x86": { + "#import": [ + "alpine.3.12", + "alpine.3.11-x86" + ] + }, "alpine.3.13": { "#import": [ "alpine.3.12" @@ -109,12 +205,36 @@ "alpine.3.12-arm64" ] }, + "alpine.3.13-armv6": { + "#import": [ + "alpine.3.13", + "alpine.3.12-armv6" + ] + }, + "alpine.3.13-ppc64le": { + "#import": [ + "alpine.3.13", + "alpine.3.12-ppc64le" + ] + }, + "alpine.3.13-s390x": { + "#import": [ + "alpine.3.13", + "alpine.3.12-s390x" + ] + }, "alpine.3.13-x64": { "#import": [ "alpine.3.13", "alpine.3.12-x64" ] }, + "alpine.3.13-x86": { + "#import": [ + "alpine.3.13", + "alpine.3.12-x86" + ] + }, "alpine.3.14": { "#import": [ "alpine.3.13" @@ -132,12 +252,36 @@ "alpine.3.13-arm64" ] }, + "alpine.3.14-armv6": { + "#import": [ + "alpine.3.14", + "alpine.3.13-armv6" + ] + }, + "alpine.3.14-ppc64le": { + "#import": [ + "alpine.3.14", + "alpine.3.13-ppc64le" + ] + }, + "alpine.3.14-s390x": { + "#import": [ + "alpine.3.14", + "alpine.3.13-s390x" + ] + }, "alpine.3.14-x64": { "#import": [ "alpine.3.14", "alpine.3.13-x64" ] }, + "alpine.3.14-x86": { + "#import": [ + "alpine.3.14", + "alpine.3.13-x86" + ] + }, "alpine.3.15": { "#import": [ "alpine.3.14" @@ -155,12 +299,36 @@ "alpine.3.14-arm64" ] }, + "alpine.3.15-armv6": { + "#import": [ + "alpine.3.15", + "alpine.3.14-armv6" + ] + }, + "alpine.3.15-ppc64le": { + "#import": [ + "alpine.3.15", + "alpine.3.14-ppc64le" + ] + }, + "alpine.3.15-s390x": { + "#import": [ + "alpine.3.15", + "alpine.3.14-s390x" + ] + }, "alpine.3.15-x64": { "#import": [ "alpine.3.15", "alpine.3.14-x64" ] }, + "alpine.3.15-x86": { + "#import": [ + "alpine.3.15", + "alpine.3.14-x86" + ] + }, "alpine.3.16": { "#import": [ "alpine.3.15" @@ -178,12 +346,130 @@ "alpine.3.15-arm64" ] }, + "alpine.3.16-armv6": { + "#import": [ + "alpine.3.16", + "alpine.3.15-armv6" + ] + }, + "alpine.3.16-ppc64le": { + "#import": [ + "alpine.3.16", + "alpine.3.15-ppc64le" + ] + }, + "alpine.3.16-s390x": { + "#import": [ + "alpine.3.16", + "alpine.3.15-s390x" + ] + }, "alpine.3.16-x64": { "#import": [ "alpine.3.16", "alpine.3.15-x64" ] }, + "alpine.3.16-x86": { + "#import": [ + "alpine.3.16", + "alpine.3.15-x86" + ] + }, + "alpine.3.17": { + "#import": [ + "alpine.3.16" + ] + }, + "alpine.3.17-arm": { + "#import": [ + "alpine.3.17", + "alpine.3.16-arm" + ] + }, + "alpine.3.17-arm64": { + "#import": [ + "alpine.3.17", + "alpine.3.16-arm64" + ] + }, + "alpine.3.17-armv6": { + "#import": [ + "alpine.3.17", + "alpine.3.16-armv6" + ] + }, + "alpine.3.17-ppc64le": { + "#import": [ + "alpine.3.17", + "alpine.3.16-ppc64le" + ] + }, + "alpine.3.17-s390x": { + "#import": [ + "alpine.3.17", + "alpine.3.16-s390x" + ] + }, + "alpine.3.17-x64": { + "#import": [ + "alpine.3.17", + "alpine.3.16-x64" + ] + }, + "alpine.3.17-x86": { + "#import": [ + "alpine.3.17", + "alpine.3.16-x86" + ] + }, + "alpine.3.18": { + "#import": [ + "alpine.3.17" + ] + }, + "alpine.3.18-arm": { + "#import": [ + "alpine.3.18", + "alpine.3.17-arm" + ] + }, + "alpine.3.18-arm64": { + "#import": [ + "alpine.3.18", + "alpine.3.17-arm64" + ] + }, + "alpine.3.18-armv6": { + "#import": [ + "alpine.3.18", + "alpine.3.17-armv6" + ] + }, + "alpine.3.18-ppc64le": { + "#import": [ + "alpine.3.18", + "alpine.3.17-ppc64le" + ] + }, + "alpine.3.18-s390x": { + "#import": [ + "alpine.3.18", + "alpine.3.17-s390x" + ] + }, + "alpine.3.18-x64": { + "#import": [ + "alpine.3.18", + "alpine.3.17-x64" + ] + }, + "alpine.3.18-x86": { + "#import": [ + "alpine.3.18", + "alpine.3.17-x86" + ] + }, "alpine.3.6": { "#import": [ "alpine" @@ -201,12 +487,36 @@ "alpine-arm64" ] }, + "alpine.3.6-armv6": { + "#import": [ + "alpine.3.6", + "alpine-armv6" + ] + }, + "alpine.3.6-ppc64le": { + "#import": [ + "alpine.3.6", + "alpine-ppc64le" + ] + }, + "alpine.3.6-s390x": { + "#import": [ + "alpine.3.6", + "alpine-s390x" + ] + }, "alpine.3.6-x64": { "#import": [ "alpine.3.6", "alpine-x64" ] }, + "alpine.3.6-x86": { + "#import": [ + "alpine.3.6", + "alpine-x86" + ] + }, "alpine.3.7": { "#import": [ "alpine.3.6" @@ -224,12 +534,36 @@ "alpine.3.6-arm64" ] }, + "alpine.3.7-armv6": { + "#import": [ + "alpine.3.7", + "alpine.3.6-armv6" + ] + }, + "alpine.3.7-ppc64le": { + "#import": [ + "alpine.3.7", + "alpine.3.6-ppc64le" + ] + }, + "alpine.3.7-s390x": { + "#import": [ + "alpine.3.7", + "alpine.3.6-s390x" + ] + }, "alpine.3.7-x64": { "#import": [ "alpine.3.7", "alpine.3.6-x64" ] }, + "alpine.3.7-x86": { + "#import": [ + "alpine.3.7", + "alpine.3.6-x86" + ] + }, "alpine.3.8": { "#import": [ "alpine.3.7" @@ -247,12 +581,36 @@ "alpine.3.7-arm64" ] }, + "alpine.3.8-armv6": { + "#import": [ + "alpine.3.8", + "alpine.3.7-armv6" + ] + }, + "alpine.3.8-ppc64le": { + "#import": [ + "alpine.3.8", + "alpine.3.7-ppc64le" + ] + }, + "alpine.3.8-s390x": { + "#import": [ + "alpine.3.8", + "alpine.3.7-s390x" + ] + }, "alpine.3.8-x64": { "#import": [ "alpine.3.8", "alpine.3.7-x64" ] }, + "alpine.3.8-x86": { + "#import": [ + "alpine.3.8", + "alpine.3.7-x86" + ] + }, "alpine.3.9": { "#import": [ "alpine.3.8" @@ -270,12 +628,36 @@ "alpine.3.8-arm64" ] }, + "alpine.3.9-armv6": { + "#import": [ + "alpine.3.9", + "alpine.3.8-armv6" + ] + }, + "alpine.3.9-ppc64le": { + "#import": [ + "alpine.3.9", + "alpine.3.8-ppc64le" + ] + }, + "alpine.3.9-s390x": { + "#import": [ + "alpine.3.9", + "alpine.3.8-s390x" + ] + }, "alpine.3.9-x64": { "#import": [ "alpine.3.9", "alpine.3.8-x64" ] }, + "alpine.3.9-x86": { + "#import": [ + "alpine.3.9", + "alpine.3.8-x86" + ] + }, "android": { "#import": [ "linux-bionic" @@ -862,6 +1244,41 @@ "debian-x86" ] }, + "debian.12": { + "#import": [ + "debian" + ] + }, + "debian.12-arm": { + "#import": [ + "debian.12", + "debian-arm" + ] + }, + "debian.12-arm64": { + "#import": [ + "debian.12", + "debian-arm64" + ] + }, + "debian.12-armel": { + "#import": [ + "debian.12", + "debian-armel" + ] + }, + "debian.12-x64": { + "#import": [ + "debian.12", + "debian-x64" + ] + }, + "debian.12-x86": { + "#import": [ + "debian.12", + "debian-x86" + ] + }, "debian.8": { "#import": [ "debian" @@ -1215,6 +1632,40 @@ "fedora-x64" ] }, + "fedora.38": { + "#import": [ + "fedora" + ] + }, + "fedora.38-arm64": { + "#import": [ + "fedora.38", + "fedora-arm64" + ] + }, + "fedora.38-x64": { + "#import": [ + "fedora.38", + "fedora-x64" + ] + }, + "fedora.39": { + "#import": [ + "fedora" + ] + }, + "fedora.39-arm64": { + "#import": [ + "fedora.39", + "fedora-arm64" + ] + }, + "fedora.39-x64": { + "#import": [ + "fedora.39", + "fedora-x64" + ] + }, "freebsd": { "#import": [ "unix" @@ -1637,10 +2088,10 @@ "linux-armel" ] }, - "linux-musl-s390x": { + "linux-musl-armv6": { "#import": [ "linux-musl", - "linux-s390x" + "linux-armv6" ] }, "linux-musl-ppc64le": { @@ -1649,6 +2100,12 @@ "linux-ppc64le" ] }, + "linux-musl-s390x": { + "#import": [ + "linux-musl", + "linux-s390x" + ] + }, "linux-musl-x64": { "#import": [ "linux-musl", @@ -1661,16 +2118,16 @@ "linux-x86" ] }, - "linux-s390x": { + "linux-ppc64le": { "#import": [ "linux", - "unix-s390x" + "unix-ppc64le" ] }, - "linux-ppc64le": { + "linux-s390x": { "#import": [ "linux", - "unix-ppc64le" + "unix-s390x" ] }, "linux-x64": { @@ -2343,6 +2800,23 @@ "osx.11.0-x64" ] }, + "osx.13": { + "#import": [ + "osx.12" + ] + }, + "osx.13-arm64": { + "#import": [ + "osx.13", + "osx.12-arm64" + ] + }, + "osx.13-x64": { + "#import": [ + "osx.13", + "osx.12-x64" + ] + }, "rhel": { "#import": [ "linux" @@ -2865,6 +3339,29 @@ "tizen.6.0.0-x86" ] }, + "tizen.7.0.0": { + "#import": [ + "tizen.6.5.0" + ] + }, + "tizen.7.0.0-arm64": { + "#import": [ + "tizen.7.0.0", + "tizen.6.5.0-arm64" + ] + }, + "tizen.7.0.0-armel": { + "#import": [ + "tizen.7.0.0", + "tizen.6.5.0-armel" + ] + }, + "tizen.7.0.0-x86": { + "#import": [ + "tizen.7.0.0", + "tizen.6.5.0-x86" + ] + }, "tvos": { "#import": [ "unix" @@ -3601,6 +4098,93 @@ "ubuntu-x86" ] }, + "ubuntu.22.10": { + "#import": [ + "ubuntu" + ] + }, + "ubuntu.22.10-arm": { + "#import": [ + "ubuntu.22.10", + "ubuntu-arm" + ] + }, + "ubuntu.22.10-arm64": { + "#import": [ + "ubuntu.22.10", + "ubuntu-arm64" + ] + }, + "ubuntu.22.10-x64": { + "#import": [ + "ubuntu.22.10", + "ubuntu-x64" + ] + }, + "ubuntu.22.10-x86": { + "#import": [ + "ubuntu.22.10", + "ubuntu-x86" + ] + }, + "ubuntu.23.04": { + "#import": [ + "ubuntu" + ] + }, + "ubuntu.23.04-arm": { + "#import": [ + "ubuntu.23.04", + "ubuntu-arm" + ] + }, + "ubuntu.23.04-arm64": { + "#import": [ + "ubuntu.23.04", + "ubuntu-arm64" + ] + }, + "ubuntu.23.04-x64": { + "#import": [ + "ubuntu.23.04", + "ubuntu-x64" + ] + }, + "ubuntu.23.04-x86": { + "#import": [ + "ubuntu.23.04", + "ubuntu-x86" + ] + }, + "ubuntu.23.10": { + "#import": [ + "ubuntu" + ] + }, + "ubuntu.23.10-arm": { + "#import": [ + "ubuntu.23.10", + "ubuntu-arm" + ] + }, + "ubuntu.23.10-arm64": { + "#import": [ + "ubuntu.23.10", + "ubuntu-arm64" + ] + }, + "ubuntu.23.10-x64": { + "#import": [ + "ubuntu.23.10", + "ubuntu-x64" + ] + }, + "ubuntu.23.10-x86": { + "#import": [ + "ubuntu.23.10", + "ubuntu-x86" + ] + }, "unix": { "#import": [ "any" @@ -3636,12 +4220,12 @@ "unix" ] }, - "unix-s390x": { + "unix-ppc64le": { "#import": [ "unix" ] }, - "unix-ppc64le": { + "unix-s390x": { "#import": [ "unix" ] @@ -3980,4 +4564,4 @@ ] } } -} +} \ No newline at end of file diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props b/src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props index 1b10a2604af28a..b2a4b7f07044ee 100644 --- a/src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props +++ b/src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props @@ -11,13 +11,13 @@ linux - x64;x86;arm;armel;arm64;s390x;ppc64le + x64;x86;arm;armv6;armel;arm64;s390x;ppc64le linux-musl - x64;arm;arm64 - 3.6;3.7;3.8;3.9;3.10;3.11;3.12;3.13;3.14;3.15;3.16 + x64;x86;arm;armv6;arm64;s390x;ppc64le + 3.6;3.7;3.8;3.9;3.10;3.11;3.12;3.13;3.14;3.15;3.16;3.17;3.18 @@ -69,7 +69,7 @@ linux x64;x86;arm;armel;arm64 - 8;9;10;11 + 8;9;10;11;12 false @@ -81,7 +81,7 @@ linux x64;arm64 - 23;24;25;26;27;28;29;30;31;32;33;34;35;36;37 + 23;24;25;26;27;28;29;30;31;32;33;34;35;36;37;38;39 false @@ -173,7 +173,7 @@ unix x64;arm64 - 10.10;10.11;10.12;10.13;10.14;10.15;10.16;11.0;12 + 10.10;10.11;10.12;10.13;10.14;10.15;10.16;11.0;12;13 @@ -250,7 +250,7 @@ linux x86;armel;arm64 - 4.0.0;5.0.0;5.5.0;6.0.0;6.5.0 + 4.0.0;5.0.0;5.5.0;6.0.0;6.5.0;7.0.0 @@ -262,7 +262,7 @@ debian x64;x86;arm;arm64 - 16.04;16.10;17.04;17.10;18.04;18.10;19.04;19.10;20.04;20.10;21.04;21.10;22.04 + 16.04;16.10;17.04;17.10;18.04;18.10;19.04;19.10;20.04;20.10;21.04;21.10;22.04;22.10;23.04;23.10 false diff --git a/src/libraries/Microsoft.NETCore.Platforms/tests/GenerateRuntimeGraphTests.cs b/src/libraries/Microsoft.NETCore.Platforms/tests/GenerateRuntimeGraphTests.cs index bce0965cf0aa8b..253b507b28e0bb 100644 --- a/src/libraries/Microsoft.NETCore.Platforms/tests/GenerateRuntimeGraphTests.cs +++ b/src/libraries/Microsoft.NETCore.Platforms/tests/GenerateRuntimeGraphTests.cs @@ -210,14 +210,15 @@ public void CanAddArchitectureToExistingGroups() [Fact] public void CanAddArchitectureAndVersionToExistingGroups() { - var additionalRIDs = new[] { "osx.12-powerpc" }; + var additionalRIDs = new[] { "osx.13-powerpc" }; var expectedAdditions = new[] { - new RuntimeDescription("osx.12-powerpc", new[] { "osx.12", "osx.11.0-powerpc" }), - new RuntimeDescription("osx.12-arm64", new[] { "osx.12", "osx.11.0-arm64" }), - new RuntimeDescription("osx.12-x64", new[] { "osx.12", "osx.11.0-x64" }), - new RuntimeDescription("osx.12", new[] { "osx.11.0" }), + new RuntimeDescription("osx.13-powerpc", new[] { "osx.13", "osx.12-powerpc" }), + new RuntimeDescription("osx.13-arm64", new[] { "osx.13", "osx.12-arm64" }), + new RuntimeDescription("osx.13-x64", new[] { "osx.13", "osx.12-x64" }), + new RuntimeDescription("osx.13", new[] { "osx.12" }), // our RID model doesn't give priority to architecture, so the new architecture is applied to all past versions + new RuntimeDescription("osx.12-powerpc", new[] { "osx.12", "osx.11.0-powerpc" }), new RuntimeDescription("osx.11.0-powerpc", new[] { "osx.11.0", "osx.10.16-powerpc" }), new RuntimeDescription("osx.10.16-powerpc", new[] { "osx.10.16", "osx.10.15-powerpc" }), new RuntimeDescription("osx.10.15-powerpc", new[] { "osx.10.15", "osx.10.14-powerpc" }), diff --git a/src/libraries/Microsoft.NETCore.Platforms/tests/RidTests.cs b/src/libraries/Microsoft.NETCore.Platforms/tests/RidTests.cs index 227bcbdd10d4db..8dee0ebeda17a4 100644 --- a/src/libraries/Microsoft.NETCore.Platforms/tests/RidTests.cs +++ b/src/libraries/Microsoft.NETCore.Platforms/tests/RidTests.cs @@ -10,35 +10,57 @@ public class RidTests { public static IEnumerable ValidRIDData() { - yield return new object[] { "win10-x64", new RID() { BaseRID = "win", OmitVersionDelimiter = true, Version = new RuntimeVersion("10"), Architecture = "x64" } }; - yield return new object[] { "win10", new RID() { BaseRID = "win", OmitVersionDelimiter = true, Version = new RuntimeVersion("10")} }; - yield return new object[] { "linux", new RID() { BaseRID = "linux" } }; - yield return new object[] { "linux-x64", new RID() { BaseRID = "linux", Architecture = "x64" } }; - yield return new object[] { "linux-x64", new RID() { BaseRID = "linux", Architecture = "x64" } }; - yield return new object[] { "debian.10-x64", new RID() { BaseRID = "debian", Version = new RuntimeVersion("10"), Architecture = "x64" } }; - yield return new object[] { "linuxmint.19.2-x64", new RID() { BaseRID = "linuxmint", Version = new RuntimeVersion("19.2"), Architecture = "x64" } }; - yield return new object[] { "ubuntu.14.04-x64", new RID() { BaseRID = "ubuntu", Version = new RuntimeVersion("14.04"), Architecture = "x64" } }; - yield return new object[] { "foo-bar.42-arm", new RID() { BaseRID = "foo-bar", Version = new RuntimeVersion("42"), Architecture = "arm" } }; - yield return new object[] { "foo-bar-arm", new RID() { BaseRID = "foo", Architecture = "bar", Qualifier = "arm" } }; // demonstrates ambiguity, avoid using `-` in base - yield return new object[] { "linux-musl-x64", new RID() { BaseRID = "linux", Architecture = "musl", Qualifier = "x64" } }; // yes, we already have ambiguous RIDs + yield return new object[] { "win10-x64", new RID() { BaseRID = "win", OmitVersionDelimiter = true, Version = new RuntimeVersion("10"), Architecture = "x64" }, null }; + yield return new object[] { "win10", new RID() { BaseRID = "win", OmitVersionDelimiter = true, Version = new RuntimeVersion("10")}, null }; + yield return new object[] { "linux", new RID() { BaseRID = "linux" }, null }; + yield return new object[] { "linux-x64", new RID() { BaseRID = "linux", Architecture = "x64" }, null }; + yield return new object[] { "linux-x64", new RID() { BaseRID = "linux", Architecture = "x64" }, null }; + yield return new object[] { "debian.10-x64", new RID() { BaseRID = "debian", Version = new RuntimeVersion("10"), Architecture = "x64" }, null }; + yield return new object[] { "linuxmint.19.2-x64", new RID() { BaseRID = "linuxmint", Version = new RuntimeVersion("19.2"), Architecture = "x64" }, null }; + yield return new object[] { "ubuntu.14.04-x64", new RID() { BaseRID = "ubuntu", Version = new RuntimeVersion("14.04"), Architecture = "x64" }, null }; + yield return new object[] { "foo-bar.42-arm", new RID() { BaseRID = "foo-bar", Version = new RuntimeVersion("42"), Architecture = "arm" }, null }; + yield return new object[] { "foo-bar-arm", new RID() { BaseRID = "foo", Architecture = "bar", Qualifier = "arm" }, // demonstrates ambiguity, avoid using `-` in base + new RID() { BaseRID = "foo-bar", Architecture = "arm" } }; + yield return new object[] { "linux-musl-x64", new RID() { BaseRID = "linux", Architecture = "musl", Qualifier = "x64" }, // yes, we already have ambiguous RIDs + new RID() { BaseRID = "linux-musl", Architecture = "x64" } }; } [Theory] [MemberData(nameof(ValidRIDData))] - internal void ParseCorrectly(string input, RID expected) + internal void ParseCorrectly(string input, RID expected, RID? expectedNoQualifier) { - RID actual = RID.Parse(input); + _ = expectedNoQualifier; // unused + + RID actual = RID.Parse(input, noQualifier: false); Assert.Equal(expected, actual); } [Theory] [MemberData(nameof(ValidRIDData))] - internal void ToStringAsExpected(string expected, RID rid) + internal void ParseCorrectlyNoQualifier(string input, RID expected, RID? expectedNoQualifier) + { + expectedNoQualifier ??= expected; + + RID actual = RID.Parse(input, noQualifier: true); + + Assert.Equal(expectedNoQualifier, actual); + } + + [Theory] + [MemberData(nameof(ValidRIDData))] + internal void ToStringAsExpected(string expected, RID rid, RID? expectedNoQualifierRid) { string actual = rid.ToString(); Assert.Equal(expected, actual); + + if (expectedNoQualifierRid is not null) + { + actual = expectedNoQualifierRid.ToString(); + + Assert.Equal(expected, actual); + } } } } diff --git a/src/libraries/Microsoft.Win32.Registry.AccessControl/src/Microsoft.Win32.Registry.AccessControl.csproj b/src/libraries/Microsoft.Win32.Registry.AccessControl/src/Microsoft.Win32.Registry.AccessControl.csproj index 7f6d66bdba8d38..ad39f8891e96f3 100644 --- a/src/libraries/Microsoft.Win32.Registry.AccessControl/src/Microsoft.Win32.Registry.AccessControl.csproj +++ b/src/libraries/Microsoft.Win32.Registry.AccessControl/src/Microsoft.Win32.Registry.AccessControl.csproj @@ -3,6 +3,9 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);$(NetCoreAppMinimum)-windows;$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + + false + 0 Provides support for managing access and audit control lists for Microsoft.Win32.RegistryKey. Commonly Used Types: diff --git a/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft.Win32.SystemEvents.csproj b/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft.Win32.SystemEvents.csproj index d7e7dc61211220..b3ae3d66c72e8d 100644 --- a/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft.Win32.SystemEvents.csproj +++ b/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft.Win32.SystemEvents.csproj @@ -3,6 +3,9 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);$(NetCoreAppMinimum)-windows;$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + + false + 0 Provides access to Windows system event notifications. Commonly Used Types: diff --git a/src/libraries/Microsoft.Windows.Compatibility/src/Microsoft.Windows.Compatibility.csproj b/src/libraries/Microsoft.Windows.Compatibility/src/Microsoft.Windows.Compatibility.csproj index 912ac95801edaf..ead61c8d31e349 100644 --- a/src/libraries/Microsoft.Windows.Compatibility/src/Microsoft.Windows.Compatibility.csproj +++ b/src/libraries/Microsoft.Windows.Compatibility/src/Microsoft.Windows.Compatibility.csproj @@ -5,6 +5,8 @@ false true + false + 3 $(NoWarn);NU5128 This Windows Compatibility Pack provides access to APIs that were previously available only for .NET Framework. It can be used from both .NET as well as .NET Standard. @@ -45,7 +47,7 @@ - + - 4.0.0.0 ECMA diff --git a/src/libraries/System.ComponentModel.Composition/Directory.Build.targets b/src/libraries/System.ComponentModel.Composition/Directory.Build.targets new file mode 100644 index 00000000000000..e8aeeb47a8da9c --- /dev/null +++ b/src/libraries/System.ComponentModel.Composition/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + 4.0.0.0 + + diff --git a/src/libraries/System.ComponentModel.Composition/src/System.ComponentModel.Composition.csproj b/src/libraries/System.ComponentModel.Composition/src/System.ComponentModel.Composition.csproj index f6dd177f01d10b..ea0c4a7596dcbf 100644 --- a/src/libraries/System.ComponentModel.Composition/src/System.ComponentModel.Composition.csproj +++ b/src/libraries/System.ComponentModel.Composition/src/System.ComponentModel.Composition.csproj @@ -3,6 +3,9 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0 false true + + false + 0 true true This namespace provides classes that constitute the core of the Managed Extensibility Framework, or MEF. diff --git a/src/libraries/System.Configuration.ConfigurationManager/src/System.Configuration.ConfigurationManager.csproj b/src/libraries/System.Configuration.ConfigurationManager/src/System.Configuration.ConfigurationManager.csproj index 2badd2c37adec6..b70f447de8dcb4 100644 --- a/src/libraries/System.Configuration.ConfigurationManager/src/System.Configuration.ConfigurationManager.csproj +++ b/src/libraries/System.Configuration.ConfigurationManager/src/System.Configuration.ConfigurationManager.csproj @@ -7,6 +7,9 @@ false $(NoWarn);CA1847 true + + false + 0 Provides types that support using configuration files. Commonly Used Types: @@ -262,7 +265,7 @@ System.Configuration.ConfigurationManager - + diff --git a/src/libraries/System.Console/src/System.Console.csproj b/src/libraries/System.Console/src/System.Console.csproj index c60368c33b8b50..1c45c5a8f932a4 100644 --- a/src/libraries/System.Console/src/System.Console.csproj +++ b/src/libraries/System.Console/src/System.Console.csproj @@ -238,7 +238,6 @@ - diff --git a/src/libraries/System.Console/src/System/IO/ConsoleStream.cs b/src/libraries/System.Console/src/System/IO/ConsoleStream.cs index 2d9a1657a5426a..04f927660cc1b3 100644 --- a/src/libraries/System.Console/src/System/IO/ConsoleStream.cs +++ b/src/libraries/System.Console/src/System/IO/ConsoleStream.cs @@ -3,8 +3,6 @@ using System.Diagnostics; using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; namespace System.IO { @@ -30,46 +28,6 @@ public override void Write(byte[] buffer, int offset, int count) public override void WriteByte(byte value) => Write(new ReadOnlySpan(in value)); - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - ValidateWrite(buffer, offset, count); - - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - try - { - Write(new ReadOnlySpan(buffer, offset, count)); - return Task.CompletedTask; - } - catch (Exception ex) - { - return Task.FromException(ex); - } - } - - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - { - ValidateCanWrite(); - - if (cancellationToken.IsCancellationRequested) - { - return ValueTask.FromCanceled(cancellationToken); - } - - try - { - Write(buffer.Span); - return ValueTask.CompletedTask; - } - catch (Exception ex) - { - return ValueTask.FromException(ex); - } - } - public override int Read(byte[] buffer, int offset, int count) { ValidateRead(buffer, offset, count); @@ -83,44 +41,6 @@ public override int ReadByte() return result != 0 ? b : -1; } - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - ValidateRead(buffer, offset, count); - - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - try - { - return Task.FromResult(Read(new Span(buffer, offset, count))); - } - catch (Exception exception) - { - return Task.FromException(exception); - } - } - - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) - { - ValidateCanRead(); - - if (cancellationToken.IsCancellationRequested) - { - return ValueTask.FromCanceled(cancellationToken); - } - - try - { - return ValueTask.FromResult(Read(buffer.Span)); - } - catch (Exception exception) - { - return ValueTask.FromException(exception); - } - } - protected override void Dispose(bool disposing) { _canRead = false; @@ -151,11 +71,7 @@ public override void Flush() { } protected void ValidateRead(byte[] buffer, int offset, int count) { ValidateBufferArguments(buffer, offset, count); - ValidateCanRead(); - } - private void ValidateCanRead() - { if (!_canRead) { throw Error.GetReadNotSupported(); @@ -165,11 +81,7 @@ private void ValidateCanRead() protected void ValidateWrite(byte[] buffer, int offset, int count) { ValidateBufferArguments(buffer, offset, count); - ValidateCanWrite(); - } - private void ValidateCanWrite() - { if (!_canWrite) { throw Error.GetWriteNotSupported(); diff --git a/src/libraries/System.Console/src/System/IO/KeyParser.cs b/src/libraries/System.Console/src/System/IO/KeyParser.cs index d54e0800a42b94..1167d00ec81c76 100644 --- a/src/libraries/System.Console/src/System/IO/KeyParser.cs +++ b/src/libraries/System.Console/src/System/IO/KeyParser.cs @@ -333,7 +333,7 @@ private static ConsoleKeyInfo ParseFromSingleChar(char single, bool isAlt) _ when char.IsAsciiLetterLower(single) => ConsoleKey.A + single - 'a', _ when char.IsAsciiLetterUpper(single) => UppercaseCharacter(single, out isShift), _ when char.IsAsciiDigit(single) => ConsoleKey.D0 + single - '0', // We can't distinguish DX and Ctrl+DX as they produce same values. Limitation: Ctrl+DX can't be mapped. - _ when char.IsBetween(single, (char)1, (char)26) => ControlAndLetterPressed(single, out keyChar, out isCtrl), + _ when char.IsBetween(single, (char)1, (char)26) => ControlAndLetterPressed(single, isAlt, out keyChar, out isCtrl), _ when char.IsBetween(single, (char)28, (char)31) => ControlAndDigitPressed(single, out keyChar, out isCtrl), '\u0000' => ControlAndDigitPressed(single, out keyChar, out isCtrl), _ => default @@ -359,7 +359,7 @@ static ConsoleKey UppercaseCharacter(char single, out bool isShift) return ConsoleKey.A + single - 'A'; } - static ConsoleKey ControlAndLetterPressed(char single, out char keyChar, out bool isCtrl) + static ConsoleKey ControlAndLetterPressed(char single, bool isAlt, out char keyChar, out bool isCtrl) { // Ctrl+(a-z) characters are mapped to values from 1 to 26. // Ctrl+H is mapped to 8, which also maps to Ctrl+Backspace. @@ -370,7 +370,9 @@ static ConsoleKey ControlAndLetterPressed(char single, out char keyChar, out boo Debug.Assert(single != 'b' && single != '\t' && single != '\n' && single != '\r'); isCtrl = true; - keyChar = default; // we could use the letter here, but it's impossible to distinguish upper vs lowercase (and Windows doesn't do it as well) + // Preserve the original character the same way Windows does (#75795), + // but only when Alt was not pressed at the same time. + keyChar = isAlt ? default : single; return ConsoleKey.A + single - 1; } diff --git a/src/libraries/System.Console/src/System/TermInfo.DatabaseFactory.cs b/src/libraries/System.Console/src/System/TermInfo.DatabaseFactory.cs index 0db908b2729bbd..4f73885d1db662 100644 --- a/src/libraries/System.Console/src/System/TermInfo.DatabaseFactory.cs +++ b/src/libraries/System.Console/src/System/TermInfo.DatabaseFactory.cs @@ -107,9 +107,8 @@ private static bool TryOpen(string filePath, [NotNullWhen(true)] out SafeFileHan { // Read in all of the terminfo data long termInfoLength = RandomAccess.GetLength(fd); - const int MaxTermInfoLength = 4096; // according to the term and tic man pages, 4096 is the terminfo file size max const int HeaderLength = 12; - if (termInfoLength <= HeaderLength || termInfoLength > MaxTermInfoLength) + if (termInfoLength <= HeaderLength) { throw new InvalidOperationException(SR.IO_TermInfoInvalid); } diff --git a/src/libraries/System.Console/src/System/TerminalFormatStrings.cs b/src/libraries/System.Console/src/System/TerminalFormatStrings.cs index f5c7c76785e9ab..398422d41cca0f 100644 --- a/src/libraries/System.Console/src/System/TerminalFormatStrings.cs +++ b/src/libraries/System.Console/src/System/TerminalFormatStrings.cs @@ -211,7 +211,7 @@ private static string GetTitle(TermInfo.Database db) case "konsole": return "\x1B]30;%p1%s\x07"; case "screen": - return "\x1Bk%p1%s\x1B"; + return "\x1Bk%p1%s\x1B\\"; default: return string.Empty; } diff --git a/src/libraries/System.Console/tests/ConsoleStreamTests.cs b/src/libraries/System.Console/tests/ConsoleStreamTests.cs deleted file mode 100644 index b8dc23865eaa65..00000000000000 --- a/src/libraries/System.Console/tests/ConsoleStreamTests.cs +++ /dev/null @@ -1,99 +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.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -public class ConsoleStreamTests -{ - [Fact] - public void WriteToOutputStream_EmptyArray() - { - Stream outStream = Console.OpenStandardOutput(); - outStream.Write(new byte[] { }, 0, 0); - } - - [ConditionalFact(typeof(Helpers), nameof(Helpers.IsConsoleInSupported))] - public void ReadAsyncRespectsCancellation() - { - Stream inStream = Console.OpenStandardInput(); - CancellationTokenSource cts = new CancellationTokenSource(); - cts.Cancel(); - - byte[] buffer = new byte[1024]; - Task result = inStream.ReadAsync(buffer, 0, buffer.Length, cts.Token); - Assert.True(result.IsCanceled); - - ValueTask valueTaskResult = inStream.ReadAsync(buffer.AsMemory(), cts.Token); - Assert.True(valueTaskResult.IsCanceled); - } - - [ConditionalFact(typeof(Helpers), nameof(Helpers.IsConsoleInSupported))] - public void ReadAsyncHandlesInvalidParams() - { - Stream inStream = Console.OpenStandardInput(); - - byte[] buffer = new byte[1024]; - Assert.Throws(() => { inStream.ReadAsync(null, 0, buffer.Length); }); - Assert.Throws(() => { inStream.ReadAsync(buffer, -1, buffer.Length); }); - Assert.Throws(() => { inStream.ReadAsync(buffer, 0, buffer.Length + 1); }); - } - - [Fact] - public void WriteAsyncRespectsCancellation() - { - Stream outStream = Console.OpenStandardOutput(); - CancellationTokenSource cts = new CancellationTokenSource(); - cts.Cancel(); - - byte[] bytes = Encoding.ASCII.GetBytes("Hi"); - Task result = outStream.WriteAsync(bytes, 0, bytes.Length, cts.Token); - Assert.True(result.IsCanceled); - - ValueTask valueTaskResult = outStream.WriteAsync(bytes.AsMemory(), cts.Token); - Assert.True(valueTaskResult.IsCanceled); - } - - [Fact] - public void WriteAsyncHandlesInvalidParams() - { - Stream outStream = Console.OpenStandardOutput(); - - byte[] bytes = Encoding.ASCII.GetBytes("Hi"); - Assert.Throws(() => { outStream.WriteAsync(null, 0, bytes.Length); }); - Assert.Throws(() => { outStream.WriteAsync(bytes, -1, bytes.Length); }); - Assert.Throws(() => { outStream.WriteAsync(bytes, 0, bytes.Length + 1); }); - } - - [ConditionalFact(typeof(Helpers), nameof(Helpers.IsConsoleInSupported))] - public void InputCannotWriteAsync() - { - Stream inStream = Console.OpenStandardInput(); - - byte[] bytes = Encoding.ASCII.GetBytes("Hi"); - Assert.Throws(() => { inStream.WriteAsync(bytes, 0, bytes.Length); }); - - Assert.Throws(() => { inStream.WriteAsync(bytes.AsMemory()); }); - } - - [Fact] - public void OutputCannotReadAsync() - { - Stream outStream = Console.OpenStandardOutput(); - - byte[] buffer = new byte[1024]; - Assert.Throws(() => - { - outStream.ReadAsync(buffer, 0, buffer.Length); - }); - - Assert.Throws(() => - { - outStream.ReadAsync(buffer.AsMemory()); - }); - } -} diff --git a/src/libraries/System.Console/tests/Helpers.cs b/src/libraries/System.Console/tests/Helpers.cs index 04abc28fd702f6..8c057a1c32dff2 100644 --- a/src/libraries/System.Console/tests/Helpers.cs +++ b/src/libraries/System.Console/tests/Helpers.cs @@ -6,11 +6,8 @@ using System.Text; using Xunit; -static class Helpers +class Helpers { - public static bool IsConsoleInSupported => - !PlatformDetection.IsAndroid && !PlatformDetection.IsiOS && !PlatformDetection.IsMacCatalyst && !PlatformDetection.IstvOS && !PlatformDetection.IsBrowser; - public static void SetAndReadHelper(Action setHelper, Func getHelper, Func readHelper) { const string TestString = "Test"; diff --git a/src/libraries/System.Console/tests/KeyParserTests.cs b/src/libraries/System.Console/tests/KeyParserTests.cs index f5cebdeeff17e8..bf672b6b8788f2 100644 --- a/src/libraries/System.Console/tests/KeyParserTests.cs +++ b/src/libraries/System.Console/tests/KeyParserTests.cs @@ -264,6 +264,8 @@ public void ExtendedStringCodePath() { get { + // Control+C + yield return (new string((char)3, 1), new[] { new ConsoleKeyInfo((char)3, ConsoleKey.C, false, false, true) }); // Backspace yield return (new string((char)127, 1), new[] { new ConsoleKeyInfo((char)127, ConsoleKey.Backspace, false, false, false) }); // Ctrl+Backspace @@ -448,7 +450,7 @@ public class GNOMETerminalData : TerminalData { yield return (new byte[] { 90 }, new ConsoleKeyInfo('Z', ConsoleKey.Z, true, false, false)); yield return (new byte[] { 97 }, new ConsoleKeyInfo('a', ConsoleKey.A, false, false, false)); - yield return (new byte[] { 1 }, new ConsoleKeyInfo(default, ConsoleKey.A, false, false, true)); + yield return (new byte[] { 1 }, new ConsoleKeyInfo((char)1, ConsoleKey.A, false, false, true)); yield return (new byte[] { 27, 97 }, new ConsoleKeyInfo('a', ConsoleKey.A, false, true, false)); yield return (new byte[] { 27, 1 }, new ConsoleKeyInfo(default, ConsoleKey.A, false, true, true)); yield return (new byte[] { 49 }, new ConsoleKeyInfo('1', ConsoleKey.D1, false, false, false)); @@ -613,7 +615,7 @@ public class XTermData : TerminalData { yield return (new byte[] { 90 }, new ConsoleKeyInfo('Z', ConsoleKey.Z, true, false, false)); yield return (new byte[] { 97 }, new ConsoleKeyInfo('a', ConsoleKey.A, false, false, false)); - yield return (new byte[] { 1 }, new ConsoleKeyInfo(default, ConsoleKey.A, false, false, true)); + yield return (new byte[] { 1 }, new ConsoleKeyInfo((char)1, ConsoleKey.A, false, false, true)); yield return (new byte[] { 195, 161 }, new ConsoleKeyInfo('\u00E1', default, false, false, false)); yield return (new byte[] { 194, 129 }, new ConsoleKeyInfo('\u0081', default, false, false, false)); yield return (new byte[] { 49 }, new ConsoleKeyInfo('1', ConsoleKey.D1, false, false, false)); @@ -886,7 +888,7 @@ public class WindowsTerminalData : TerminalData { yield return (new byte[] { 90 }, new ConsoleKeyInfo('Z', ConsoleKey.Z, true, false, false)); yield return (new byte[] { 97 }, new ConsoleKeyInfo('a', ConsoleKey.A, false, false, false)); - yield return (new byte[] { 1 }, new ConsoleKeyInfo(default, ConsoleKey.A, false, false, true)); + yield return (new byte[] { 1 }, new ConsoleKeyInfo((char)1, ConsoleKey.A, false, false, true)); yield return (new byte[] { 27, 97 }, new ConsoleKeyInfo('a', ConsoleKey.A, false, true, false)); yield return (new byte[] { 27, 1 }, new ConsoleKeyInfo(default, ConsoleKey.A, false, true, true)); yield return (new byte[] { 49 }, new ConsoleKeyInfo('1', ConsoleKey.D1, false, false, false)); diff --git a/src/libraries/System.Console/tests/ReadAndWrite.cs b/src/libraries/System.Console/tests/ReadAndWrite.cs index 664629fd7d5845..5fbf425fc84855 100644 --- a/src/libraries/System.Console/tests/ReadAndWrite.cs +++ b/src/libraries/System.Console/tests/ReadAndWrite.cs @@ -31,6 +31,13 @@ public static void WriteOverloads() } } + [Fact] + public static void WriteToOutputStream_EmptyArray() + { + Stream outStream = Console.OpenStandardOutput(); + outStream.Write(new byte[] { }, 0, 0); + } + [Fact] [OuterLoop] public static void WriteOverloadsToRealConsole() diff --git a/src/libraries/System.Console/tests/SetIn.cs b/src/libraries/System.Console/tests/SetIn.cs index 34e0fd00f46f95..4df9bdef462884 100644 --- a/src/libraries/System.Console/tests/SetIn.cs +++ b/src/libraries/System.Console/tests/SetIn.cs @@ -10,7 +10,8 @@ // public class SetIn { - [ConditionalFact(typeof(Helpers), nameof(Helpers.IsConsoleInSupported))] + [Fact] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS, "Not supported on Browser, iOS, MacCatalyst, or tvOS.")] public static void SetInThrowsOnNull() { TextReader savedIn = Console.In; @@ -24,7 +25,8 @@ public static void SetInThrowsOnNull() } } - [ConditionalFact(typeof(Helpers), nameof(Helpers.IsConsoleInSupported))] + [Fact] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS, "Not supported on Browser, iOS, MacCatalyst, or tvOS.")] public static void SetInReadLine() { const string TextStringFormat = "Test {0}"; diff --git a/src/libraries/System.Console/tests/System.Console.Tests.csproj b/src/libraries/System.Console/tests/System.Console.Tests.csproj index f4b451d30e147b..39268a44b5db0a 100644 --- a/src/libraries/System.Console/tests/System.Console.Tests.csproj +++ b/src/libraries/System.Console/tests/System.Console.Tests.csproj @@ -8,7 +8,6 @@ - @@ -44,8 +43,8 @@ + Link="%(RecursiveDir)%(Filename)%(Extension)" + CopyToOutputDirectory="PreserveNewest" /> diff --git a/src/libraries/System.Data.Common/ref/System.Data.Common.cs b/src/libraries/System.Data.Common/ref/System.Data.Common.cs index e46b17a11d3029..d731d51625dfa5 100644 --- a/src/libraries/System.Data.Common/ref/System.Data.Common.cs +++ b/src/libraries/System.Data.Common/ref/System.Data.Common.cs @@ -937,6 +937,7 @@ public override void Close() { } public override decimal GetDecimal(int ordinal) { throw null; } public override double GetDouble(int ordinal) { throw null; } public override System.Collections.IEnumerator GetEnumerator() { throw null; } + [return: System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields)] public override System.Type GetFieldType(int ordinal) { throw null; } public override float GetFloat(int ordinal) { throw null; } public override System.Guid GetGuid(int ordinal) { throw null; } @@ -945,6 +946,7 @@ public override void Close() { } public override long GetInt64(int ordinal) { throw null; } public override string GetName(int ordinal) { throw null; } public override int GetOrdinal(string name) { throw null; } + [return: System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields)] public override System.Type GetProviderSpecificFieldType(int ordinal) { throw null; } public override object GetProviderSpecificValue(int ordinal) { throw null; } public override int GetProviderSpecificValues(object[] values) { throw null; } @@ -2315,6 +2317,7 @@ protected virtual void Dispose(bool disposing) { } public abstract double GetDouble(int ordinal); [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public abstract System.Collections.IEnumerator GetEnumerator(); + [return: System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields)] public abstract System.Type GetFieldType(int ordinal); public System.Threading.Tasks.Task GetFieldValueAsync(int ordinal) { throw null; } public virtual System.Threading.Tasks.Task GetFieldValueAsync(int ordinal, System.Threading.CancellationToken cancellationToken) { throw null; } @@ -2327,6 +2330,7 @@ protected virtual void Dispose(bool disposing) { } public abstract string GetName(int ordinal); public abstract int GetOrdinal(string name); [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + [return: System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields)] public virtual System.Type GetProviderSpecificFieldType(int ordinal) { throw null; } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public virtual object GetProviderSpecificValue(int ordinal) { throw null; } @@ -2373,6 +2377,7 @@ protected DbDataRecord() { } protected virtual System.Data.Common.DbDataReader GetDbDataReader(int i) { throw null; } public abstract decimal GetDecimal(int i); public abstract double GetDouble(int i); + [return: System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields)] public abstract System.Type GetFieldType(int i); public abstract float GetFloat(int i); public abstract System.Guid GetGuid(int i); diff --git a/src/libraries/System.Data.Common/src/System/Data/Common/SqlUDTStorage.cs b/src/libraries/System.Data.Common/src/System/Data/Common/SqlUDTStorage.cs index 8ed9938ac835fb..97ac551b1cc87d 100644 --- a/src/libraries/System.Data.Common/src/System/Data/Common/SqlUDTStorage.cs +++ b/src/libraries/System.Data.Common/src/System/Data/Common/SqlUDTStorage.cs @@ -182,6 +182,9 @@ public override object ConvertXmlToObject(XmlReader xmlReader, XmlRootAttribute? } } Type type = (typeName == null) ? _dataType : Type.GetType(typeName)!; + + TypeLimiter.EnsureTypeIsAllowed(type); + object Obj = System.Activator.CreateInstance(type, true)!; Debug.Assert(xmlReader is DataTextReader, "Invalid DataTextReader is being passed to customer"); ((IXmlSerializable)Obj).ReadXml(xmlReader); diff --git a/src/libraries/System.Data.Common/tests/System.Data.Common.Tests.csproj b/src/libraries/System.Data.Common/tests/System.Data.Common.Tests.csproj index 9cc67ab8f40476..84d25bfe8e5c26 100644 --- a/src/libraries/System.Data.Common/tests/System.Data.Common.Tests.csproj +++ b/src/libraries/System.Data.Common/tests/System.Data.Common.Tests.csproj @@ -121,6 +121,7 @@ + diff --git a/src/libraries/System.Data.Common/tests/System/Data/RestrictedTypeHandlingTests.cs b/src/libraries/System.Data.Common/tests/System/Data/RestrictedTypeHandlingTests.cs index d42609fed6b5cf..4b425eaea464e5 100644 --- a/src/libraries/System.Data.Common/tests/System/Data/RestrictedTypeHandlingTests.cs +++ b/src/libraries/System.Data.Common/tests/System/Data/RestrictedTypeHandlingTests.cs @@ -242,6 +242,59 @@ public void DataTable_HonorsGloballyDefinedAllowList() } } + [Fact] + public void DataTable_HonorsGloballyDefinedAllowListForSqlTypes() + { + // Arrange + + DataTable table = new DataTable("MyTable"); + table.Columns.Add("MyNullableColumn", typeof(MyCustomNullable1)); + table.Rows.Add(new MyCustomNullable1()); + table.AcceptChanges(); + + var asXml = @$" + + + + + + + + + + + + + + + + + + false + + +"; + + // Act & assert + // Deserialization should fail since MyCustomNullable2 is not on the allow list, + // even though MyCustomNullable1 is on the allow list. + + try + { + AppDomain.CurrentDomain.SetData(AppDomainDataSetDefaultAllowedTypesKey, new Type[] + { + typeof(MyCustomNullable1) + }); + + table = new DataTable(); + Assert.Throws(() => table.ReadXml(new StringReader(asXml))); + } + finally + { + AppDomain.CurrentDomain.SetData(AppDomainDataSetDefaultAllowedTypesKey, null); + } + } + [Fact] public void DataColumn_ConvertExpression_SubjectToAllowList_Success() { @@ -401,6 +454,20 @@ private sealed class MyCustomClass { } + public sealed class MyCustomNullable1 : INullable + { + public static MyCustomNullable1 Null { get; } = new MyCustomNullable1(); + + public bool IsNull => false; + } + + public sealed class MyCustomNullable2 : INullable + { + public static MyCustomNullable2 Null { get; } = new MyCustomNullable2(); + + public bool IsNull => false; + } + public sealed class MyXmlSerializableClass : IXmlSerializable { public XmlSchema GetSchema() diff --git a/src/libraries/System.Data.Common/tests/System/Data/SqlTypes/SqlXmlTest.cs b/src/libraries/System.Data.Common/tests/System/Data/SqlTypes/SqlXmlTest.cs index bd49bed9f3e7d6..4504ded0de7ee6 100644 --- a/src/libraries/System.Data.Common/tests/System/Data/SqlTypes/SqlXmlTest.cs +++ b/src/libraries/System.Data.Common/tests/System/Data/SqlTypes/SqlXmlTest.cs @@ -1,28 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - +using System.Collections; +using System.Collections.Generic; using System.Data.SqlTypes; using System.IO; +using System.Linq; using System.Text; using System.Xml; @@ -32,10 +15,8 @@ namespace System.Data.Tests.SqlTypes { public class SqlXmlTest { - // Test constructor - [Fact] // .ctor (Stream) - //[Category ("NotDotNet")] // Name cannot begin with the '.' character, hexadecimal value 0x00. Line 1, position 2 - public void Constructor2_Stream_Unicode() + [Fact] + public void Constructor_Stream_Unicode() { string xmlStr = "VaradhanVeerapuram"; MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(xmlStr)); @@ -44,8 +25,8 @@ public void Constructor2_Stream_Unicode() Assert.Equal(xmlStr, xmlSql.Value); } - [Fact] // .ctor (Stream) - public void Constructor2_Stream_Empty() + [Fact] + public void Constructor_Stream_Empty() { MemoryStream ms = new MemoryStream(); SqlXml xmlSql = new SqlXml(ms); @@ -54,7 +35,7 @@ public void Constructor2_Stream_Empty() } [Fact] - public void Constructor2_Stream_Null() + public void Constructor_Stream_Null() { SqlXml xmlSql = new SqlXml((Stream)null); Assert.True(xmlSql.IsNull); @@ -62,8 +43,8 @@ public void Constructor2_Stream_Null() Assert.Throws(() => xmlSql.Value); } - [Fact] // .ctor (XmlReader) - public void Constructor3() + [Fact] + public void Constructor_StringReader() { string xmlStr = "VaradhanVeerapuram"; XmlReader xrdr = new XmlTextReader(new StringReader(xmlStr)); @@ -72,8 +53,8 @@ public void Constructor3() Assert.Equal(xmlStr, xmlSql.Value); } - [Fact] // .ctor (XmlReader) - public void Constructor3_XmlReader_Empty() + [Fact] + public void Constructor_XmlReader_Empty() { XmlReaderSettings xs = new XmlReaderSettings(); xs.ConformanceLevel = ConformanceLevel.Fragment; @@ -84,7 +65,7 @@ public void Constructor3_XmlReader_Empty() } [Fact] - public void Constructor3_XmlReader_Null() + public void Constructor_XmlReader_Null() { SqlXml xmlSql = new SqlXml((XmlReader)null); Assert.True(xmlSql.IsNull); @@ -93,7 +74,6 @@ public void Constructor3_XmlReader_Null() } [Fact] - //[Category ("NotDotNet")] // Name cannot begin with the '.' character, hexadecimal value 0x00. Line 1, position 2 public void CreateReader_Stream_Unicode() { string xmlStr = "VaradhanVeerapuram"; @@ -107,7 +87,7 @@ public void CreateReader_Stream_Unicode() } [Fact] - public void SqlXml_fromXmlReader_CreateReaderTest() + public void CreateReader_XmlTextReader_CanReadContent() { string xmlStr = "VaradhanVeerapuram"; XmlReader rdr = new XmlTextReader(new StringReader(xmlStr)); @@ -119,8 +99,82 @@ public void SqlXml_fromXmlReader_CreateReaderTest() Assert.Equal(xmlStr, xrdr.ReadOuterXml()); } + public static class CreateReader_TestFiles + { + private static TheoryData _filesAndBaselines; + + // The test files are made available through the System.Data.Common.TestData package included in dotnet/runtime-assets + private static void EnsureFileList() + { + if (_filesAndBaselines is null) + { + IEnumerable text = Directory.EnumerateFiles(Path.Combine("SqlXml.CreateReader", "Baseline-Text"), "*.xml"); + IEnumerable binary = Directory.EnumerateFiles(Path.Combine("SqlXml.CreateReader", "SqlBinaryXml"), "*.bmx"); + + // Make sure that we found our test files; otherwise the theories would succeed without validating anything + Assert.NotEmpty(text); + Assert.NotEmpty(binary); + + TheoryData filesAndBaselines = new TheoryData(); + + // Use the Text XML files as their own baselines + filesAndBaselines.Append(text.Select(f => new string[] { TextXmlFileName(f), TextXmlFileName(f) }).ToArray()); + + // Use the matching Text XML files as the baselines for the SQL Binary XML files + filesAndBaselines.Append(binary + .Select(Path.GetFileNameWithoutExtension) + .Intersect(text.Select(Path.GetFileNameWithoutExtension)) + .Select(f => new string[] { SqlBinaryXmlFileName(f), TextXmlFileName(f) }).ToArray()); + + _filesAndBaselines = filesAndBaselines; + + string TextXmlFileName(string name) => Path.Combine("SqlXml.CreateReader", "Baseline-Text", $"{name}.xml"); + string SqlBinaryXmlFileName(string name) => Path.Combine("SqlXml.CreateReader", "SqlBinaryXml", $"{name}.bmx"); + } + } + + public static TheoryData FilesAndBaselines + { + get + { + EnsureFileList(); + return _filesAndBaselines; + } + } + + public static string ReadAllXml(XmlReader reader) + { + using StringWriter writer = new StringWriter(); + using XmlWriter xmlWriter = new XmlTextWriter(writer); + + while (reader.Read()) xmlWriter.WriteNode(reader, false); + + return writer.ToString(); + } + } + + [Theory] + [MemberData(nameof(CreateReader_TestFiles.FilesAndBaselines), MemberType = typeof(CreateReader_TestFiles))] + public void CreateReader_TestAgainstBaseline(string testFile, string baselineFile) + { + // Get our expected output by using XmlReader directly + using XmlReader baselineReader = XmlReader.Create(baselineFile); + string expected = CreateReader_TestFiles.ReadAllXml(baselineReader); + + // Now produce the actual output through SqlXml.CreateReader + using FileStream xmlStream = new FileStream(testFile, FileMode.Open); + SqlXml sqlXml = new SqlXml(xmlStream); + + // When the input is text, an XmlTextReader will be returned + // When the input is SQL Binary XML, an XmlSqlBinaryReader will be returned + using XmlReader actualReader = sqlXml.CreateReader(); + string actual = CreateReader_TestFiles.ReadAllXml(actualReader); + + Assert.Equal(expected, actual); + } + [Fact] - public void SqlXml_fromZeroLengthStream_CreateReaderTest() + public void SqlXml_FromZeroLengthStream_CreateReaderTest() { MemoryStream stream = new MemoryStream(); SqlXml xmlSql = new SqlXml(stream); @@ -131,7 +185,7 @@ public void SqlXml_fromZeroLengthStream_CreateReaderTest() } [Fact] - public void SqlXml_fromZeroLengthXmlReader_CreateReaderTest_withFragment() + public void SqlXml_FromZeroLengthXmlReader_CreateReaderTest_withFragment() { XmlReaderSettings xs = new XmlReaderSettings(); xs.ConformanceLevel = ConformanceLevel.Fragment; @@ -145,7 +199,7 @@ public void SqlXml_fromZeroLengthXmlReader_CreateReaderTest_withFragment() } [Fact] - public void SqlXml_fromZeroLengthXmlReader_CreateReaderTest() + public void SqlXml_FromZeroLengthXmlReader_CreateReaderTest() { XmlReader rdr = new XmlTextReader(new StringReader(string.Empty)); diff --git a/src/libraries/System.Data.Odbc/src/System.Data.Odbc.csproj b/src/libraries/System.Data.Odbc/src/System.Data.Odbc.csproj index 8fad480d961a73..478786932ce7d6 100644 --- a/src/libraries/System.Data.Odbc/src/System.Data.Odbc.csproj +++ b/src/libraries/System.Data.Odbc/src/System.Data.Odbc.csproj @@ -6,6 +6,9 @@ $(NoWarn);CA1845 true + + false + 0 Provides a collection of classes used to access an ODBC data source in the managed space Commonly Used Types: diff --git a/src/libraries/System.Data.OleDb/src/System.Data.OleDb.csproj b/src/libraries/System.Data.OleDb/src/System.Data.OleDb.csproj index 1a1756a85adf69..87422949766dbf 100644 --- a/src/libraries/System.Data.OleDb/src/System.Data.OleDb.csproj +++ b/src/libraries/System.Data.OleDb/src/System.Data.OleDb.csproj @@ -9,6 +9,9 @@ $(NoWarn);CA1845 true + + false + 0 Provides a collection of classes for OLEDB. Commonly Used Types: diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.cs index 75b85af1913d47..1b08291cccd2ab 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.cs @@ -20,7 +20,6 @@ public virtual void Dispose() { } public virtual System.IDisposable Subscribe(System.IObserver> observer, System.Predicate? isEnabled) { throw null; } public override string ToString() { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of object being written to DiagnosticSource cannot be discovered statically.")] - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("DiagnosticSource may require creating new generic types or methods, which requires creating code at runtime. This may not work when AOT compiling.")] public override void Write(string name, object? value) { } } public abstract partial class DiagnosticSource @@ -29,7 +28,6 @@ protected DiagnosticSource() { } public abstract bool IsEnabled(string name); public virtual bool IsEnabled(string name, object? arg1, object? arg2 = null) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of object being written to DiagnosticSource cannot be discovered statically.")] - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("DiagnosticSource may require creating new generic types or methods, which requires creating code at runtime. This may not work when AOT compiling.")] public abstract void Write(string name, object? value); } } 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 5177cb52339eca..488e6c39224fe0 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs @@ -197,10 +197,8 @@ public abstract partial class DiagnosticSource public virtual void OnActivityExport(System.Diagnostics.Activity activity, object? payload) { } public virtual void OnActivityImport(System.Diagnostics.Activity activity, object? payload) { } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of object being written to DiagnosticSource cannot be discovered statically.")] - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("DiagnosticSource may require creating new generic types or methods, which requires creating code at runtime. This may not work when AOT compiling.")] public System.Diagnostics.Activity StartActivity(System.Diagnostics.Activity activity, object? args) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of object being written to DiagnosticSource cannot be discovered statically.")] - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("DiagnosticSource may require creating new generic types or methods, which requires creating code at runtime. This may not work when AOT compiling.")] public void StopActivity(System.Diagnostics.Activity activity, object? args) { } } public enum ActivitySamplingResult 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 9c22bce30dae9b..ccac4a2630bfeb 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj @@ -6,14 +6,14 @@ $(NoWarn);SA1205;CA1845 false true - - 10 true Provides Classes that allow you to decouple code logging rich (unserializable) diagnostics/telemetry (e.g. framework) from code that consumes it (e.g. tools) Commonly Used Types: System.Diagnostics.DiagnosticListener System.Diagnostics.DiagnosticSource + false + 2 @@ -87,6 +87,8 @@ System.Diagnostics.DiagnosticSource + diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticListener.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticListener.cs index 75a7116c58fc16..c1677307acb5e0 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticListener.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticListener.cs @@ -34,8 +34,6 @@ public partial class DiagnosticListener : DiagnosticSource, IObservable public static IObservable AllListeners { - [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", - Justification = "ENABLE_HTTP_HANDLER is not enabled in the .NET current version")] get { #if ENABLE_HTTP_HANDLER @@ -255,7 +253,6 @@ public override bool IsEnabled(string name, object? arg1, object? arg2 = null) /// Override abstract method /// [RequiresUnreferencedCode(WriteRequiresUnreferencedCode)] - [RequiresDynamicCode(WriteRequiresDynamicCode)] public override void Write(string name, object? value) { for (DiagnosticSubscription? curSubscription = _subscriptions; curSubscription != null; curSubscription = curSubscription.Next) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSource.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSource.cs index 47fad270bab4b4..89e62d4102c988 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSource.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSource.cs @@ -17,7 +17,6 @@ namespace System.Diagnostics public abstract partial class DiagnosticSource { internal const string WriteRequiresUnreferencedCode = "The type of object being written to DiagnosticSource cannot be discovered statically."; - internal const string WriteRequiresDynamicCode = "DiagnosticSource may require creating new generic types or methods, which requires creating code at runtime. This may not work when AOT compiling."; /// /// Write is a generic way of logging complex payloads. Each notification @@ -36,7 +35,6 @@ public abstract partial class DiagnosticSource /// An object that represent the value being passed as a payload for the event. /// This is often an anonymous type which contains several sub-values. [RequiresUnreferencedCode(WriteRequiresUnreferencedCode)] - [RequiresDynamicCode(WriteRequiresDynamicCode)] public abstract void Write(string name, object? value); /// diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceActivity.cs index 1156ed5fe3ee62..5bf9fa66916692 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceActivity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceActivity.cs @@ -26,7 +26,6 @@ public abstract partial class DiagnosticSource /// Started Activity for convenient chaining /// [RequiresUnreferencedCode(WriteRequiresUnreferencedCode)] - [RequiresDynamicCode(WriteRequiresDynamicCode)] public Activity StartActivity(Activity activity, object? args) { activity.Start(); @@ -45,7 +44,6 @@ public Activity StartActivity(Activity activity, object? args) /// An object that represent the value being passed as a payload for the event. /// [RequiresUnreferencedCode(WriteRequiresUnreferencedCode)] - [RequiresDynamicCode(WriteRequiresDynamicCode)] public void StopActivity(Activity activity, object? args) { // Stop sets the end time if it was unset, but we want it set before we issue the write 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 decd70bd442513..159050c2e966e8 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.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.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -234,7 +235,8 @@ public void Message(string? Message) /// Events from DiagnosticSource can be forwarded to EventSource using this event. /// [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "Arguments parameter is trimmer safe")] + Justification = "Arguments parameter is preserved by DynamicDependency")] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(KeyValuePair<,>))] [Event(2, Keywords = Keywords.Events)] private void Event(string SourceName, string EventName, IEnumerable>? Arguments) { @@ -255,7 +257,8 @@ private void EventJson(string SourceName, string EventName, string ArgmentsJson) /// Used to mark the beginning of an activity /// [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "Arguments parameter is trimmer safe")] + Justification = "Arguments parameter is preserved by DynamicDependency")] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(KeyValuePair<,>))] [Event(4, Keywords = Keywords.Events)] private void Activity1Start(string SourceName, string EventName, IEnumerable> Arguments) { @@ -266,7 +269,8 @@ private void Activity1Start(string SourceName, string EventName, IEnumerable [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "Arguments parameter is trimmer safe")] + Justification = "Arguments parameter is preserved by DynamicDependency")] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(KeyValuePair<,>))] [Event(5, Keywords = Keywords.Events)] private void Activity1Stop(string SourceName, string EventName, IEnumerable> Arguments) { @@ -277,7 +281,8 @@ private void Activity1Stop(string SourceName, string EventName, IEnumerable [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "Arguments parameter is trimmer safe")] + Justification = "Arguments parameter is preserved by DynamicDependency")] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(KeyValuePair<,>))] [Event(6, Keywords = Keywords.Events)] private void Activity2Start(string SourceName, string EventName, IEnumerable> Arguments) { @@ -288,7 +293,8 @@ private void Activity2Start(string SourceName, string EventName, IEnumerable [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "Arguments parameter is trimmer safe")] + Justification = "Arguments parameter is preserved by DynamicDependency")] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(KeyValuePair<,>))] [Event(7, Keywords = Keywords.Events)] private void Activity2Stop(string SourceName, string EventName, IEnumerable> Arguments) { @@ -299,7 +305,8 @@ private void Activity2Stop(string SourceName, string EventName, IEnumerable [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "Arguments parameter is trimmer safe")] + Justification = "Arguments parameter is preserved by DynamicDependency")] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(KeyValuePair<,>))] [Event(8, Keywords = Keywords.Events, ActivityOptions = EventActivityOptions.Recursive)] private void RecursiveActivity1Start(string SourceName, string EventName, IEnumerable> Arguments) { @@ -310,7 +317,8 @@ private void RecursiveActivity1Start(string SourceName, string EventName, IEnume /// Used to mark the end of an activity that can be recursive. /// [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "Arguments parameter is trimmer safe")] + Justification = "Arguments parameter is preserved by DynamicDependency")] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(KeyValuePair<,>))] [Event(9, Keywords = Keywords.Events, ActivityOptions = EventActivityOptions.Recursive)] private void RecursiveActivity1Stop(string SourceName, string EventName, IEnumerable> Arguments) { @@ -334,7 +342,8 @@ private void NewDiagnosticListener(string SourceName) /// The Activity name /// Name and value pairs of the Activity properties [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "Arguments parameter is trimmer safe")] + Justification = "Arguments parameter is preserved by DynamicDependency")] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(KeyValuePair<,>))] [Event(11, Keywords = Keywords.Events, ActivityOptions = EventActivityOptions.Recursive)] private void ActivityStart(string SourceName, string ActivityName, IEnumerable> Arguments) => WriteEvent(11, SourceName, ActivityName, Arguments); @@ -346,7 +355,8 @@ private void ActivityStart(string SourceName, string ActivityName, IEnumerableThe Activity name /// Name and value pairs of the Activity properties [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "Arguments parameter is trimmer safe")] + Justification = "Arguments parameter is preserved by DynamicDependency")] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(KeyValuePair<,>))] [Event(12, Keywords = Keywords.Events, ActivityOptions = EventActivityOptions.Recursive)] private void ActivityStop(string SourceName, string ActivityName, IEnumerable> Arguments) => WriteEvent(12, SourceName, ActivityName, Arguments); @@ -641,8 +651,6 @@ public FilterAndTransform(string filterAndPayloadSpec, int startIdx, int endIdx, [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2119", Justification = "DAM on EventSource references this compiler-generated local function which calls a " + "method that requires unreferenced code. EventSource will not access this local function.")] - [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", - Justification = "DiagnosticSource.Write is marked with RequiresDynamicCode.")] void OnEventWritten(KeyValuePair evnt) { // The filter given to the DiagnosticSource may not work if users don't is 'IsEnabled' as expected. @@ -890,8 +898,6 @@ internal static void CreateActivityListener(DiagnosticSourceEventSource eventSou [DynamicDependency(nameof(TimeSpan.Ticks), typeof(TimeSpan))] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Activity's properties are being preserved with the DynamicDependencies on OnActivityStarted.")] - [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", - Justification = "Activity is a reference type and is safe in aot.")] private static void OnActivityStarted(DiagnosticSourceEventSource eventSource, Activity activity) { FilterAndTransform? list = eventSource._activitySourceSpecs; @@ -911,8 +917,6 @@ private static void OnActivityStarted(DiagnosticSourceEventSource eventSource, A [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Activity's properties are being preserved with the DynamicDependencies on OnActivityStarted.")] - [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", - Justification = "Activity is a reference type and is safe with aot.")] private static void OnActivityStopped(DiagnosticSourceEventSource eventSource, Activity activity) { FilterAndTransform? list = eventSource._activitySourceSpecs; @@ -1015,7 +1019,6 @@ private void Dispose() Justification = "In EventSource, EnsureDescriptorsInitialized's use of GetType preserves this method which " + "requires unreferenced code, but EnsureDescriptorsInitialized does not access this member and is safe to call.")] [RequiresUnreferencedCode(DiagnosticSource.WriteRequiresUnreferencedCode)] - [RequiresDynamicCode(DiagnosticSource.WriteRequiresDynamicCode)] public List> Morph(object? args) { // Transform the args into a bag of key-value strings. @@ -1197,7 +1200,6 @@ public TransformSpec(string transformSpec, int startIdx, int endIdx, TransformSp Justification = "In EventSource, EnsureDescriptorsInitialized's use of GetType preserves this method which " + "requires unreferenced code, but EnsureDescriptorsInitialized does not access this member and is safe to call.")] [RequiresUnreferencedCode(DiagnosticSource.WriteRequiresUnreferencedCode)] - [RequiresDynamicCode(DiagnosticSource.WriteRequiresDynamicCode)] public KeyValuePair Morph(object? obj) { for (PropertySpec? cur = _fetches; cur != null; cur = cur.Next) @@ -1252,7 +1254,6 @@ public PropertySpec(string propertyName, PropertySpec? next) Justification = "In EventSource, EnsureDescriptorsInitialized's use of GetType preserves this method which " + "requires unreferenced code, but EnsureDescriptorsInitialized does not access this member and is safe to call.")] [RequiresUnreferencedCode(DiagnosticSource.WriteRequiresUnreferencedCode)] - [RequiresDynamicCode(DiagnosticSource.WriteRequiresDynamicCode)] public object? Fetch(object? obj) { PropertyFetch? fetch = _fetchForExpectedType; @@ -1299,7 +1300,6 @@ public PropertyFetch(Type? type) Justification = "In EventSource, EnsureDescriptorsInitialized's use of GetType preserves this method which " + "requires unreferenced code, but EnsureDescriptorsInitialized does not access this member and is safe to call.")] [RequiresUnreferencedCode(DiagnosticSource.WriteRequiresUnreferencedCode)] - [RequiresDynamicCode(DiagnosticSource.WriteRequiresDynamicCode)] public static PropertyFetch FetcherForProperty(Type? type, string propertyName) { if (propertyName == null) @@ -1323,10 +1323,7 @@ public static PropertyFetch FetcherForProperty(Type? type, string propertyName) continue; } - Type elemType = iFaceTypeInfo.GetGenericArguments()[0]; - Type instantiatedTypedPropertyFetcher = typeof(EnumeratePropertyFetch<>) - .GetTypeInfo().MakeGenericType(elemType); - return (PropertyFetch)Activator.CreateInstance(instantiatedTypedPropertyFetcher, type)!; + return CreateEnumeratePropertyFetch(type, iFaceTypeInfo); } // no implementation of IEnumerable found, return a null fetcher @@ -1359,20 +1356,50 @@ public static PropertyFetch FetcherForProperty(Type? type, string propertyName) Log.Message($"Property {propertyName} is static."); return new PropertyFetch(type); } - Type typedPropertyFetcher = typeInfo.IsValueType ? - typeof(ValueTypedFetchProperty<,>) : typeof(RefTypedFetchProperty<,>); - Type instantiatedTypedPropertyFetcher = typedPropertyFetcher.GetTypeInfo().MakeGenericType( - propertyInfo.DeclaringType!, propertyInfo.PropertyType); - return (PropertyFetch)Activator.CreateInstance(instantiatedTypedPropertyFetcher, type, propertyInfo)!; + + return CreatePropertyFetch(typeInfo, propertyInfo); } } + [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", + Justification = "MakeGenericType is only called when IsDynamicCodeSupported is true or only with ref types.")] + private static PropertyFetch CreateEnumeratePropertyFetch(Type type, Type enumerableOfTType) + { + Type elemType = enumerableOfTType.GetGenericArguments()[0]; +#if NETCOREAPP + if (!RuntimeFeature.IsDynamicCodeSupported && elemType.IsValueType) + { + return new EnumeratePropertyFetch(type); + } +#endif + Type instantiatedTypedPropertyFetcher = typeof(EnumeratePropertyFetch<>) + .GetTypeInfo().MakeGenericType(elemType); + return (PropertyFetch)Activator.CreateInstance(instantiatedTypedPropertyFetcher, type)!; + } + + [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", + Justification = "MakeGenericType is only called when IsDynamicCodeSupported is true or only with ref types.")] + private static PropertyFetch CreatePropertyFetch(Type type, PropertyInfo propertyInfo) + { +#if NETCOREAPP + if (!RuntimeFeature.IsDynamicCodeSupported && (propertyInfo.DeclaringType!.IsValueType || propertyInfo.PropertyType.IsValueType)) + { + return new ReflectionPropertyFetch(type, propertyInfo); + } +#endif + Type typedPropertyFetcher = type.IsValueType ? + typeof(ValueTypedFetchProperty<,>) : typeof(RefTypedFetchProperty<,>); + Type instantiatedTypedPropertyFetcher = typedPropertyFetcher.GetTypeInfo().MakeGenericType( + propertyInfo.DeclaringType!, propertyInfo.PropertyType); + return (PropertyFetch)Activator.CreateInstance(instantiatedTypedPropertyFetcher, type, propertyInfo)!; + } + /// /// Given an object, fetch the property that this propertyFech represents. /// public virtual object? Fetch(object? obj) { return null; } - #region private +#region private private sealed class RefTypedFetchProperty : PropertyFetch { @@ -1411,6 +1438,74 @@ public ValueTypedFetchProperty(Type type, PropertyInfo property) : base(type) private readonly StructFunc _propertyFetch; } +#if NETCOREAPP + /// + /// A fetcher that can be used when MakeGenericType isn't available. + /// + private sealed class ReflectionPropertyFetch : PropertyFetch + { + private readonly PropertyInfo _property; + public ReflectionPropertyFetch(Type type, PropertyInfo property) : base(type) + { + _property = property; + } + + public override object? Fetch(object? obj) => _property.GetValue(obj); + } + + /// + /// A fetcher that enumerates and formats an IEnumerable when MakeGenericType isn't available. + /// + private sealed class EnumeratePropertyFetch : PropertyFetch + { + public EnumeratePropertyFetch(Type type) : base(type) { } + + public override object? Fetch(object? obj) + { + IEnumerable? enumerable = obj as IEnumerable; + Debug.Assert(enumerable is not null); + + // string.Join for a non-generic IEnumerable + IEnumerator en = enumerable.GetEnumerator(); + using (IDisposable? disposable = en as IDisposable) + { + if (!en.MoveNext()) + { + return string.Empty; + } + + object? currentValue = en.Current; + string? firstString = currentValue?.ToString(); + + // If there's only 1 item, simply return the ToString of that + if (!en.MoveNext()) + { + // Only one value available + return firstString ?? string.Empty; + } + + var result = new ValueStringBuilder(stackalloc char[256]); + + result.Append(firstString); + + do + { + currentValue = en.Current; + + result.Append(","); + if (currentValue != null) + { + result.Append(currentValue.ToString()); + } + } + while (en.MoveNext()); + + return result.ToString(); + } + } + } +#endif + /// /// A fetcher that returns the result of Activity.Current /// @@ -1435,17 +1530,17 @@ public EnumeratePropertyFetch(Type type) : base(type) { } return string.Join(",", (IEnumerable)obj); } } - #endregion +#endregion } private readonly string _propertyName; private volatile PropertyFetch? _fetchForExpectedType; - #endregion +#endregion } private readonly string _outputName = null!; private readonly PropertySpec? _fetches; - #endregion +#endregion } /// @@ -1458,13 +1553,13 @@ internal sealed class CallbackObserver : IObserver { public CallbackObserver(Action callback) { _callback = callback; } - #region private +#region private public void OnCompleted() { } public void OnError(Exception error) { } public void OnNext(T value) { _callback(value); } private readonly Action _callback; - #endregion +#endregion } // A linked list of IObservable subscriptions (which are IDisposable). @@ -1481,11 +1576,11 @@ public Subscriptions(IDisposable subscription, Subscriptions? next) public Subscriptions? Next; } - #endregion +#endregion private FilterAndTransform? _specs; // Transformation specifications that indicate which sources/events are forwarded. private FilterAndTransform? _activitySourceSpecs; // ActivitySource Transformation specifications that indicate which sources/events are forwarded. private ActivityListener? _activityListener; - #endregion +#endregion } } diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/HttpHandlerDiagnosticListener.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/HttpHandlerDiagnosticListener.cs index a35ea9ce5807d4..94d4192721ca54 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/HttpHandlerDiagnosticListener.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/HttpHandlerDiagnosticListener.cs @@ -25,7 +25,6 @@ namespace System.Diagnostics /// when it sees the System.Net.Http.Desktop source, subscribe to it. This will trigger the /// initialization of this DiagnosticListener. /// - [RequiresDynamicCode(WriteRequiresDynamicCode)] internal sealed class HttpHandlerDiagnosticListener : DiagnosticListener { /// @@ -204,7 +203,6 @@ public override void Remove(object key) /// intercept each new ServicePoint object being added to ServicePointManager.s_ServicePointTable /// and replace its ConnectionGroupList hashtable field. /// - [RequiresDynamicCode(WriteRequiresDynamicCode)] private sealed class ServicePointHashtable : HashtableWrapper { public ServicePointHashtable(Hashtable table) : base(table) @@ -245,7 +243,6 @@ public override object this[object key] /// intercept each new ConnectionGroup object being added to ServicePoint.m_ConnectionGroupList /// and replace its m_ConnectionList arraylist field. /// - [RequiresDynamicCode(WriteRequiresDynamicCode)] private sealed class ConnectionGroupHashtable : HashtableWrapper { public ConnectionGroupHashtable(Hashtable table) : base(table) @@ -485,7 +482,6 @@ public override void TrimToSize() /// intercept each new Connection object being added to ConnectionGroup.m_ConnectionList /// and replace its m_WriteList arraylist field. /// - [RequiresDynamicCode(WriteRequiresDynamicCode)] private sealed class ConnectionArrayList : ArrayListWrapper { public ConnectionArrayList(ArrayList list) : base(list) @@ -516,7 +512,6 @@ public override int Add(object value) /// It also intercepts all HttpWebRequest objects that are about to get removed from /// Connection.m_WriteList as they have completed the request. /// - [RequiresDynamicCode(WriteRequiresDynamicCode)] private sealed class HttpWebRequestArrayList : ArrayListWrapper { public HttpWebRequestArrayList(ArrayList list) : base(list) 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 403ba0bb095484..458d1c9d34e5a4 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 @@ -7,6 +7,7 @@ using System.Globalization; using System.Runtime.Versioning; using System.Text; +using System.Threading; namespace System.Diagnostics.Metrics { @@ -60,13 +61,22 @@ public static class Keywords public const EventKeywords InstrumentPublishing = (EventKeywords)0x4; } - private CommandHandler _handler; + private CommandHandler? _handler; - private MetricsEventSource() + private CommandHandler Handler { - _handler = new CommandHandler(); + get + { + if (_handler == null) + { + Interlocked.CompareExchange(ref _handler, new CommandHandler(this), null); + } + return _handler; + } } + private MetricsEventSource() { } + /// /// Used to send ad-hoc diagnostics to humans. /// @@ -189,7 +199,7 @@ protected override void OnEventCommand(EventCommandEventArgs command) { lock (this) { - _handler.OnEventCommand(command); + Handler.OnEventCommand(command); } } @@ -202,6 +212,13 @@ private sealed class CommandHandler private AggregationManager? _aggregationManager; private string _sessionId = ""; + public CommandHandler(MetricsEventSource parent) + { + Parent = parent; + } + + public MetricsEventSource Parent { get; private set;} + public void OnEventCommand(EventCommandEventArgs command) { try @@ -215,7 +232,7 @@ public void OnEventCommand(EventCommandEventArgs command) // This limitation shouldn't really matter because browser also doesn't support out-of-proc EventSource communication // which is the intended scenario for this EventSource. If it matters in the future AggregationManager can be // modified to have some other fallback path that works for browser. - Log.Error("", "System.Diagnostics.Metrics EventSource not supported on browser"); + Parent.Error("", "System.Diagnostics.Metrics EventSource not supported on browser"); return; } #endif @@ -233,13 +250,13 @@ public void OnEventCommand(EventCommandEventArgs command) // one other listener is active. In the future we might be able to figure out how // to infer the changes from the info we do have or add a better API but for now // I am taking the simple route and not supporting it. - Log.MultipleSessionsNotSupportedError(_sessionId); + Parent.MultipleSessionsNotSupportedError(_sessionId); return; } _aggregationManager.Dispose(); _aggregationManager = null; - Log.Message($"Previous session with id {_sessionId} is stopped"); + Parent.Message($"Previous session with id {_sessionId} is stopped"); } _sessionId = ""; } @@ -249,12 +266,12 @@ public void OnEventCommand(EventCommandEventArgs command) if (command.Arguments!.TryGetValue("SessionId", out string? id)) { _sessionId = id!; - Log.Message($"SessionId argument received: {_sessionId}"); + Parent.Message($"SessionId argument received: {_sessionId}"); } else { _sessionId = System.Guid.NewGuid().ToString(); - Log.Message($"New session started. SessionId auto-generated: {_sessionId}"); + Parent.Message($"New session started. SessionId auto-generated: {_sessionId}"); } @@ -263,21 +280,21 @@ public void OnEventCommand(EventCommandEventArgs command) double refreshIntervalSecs; if (command.Arguments!.TryGetValue("RefreshInterval", out string? refreshInterval)) { - Log.Message($"RefreshInterval argument received: {refreshInterval}"); + Parent.Message($"RefreshInterval argument received: {refreshInterval}"); if (!double.TryParse(refreshInterval, out refreshIntervalSecs)) { - Log.Message($"Failed to parse RefreshInterval. Using default {defaultIntervalSecs}s."); + Parent.Message($"Failed to parse RefreshInterval. Using default {defaultIntervalSecs}s."); refreshIntervalSecs = defaultIntervalSecs; } else if (refreshIntervalSecs < AggregationManager.MinCollectionTimeSecs) { - Log.Message($"RefreshInterval too small. Using minimum interval {AggregationManager.MinCollectionTimeSecs} seconds."); + Parent.Message($"RefreshInterval too small. Using minimum interval {AggregationManager.MinCollectionTimeSecs} seconds."); refreshIntervalSecs = AggregationManager.MinCollectionTimeSecs; } } else { - Log.Message($"No RefreshInterval argument received. Using default {defaultIntervalSecs}s."); + Parent.Message($"No RefreshInterval argument received. Using default {defaultIntervalSecs}s."); refreshIntervalSecs = defaultIntervalSecs; } @@ -285,16 +302,16 @@ public void OnEventCommand(EventCommandEventArgs command) int maxTimeSeries; if (command.Arguments!.TryGetValue("MaxTimeSeries", out string? maxTimeSeriesString)) { - Log.Message($"MaxTimeSeries argument received: {maxTimeSeriesString}"); + Parent.Message($"MaxTimeSeries argument received: {maxTimeSeriesString}"); if (!int.TryParse(maxTimeSeriesString, out maxTimeSeries)) { - Log.Message($"Failed to parse MaxTimeSeries. Using default {defaultMaxTimeSeries}"); + Parent.Message($"Failed to parse MaxTimeSeries. Using default {defaultMaxTimeSeries}"); maxTimeSeries = defaultMaxTimeSeries; } } else { - Log.Message($"No MaxTimeSeries argument received. Using default {defaultMaxTimeSeries}"); + Parent.Message($"No MaxTimeSeries argument received. Using default {defaultMaxTimeSeries}"); maxTimeSeries = defaultMaxTimeSeries; } @@ -302,16 +319,16 @@ public void OnEventCommand(EventCommandEventArgs command) int maxHistograms; if (command.Arguments!.TryGetValue("MaxHistograms", out string? maxHistogramsString)) { - Log.Message($"MaxHistograms argument received: {maxHistogramsString}"); + Parent.Message($"MaxHistograms argument received: {maxHistogramsString}"); if (!int.TryParse(maxHistogramsString, out maxHistograms)) { - Log.Message($"Failed to parse MaxHistograms. Using default {defaultMaxHistograms}"); + Parent.Message($"Failed to parse MaxHistograms. Using default {defaultMaxHistograms}"); maxHistograms = defaultMaxHistograms; } } else { - Log.Message($"No MaxHistogram argument received. Using default {defaultMaxHistograms}"); + Parent.Message($"No MaxHistogram argument received. Using default {defaultMaxHistograms}"); maxHistograms = defaultMaxHistograms; } @@ -320,27 +337,27 @@ public void OnEventCommand(EventCommandEventArgs command) maxTimeSeries, maxHistograms, (i, s) => TransmitMetricValue(i, s, sessionId), - (startIntervalTime, endIntervalTime) => Log.CollectionStart(sessionId, startIntervalTime, endIntervalTime), - (startIntervalTime, endIntervalTime) => Log.CollectionStop(sessionId, startIntervalTime, endIntervalTime), - i => Log.BeginInstrumentReporting(sessionId, i.Meter.Name, i.Meter.Version, i.Name, i.GetType().Name, i.Unit, i.Description), - i => Log.EndInstrumentReporting(sessionId, i.Meter.Name, i.Meter.Version, i.Name, i.GetType().Name, i.Unit, i.Description), - i => Log.InstrumentPublished(sessionId, i.Meter.Name, i.Meter.Version, i.Name, i.GetType().Name, i.Unit, i.Description), - () => Log.InitialInstrumentEnumerationComplete(sessionId), - e => Log.Error(sessionId, e.ToString()), - () => Log.TimeSeriesLimitReached(sessionId), - () => Log.HistogramLimitReached(sessionId), - e => Log.ObservableInstrumentCallbackError(sessionId, e.ToString())); + (startIntervalTime, endIntervalTime) => Parent.CollectionStart(sessionId, startIntervalTime, endIntervalTime), + (startIntervalTime, endIntervalTime) => Parent.CollectionStop(sessionId, startIntervalTime, endIntervalTime), + i => Parent.BeginInstrumentReporting(sessionId, i.Meter.Name, i.Meter.Version, i.Name, i.GetType().Name, i.Unit, i.Description), + i => Parent.EndInstrumentReporting(sessionId, i.Meter.Name, i.Meter.Version, i.Name, i.GetType().Name, i.Unit, i.Description), + i => Parent.InstrumentPublished(sessionId, i.Meter.Name, i.Meter.Version, i.Name, i.GetType().Name, i.Unit, i.Description), + () => Parent.InitialInstrumentEnumerationComplete(sessionId), + e => Parent.Error(sessionId, e.ToString()), + () => Parent.TimeSeriesLimitReached(sessionId), + () => Parent.HistogramLimitReached(sessionId), + e => Parent.ObservableInstrumentCallbackError(sessionId, e.ToString())); _aggregationManager.SetCollectionPeriod(TimeSpan.FromSeconds(refreshIntervalSecs)); if (command.Arguments!.TryGetValue("Metrics", out string? metricsSpecs)) { - Log.Message($"Metrics argument received: {metricsSpecs}"); + Parent.Message($"Metrics argument received: {metricsSpecs}"); ParseSpecs(metricsSpecs); } else { - Log.Message("No Metrics argument received"); + Parent.Message("No Metrics argument received"); } _aggregationManager.Start(); @@ -354,7 +371,7 @@ public void OnEventCommand(EventCommandEventArgs command) private bool LogError(Exception e) { - Log.Error(_sessionId, e.ToString()); + Parent.Error(_sessionId, e.ToString()); // this code runs as an exception filter // returning false ensures the catch handler isn't run return false; @@ -374,11 +391,11 @@ private void ParseSpecs(string? metricsSpecs) { if (!MetricSpec.TryParse(specString, out MetricSpec spec)) { - Log.Message($"Failed to parse metric spec: {specString}"); + Parent.Message($"Failed to parse metric spec: {specString}"); } else { - Log.Message($"Parsed metric: {spec}"); + Parent.Message($"Parsed metric: {spec}"); if (spec.InstrumentName != null) { _aggregationManager!.Include(spec.MeterName, spec.InstrumentName); diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/NativeAotTests/DiagnosticSourceEventSourceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/NativeAotTests/DiagnosticSourceEventSourceTests.cs new file mode 100644 index 00000000000000..fab21b5a7b716c --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/NativeAotTests/DiagnosticSourceEventSourceTests.cs @@ -0,0 +1,110 @@ +// 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.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Tracing; + +/// +/// Tests that using writing to a DiagnosticSource writes the correct payloads +/// to the DiagnosticSourceEventSource. +/// +internal class Program +{ + private class TestEventListener : EventListener + { + public ReadOnlyCollection LogDataPayload { get; set; } + + protected override void OnEventSourceCreated(EventSource eventSource) + { + if (eventSource.Name == "Microsoft-Diagnostics-DiagnosticSource") + { + EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All, new Dictionary + { + { "FilterAndPayloadSpecs", "TestDiagnosticListener/Test.Start@Activity2Start:-Id;Ints.*Enumerate"} + }); + } + + base.OnEventSourceCreated(eventSource); + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + if (eventData.EventName == "Activity2Start") + { + LogDataPayload = eventData.Payload; + } + + base.OnEventWritten(eventData); + } + } + + public static int Main() + { + DiagnosticSource diagnosticSource = new DiagnosticListener("TestDiagnosticListener"); + using (var listener = new TestEventListener()) + { + var data = new EventData() + { + Id = Guid.NewGuid(), + }; + + Write(diagnosticSource, "Test.Start", data); + + if (!(listener.LogDataPayload?.Count == 3 && + (string)listener.LogDataPayload[0] == "TestDiagnosticListener" && + (string)listener.LogDataPayload[1] == "Test.Start")) + { + return -1; + } + + object[] args = (object[])listener.LogDataPayload[2]; + if (args.Length != 2) + { + return -2; + } + + IDictionary arg = (IDictionary)args[0]; + if (!((string)arg["Key"] == "Id" && (string)arg["Value"] == data.Id.ToString())) + { + return -3; + } + + arg = (IDictionary)args[1]; + if (!((string)arg["Key"] == "*Enumerate" && (string)arg["Value"] == "1,2,3")) + { + return -4; + } + + return 100; + } + } + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", + Justification = "The value being passed into Write has the necessary properties being preserved with DynamicallyAccessedMembers.")] + private static void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>( + DiagnosticSource diagnosticSource, + string name, + T value) + { + diagnosticSource.Write(name, value); + } + + public class EventData + { + public Guid Id { get; set; } + + public IEnumerable Ints + { + get + { + yield return 1; + yield return 2; + yield return 3; + } + } + } +} diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/NativeAotTests/System.Diagnostics.DiagnosticSource.NativeAotTests.proj b/src/libraries/System.Diagnostics.DiagnosticSource/tests/NativeAotTests/System.Diagnostics.DiagnosticSource.NativeAotTests.proj new file mode 100644 index 00000000000000..8001203352b581 --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/NativeAotTests/System.Diagnostics.DiagnosticSource.NativeAotTests.proj @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/System.Diagnostics.DiagnosticSource.Switches.Tests.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/System.Diagnostics.DiagnosticSource.Switches.Tests.csproj index e9dd8a7adec528..90a660f236118d 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/System.Diagnostics.DiagnosticSource.Switches.Tests.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/System.Diagnostics.DiagnosticSource.Switches.Tests.csproj @@ -3,6 +3,9 @@ $(NetCoreAppCurrent) true + + + diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/runtimeconfig.template.json b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/runtimeconfig.template.json deleted file mode 100644 index 1b600a96bff58a..00000000000000 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/runtimeconfig.template.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "configProperties": { - "System.Diagnostics.DefaultActivityIdFormatIsHierarchial": true - } -} \ No newline at end of file diff --git a/src/libraries/System.Diagnostics.EventLog/src/System.Diagnostics.EventLog.csproj b/src/libraries/System.Diagnostics.EventLog/src/System.Diagnostics.EventLog.csproj index dfae87cc90a726..f4923e920a2a36 100644 --- a/src/libraries/System.Diagnostics.EventLog/src/System.Diagnostics.EventLog.csproj +++ b/src/libraries/System.Diagnostics.EventLog/src/System.Diagnostics.EventLog.csproj @@ -9,6 +9,9 @@ $(NoWarn);CA1845;CA1846 annotations true + + false + 0 Provides the System.Diagnostics.EventLog class, which allows the applications to use the windows event log service. Commonly Used Types: diff --git a/src/libraries/System.Diagnostics.PerformanceCounter/src/System.Diagnostics.PerformanceCounter.csproj b/src/libraries/System.Diagnostics.PerformanceCounter/src/System.Diagnostics.PerformanceCounter.csproj index a0dbb8e208bd40..e33e497a18f2a5 100644 --- a/src/libraries/System.Diagnostics.PerformanceCounter/src/System.Diagnostics.PerformanceCounter.csproj +++ b/src/libraries/System.Diagnostics.PerformanceCounter/src/System.Diagnostics.PerformanceCounter.csproj @@ -5,6 +5,9 @@ $(NoWarn);CA1847 annotations true + + false + 0 Provides the System.Diagnostics.PerformanceCounter class, which allows access to Windows performance counters. Commonly Used Types: 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 2c273820afb4c9..6f729f78ed0a48 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -302,6 +302,8 @@ Link="Common\Interop\Linux\Interop.ProcFsStat.ParseMapModules.cs" /> + diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs index bae58c8e8bcac8..e25aa2bf9fd88a 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs @@ -9,6 +9,7 @@ using System.IO; using System.Runtime.Versioning; using System.Text; +using System.Threading; namespace System.Diagnostics { @@ -79,33 +80,25 @@ internal static DateTime BootTimeToDateTime(TimeSpan timespanAfterBoot) return dt.ToLocalTime(); } + private static long s_bootTimeTicks; /// Gets the system boot time. private static DateTime BootTime { get { - // '/proc/stat -> btime' gets the boot time. - // btime is the time of system boot in seconds since the Unix epoch. - // It includes suspended time and is updated based on the system time (settimeofday). - const string StatFile = Interop.procfs.ProcStatFilePath; - string text = File.ReadAllText(StatFile); - int btimeLineStart = text.IndexOf("\nbtime ", StringComparison.Ordinal); - if (btimeLineStart >= 0) - { - int btimeStart = btimeLineStart + "\nbtime ".Length; - int btimeEnd = text.IndexOf('\n', btimeStart); - if (btimeEnd > btimeStart) + long bootTimeTicks = Interlocked.Read(ref s_bootTimeTicks); + if (bootTimeTicks == 0) + { + bootTimeTicks = Interop.Sys.GetBootTimeTicks(); + long oldValue = Interlocked.CompareExchange(ref s_bootTimeTicks, bootTimeTicks, 0); + if (oldValue != 0) // a different thread has managed to update the ticks first { - if (long.TryParse(text.AsSpan(btimeStart, btimeEnd - btimeStart), out long bootTimeSeconds)) - { - return DateTime.UnixEpoch + TimeSpan.FromSeconds(bootTimeSeconds); - } + bootTimeTicks = oldValue; // consistency } - } - - return DateTime.UtcNow; - } - } + } + return new DateTime(bootTimeTicks); + } + } /// Gets the parent process ID private int ParentProcessId => @@ -258,11 +251,8 @@ private static void SetWorkingSetLimitsCore(IntPtr? newMin, IntPtr? newMax, out /// The pid for the target process, or -1 for the current process. internal static string? GetExePath(int processId = -1) { - string exeFilePath = processId == -1 ? - Interop.procfs.SelfExeFilePath : - Interop.procfs.GetExeFilePathForProcess(processId); - - return Interop.Sys.ReadLink(exeFilePath); + return processId == -1 ? Environment.ProcessPath : + Interop.Sys.ReadLink(Interop.procfs.GetExeFilePathForProcess(processId)); } /// Gets the name that was used to start the process, or null if it could not be retrieved. diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.cs index d807debe14a8ce..e2d7771fe431e6 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.cs @@ -551,6 +551,24 @@ private Task WaitForExitAsync(CancellationToken cancellationToken = default) }, cancellationToken); } + private void ChildReaped(int exitCode, bool configureConsole) + { + lock (_gate) + { + Debug.Assert(!_exited); + + _exitCode = exitCode; + + if (_usesTerminal) + { + // Update terminal settings before calling SetExited. + Process.ConfigureTerminalForChildProcesses(-1, configureConsole); + } + + SetExited(); + } + } + private bool TryReapChild(bool configureConsole) { lock (_gate) @@ -566,16 +584,7 @@ private bool TryReapChild(bool configureConsole) if (waitResult == _processId) { - _exitCode = exitCode; - - if (_usesTerminal) - { - // Update terminal settings before calling SetExited. - Process.ConfigureTerminalForChildProcesses(-1, configureConsole); - } - - SetExited(); - + ChildReaped(exitCode, configureConsole); return true; } else if (waitResult == 0) @@ -636,7 +645,7 @@ internal static void CheckChildren(bool reapAll, bool configureConsole) } } while (pid > 0); - if (checkAll) + if (checkAll && !reapAll) { // We track things to unref so we don't invalidate our iterator by changing s_childProcessWaitStates. ProcessWaitState? firstToRemove = null; @@ -675,8 +684,20 @@ internal static void CheckChildren(bool reapAll, bool configureConsole) { do { - pid = Interop.Sys.WaitPidExitedNoHang(-1, out _); - } while (pid > 0); + int exitCode; + pid = Interop.Sys.WaitPidExitedNoHang(-1, out exitCode); + if (pid <= 0) + { + break; + } + + // Check if the process is a child that has just terminated. + if (s_childProcessWaitStates.TryGetValue(pid, out ProcessWaitState? pws)) + { + pws.ChildReaped(exitCode, configureConsole); + pws.ReleaseRef(); + } + } while (true); } } } diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs index 6fa4ee16248878..80fa5b8bb75b45 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs @@ -1151,13 +1151,13 @@ public void TestGetProcesses() // Get all the processes running on the machine, and check if the current process is one of them. var foundCurrentProcess = (from p in Process.GetProcesses() - where (p.Id == currentProcess.Id) && (p.ProcessName.Equals(currentProcess.ProcessName)) + where (p.Id == currentProcess.Id) && (p.ProcessName.Equals(currentProcess.ProcessName)) && (p.StartTime == currentProcess.StartTime) select p).Any(); Assert.True(foundCurrentProcess, "TestGetProcesses001 failed"); foundCurrentProcess = (from p in Process.GetProcesses(currentProcess.MachineName) - where (p.Id == currentProcess.Id) && (p.ProcessName.Equals(currentProcess.ProcessName)) + where (p.Id == currentProcess.Id) && (p.ProcessName.Equals(currentProcess.ProcessName)) && (p.StartTime == currentProcess.StartTime) select p).Any(); Assert.True(foundCurrentProcess, "TestGetProcesses002 failed"); diff --git a/src/libraries/System.Diagnostics.TraceSource/tests/System.Diagnostics.TraceSource.Config.Tests/TraceSourceWithConfigurationTests.cs b/src/libraries/System.Diagnostics.TraceSource/tests/System.Diagnostics.TraceSource.Config.Tests/TraceSourceWithConfigurationTests.cs index 0f0124469a9a13..797b35baf9b77d 100644 --- a/src/libraries/System.Diagnostics.TraceSource/tests/System.Diagnostics.TraceSource.Config.Tests/TraceSourceWithConfigurationTests.cs +++ b/src/libraries/System.Diagnostics.TraceSource/tests/System.Diagnostics.TraceSource.Config.Tests/TraceSourceWithConfigurationTests.cs @@ -35,6 +35,7 @@ private static void CreateAndLoadConfigFile(string filename) [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74244", TestPlatforms.tvOS)] public void RuntimeFilterChange() { CreateAndLoadConfigFile("testhost_ConfigWithRuntime.config"); @@ -95,6 +96,7 @@ public void RuntimeFilterChange() [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74244", TestPlatforms.tvOS)] public void Refresh_RemoveSwitch() { // Use a SourceSwitch that logs Error. @@ -135,6 +137,7 @@ void Log() [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74244", TestPlatforms.tvOS)] public void Refresh_ChangeSwitch() { // Use a SourceSwitch that logs Error. @@ -160,6 +163,7 @@ public void Refresh_ChangeSwitch() [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74244", TestPlatforms.tvOS)] public void Refresh_RemoveSource() { // Use a SourceSwitch that logs Error. @@ -193,6 +197,7 @@ public void Refresh_RemoveSource() [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74244", TestPlatforms.tvOS)] public void ConfigWithEvents_RuntimeListener() { CreateAndLoadConfigFile("testhost_ConfigWithRuntime.config"); @@ -259,6 +264,7 @@ private void SubscribeToSwitch_Initializing(object? sender, InitializingSwitchEv [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74244", TestPlatforms.tvOS)] public void AllTypes() { CreateAndLoadConfigFile("testhost_AllTypes.config"); @@ -303,6 +309,7 @@ public void AllTypes() [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74244", TestPlatforms.tvOS)] public void Switch_MissingValue_Throws() { Exception e = Assert.Throws(() => diff --git a/src/libraries/System.Diagnostics.Tracing/src/System.Diagnostics.Tracing.csproj b/src/libraries/System.Diagnostics.Tracing/src/System.Diagnostics.Tracing.csproj index a08b302253bf84..ccbd35a5a34fce 100644 --- a/src/libraries/System.Diagnostics.Tracing/src/System.Diagnostics.Tracing.csproj +++ b/src/libraries/System.Diagnostics.Tracing/src/System.Diagnostics.Tracing.csproj @@ -6,10 +6,10 @@ - + - \ No newline at end of file + diff --git a/src/libraries/System.Diagnostics.Tracing/tests/System.Diagnostics.Tracing.Tests.csproj b/src/libraries/System.Diagnostics.Tracing/tests/System.Diagnostics.Tracing.Tests.csproj index 75300d8460ba8d..35b1a833abb569 100644 --- a/src/libraries/System.Diagnostics.Tracing/tests/System.Diagnostics.Tracing.Tests.csproj +++ b/src/libraries/System.Diagnostics.Tracing/tests/System.Diagnostics.Tracing.Tests.csproj @@ -6,7 +6,7 @@ true - diagnostics_tracing;marshal-ilgen + diagnostics_tracing diff --git a/src/libraries/System.DirectoryServices.AccountManagement/Directory.Build.props b/src/libraries/System.DirectoryServices.AccountManagement/Directory.Build.props index 4cf151da3064fc..de81d80c303d7d 100644 --- a/src/libraries/System.DirectoryServices.AccountManagement/Directory.Build.props +++ b/src/libraries/System.DirectoryServices.AccountManagement/Directory.Build.props @@ -1,10 +1,6 @@  - - 4.0.0.0 ECMA windows diff --git a/src/libraries/System.DirectoryServices.AccountManagement/Directory.Build.targets b/src/libraries/System.DirectoryServices.AccountManagement/Directory.Build.targets new file mode 100644 index 00000000000000..e8aeeb47a8da9c --- /dev/null +++ b/src/libraries/System.DirectoryServices.AccountManagement/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + 4.0.0.0 + + diff --git a/src/libraries/System.DirectoryServices.AccountManagement/src/System.DirectoryServices.AccountManagement.csproj b/src/libraries/System.DirectoryServices.AccountManagement/src/System.DirectoryServices.AccountManagement.csproj index 8152e3c7f051ee..4c8dbdae098584 100644 --- a/src/libraries/System.DirectoryServices.AccountManagement/src/System.DirectoryServices.AccountManagement.csproj +++ b/src/libraries/System.DirectoryServices.AccountManagement/src/System.DirectoryServices.AccountManagement.csproj @@ -10,6 +10,9 @@ $(NoWarn);CA1845;CA1846;IDE0059;CA1822 annotations true + + false + 0 true true Provides uniform access and manipulation of user, computer, and group security principals across the multiple principal stores: Active Directory Domain Services (AD DS), Active Directory Lightweight Directory Services (AD LDS), and Machine SAM (MSAM). diff --git a/src/libraries/System.DirectoryServices.Protocols/Directory.Build.props b/src/libraries/System.DirectoryServices.Protocols/Directory.Build.props index 525d9a0d12ab6f..831e8089e2459c 100644 --- a/src/libraries/System.DirectoryServices.Protocols/Directory.Build.props +++ b/src/libraries/System.DirectoryServices.Protocols/Directory.Build.props @@ -1,10 +1,6 @@  - - 4.0.0.0 Microsoft true browser;android;ios;tvos diff --git a/src/libraries/System.DirectoryServices.Protocols/Directory.Build.targets b/src/libraries/System.DirectoryServices.Protocols/Directory.Build.targets new file mode 100644 index 00000000000000..69fc811d007beb --- /dev/null +++ b/src/libraries/System.DirectoryServices.Protocols/Directory.Build.targets @@ -0,0 +1,10 @@ + + + + + 4.0.0.0 + + diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj b/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj index 568625193556fd..8abef04772e175 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj +++ b/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj @@ -4,6 +4,9 @@ true true true + + false + 1 annotations true true diff --git a/src/libraries/System.DirectoryServices/Directory.Build.props b/src/libraries/System.DirectoryServices/Directory.Build.props index 0740890146a71c..7b8070be7d21e6 100644 --- a/src/libraries/System.DirectoryServices/Directory.Build.props +++ b/src/libraries/System.DirectoryServices/Directory.Build.props @@ -1,10 +1,6 @@  - - 4.0.0.0 true diff --git a/src/libraries/System.DirectoryServices/Directory.Build.targets b/src/libraries/System.DirectoryServices/Directory.Build.targets new file mode 100644 index 00000000000000..e8aeeb47a8da9c --- /dev/null +++ b/src/libraries/System.DirectoryServices/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + 4.0.0.0 + + diff --git a/src/libraries/System.DirectoryServices/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.DirectoryServices/src/ILLink/ILLink.Suppressions.xml index ca9681a8e8f63b..b27ed192851d2f 100644 --- a/src/libraries/System.DirectoryServices/src/ILLink/ILLink.Suppressions.xml +++ b/src/libraries/System.DirectoryServices/src/ILLink/ILLink.Suppressions.xml @@ -5,7 +5,7 @@ ILLink IL2050 member - M:System.DirectoryServices.Interop.UnsafeNativeMethods.ADsOpenObject(System.String,System.String,System.String,System.Int32,System.Guid@,System.Object@) + M:System.DirectoryServices.UnsafeNativeMethods.ADsOpenObject(System.String,System.String,System.String,System.Int32,System.Guid@,System.Object@) ILLink diff --git a/src/libraries/System.DirectoryServices/src/Interop/AdsOptions.cs b/src/libraries/System.DirectoryServices/src/Interop/AdsOptions.cs index fe0ad8b59edce1..d69988da76d4fa 100644 --- a/src/libraries/System.DirectoryServices/src/Interop/AdsOptions.cs +++ b/src/libraries/System.DirectoryServices/src/Interop/AdsOptions.cs @@ -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. -namespace System.DirectoryServices.Interop +namespace System.DirectoryServices { internal enum AdsOptions { diff --git a/src/libraries/System.DirectoryServices/src/Interop/AdsPropertyOperation.cs b/src/libraries/System.DirectoryServices/src/Interop/AdsPropertyOperation.cs index 4d5baf7145e79c..51e3ca30f7e91d 100644 --- a/src/libraries/System.DirectoryServices/src/Interop/AdsPropertyOperation.cs +++ b/src/libraries/System.DirectoryServices/src/Interop/AdsPropertyOperation.cs @@ -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. -namespace System.DirectoryServices.Interop +namespace System.DirectoryServices { internal enum AdsPropertyOperation { diff --git a/src/libraries/System.DirectoryServices/src/Interop/AdsSearchColumn.cs b/src/libraries/System.DirectoryServices/src/Interop/AdsSearchColumn.cs index 76dca56431ad3c..eec178f3802bbc 100644 --- a/src/libraries/System.DirectoryServices/src/Interop/AdsSearchColumn.cs +++ b/src/libraries/System.DirectoryServices/src/Interop/AdsSearchColumn.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; -namespace System.DirectoryServices.Interop +namespace System.DirectoryServices { [StructLayout(LayoutKind.Sequential)] internal unsafe struct AdsSearchColumn diff --git a/src/libraries/System.DirectoryServices/src/Interop/AdsSearchPreferenceInfo.cs b/src/libraries/System.DirectoryServices/src/Interop/AdsSearchPreferenceInfo.cs index e1b5936c71fd35..0e8c328615446f 100644 --- a/src/libraries/System.DirectoryServices/src/Interop/AdsSearchPreferenceInfo.cs +++ b/src/libraries/System.DirectoryServices/src/Interop/AdsSearchPreferenceInfo.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; -namespace System.DirectoryServices.Interop +namespace System.DirectoryServices { [StructLayout(LayoutKind.Sequential)] internal struct AdsSearchPreferenceInfo diff --git a/src/libraries/System.DirectoryServices/src/Interop/AdsSearchPreferences.cs b/src/libraries/System.DirectoryServices/src/Interop/AdsSearchPreferences.cs index ec409e3a53c309..dd4df804005e0d 100644 --- a/src/libraries/System.DirectoryServices/src/Interop/AdsSearchPreferences.cs +++ b/src/libraries/System.DirectoryServices/src/Interop/AdsSearchPreferences.cs @@ -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. -namespace System.DirectoryServices.Interop +namespace System.DirectoryServices { internal enum AdsSearchPreferences { diff --git a/src/libraries/System.DirectoryServices/src/Interop/AdsSortKey.cs b/src/libraries/System.DirectoryServices/src/Interop/AdsSortKey.cs index 1c844d7741c979..b25a14b9a29cf7 100644 --- a/src/libraries/System.DirectoryServices/src/Interop/AdsSortKey.cs +++ b/src/libraries/System.DirectoryServices/src/Interop/AdsSortKey.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; -namespace System.DirectoryServices.Interop +namespace System.DirectoryServices { [StructLayout(LayoutKind.Sequential)] internal struct AdsSortKey diff --git a/src/libraries/System.DirectoryServices/src/Interop/AdsType.cs b/src/libraries/System.DirectoryServices/src/Interop/AdsType.cs index 7320c9d512cb4b..2c1d54779ab44c 100644 --- a/src/libraries/System.DirectoryServices/src/Interop/AdsType.cs +++ b/src/libraries/System.DirectoryServices/src/Interop/AdsType.cs @@ -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. -namespace System.DirectoryServices.Interop +namespace System.DirectoryServices { internal enum AdsType { diff --git a/src/libraries/System.DirectoryServices/src/Interop/AdsValue2.cs b/src/libraries/System.DirectoryServices/src/Interop/AdsValue2.cs index 82c2413f1ec716..5aa77b9ea7c95f 100644 --- a/src/libraries/System.DirectoryServices/src/Interop/AdsValue2.cs +++ b/src/libraries/System.DirectoryServices/src/Interop/AdsValue2.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; -namespace System.DirectoryServices.Interop +namespace System.DirectoryServices { [StructLayout(LayoutKind.Sequential)] internal struct Ads_Pointer diff --git a/src/libraries/System.DirectoryServices/src/Interop/AdsValueHelper2.cs b/src/libraries/System.DirectoryServices/src/Interop/AdsValueHelper2.cs index 10b208f0784ccf..3a19d778c24644 100644 --- a/src/libraries/System.DirectoryServices/src/Interop/AdsValueHelper2.cs +++ b/src/libraries/System.DirectoryServices/src/Interop/AdsValueHelper2.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; using System.Globalization; -namespace System.DirectoryServices.Interop +namespace System.DirectoryServices { [StructLayout(LayoutKind.Sequential)] internal struct SystemTime diff --git a/src/libraries/System.DirectoryServices/src/Interop/NativeMethods.cs b/src/libraries/System.DirectoryServices/src/Interop/NativeMethods.cs index 7ce5748edbc6c7..ca45d792515d66 100644 --- a/src/libraries/System.DirectoryServices/src/Interop/NativeMethods.cs +++ b/src/libraries/System.DirectoryServices/src/Interop/NativeMethods.cs @@ -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. -namespace System.DirectoryServices.Interop +namespace System.DirectoryServices { internal static class NativeMethods { diff --git a/src/libraries/System.DirectoryServices/src/Interop/SafeNativeMethods.cs b/src/libraries/System.DirectoryServices/src/Interop/SafeNativeMethods.cs index db401906f69eea..9ae1fdfd26811c 100644 --- a/src/libraries/System.DirectoryServices/src/Interop/SafeNativeMethods.cs +++ b/src/libraries/System.DirectoryServices/src/Interop/SafeNativeMethods.cs @@ -5,7 +5,7 @@ using System.Security; using System.Runtime.InteropServices; -namespace System.DirectoryServices.Interop +namespace System.DirectoryServices { internal static partial class SafeNativeMethods { diff --git a/src/libraries/System.DirectoryServices/src/Interop/UnsafeNativeMethods.cs b/src/libraries/System.DirectoryServices/src/Interop/UnsafeNativeMethods.cs index 30912155a31fc5..b6f6e72aad9bd2 100644 --- a/src/libraries/System.DirectoryServices/src/Interop/UnsafeNativeMethods.cs +++ b/src/libraries/System.DirectoryServices/src/Interop/UnsafeNativeMethods.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; using System.Security; -namespace System.DirectoryServices.Interop +namespace System.DirectoryServices { [StructLayout(LayoutKind.Explicit)] diff --git a/src/libraries/System.DirectoryServices/src/System.DirectoryServices.csproj b/src/libraries/System.DirectoryServices/src/System.DirectoryServices.csproj index 83a118e940de06..e9f9e651841545 100644 --- a/src/libraries/System.DirectoryServices/src/System.DirectoryServices.csproj +++ b/src/libraries/System.DirectoryServices/src/System.DirectoryServices.csproj @@ -8,6 +8,9 @@ CA1846: Prefer 'AsSpan' over 'Substring' when span-based overloads are available --> $(NoWarn);CA1845;CA1846;IDE0059;CA1822 true + + false + 1 true true Provides easy access to Active Directory Domain Services. diff --git a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/ActiveDirectory/DomainController.cs b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/ActiveDirectory/DomainController.cs index 98eb7d72044970..0a855c618d928c 100644 --- a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/ActiveDirectory/DomainController.cs +++ b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/ActiveDirectory/DomainController.cs @@ -410,7 +410,7 @@ public void SeizeRoleOwnership(ActiveDirectoryRole role) // Increment the RIDAvailablePool by 30k. if (role == ActiveDirectoryRole.RidRole) { - System.DirectoryServices.Interop.UnsafeNativeMethods.IADsLargeInteger ridPool = (System.DirectoryServices.Interop.UnsafeNativeMethods.IADsLargeInteger)roleObjectEntry.Properties[PropertyManager.RIDAvailablePool].Value!; + System.DirectoryServices.UnsafeNativeMethods.IADsLargeInteger ridPool = (System.DirectoryServices.UnsafeNativeMethods.IADsLargeInteger)roleObjectEntry.Properties[PropertyManager.RIDAvailablePool].Value!; // check the overflow of the low part if (ridPool.LowPart + UpdateRidPoolSeizureValue < ridPool.LowPart) diff --git a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/AuthenticationTypes.cs b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/AuthenticationTypes.cs index 0a7ece6d9ddb17..f44e180c4c384f 100644 --- a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/AuthenticationTypes.cs +++ b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/AuthenticationTypes.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.DirectoryServices.Interop; - namespace System.DirectoryServices { /// diff --git a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryEntries.cs b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryEntries.cs index 71d53ef6901198..cb2082a0f1ffbd 100644 --- a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryEntries.cs +++ b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryEntries.cs @@ -3,7 +3,6 @@ using System.Runtime.InteropServices; using System.Collections; -using System.DirectoryServices.Interop; namespace System.DirectoryServices { diff --git a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryEntry.cs b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryEntry.cs index 429c4a4f779e80..c4d046cf006568 100644 --- a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryEntry.cs +++ b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryEntry.cs @@ -3,7 +3,6 @@ using System.Runtime.InteropServices; using System.Diagnostics; -using System.DirectoryServices.Interop; using System.ComponentModel; using System.Threading; using System.Reflection; diff --git a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryEntryConfiguration.cs b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryEntryConfiguration.cs index df98c453502818..1ff7fcb1e3d2a4 100644 --- a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryEntryConfiguration.cs +++ b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryEntryConfiguration.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.DirectoryServices.Interop; using System.ComponentModel; namespace System.DirectoryServices diff --git a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectorySearcher.cs b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectorySearcher.cs index 846fffa58e4f2b..37f2018d70bc67 100644 --- a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectorySearcher.cs +++ b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectorySearcher.cs @@ -4,7 +4,6 @@ using System.Runtime.InteropServices; using System.Collections; using System.Collections.Specialized; -using System.DirectoryServices.Interop; using System.ComponentModel; using INTPTR_INTPTRCAST = System.IntPtr; @@ -608,6 +607,8 @@ public DirectoryVirtualListView? VirtualListView private SearchResultCollection FindAll(bool findMoreThanOne) { + searchResult = null; + DirectoryEntry clonedRoot = SearchRoot!.CloneBrowsable(); UnsafeNativeMethods.IAds adsObject = clonedRoot.AdsObject; @@ -651,7 +652,9 @@ private SearchResultCollection FindAll(bool findMoreThanOne) properties = Array.Empty(); } - return new SearchResultCollection(clonedRoot, resultsHandle, properties, this); + SearchResultCollection result = new SearchResultCollection(clonedRoot, resultsHandle, properties, this); + searchResult = result; + return result; } private unsafe void SetSearchPreferences(UnsafeNativeMethods.IDirectorySearch adsSearch, bool findMoreThanOne) diff --git a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryServicesCOMException.cs b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryServicesCOMException.cs index ac1ee821b1c46d..6ff6d88c562415 100644 --- a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryServicesCOMException.cs +++ b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/DirectoryServicesCOMException.cs @@ -4,7 +4,6 @@ using System.ComponentModel; using System.Runtime.InteropServices; using System.Runtime.Serialization; -using System.DirectoryServices.Interop; namespace System.DirectoryServices { diff --git a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/PropertyCollection.cs b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/PropertyCollection.cs index 51878e2cc52d1e..38d202f7053640 100644 --- a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/PropertyCollection.cs +++ b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/PropertyCollection.cs @@ -3,7 +3,6 @@ using System.Runtime.InteropServices; using System.Collections; -using System.DirectoryServices.Interop; using System.Globalization; namespace System.DirectoryServices diff --git a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/PropertyValueCollection.cs b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/PropertyValueCollection.cs index 4ae15eb0b7a9d5..a006b65e7e4bbf 100644 --- a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/PropertyValueCollection.cs +++ b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/PropertyValueCollection.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; -using System.DirectoryServices.Interop; namespace System.DirectoryServices { diff --git a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/SchemaNameCollection.cs b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/SchemaNameCollection.cs index 853c659ecfd88d..7acb9bf62334e7 100644 --- a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/SchemaNameCollection.cs +++ b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/SchemaNameCollection.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; -using System.DirectoryServices.Interop; namespace System.DirectoryServices { diff --git a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/SearchResultCollection.cs b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/SearchResultCollection.cs index 4711b60e97b54c..dac7ae1f9f7542 100644 --- a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/SearchResultCollection.cs +++ b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/SearchResultCollection.cs @@ -4,7 +4,6 @@ using System.Net; using System.Runtime.InteropServices; using System.Collections; -using System.DirectoryServices.Interop; using System.Text; using INTPTR_INTPTRCAST = System.IntPtr; diff --git a/src/libraries/System.Drawing.Common/src/Interop/Windows/Interop.Gdi32.cs b/src/libraries/System.Drawing.Common/src/Interop/Windows/Interop.Gdi32.cs index 8b754d321fa268..192cc9b591fac1 100644 --- a/src/libraries/System.Drawing.Common/src/Interop/Windows/Interop.Gdi32.cs +++ b/src/libraries/System.Drawing.Common/src/Interop/Windows/Interop.Gdi32.cs @@ -52,7 +52,7 @@ internal static partial int StartDoc( #if NET7_0_OR_GREATER [MarshalUsing(typeof(HandleRefMarshaller))] #endif - HandleRef hDC, DOCINFO lpDocInfo); + HandleRef hDC, in DOCINFO lpDocInfo); [LibraryImport(Libraries.Gdi32, SetLastError = true)] internal static partial int StartPage( @@ -179,7 +179,7 @@ internal unsafe struct BITMAPINFO_FLAT [NativeMarshalling(typeof(Marshaller))] #endif [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - internal sealed class DOCINFO + internal struct DOCINFO { internal int cbSize = 20; internal string? lpszDocName; @@ -187,6 +187,8 @@ internal sealed class DOCINFO internal string? lpszDatatype; internal int fwType; + public DOCINFO() { } + #if NET7_0_OR_GREATER [CustomMarshaller(typeof(DOCINFO), MarshalMode.ManagedToUnmanagedIn, typeof(Marshaller))] public static class Marshaller diff --git a/src/libraries/System.Drawing.Common/src/MatchingRefApiCompatBaseline.txt b/src/libraries/System.Drawing.Common/src/MatchingRefApiCompatBaseline.txt new file mode 100644 index 00000000000000..5d3bc4b8745f80 --- /dev/null +++ b/src/libraries/System.Drawing.Common/src/MatchingRefApiCompatBaseline.txt @@ -0,0 +1,10 @@ +# the following suppression are for back-compatibility with legacy Xamarin which had these types in System.Drawing.Common.dll + +TypesMustExist : Type 'System.Drawing.Color' does not exist in the reference but it does exist in the implementation. +TypesMustExist : Type 'System.Drawing.KnownColor' does not exist in the reference but it does exist in the implementation. +TypesMustExist : Type 'System.Drawing.Point' does not exist in the reference but it does exist in the implementation. +TypesMustExist : Type 'System.Drawing.PointF' does not exist in the reference but it does exist in the implementation. +TypesMustExist : Type 'System.Drawing.Rectangle' does not exist in the reference but it does exist in the implementation. +TypesMustExist : Type 'System.Drawing.RectangleF' does not exist in the reference but it does exist in the implementation. +TypesMustExist : Type 'System.Drawing.Size' does not exist in the reference but it does exist in the implementation. +TypesMustExist : Type 'System.Drawing.SizeF' does not exist in the reference but it does exist in the implementation. diff --git a/src/libraries/System.Drawing.Common/src/System.Drawing.Common.Forwards.cs b/src/libraries/System.Drawing.Common/src/System.Drawing.Common.Forwards.cs new file mode 100644 index 00000000000000..82ba631d1ce220 --- /dev/null +++ b/src/libraries/System.Drawing.Common/src/System.Drawing.Common.Forwards.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// This is required for back-compatibility with legacy Xamarin which had these types in System.Drawing.Common.dll +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Drawing.Color))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Drawing.ColorTranslator))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Drawing.KnownColor))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Drawing.Point))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Drawing.PointF))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Drawing.Rectangle))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Drawing.RectangleF))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Drawing.Size))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Drawing.SizeF))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Drawing.SystemColors))] diff --git a/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj b/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj index df9c7ef864aede..26c3e7488e4735 100644 --- a/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj +++ b/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj @@ -6,6 +6,9 @@ CS0618 true true + + false + 0 true Provides access to GDI+ graphics functionality. @@ -30,6 +33,7 @@ Since .NET 7, non-Windows platforms are not supported, even with the runtime con + diff --git a/src/libraries/System.Formats.Tar/src/Resources/Strings.resx b/src/libraries/System.Formats.Tar/src/Resources/Strings.resx index 79e3188410c3c9..811972432933ef 100644 --- a/src/libraries/System.Formats.Tar/src/Resources/Strings.resx +++ b/src/libraries/System.Formats.Tar/src/Resources/Strings.resx @@ -1,17 +1,17 @@  - @@ -205,6 +205,9 @@ The entry is a symbolic link or a hard link but the LinkName field is null or empty. + Entry type '{0}' not supported. + + Entry type '{0}' not supported in format '{1}'. @@ -235,7 +238,7 @@ The size field is negative in a tar entry. - The value of the size field for the current entry of type '{0}' is beyond the expected length. + The value of the size field for the current entry of type '{0}' is greater than the expected length. Cannot create the symbolic link '{0}' because the specified target '{1}' does not exist. @@ -255,4 +258,13 @@ An attempt was made to move the position before the beginning of the stream. - + + Unable to parse number. + + + The field '{0}' exceeds the maximum allowed length for this format. + + + The value of the size field for the current entry of format '{0}' is greater than the format allows. + + \ No newline at end of file diff --git a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj index c0487caf28049e..9c219ec9ef564c 100644 --- a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj +++ b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj @@ -33,7 +33,9 @@ + + @@ -47,7 +49,6 @@ - @@ -65,6 +66,7 @@ + diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/GnuTarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/GnuTarEntry.cs index eda97e2b8a26f0..11e3b77c5f8f2d 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/GnuTarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/GnuTarEntry.cs @@ -20,14 +20,16 @@ internal GnuTarEntry(TarHeader header, TarReader readerOfOrigin) /// /// The type of the entry. /// A string with the path and file name of this entry. - /// is null or empty. - /// The entry type is not supported for creating an entry. /// When creating an instance using the constructor, only the following entry types are supported: /// /// In all platforms: , , , . /// In Unix platforms only: , and . /// /// + /// is . + /// is empty. + /// -or- + /// is not supported in the specified format. public GnuTarEntry(TarEntryType entryType, string entryName) : base(entryType, entryName, TarEntryFormat.Gnu, isGea: false) { @@ -38,6 +40,9 @@ public GnuTarEntry(TarEntryType entryType, string entryName) /// /// Initializes a new instance by converting the specified entry into the GNU format. /// + /// is a and cannot be converted. + /// -or- + /// The entry type of is not supported for conversion to the GNU format. public GnuTarEntry(TarEntry other) : base(other, TarEntryFormat.Gnu) { diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxTarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxTarEntry.cs index db317f7a884112..555e4feaa27f73 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxTarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxTarEntry.cs @@ -25,8 +25,6 @@ internal PaxTarEntry(TarHeader header, TarReader readerOfOrigin) /// /// The type of the entry. /// A string with the path and file name of this entry. - /// is null or empty. - /// The entry type is not supported for creating an entry. /// When creating an instance using the constructor, only the following entry types are supported: /// /// In all platforms: , , , . @@ -47,6 +45,10 @@ internal PaxTarEntry(TarHeader header, TarReader readerOfOrigin) /// File length, under the name size, as an , if the string representation of the number is larger than 12 bytes. /// /// + /// is . + /// is empty. + /// -or- + /// is not supported in the specified format. public PaxTarEntry(TarEntryType entryType, string entryName) : base(entryType, entryName, TarEntryFormat.Pax, isGea: false) { @@ -62,9 +64,6 @@ public PaxTarEntry(TarEntryType entryType, string entryName) /// The type of the entry. /// A string with the path and file name of this entry. /// An enumeration of string key-value pairs that represents the metadata to include in the Extended Attributes entry that precedes the current entry. - /// is . - /// is null or empty. - /// The entry type is not supported for creating an entry. /// When creating an instance using the constructor, only the following entry types are supported: /// /// In all platforms: , , , . @@ -85,6 +84,10 @@ public PaxTarEntry(TarEntryType entryType, string entryName) /// File length, under the name size, as an , if the string representation of the number is larger than 12 bytes. /// /// + /// or is . + /// is empty. + /// -or- + /// is not supported in the specified format. public PaxTarEntry(TarEntryType entryType, string entryName, IEnumerable> extendedAttributes) : base(entryType, entryName, TarEntryFormat.Pax, isGea: false) { @@ -100,6 +103,9 @@ public PaxTarEntry(TarEntryType entryType, string entryName, IEnumerable /// Initializes a new instance by converting the specified entry into the PAX format. /// + /// is a and cannot be converted. + /// -or- + /// The entry type of is not supported for conversion to the PAX format. public PaxTarEntry(TarEntry other) : base(other, TarEntryFormat.Pax) { diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SeekableSubReadStream.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SeekableSubReadStream.cs index e8fe9b7e01a16f..b35c958b4d53f0 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SeekableSubReadStream.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SeekableSubReadStream.cs @@ -18,7 +18,7 @@ public SeekableSubReadStream(Stream superStream, long startPosition, long maxLen { if (!superStream.CanSeek) { - throw new InvalidOperationException(SR.IO_NotSupported_UnseekableStream); + throw new ArgumentException(SR.IO_NotSupported_UnseekableStream, nameof(superStream)); } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SubReadStream.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SubReadStream.cs index c6b9b6afc8d6db..998e53ea6fc99f 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SubReadStream.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SubReadStream.cs @@ -25,7 +25,7 @@ public SubReadStream(Stream superStream, long startPosition, long maxLength) { if (!superStream.CanRead) { - throw new InvalidOperationException(SR.IO_NotSupported_UnreadableStream); + throw new ArgumentException(SR.IO_NotSupported_UnreadableStream, nameof(superStream)); } _startInSuperStream = startPosition; _positionInSuperStream = startPosition; @@ -188,10 +188,7 @@ public override Task FlushAsync(CancellationToken cancellationToken) => // the substream is just 'a chunk' of the super-stream protected override void Dispose(bool disposing) { - if (disposing && !_isDisposed) - { - _isDisposed = true; - } + _isDisposed = true; base.Dispose(disposing); } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs index 00c790762e4b65..36cbdab8a7cc78 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs @@ -51,12 +51,12 @@ internal TarEntry(TarEntry other, TarEntryFormat format) { if (other is PaxGlobalExtendedAttributesTarEntry) { - throw new InvalidOperationException(SR.TarCannotConvertPaxGlobalExtendedAttributesEntry); + throw new ArgumentException(SR.TarCannotConvertPaxGlobalExtendedAttributesEntry, nameof(other)); } TarEntryType compatibleEntryType = TarHelpers.GetCorrectTypeFlagForFormat(format, other.EntryType); - TarHelpers.ThrowIfEntryTypeNotSupported(compatibleEntryType, format); + TarHelpers.ThrowIfEntryTypeNotSupported(compatibleEntryType, format, nameof(other)); _readerOfOrigin = other._readerOfOrigin; @@ -92,6 +92,7 @@ public int Gid /// A timestamps that represents the last time the contents of the file represented by this entry were modified. /// /// In Unix platforms, this timestamp is commonly known as mtime. + /// The specified value is larger than . public DateTimeOffset ModificationTime { get => _header._mTime; @@ -114,7 +115,9 @@ public DateTimeOffset ModificationTime /// /// When the indicates a or a , this property returns the link target path of such link. /// - /// Cannot set the link name if the entry type is not or . + /// The entry type is not or . + /// The specified value is . + /// The specified value is empty. public string LinkName { get => _header._linkName ?? string.Empty; @@ -124,6 +127,7 @@ public string LinkName { throw new InvalidOperationException(SR.TarEntryHardLinkOrSymLinkExpected); } + ArgumentException.ThrowIfNullOrEmpty(value); _header._linkName = value; } } @@ -134,10 +138,12 @@ public string LinkName /// The value in this field has no effect on Windows platforms. public UnixFileMode Mode { - get => (UnixFileMode)_header._mode; + // Some paths do not use the setter, and we want to return valid UnixFileMode. + // This mask only keeps the least significant 12 bits. + get => (UnixFileMode)(_header._mode & (int)TarHelpers.ValidUnixFileModes); set { - if ((int)value is < 0 or > 4095) // 4095 in decimal is 7777 in octal + if ((value & ~TarHelpers.ValidUnixFileModes) != 0) // throw on invalid UnixFileModes { throw new ArgumentOutOfRangeException(nameof(value)); } @@ -177,7 +183,8 @@ public int Uid /// Elevation is required to extract a or to disk. /// Symbolic links can be recreated using , or . /// Hard links can only be extracted when using or . - /// is or empty. + /// is . + /// is empty. /// The parent directory of does not exist. /// -or- /// is and a file already exists in . @@ -206,7 +213,8 @@ public void ExtractToFile(string destinationFileName, bool overwrite) /// A task that represents the asynchronous extraction operation. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. - /// is or empty. + /// is . + /// is empty. /// The parent directory of does not exist. /// -or- /// is and a file already exists in . @@ -237,9 +245,8 @@ public Task ExtractToFileAsync(string destinationFileName, bool overwrite, Cance /// Sets a new stream that represents the data section, if it makes sense for the to contain data; if a stream already existed, the old stream gets disposed before substituting it with the new stream. Setting a stream is allowed. /// If you write data to this data stream, make sure to rewind it to the desired start position before writing this entry into an archive using . /// Setting a data section is not supported because the is not (or for an archive of format). - /// Cannot set an unreadable stream. - /// -or- - /// An I/O problem occurred. + /// Cannot set an unreadable stream. + /// An I/O problem occurred. public Stream? DataStream { get => _header._dataStream; @@ -252,7 +259,7 @@ public Stream? DataStream if (value != null && !value.CanRead) { - throw new IOException(SR.IO_NotSupported_UnreadableStream); + throw new ArgumentException(SR.IO_NotSupported_UnreadableStream, nameof(value)); } if (_readerOfOrigin != null) @@ -287,12 +294,12 @@ internal void ExtractRelativeToDirectory(string destinationDirectoryPath, bool o if (EntryType == TarEntryType.Directory) { - TarHelpers.CreateDirectory(fileDestinationPath, Mode, overwrite, pendingModes); + TarHelpers.CreateDirectory(fileDestinationPath, Mode, pendingModes); } else { // If it is a file, create containing directory. - TarHelpers.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!, mode: null, overwrite, pendingModes); + TarHelpers.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!, mode: null, pendingModes); ExtractToFileInternal(fileDestinationPath, linkTargetPath, overwrite); } } @@ -309,13 +316,13 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b if (EntryType == TarEntryType.Directory) { - TarHelpers.CreateDirectory(fileDestinationPath, Mode, overwrite, pendingModes); + TarHelpers.CreateDirectory(fileDestinationPath, Mode, pendingModes); return Task.CompletedTask; } else { // If it is a file, create containing directory. - TarHelpers.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!, mode: null, overwrite, pendingModes); + TarHelpers.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!, mode: null, pendingModes); return ExtractToFileInternalAsync(fileDestinationPath, linkTargetPath, overwrite, cancellationToken); } } @@ -339,14 +346,20 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b { if (string.IsNullOrEmpty(LinkName)) { - throw new FormatException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty); + throw new InvalidDataException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty); } - linkTargetPath = GetSanitizedFullPath(destinationDirectoryPath, LinkName); + linkTargetPath = GetSanitizedFullPath(destinationDirectoryPath, + Path.IsPathFullyQualified(LinkName) ? LinkName : Path.Join(Path.GetDirectoryName(fileDestinationPath), LinkName)); + if (linkTargetPath == null) { throw new IOException(string.Format(SR.TarExtractingResultsLinkOutside, LinkName, destinationDirectoryPath)); } + + // after TarExtractingResultsLinkOutside validation, preserve the original + // symlink target path (to match behavior of other utilities). + linkTargetPath = LinkName; } return (fileDestinationPath, linkTargetPath); @@ -355,9 +368,17 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b // If the path can be extracted in the specified destination directory, returns the full path with sanitized file name. Otherwise, returns null. private static string? GetSanitizedFullPath(string destinationDirectoryFullPath, string path) { + destinationDirectoryFullPath = PathInternal.EnsureTrailingSeparator(destinationDirectoryFullPath); + string fullyQualifiedPath = Path.IsPathFullyQualified(path) ? path : Path.Combine(destinationDirectoryFullPath, path); string normalizedPath = Path.GetFullPath(fullyQualifiedPath); // Removes relative segments - string sanitizedPath = Path.Join(Path.GetDirectoryName(normalizedPath), ArchivingUtils.SanitizeEntryFilePath(Path.GetFileName(normalizedPath))); + string? fileName = Path.GetFileName(normalizedPath); + if (string.IsNullOrEmpty(fileName)) // It's a directory + { + fileName = PathInternal.DirectorySeparatorCharAsString; + } + + string sanitizedPath = Path.Join(Path.GetDirectoryName(normalizedPath), ArchivingUtils.SanitizeEntryFilePath(fileName)); return sanitizedPath.StartsWith(destinationDirectoryFullPath, PathInternal.StringComparison) ? sanitizedPath : null; } @@ -465,7 +486,7 @@ private void VerifyPathsForEntryType(string filePath, string? linkTargetPath, bo // If the destination contains a directory segment, need to check that it exists if (!string.IsNullOrEmpty(directoryPath) && !Path.Exists(directoryPath)) { - throw new IOException(string.Format(SR.IO_PathNotFound_NoPathName, filePath)); + throw new IOException(string.Format(SR.IO_PathNotFound_Path, filePath)); } if (!Path.Exists(filePath)) @@ -511,7 +532,7 @@ private void VerifyPathsForEntryType(string filePath, string? linkTargetPath, bo } else { - throw new FormatException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty); + throw new InvalidDataException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty); } } } @@ -529,7 +550,7 @@ private void ExtractAsRegularFile(string destinationFileName) DataStream?.CopyTo(fs); } - ArchivingUtils.AttemptSetLastWriteTime(destinationFileName, ModificationTime); + AttemptSetLastWriteTime(destinationFileName, ModificationTime); } // Asynchronously extracts the current entry as a regular file into the specified destination. @@ -551,7 +572,19 @@ private async Task ExtractAsRegularFileAsync(string destinationFileName, Cancell } } - ArchivingUtils.AttemptSetLastWriteTime(destinationFileName, ModificationTime); + AttemptSetLastWriteTime(destinationFileName, ModificationTime); + } + + private static void AttemptSetLastWriteTime(string destinationFileName, DateTimeOffset lastWriteTime) + { + try + { + File.SetLastWriteTime(destinationFileName, lastWriteTime.LocalDateTime); // SetLastWriteTime expects local time + } + catch + { + // Some OSes like Android might not support setting the last write time, the extraction should not fail because of that + } } private FileStreamOptions CreateFileStreamOptions(bool isAsync) diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs index a77cb2c36a65ea..2ed7865a2e13e8 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.Enumeration; using System.Threading; using System.Threading.Tasks; @@ -15,17 +16,18 @@ namespace System.Formats.Tar /// public static class TarFile { - // Windows' MaxPath (260) is used as an arbitrary default capacity, as it is likely - // to be greater than the length of typical entry names from the file system, even - // on non-Windows platforms. The capacity will be increased, if needed. - private const int DefaultCapacity = 260; - /// /// Creates a tar stream that contains all the filesystem entries from the specified directory. /// /// The path of the directory to archive. /// The destination stream the archive. /// to include the base directory name as the first segment in all the names of the archive entries. to exclude the base directory name from the archive entry names. + /// or is . + /// is empty. + /// -or- + /// does not support writing. + /// The directory path was not found. + /// An I/O exception occurred. public static void CreateFromDirectory(string sourceDirectoryName, Stream destination, bool includeBaseDirectory) { ArgumentException.ThrowIfNullOrEmpty(sourceDirectoryName); @@ -33,7 +35,7 @@ public static void CreateFromDirectory(string sourceDirectoryName, Stream destin if (!destination.CanWrite) { - throw new IOException(SR.IO_NotSupported_UnwritableStream); + throw new ArgumentException(SR.IO_NotSupported_UnwritableStream, nameof(destination)); } if (!Directory.Exists(sourceDirectoryName)) @@ -55,6 +57,12 @@ public static void CreateFromDirectory(string sourceDirectoryName, Stream destin /// to include the base directory name as the first path segment in all the names of the archive entries. to exclude the base directory name from the entry name paths. /// The token to monitor for cancellation requests. The default value is . /// A task that represents the asynchronous creation operation. + /// or is . + /// is empty. + /// -or- + /// does not support writing. + /// The directory path was not found. + /// An I/O exception occurred. public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream destination, bool includeBaseDirectory, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) @@ -66,7 +74,7 @@ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream d if (!destination.CanWrite) { - return Task.FromException(new IOException(SR.IO_NotSupported_UnwritableStream)); + return Task.FromException(new ArgumentException(SR.IO_NotSupported_UnwritableStream, nameof(destination))); } if (!Directory.Exists(sourceDirectoryName)) @@ -86,6 +94,10 @@ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream d /// The path of the directory to archive. /// The path of the destination archive file. /// to include the base directory name as the first path segment in all the names of the archive entries. to exclude the base directory name from the entry name paths. + /// or is . + /// or is empty. + /// The directory path was not found. + /// An I/O exception occurred. public static void CreateFromDirectory(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory) { ArgumentException.ThrowIfNullOrEmpty(sourceDirectoryName); @@ -114,6 +126,10 @@ public static void CreateFromDirectory(string sourceDirectoryName, string destin /// to include the base directory name as the first path segment in all the names of the archive entries. to exclude the base directory name from the entry name paths. /// The token to monitor for cancellation requests. The default value is . /// A task that represents the asynchronous creation operation. + /// or is . + /// or is empty. + /// The directory path was not found. + /// An I/O exception occurred. public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) @@ -143,8 +159,15 @@ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string d /// to overwrite files and directories in ; to avoid overwriting, and throw if any files or directories are found with existing names. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. + /// or is . + /// The directory path was not found. /// Operation not permitted due to insufficient permissions. - /// Extracting tar entry would have resulted in a file outside the specified destination directory. + /// Extracting tar entry would have resulted in a file outside the specified destination directory. + /// -or- + /// is empty. + /// -or- + /// does not support reading. + /// An I/O exception occurred. public static void ExtractToDirectory(Stream source, string destinationDirectoryName, bool overwriteFiles) { ArgumentNullException.ThrowIfNull(source); @@ -152,7 +175,7 @@ public static void ExtractToDirectory(Stream source, string destinationDirectory if (!source.CanRead) { - throw new IOException(SR.IO_NotSupported_UnreadableStream); + throw new ArgumentException(SR.IO_NotSupported_UnreadableStream, nameof(source)); } if (!Directory.Exists(destinationDirectoryName)) @@ -176,7 +199,15 @@ public static void ExtractToDirectory(Stream source, string destinationDirectory /// A task that represents the asynchronous extraction operation. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. + /// or is . + /// The directory path was not found. /// Operation not permitted due to insufficient permissions. + /// Extracting tar entry would have resulted in a file outside the specified destination directory. + /// -or- + /// is empty. + /// -or- + /// does not support reading. + /// An I/O exception occurred. public static Task ExtractToDirectoryAsync(Stream source, string destinationDirectoryName, bool overwriteFiles, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) @@ -188,7 +219,7 @@ public static Task ExtractToDirectoryAsync(Stream source, string destinationDire if (!source.CanRead) { - return Task.FromException(new IOException(SR.IO_NotSupported_UnreadableStream)); + return Task.FromException(new ArgumentException(SR.IO_NotSupported_UnreadableStream, nameof(source))); } if (!Directory.Exists(destinationDirectoryName)) @@ -210,7 +241,14 @@ public static Task ExtractToDirectoryAsync(Stream source, string destinationDire /// to overwrite files and directories in ; to avoid overwriting, and throw if any files or directories are found with existing names. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. + /// or is . + /// The directory path was not found. + /// The file path was not found. /// Operation not permitted due to insufficient permissions. + /// Extracting tar entry would have resulted in a file outside the specified destination directory. + /// -or- + /// or is empty. + /// An I/O exception occurred. public static void ExtractToDirectory(string sourceFileName, string destinationDirectoryName, bool overwriteFiles) { ArgumentException.ThrowIfNullOrEmpty(sourceFileName); @@ -222,7 +260,7 @@ public static void ExtractToDirectory(string sourceFileName, string destinationD if (!File.Exists(sourceFileName)) { - throw new FileNotFoundException(string.Format(SR.IO_FileNotFound, sourceFileName)); + throw new FileNotFoundException(string.Format(SR.IO_FileNotFound_FileName, sourceFileName)); } if (!Directory.Exists(destinationDirectoryName)) @@ -245,7 +283,14 @@ public static void ExtractToDirectory(string sourceFileName, string destinationD /// A task that represents the asynchronous extraction operation. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. + /// or is . + /// The directory path was not found. + /// The file path was not found. /// Operation not permitted due to insufficient permissions. + /// Extracting tar entry would have resulted in a file outside the specified destination directory. + /// -or- + /// or is empty. + /// An I/O exception occurred. public static Task ExtractToDirectoryAsync(string sourceFileName, string destinationDirectoryName, bool overwriteFiles, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) @@ -261,7 +306,7 @@ public static Task ExtractToDirectoryAsync(string sourceFileName, string destina if (!File.Exists(sourceFileName)) { - return Task.FromException(new FileNotFoundException(string.Format(SR.IO_FileNotFound, sourceFileName))); + return Task.FromException(new FileNotFoundException(string.Format(SR.IO_FileNotFound_FileName, sourceFileName))); } if (!Directory.Exists(destinationDirectoryName)) @@ -283,23 +328,22 @@ private static void CreateFromDirectoryInternal(string sourceDirectoryName, Stre DirectoryInfo di = new(sourceDirectoryName); string basePath = GetBasePathForCreateFromDirectory(di, includeBaseDirectory); - char[] entryNameBuffer = ArrayPool.Shared.Rent(DefaultCapacity); - - try + bool skipBaseDirRecursion = false; + if (includeBaseDirectory) { - if (includeBaseDirectory) - { - writer.WriteEntry(di.FullName, GetEntryNameForBaseDirectory(di.Name, ref entryNameBuffer)); - } + writer.WriteEntry(di.FullName, GetEntryNameForBaseDirectory(di.Name)); + skipBaseDirRecursion = (di.Attributes & FileAttributes.ReparsePoint) != 0; + } - foreach (FileSystemInfo file in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) - { - writer.WriteEntry(file.FullName, GetEntryNameForFileSystemInfo(file, basePath.Length, ref entryNameBuffer)); - } + if (skipBaseDirRecursion) + { + // The base directory is a symlink, do not recurse into it + return; } - finally + + foreach (FileSystemInfo file in GetFileSystemEnumerationForCreation(sourceDirectoryName)) { - ArrayPool.Shared.Return(entryNameBuffer); + writer.WriteEntry(file.FullName, GetEntryNameForFileSystemInfo(file, basePath.Length)); } } } @@ -339,44 +383,58 @@ private static async Task CreateFromDirectoryInternalAsync(string sourceDirector DirectoryInfo di = new(sourceDirectoryName); string basePath = GetBasePathForCreateFromDirectory(di, includeBaseDirectory); - char[] entryNameBuffer = ArrayPool.Shared.Rent(DefaultCapacity); - - try + bool skipBaseDirRecursion = false; + if (includeBaseDirectory) { - if (includeBaseDirectory) - { - await writer.WriteEntryAsync(di.FullName, GetEntryNameForBaseDirectory(di.Name, ref entryNameBuffer), cancellationToken).ConfigureAwait(false); - } + await writer.WriteEntryAsync(di.FullName, GetEntryNameForBaseDirectory(di.Name), cancellationToken).ConfigureAwait(false); + skipBaseDirRecursion = (di.Attributes & FileAttributes.ReparsePoint) != 0; + } - foreach (FileSystemInfo file in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) - { - await writer.WriteEntryAsync(file.FullName, GetEntryNameForFileSystemInfo(file, basePath.Length, ref entryNameBuffer), cancellationToken).ConfigureAwait(false); - } + if (skipBaseDirRecursion) + { + // The base directory is a symlink, do not recurse into it + return; } - finally + + foreach (FileSystemInfo file in GetFileSystemEnumerationForCreation(sourceDirectoryName)) { - ArrayPool.Shared.Return(entryNameBuffer); + await writer.WriteEntryAsync(file.FullName, GetEntryNameForFileSystemInfo(file, basePath.Length), cancellationToken).ConfigureAwait(false); } } } + // Generates a recursive enumeration of the filesystem entries inside the specified source directory, while + // making sure that directory symlinks do not get recursed. + private static IEnumerable GetFileSystemEnumerationForCreation(string sourceDirectoryName) + { + return new FileSystemEnumerable( + directory: sourceDirectoryName, + transform: (ref FileSystemEntry entry) => entry.ToFileSystemInfo(), + options: new EnumerationOptions() + { + RecurseSubdirectories = true + }) + { + ShouldRecursePredicate = IsNotADirectorySymlink + }; + + static bool IsNotADirectorySymlink(ref FileSystemEntry entry) => entry.IsDirectory && (entry.Attributes & FileAttributes.ReparsePoint) == 0; + } + // Determines what should be the base path for all the entries when creating an archive. private static string GetBasePathForCreateFromDirectory(DirectoryInfo di, bool includeBaseDirectory) => includeBaseDirectory && di.Parent != null ? di.Parent.FullName : di.FullName; // Constructs the entry name used for a filesystem entry when creating an archive. - private static string GetEntryNameForFileSystemInfo(FileSystemInfo file, int basePathLength, ref char[] entryNameBuffer) + private static string GetEntryNameForFileSystemInfo(FileSystemInfo file, int basePathLength) { - int entryNameLength = file.FullName.Length - basePathLength; - Debug.Assert(entryNameLength > 0); - - bool isDirectory = file.Attributes.HasFlag(FileAttributes.Directory); - return ArchivingUtils.EntryFromPath(file.FullName, basePathLength, entryNameLength, ref entryNameBuffer, appendPathSeparator: isDirectory); + bool isDirectory = (file.Attributes & FileAttributes.Directory) != 0; + return TarHelpers.EntryFromPath(file.FullName.AsSpan(basePathLength), appendPathSeparator: isDirectory); } - private static string GetEntryNameForBaseDirectory(string name, ref char[] entryNameBuffer) + private static string GetEntryNameForBaseDirectory(string name) { - return ArchivingUtils.EntryFromPath(name, 0, name.Length, ref entryNameBuffer, appendPathSeparator: true); + return TarHelpers.EntryFromPath(name, appendPathSeparator: true); } // Extracts an archive into the specified directory. diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs index e89c9d4579dd38..1824c2c965926f 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs @@ -20,7 +20,7 @@ internal sealed partial class TarHeader // Attempts to retrieve the next header from the specified tar archive stream. // Throws if end of stream is reached or if any data type conversion fails. // Returns a valid TarHeader object if the attributes were read successfully, null otherwise. - internal static TarHeader? TryGetNextHeader(Stream archiveStream, bool copyData, TarEntryFormat initialFormat) + internal static TarHeader? TryGetNextHeader(Stream archiveStream, bool copyData, TarEntryFormat initialFormat, bool processDataBlock) { // The four supported formats have a header that fits in the default record size Span buffer = stackalloc byte[TarHelpers.RecordSize]; @@ -28,7 +28,7 @@ internal sealed partial class TarHeader archiveStream.ReadExactly(buffer); TarHeader? header = TryReadAttributes(initialFormat, buffer); - if (header != null) + if (header != null && processDataBlock) { header.ProcessDataBlock(archiveStream, copyData); } @@ -39,7 +39,7 @@ internal sealed partial class TarHeader // Asynchronously attempts read all the fields of the next header. // Throws if end of stream is reached or if any data type conversion fails. // Returns true if all the attributes were read successfully, false otherwise. - internal static async ValueTask TryGetNextHeaderAsync(Stream archiveStream, bool copyData, TarEntryFormat initialFormat, CancellationToken cancellationToken) + internal static async ValueTask TryGetNextHeaderAsync(Stream archiveStream, bool copyData, TarEntryFormat initialFormat, bool processDataBlock, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -50,7 +50,7 @@ internal sealed partial class TarHeader await archiveStream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false); TarHeader? header = TryReadAttributes(initialFormat, buffer.Span); - if (header != null) + if (header != null && processDataBlock) { await header.ProcessDataBlockAsync(archiveStream, copyData, cancellationToken).ConfigureAwait(false); } @@ -180,7 +180,7 @@ internal void ReplaceNormalAttributesWithExtended(Dictionary? di // will get all the data section read and the stream pointer positioned at the beginning of the next header. // - Block, Character, Directory, Fifo, HardLink and SymbolicLink typeflag entries have no data section so the archive stream pointer will be positioned at the beginning of the next header. // - All other typeflag entries with a data section will generate a stream wrapping the data section: SeekableSubReadStream for seekable archive streams, and SubReadStream for unseekable archive streams. - private void ProcessDataBlock(Stream archiveStream, bool copyData) + internal void ProcessDataBlock(Stream archiveStream, bool copyData) { bool skipBlockAlignmentPadding = true; @@ -199,6 +199,10 @@ private void ProcessDataBlock(Stream archiveStream, bool copyData) case TarEntryType.HardLink: case TarEntryType.SymbolicLink: // No data section + if (_size > 0) + { + throw new InvalidDataException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag)); + } break; case TarEntryType.RegularFile: case TarEntryType.V7RegularFile: // Treated as regular file @@ -257,6 +261,10 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca case TarEntryType.HardLink: case TarEntryType.SymbolicLink: // No data section + if (_size > 0) + { + throw new InvalidDataException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag)); + } break; case TarEntryType.RegularFile: case TarEntryType.V7RegularFile: // Treated as regular file @@ -311,6 +319,8 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca { MemoryStream copiedData = new MemoryStream(); TarHelpers.CopyBytes(archiveStream, copiedData, _size); + // Reset position pointer so the user can do the first DataStream read from the beginning + copiedData.Position = 0; return copiedData; } @@ -336,6 +346,8 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca { MemoryStream copiedData = new MemoryStream(); await TarHelpers.CopyBytesAsync(archiveStream, copiedData, size, cancellationToken).ConfigureAwait(false); + // Reset position pointer so the user can do the first DataStream read from the beginning + copiedData.Position = 0; return copiedData; } @@ -357,30 +369,31 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca { return null; } - int checksum = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(spanChecksum); + int checksum = (int)TarHelpers.ParseOctal(spanChecksum); // Zero checksum means the whole header is empty if (checksum == 0) { return null; } - long size = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Size, FieldLengths.Size)); + long size = (long)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.Size, FieldLengths.Size)); + Debug.Assert(size <= TarHelpers.MaxSizeLength, "size exceeded the max value possible with 11 octal digits. Actual size " + size); if (size < 0) { - throw new FormatException(string.Format(SR.TarSizeFieldNegative)); + throw new InvalidDataException(string.Format(SR.TarSizeFieldNegative)); } // Continue with the rest of the fields that require no special checks TarHeader header = new(initialFormat, name: TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.Name, FieldLengths.Name)), - mode: TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Mode, FieldLengths.Mode)), - mTime: TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch(TarHelpers.GetTenBaseLongFromOctalAsciiChars(buffer.Slice(FieldLocations.MTime, FieldLengths.MTime))), + mode: (int)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.Mode, FieldLengths.Mode)), + mTime: TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch((long)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.MTime, FieldLengths.MTime))), typeFlag: (TarEntryType)buffer[FieldLocations.TypeFlag]) { _checksum = checksum, _size = size, - _uid = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Uid, FieldLengths.Uid)), - _gid = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Gid, FieldLengths.Gid)), + _uid = (int)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.Uid, FieldLengths.Uid)), + _gid = (int)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.Gid, FieldLengths.Gid)), _linkName = TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.LinkName, FieldLengths.LinkName)) }; @@ -396,12 +409,13 @@ TarEntryType.LongLink or TarEntryType.LongPath or TarEntryType.MultiVolume or TarEntryType.RenamedOrSymlinked or - TarEntryType.SparseFile or TarEntryType.TapeVolume => TarEntryFormat.Gnu, // V7 is the only one that uses 'V7RegularFile'. TarEntryType.V7RegularFile => TarEntryFormat.V7, + TarEntryType.SparseFile => throw new NotSupportedException(string.Format(SR.TarEntryTypeNotSupported, header._typeFlag)), + // We can quickly determine the *minimum* possible format if the entry type // is the POSIX 'RegularFile', although later we could upgrade it to PAX or GNU _ => (header._typeFlag == TarEntryType.RegularFile) ? TarEntryFormat.Ustar : TarEntryFormat.V7 @@ -425,16 +439,23 @@ private void ReadMagicAttribute(Span buffer) } // When the magic field is set, the archive is newer than v7. - _magic = Encoding.ASCII.GetString(magic); - - if (_magic == GnuMagic) + if (magic.SequenceEqual(GnuMagicBytes)) { + _magic = GnuMagic; _format = TarEntryFormat.Gnu; } - else if (_format == TarEntryFormat.V7 && _magic == UstarMagic) + else if (magic.SequenceEqual(UstarMagicBytes)) { - // Important: Only change to ustar if we had not changed the format to pax already - _format = TarEntryFormat.Ustar; + _magic = UstarMagic; + if (_format == TarEntryFormat.V7) + { + // Important: Only change to ustar if we had not changed the format to pax already + _format = TarEntryFormat.Ustar; + } + } + else + { + _magic = Encoding.ASCII.GetString(magic); } } @@ -448,19 +469,47 @@ private void ReadVersionAttribute(Span buffer) } Span version = buffer.Slice(FieldLocations.Version, FieldLengths.Version); + switch (_format) + { + case TarEntryFormat.Ustar or TarEntryFormat.Pax: + // The POSIX formats have a 6 byte Magic "ustar\0", followed by a 2 byte Version "00" + if (!version.SequenceEqual(UstarVersionBytes)) + { + // Check for gnu version header for mixed case + if (!version.SequenceEqual(GnuVersionBytes)) + { + throw new InvalidDataException(string.Format(SR.TarPosixFormatExpected, _name)); + } - _version = Encoding.ASCII.GetString(version); + _version = GnuVersion; + } + else + { + _version = UstarVersion; + } + break; - // The POSIX formats have a 6 byte Magic "ustar\0", followed by a 2 byte Version "00" - if ((_format is TarEntryFormat.Ustar or TarEntryFormat.Pax) && _version != UstarVersion) - { - throw new FormatException(string.Format(SR.TarPosixFormatExpected, _name)); - } + case TarEntryFormat.Gnu: + // The GNU format has a Magic+Version 8 byte string "ustar \0" + if (!version.SequenceEqual(GnuVersionBytes)) + { + // Check for ustar or pax version header for mixed case + if (!version.SequenceEqual(UstarVersionBytes)) + { + throw new InvalidDataException(string.Format(SR.TarGnuFormatExpected, _name)); + } - // The GNU format has a Magic+Version 8 byte string "ustar \0" - if (_format == TarEntryFormat.Gnu && _version != GnuVersion) - { - throw new FormatException(string.Format(SR.TarGnuFormatExpected, _name)); + _version = UstarVersion; + } + else + { + _version = GnuVersion; + } + break; + + default: + _version = Encoding.ASCII.GetString(version); + break; } } @@ -469,18 +518,18 @@ private void ReadVersionAttribute(Span buffer) private void ReadPosixAndGnuSharedAttributes(Span buffer) { // Convert the byte arrays - _uName = TarHelpers.GetTrimmedAsciiString(buffer.Slice(FieldLocations.UName, FieldLengths.UName)); - _gName = TarHelpers.GetTrimmedAsciiString(buffer.Slice(FieldLocations.GName, FieldLengths.GName)); + _uName = TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.UName, FieldLengths.UName)); + _gName = TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.GName, FieldLengths.GName)); // DevMajor and DevMinor only have values with character devices and block devices. // For all other typeflags, the values in these fields are irrelevant. if (_typeFlag is TarEntryType.CharacterDevice or TarEntryType.BlockDevice) { // Major number for a character device or block device entry. - _devMajor = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.DevMajor, FieldLengths.DevMajor)); + _devMajor = (int)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.DevMajor, FieldLengths.DevMajor)); // Minor number for a character device or block device entry. - _devMinor = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.DevMinor, FieldLengths.DevMinor)); + _devMinor = (int)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.DevMinor, FieldLengths.DevMinor)); } } @@ -489,10 +538,10 @@ private void ReadPosixAndGnuSharedAttributes(Span buffer) private void ReadGnuAttributes(Span buffer) { // Convert byte arrays - long aTime = TarHelpers.GetTenBaseLongFromOctalAsciiChars(buffer.Slice(FieldLocations.ATime, FieldLengths.ATime)); + long aTime = (long)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.ATime, FieldLengths.ATime)); _aTime = TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch(aTime); - long cTime = TarHelpers.GetTenBaseLongFromOctalAsciiChars(buffer.Slice(FieldLocations.CTime, FieldLengths.CTime)); + long cTime = (long)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.CTime, FieldLengths.CTime)); _cTime = TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch(cTime); // TODO: Read the bytes of the currently unsupported GNU fields, in case user wants to write this entry into another GNU archive, they need to be preserved. https://github.com/dotnet/runtime/issues/68230 @@ -518,11 +567,23 @@ private void ReadUstarAttributes(Span buffer) // Throws if end of stream is reached or if an attribute is malformed. private void ReadExtendedAttributesBlock(Stream archiveStream) { - byte[]? buffer = CreateExtendedAttributesBufferIfSizeIsValid(); - if (buffer != null) + if (_size != 0) { - archiveStream.ReadExactly(buffer); - ReadExtendedAttributesFromBuffer(buffer, _name); + ValidateSize(); + + byte[]? buffer = null; + Span span = _size <= 256 ? + stackalloc byte[256] : + (buffer = ArrayPool.Shared.Rent((int)_size)); + span = span.Slice(0, (int)_size); + + archiveStream.ReadExactly(span); + ReadExtendedAttributesFromBuffer(span, _name); + + if (buffer is not null) + { + ArrayPool.Shared.Return(buffer); + } } } @@ -531,48 +592,43 @@ private void ReadExtendedAttributesBlock(Stream archiveStream) private async ValueTask ReadExtendedAttributesBlockAsync(Stream archiveStream, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - byte[]? buffer = CreateExtendedAttributesBufferIfSizeIsValid(); - if (buffer != null) + + if (_size != 0) { - await archiveStream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false); - ReadExtendedAttributesFromBuffer(buffer, _name); + ValidateSize(); + byte[] buffer = ArrayPool.Shared.Rent((int)_size); + Memory memory = buffer.AsMemory(0, (int)_size); + + await archiveStream.ReadExactlyAsync(memory, cancellationToken).ConfigureAwait(false); + ReadExtendedAttributesFromBuffer(memory.Span, _name); + + ArrayPool.Shared.Return(buffer); } } - // Return a byte array if the size field has a valid value for extended attributes. Otherwise, return null, or throw. - private byte[]? CreateExtendedAttributesBufferIfSizeIsValid() + private void ValidateSize() { - Debug.Assert(_typeFlag is TarEntryType.ExtendedAttributes or TarEntryType.GlobalExtendedAttributes); - - // It is not expected that the extended attributes data section will be longer than Array.MaxLength, considering - // the size field is 12 bytes long, which fits a number with a value under int.MaxValue. - if (_size > Array.MaxLength) + if ((uint)_size > (uint)Array.MaxLength) { - throw new InvalidOperationException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag.ToString())); + ThrowSizeFieldTooLarge(); } - if (_size == 0) - { - return null; - } - - return new byte[(int)_size]; + [DoesNotReturn] + void ThrowSizeFieldTooLarge() => + throw new InvalidOperationException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag.ToString())); } // Returns a dictionary containing the extended attributes collected from the provided byte buffer. private void ReadExtendedAttributesFromBuffer(ReadOnlySpan buffer, string name) { - string dataAsString = TarHelpers.GetTrimmedUtf8String(buffer); + buffer = TarHelpers.TrimEndingNullsAndSpaces(buffer); - using StringReader reader = new(dataAsString); - - while (TryGetNextExtendedAttribute(reader, out string? key, out string? value)) + while (TryGetNextExtendedAttribute(ref buffer, out string? key, out string? value)) { - if (ExtendedAttributes.ContainsKey(key)) + if (!ExtendedAttributes.TryAdd(key, value)) { - throw new FormatException(string.Format(SR.TarDuplicateExtendedAttribute, name)); + throw new InvalidDataException(string.Format(SR.TarDuplicateExtendedAttribute, name)); } - ExtendedAttributes.Add(key, value); } } @@ -581,11 +637,23 @@ private void ReadExtendedAttributesFromBuffer(ReadOnlySpan buffer, string // Throws if end of stream is reached. private void ReadGnuLongPathDataBlock(Stream archiveStream) { - byte[]? buffer = CreateGnuLongDataBufferIfSizeIsValid(); - if (buffer != null) + if (_size != 0) { - archiveStream.ReadExactly(buffer); - ReadGnuLongPathDataFromBuffer(buffer); + ValidateSize(); + + byte[]? buffer = null; + Span span = _size <= 256 ? + stackalloc byte[256] : + (buffer = ArrayPool.Shared.Rent((int)_size)); + span = span.Slice(0, (int)_size); + + archiveStream.ReadExactly(span); + ReadGnuLongPathDataFromBuffer(span); + + if (buffer is not null) + { + ArrayPool.Shared.Return(buffer); + } } } @@ -595,11 +663,17 @@ private void ReadGnuLongPathDataBlock(Stream archiveStream) private async ValueTask ReadGnuLongPathDataBlockAsync(Stream archiveStream, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - byte[]? buffer = CreateGnuLongDataBufferIfSizeIsValid(); - if (buffer != null) + + if (_size != 0) { - await archiveStream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false); - ReadGnuLongPathDataFromBuffer(buffer); + ValidateSize(); + byte[] buffer = ArrayPool.Shared.Rent((int)_size); + Memory memory = buffer.AsMemory(0, (int)_size); + + await archiveStream.ReadExactlyAsync(memory, cancellationToken).ConfigureAwait(false); + ReadGnuLongPathDataFromBuffer(memory.Span); + + ArrayPool.Shared.Return(buffer); } } @@ -618,61 +692,52 @@ private void ReadGnuLongPathDataFromBuffer(ReadOnlySpan buffer) } } - // Return a byte array if the size field has a valid value for GNU long metadata entry data. Otherwise, return null, or throw. - private byte[]? CreateGnuLongDataBufferIfSizeIsValid() - { - Debug.Assert(_typeFlag is TarEntryType.LongLink or TarEntryType.LongPath); - - if (_size > Array.MaxLength) - { - throw new InvalidOperationException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag.ToString())); - } - - if (_size == 0) - { - return null; - } - - return new byte[(int)_size]; - } - - // Tries to collect the next extended attribute from the string wrapped by the specified reader. + // Tries to collect the next extended attribute from the string. // Extended attributes are saved in the ISO/IEC 10646-1:2000 standard UTF-8 encoding format. // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html // "LENGTH KEY=VALUE\n" // Where LENGTH is the total number of bytes of that line, from LENGTH itself to the endline, inclusive. // Throws if end of stream is reached or if an attribute is malformed. private static bool TryGetNextExtendedAttribute( - StringReader reader, + ref ReadOnlySpan buffer, [NotNullWhen(returnValue: true)] out string? key, [NotNullWhen(returnValue: true)] out string? value) { key = null; value = null; - string? nextLine = reader.ReadLine(); - if (string.IsNullOrWhiteSpace(nextLine)) + // Slice off the next line. + int newlinePos = buffer.IndexOf((byte)'\n'); + if (newlinePos < 0) { return false; } + ReadOnlySpan line = buffer.Slice(0, newlinePos); - StringSplitOptions splitOptions = StringSplitOptions.RemoveEmptyEntries; + // Update buffer to point to the next line for the next call + buffer = buffer.Slice(newlinePos + 1); - string[] attributeArray = nextLine.Split(' ', 2, splitOptions); - if (attributeArray.Length != 2) + // Find the end of the length and remove everything up through it. + int spacePos = line.IndexOf((byte)' '); + if (spacePos < 0) { return false; } + line = line.Slice(spacePos + 1); - string[] keyAndValueArray = attributeArray[1].Split('=', 2, splitOptions); - if (keyAndValueArray.Length != 2) + // Find the equal separator. + int equalPos = line.IndexOf((byte)'='); + if (equalPos < 0) { return false; } - key = keyAndValueArray[0]; - value = keyAndValueArray[1]; + ReadOnlySpan keySlice = line.Slice(0, equalPos); + ReadOnlySpan valueSlice = line.Slice(equalPos + 1); + // Return the parsed key and value. + key = Encoding.UTF8.GetString(keySlice); + value = Encoding.UTF8.GetString(valueSlice); return true; } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs index ad5cdae22d0315..884c2bf1bcaf90 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs @@ -1,9 +1,10 @@ // 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.Buffers.Text; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; using System.Threading; @@ -14,29 +15,26 @@ namespace System.Formats.Tar // Writes header attributes of a tar archive entry. internal sealed partial class TarHeader { - private static ReadOnlySpan PaxMagicBytes => "ustar\0"u8; - private static ReadOnlySpan PaxVersionBytes => "00"u8; + private static ReadOnlySpan UstarMagicBytes => "ustar\0"u8; + private static ReadOnlySpan UstarVersionBytes => "00"u8; private static ReadOnlySpan GnuMagicBytes => "ustar "u8; private static ReadOnlySpan GnuVersionBytes => " \0"u8; - // Extended Attribute entries have a special format in the Name field: - // "{dirName}/PaxHeaders.{processId}/{fileName}{trailingSeparator}" - private const string PaxHeadersFormat = "{0}/PaxHeaders.{1}/{2}{3}"; - // Predefined text for the Name field of a GNU long metadata entry. Applies for both LongPath ('L') and LongLink ('K'). private const string GnuLongMetadataName = "././@LongLink"; + private const string ArgNameEntry = "entry"; // Writes the current header as a V7 entry into the archive stream. internal void WriteAsV7(Stream archiveStream, Span buffer) { - long actualLength = WriteV7FieldsToBuffer(buffer); + WriteV7FieldsToBuffer(buffer); archiveStream.Write(buffer); if (_dataStream != null) { - WriteData(archiveStream, _dataStream, actualLength); + WriteData(archiveStream, _dataStream, _size); } } @@ -45,39 +43,37 @@ internal async Task WriteAsV7Async(Stream archiveStream, Memory buffer, Ca { cancellationToken.ThrowIfCancellationRequested(); - long actualLength = WriteV7FieldsToBuffer(buffer.Span); + WriteV7FieldsToBuffer(buffer.Span); await archiveStream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); if (_dataStream != null) { - await WriteDataAsync(archiveStream, _dataStream, actualLength, cancellationToken).ConfigureAwait(false); + await WriteDataAsync(archiveStream, _dataStream, _size, cancellationToken).ConfigureAwait(false); } } // Writes the V7 header fields to the specified buffer, calculates and writes the checksum, then returns the final data length. - private long WriteV7FieldsToBuffer(Span buffer) + private void WriteV7FieldsToBuffer(Span buffer) { - long actualLength = GetTotalDataBytesToWrite(); + _size = GetTotalDataBytesToWrite(); TarEntryType actualEntryType = TarHelpers.GetCorrectTypeFlagForFormat(TarEntryFormat.V7, _typeFlag); - int tmpChecksum = WriteName(buffer, out _); - tmpChecksum += WriteCommonFields(buffer, actualLength, actualEntryType); + int tmpChecksum = WriteName(buffer); + tmpChecksum += WriteCommonFields(buffer, actualEntryType); _checksum = WriteChecksum(tmpChecksum, buffer); - - return actualLength; } // Writes the current header as a Ustar entry into the archive stream. internal void WriteAsUstar(Stream archiveStream, Span buffer) { - long actualLength = WriteUstarFieldsToBuffer(buffer); + WriteUstarFieldsToBuffer(buffer); archiveStream.Write(buffer); if (_dataStream != null) { - WriteData(archiveStream, _dataStream, actualLength); + WriteData(archiveStream, _dataStream, _size); } } @@ -86,29 +82,27 @@ internal async Task WriteAsUstarAsync(Stream archiveStream, Memory buffer, { cancellationToken.ThrowIfCancellationRequested(); - long actualLength = WriteUstarFieldsToBuffer(buffer.Span); + WriteUstarFieldsToBuffer(buffer.Span); await archiveStream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); if (_dataStream != null) { - await WriteDataAsync(archiveStream, _dataStream, actualLength, cancellationToken).ConfigureAwait(false); + await WriteDataAsync(archiveStream, _dataStream, _size, cancellationToken).ConfigureAwait(false); } } // Writes the Ustar header fields to the specified buffer, calculates and writes the checksum, then returns the final data length. - private long WriteUstarFieldsToBuffer(Span buffer) + private void WriteUstarFieldsToBuffer(Span buffer) { - long actualLength = GetTotalDataBytesToWrite(); + _size = GetTotalDataBytesToWrite(); TarEntryType actualEntryType = TarHelpers.GetCorrectTypeFlagForFormat(TarEntryFormat.Ustar, _typeFlag); - int tmpChecksum = WritePosixName(buffer); - tmpChecksum += WriteCommonFields(buffer, actualLength, actualEntryType); + int tmpChecksum = WriteUstarName(buffer); + tmpChecksum += WriteCommonFields(buffer, actualEntryType); tmpChecksum += WritePosixMagicAndVersion(buffer); tmpChecksum += WritePosixAndGnuSharedFields(buffer); _checksum = WriteChecksum(tmpChecksum, buffer); - - return actualLength; } // Writes the current header as a PAX Global Extended Attributes entry into the archive stream. @@ -146,6 +140,7 @@ internal void WriteAsPax(Stream archiveStream, Span buffer) // First, we write the preceding extended attributes header TarHeader extendedAttributesHeader = new(TarEntryFormat.Pax); // Fill the current header's dict + _size = GetTotalDataBytesToWrite(); CollectExtendedAttributesFromStandardFieldsIfNeeded(); // And pass the attributes to the preceding extended attributes header for writing extendedAttributesHeader.WriteAsPaxExtendedAttributes(archiveStream, buffer, ExtendedAttributes, isGea: false, globalExtendedAttributesEntryNumber: -1); @@ -159,12 +154,12 @@ internal void WriteAsPax(Stream archiveStream, Span buffer) internal async Task WriteAsPaxAsync(Stream archiveStream, Memory buffer, CancellationToken cancellationToken) { Debug.Assert(_typeFlag is not TarEntryType.GlobalExtendedAttributes); - cancellationToken.ThrowIfCancellationRequested(); // First, we write the preceding extended attributes header TarHeader extendedAttributesHeader = new(TarEntryFormat.Pax); // Fill the current header's dict + _size = GetTotalDataBytesToWrite(); CollectExtendedAttributesFromStandardFieldsIfNeeded(); // And pass the attributes to the preceding extended attributes header for writing await extendedAttributesHeader.WriteAsPaxExtendedAttributesAsync(archiveStream, buffer, ExtendedAttributes, isGea: false, globalExtendedAttributesEntryNumber: -1, cancellationToken).ConfigureAwait(false); @@ -179,7 +174,7 @@ internal async Task WriteAsPaxAsync(Stream archiveStream, Memory buffer, C internal void WriteAsGnu(Stream archiveStream, Span buffer) { // First, we determine if we need a preceding LongLink, and write it if needed - if (_linkName?.Length > FieldLengths.LinkName) + if (_linkName != null && Encoding.UTF8.GetByteCount(_linkName) > FieldLengths.LinkName) { TarHeader longLinkHeader = GetGnuLongMetadataHeader(TarEntryType.LongLink, _linkName); longLinkHeader.WriteAsGnuInternal(archiveStream, buffer); @@ -187,7 +182,7 @@ internal void WriteAsGnu(Stream archiveStream, Span buffer) } // Second, we determine if we need a preceding LongPath, and write it if needed - if (_name.Length > FieldLengths.Name) + if (Encoding.UTF8.GetByteCount(_name) > FieldLengths.Name) { TarHeader longPathHeader = GetGnuLongMetadataHeader(TarEntryType.LongPath, _name); longPathHeader.WriteAsGnuInternal(archiveStream, buffer); @@ -205,7 +200,7 @@ internal async Task WriteAsGnuAsync(Stream archiveStream, Memory buffer, C cancellationToken.ThrowIfCancellationRequested(); // First, we determine if we need a preceding LongLink, and write it if needed - if (_linkName?.Length > FieldLengths.LinkName) + if (_linkName != null && Encoding.UTF8.GetByteCount(_linkName) > FieldLengths.LinkName) { TarHeader longLinkHeader = GetGnuLongMetadataHeader(TarEntryType.LongLink, _linkName); await longLinkHeader.WriteAsGnuInternalAsync(archiveStream, buffer, cancellationToken).ConfigureAwait(false); @@ -213,9 +208,9 @@ internal async Task WriteAsGnuAsync(Stream archiveStream, Memory buffer, C } // Second, we determine if we need a preceding LongPath, and write it if needed - if (_name.Length > FieldLengths.Name) + if (Encoding.UTF8.GetByteCount(_name) > FieldLengths.Name) { - TarHeader longPathHeader = await GetGnuLongMetadataHeaderAsync(TarEntryType.LongPath, _name, cancellationToken).ConfigureAwait(false); + TarHeader longPathHeader = GetGnuLongMetadataHeader(TarEntryType.LongPath, _name); await longPathHeader.WriteAsGnuInternalAsync(archiveStream, buffer, cancellationToken).ConfigureAwait(false); buffer.Span.Clear(); // Reset it to reuse it } @@ -227,34 +222,7 @@ internal async Task WriteAsGnuAsync(Stream archiveStream, Memory buffer, C // Creates and returns a GNU long metadata header, with the specified long text written into its data stream. private static TarHeader GetGnuLongMetadataHeader(TarEntryType entryType, string longText) { - TarHeader longMetadataHeader = GetDefaultGnuLongMetadataHeader(longText.Length, entryType); - Debug.Assert(longMetadataHeader._dataStream != null); - - longMetadataHeader._dataStream.Write(Encoding.UTF8.GetBytes(longText)); - longMetadataHeader._dataStream.Seek(0, SeekOrigin.Begin); // Ensure it gets written into the archive from the beginning - - return longMetadataHeader; - } - - // Asynchronously creates and returns a GNU long metadata header, with the specified long text written into its data stream. - private static async Task GetGnuLongMetadataHeaderAsync(TarEntryType entryType, string longText, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - TarHeader longMetadataHeader = GetDefaultGnuLongMetadataHeader(longText.Length, entryType); - Debug.Assert(longMetadataHeader._dataStream != null); - - await longMetadataHeader._dataStream.WriteAsync(Encoding.UTF8.GetBytes(longText), cancellationToken).ConfigureAwait(false); - longMetadataHeader._dataStream.Seek(0, SeekOrigin.Begin); // Ensure it gets written into the archive from the beginning - - return longMetadataHeader; - } - - // Constructs a GNU metadata header with default values for the specified entry type. - private static TarHeader GetDefaultGnuLongMetadataHeader(int longTextLength, TarEntryType entryType) - { - Debug.Assert((entryType is TarEntryType.LongPath && longTextLength > FieldLengths.Name) || - (entryType is TarEntryType.LongLink && longTextLength > FieldLengths.LinkName)); + Debug.Assert(entryType is TarEntryType.LongPath or TarEntryType.LongLink); TarHeader longMetadataHeader = new(TarEntryFormat.Gnu); @@ -264,7 +232,7 @@ private static TarHeader GetDefaultGnuLongMetadataHeader(int longTextLength, Tar longMetadataHeader._gid = 0; longMetadataHeader._mTime = DateTimeOffset.MinValue; // 0 longMetadataHeader._typeFlag = entryType; - longMetadataHeader._dataStream = new MemoryStream(); + longMetadataHeader._dataStream = new MemoryStream(Encoding.UTF8.GetBytes(longText)); return longMetadataHeader; } @@ -272,13 +240,13 @@ private static TarHeader GetDefaultGnuLongMetadataHeader(int longTextLength, Tar // Writes the current header as a GNU entry into the archive stream. internal void WriteAsGnuInternal(Stream archiveStream, Span buffer) { - WriteAsGnuSharedInternal(buffer, out long actualLength); + WriteAsGnuSharedInternal(buffer); archiveStream.Write(buffer); if (_dataStream != null) { - WriteData(archiveStream, _dataStream, actualLength); + WriteData(archiveStream, _dataStream, _size); } } @@ -287,23 +255,23 @@ internal async Task WriteAsGnuInternalAsync(Stream archiveStream, Memory b { cancellationToken.ThrowIfCancellationRequested(); - WriteAsGnuSharedInternal(buffer.Span, out long actualLength); + WriteAsGnuSharedInternal(buffer.Span); await archiveStream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); if (_dataStream != null) { - await WriteDataAsync(archiveStream, _dataStream, actualLength, cancellationToken).ConfigureAwait(false); + await WriteDataAsync(archiveStream, _dataStream, _size, cancellationToken).ConfigureAwait(false); } } // Shared checksum and data length calculations for GNU entry writing. - private void WriteAsGnuSharedInternal(Span buffer, out long actualLength) + private void WriteAsGnuSharedInternal(Span buffer) { - actualLength = GetTotalDataBytesToWrite(); + _size = GetTotalDataBytesToWrite(); - int tmpChecksum = WriteName(buffer, out _); - tmpChecksum += WriteCommonFields(buffer, actualLength, TarHelpers.GetCorrectTypeFlagForFormat(TarEntryFormat.Gnu, _typeFlag)); + int tmpChecksum = WriteName(buffer); + tmpChecksum += WriteCommonFields(buffer, TarHelpers.GetCorrectTypeFlagForFormat(TarEntryFormat.Gnu, _typeFlag)); tmpChecksum += WriteGnuMagicAndVersion(buffer); tmpChecksum += WritePosixAndGnuSharedFields(buffer); tmpChecksum += WriteGnuFields(buffer); @@ -314,31 +282,30 @@ private void WriteAsGnuSharedInternal(Span buffer, out long actualLength) // Writes the current header as a PAX Extended Attributes entry into the archive stream. private void WriteAsPaxExtendedAttributes(Stream archiveStream, Span buffer, Dictionary extendedAttributes, bool isGea, int globalExtendedAttributesEntryNumber) { - WriteAsPaxExtendedAttributesShared(isGea, globalExtendedAttributesEntryNumber); - _dataStream = GenerateExtendedAttributesDataStream(extendedAttributes); + WriteAsPaxExtendedAttributesShared(isGea, globalExtendedAttributesEntryNumber, extendedAttributes); WriteAsPaxInternal(archiveStream, buffer); } // Asynchronously writes the current header as a PAX Extended Attributes entry into the archive stream and returns the value of the final checksum. - private async Task WriteAsPaxExtendedAttributesAsync(Stream archiveStream, Memory buffer, Dictionary extendedAttributes, bool isGea, int globalExtendedAttributesEntryNumber, CancellationToken cancellationToken) + private Task WriteAsPaxExtendedAttributesAsync(Stream archiveStream, Memory buffer, Dictionary extendedAttributes, bool isGea, int globalExtendedAttributesEntryNumber, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - - WriteAsPaxExtendedAttributesShared(isGea, globalExtendedAttributesEntryNumber); - _dataStream = await GenerateExtendedAttributesDataStreamAsync(extendedAttributes, cancellationToken).ConfigureAwait(false); - await WriteAsPaxInternalAsync(archiveStream, buffer, cancellationToken).ConfigureAwait(false); + WriteAsPaxExtendedAttributesShared(isGea, globalExtendedAttributesEntryNumber, extendedAttributes); + return WriteAsPaxInternalAsync(archiveStream, buffer, cancellationToken); } // Initializes the name, mode and type flag of a PAX extended attributes entry. - private void WriteAsPaxExtendedAttributesShared(bool isGea, int globalExtendedAttributesEntryNumber) + private void WriteAsPaxExtendedAttributesShared(bool isGea, int globalExtendedAttributesEntryNumber, Dictionary extendedAttributes) { Debug.Assert(isGea && globalExtendedAttributesEntryNumber >= 0 || !isGea && globalExtendedAttributesEntryNumber < 0); + _dataStream = GenerateExtendedAttributesDataStream(extendedAttributes); _name = isGea ? GenerateGlobalExtendedAttributeName(globalExtendedAttributesEntryNumber) : GenerateExtendedAttributeName(); _mode = TarHelpers.GetDefaultMode(_typeFlag); + _size = GetTotalDataBytesToWrite(); _typeFlag = isGea ? TarEntryType.GlobalExtendedAttributes : TarEntryType.ExtendedAttributes; } @@ -346,13 +313,13 @@ private void WriteAsPaxExtendedAttributesShared(bool isGea, int globalExtendedAt // This method writes an entry as both entries require, using the data from the current header instance. private void WriteAsPaxInternal(Stream archiveStream, Span buffer) { - WriteAsPaxSharedInternal(buffer, out long actualLength); + WriteAsPaxSharedInternal(buffer); archiveStream.Write(buffer); if (_dataStream != null) { - WriteData(archiveStream, _dataStream, actualLength); + WriteData(archiveStream, _dataStream, _size); } } @@ -362,78 +329,157 @@ private async Task WriteAsPaxInternalAsync(Stream archiveStream, Memory bu { cancellationToken.ThrowIfCancellationRequested(); - WriteAsPaxSharedInternal(buffer.Span, out long actualLength); + WriteAsPaxSharedInternal(buffer.Span); await archiveStream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); if (_dataStream != null) { - await WriteDataAsync(archiveStream, _dataStream, actualLength, cancellationToken).ConfigureAwait(false); + await WriteDataAsync(archiveStream, _dataStream, _size, cancellationToken).ConfigureAwait(false); } } // Shared checksum and data length calculations for PAX entry writing. - private void WriteAsPaxSharedInternal(Span buffer, out long actualLength) + private void WriteAsPaxSharedInternal(Span buffer) { - actualLength = GetTotalDataBytesToWrite(); - - int tmpChecksum = WritePosixName(buffer); - tmpChecksum += WriteCommonFields(buffer, actualLength, TarHelpers.GetCorrectTypeFlagForFormat(TarEntryFormat.Pax, _typeFlag)); + int tmpChecksum = WriteName(buffer); + tmpChecksum += WriteCommonFields(buffer, TarHelpers.GetCorrectTypeFlagForFormat(TarEntryFormat.Pax, _typeFlag)); tmpChecksum += WritePosixMagicAndVersion(buffer); tmpChecksum += WritePosixAndGnuSharedFields(buffer); _checksum = WriteChecksum(tmpChecksum, buffer); } - // All formats save in the name byte array only the ASCII bytes that fit. The full string is returned in the out byte array. - private int WriteName(Span buffer, out byte[] fullNameBytes) + // Gnu and pax save in the name byte array only the UTF8 bytes that fit. + // V7 does not support more than 100 bytes so it throws. + private int WriteName(Span buffer) { - fullNameBytes = Encoding.ASCII.GetBytes(_name); - int nameBytesLength = Math.Min(fullNameBytes.Length, FieldLengths.Name); - int checksum = WriteLeftAlignedBytesAndGetChecksum(fullNameBytes.AsSpan(0, nameBytesLength), buffer.Slice(FieldLocations.Name, FieldLengths.Name)); - return checksum; + ReadOnlySpan name = _name; + int encodedLength = GetUtf8TextLength(name); + + if (encodedLength > FieldLengths.Name) + { + if (_format is TarEntryFormat.V7) + { + throw new ArgumentException(SR.Format(SR.TarEntryFieldExceedsMaxLength, nameof(TarEntry.Name)), ArgNameEntry); + } + + int utf16NameTruncatedLength = GetUtf16TruncatedTextLength(name, FieldLengths.Name); + name = name.Slice(0, utf16NameTruncatedLength); + } + + return WriteAsUtf8String(name, buffer.Slice(FieldLocations.Name, FieldLengths.Name)); } - // Ustar and PAX save in the name byte array only the ASCII bytes that fit, and the rest of that string is saved in the prefix field. - private int WritePosixName(Span buffer) + // 'https://www.freebsd.org/cgi/man.cgi?tar(5)' + // If the path name is too long to fit in the 100 bytes provided by the standard format, + // it can be split at any / character with the first portion going into the prefix field. + private int WriteUstarName(Span buffer) { - int checksum = WriteName(buffer, out byte[] fullNameBytes); - if (fullNameBytes.Length > FieldLengths.Name) + // We can have a path name as big as 256, prefix + '/' + name, + // the separator in between can be neglected as the reader will append it when it joins both fields. + const int MaxPathName = FieldLengths.Prefix + 1 + FieldLengths.Name; + + if (GetUtf8TextLength(_name) > MaxPathName) { - int prefixBytesLength = Math.Min(fullNameBytes.Length - FieldLengths.Name, FieldLengths.Name); - checksum += WriteLeftAlignedBytesAndGetChecksum(fullNameBytes.AsSpan(FieldLengths.Name, prefixBytesLength), buffer.Slice(FieldLocations.Prefix, FieldLengths.Prefix)); + throw new ArgumentException(SR.Format(SR.TarEntryFieldExceedsMaxLength, nameof(TarEntry.Name)), ArgNameEntry); + } + + Span encodingBuffer = stackalloc byte[MaxPathName]; + int encoded = Encoding.UTF8.GetBytes(_name, encodingBuffer); + ReadOnlySpan pathNameBytes = encodingBuffer.Slice(0, encoded); + + // If the pathname is able to fit in Name, we can write it down there and avoid calculating Prefix. + if (pathNameBytes.Length <= FieldLengths.Name) + { + return WriteLeftAlignedBytesAndGetChecksum(pathNameBytes, buffer.Slice(FieldLocations.Name, FieldLengths.Name)); + } + + int lastIdx = pathNameBytes.LastIndexOfAny(PathInternal.Utf8DirectorySeparators); + scoped ReadOnlySpan name; + scoped ReadOnlySpan prefix; + + if (lastIdx < 1) // splitting at the root is not allowed. + { + name = pathNameBytes; + prefix = default; + } + else + { + name = pathNameBytes.Slice(lastIdx + 1); + prefix = pathNameBytes.Slice(0, lastIdx); + } + + // At this point path name is > 100. + // Attempt to split it in a way it can use prefix. + while (prefix.Length - name.Length > FieldLengths.Prefix) + { + lastIdx = prefix.LastIndexOfAny(PathInternal.Utf8DirectorySeparators); + if (lastIdx < 1) + { + break; + } + + name = pathNameBytes.Slice(lastIdx + 1); + prefix = pathNameBytes.Slice(0, lastIdx); + } + + if (prefix.Length <= FieldLengths.Prefix && name.Length <= FieldLengths.Name) + { + Debug.Assert(prefix.Length != 1 || !PathInternal.Utf8DirectorySeparators.Contains(prefix[0])); + + int checksum = WriteLeftAlignedBytesAndGetChecksum(prefix, buffer.Slice(FieldLocations.Prefix, FieldLengths.Prefix)); + checksum += WriteLeftAlignedBytesAndGetChecksum(name, buffer.Slice(FieldLocations.Name, FieldLengths.Name)); + + return checksum; + } + else + { + throw new ArgumentException(SR.Format(SR.TarEntryFieldExceedsMaxLength, nameof(TarEntry.Name)), ArgNameEntry); } - return checksum; } // Writes all the common fields shared by all formats into the specified spans. - private int WriteCommonFields(Span buffer, long actualLength, TarEntryType actualEntryType) + private int WriteCommonFields(Span buffer, TarEntryType actualEntryType) { + // Don't write an empty LinkName if the entry is a hardlink or symlink + Debug.Assert(!string.IsNullOrEmpty(_linkName) ^ (_typeFlag is not TarEntryType.SymbolicLink and not TarEntryType.HardLink)); + int checksum = 0; if (_mode > 0) { - checksum += WriteAsOctal(_mode, buffer, FieldLocations.Mode, FieldLengths.Mode); + checksum += FormatOctal(_mode, buffer.Slice(FieldLocations.Mode, FieldLengths.Mode)); } if (_uid > 0) { - checksum += WriteAsOctal(_uid, buffer, FieldLocations.Uid, FieldLengths.Uid); + checksum += FormatOctal(_uid, buffer.Slice(FieldLocations.Uid, FieldLengths.Uid)); } if (_gid > 0) { - checksum += WriteAsOctal(_gid, buffer, FieldLocations.Gid, FieldLengths.Gid); + checksum += FormatOctal(_gid, buffer.Slice(FieldLocations.Gid, FieldLengths.Gid)); } - _size = actualLength; - if (_size > 0) { - checksum += WriteAsOctal(_size, buffer, FieldLocations.Size, FieldLengths.Size); + if (_size <= TarHelpers.MaxSizeLength) + { + checksum += FormatOctal(_size, buffer.Slice(FieldLocations.Size, FieldLengths.Size)); + } + else if (_format is not TarEntryFormat.Pax) + { + throw new ArgumentException(SR.Format(SR.TarSizeFieldTooLargeForEntryFormat, _format)); + } + else + { + Debug.Assert(_typeFlag is not TarEntryType.ExtendedAttributes and not TarEntryType.GlobalExtendedAttributes); + Debug.Assert(Convert.ToInt64(ExtendedAttributes[PaxEaSize]) > TarHelpers.MaxSizeLength); + } } - checksum += WriteAsTimestamp(_mTime, buffer, FieldLocations.MTime, FieldLengths.MTime); + checksum += WriteAsTimestamp(_mTime, buffer.Slice(FieldLocations.MTime, FieldLengths.MTime)); char typeFlagChar = (char)actualEntryType; buffer[FieldLocations.TypeFlag] = (byte)typeFlagChar; @@ -441,7 +487,20 @@ private int WriteCommonFields(Span buffer, long actualLength, TarEntryType if (!string.IsNullOrEmpty(_linkName)) { - checksum += WriteAsAsciiString(_linkName, buffer, FieldLocations.LinkName, FieldLengths.LinkName); + ReadOnlySpan linkName = _linkName; + + if (GetUtf8TextLength(linkName) > FieldLengths.LinkName) + { + if (_format is not TarEntryFormat.Pax and not TarEntryFormat.Gnu) + { + throw new ArgumentException(SR.Format(SR.TarEntryFieldExceedsMaxLength, nameof(TarEntry.LinkName)), ArgNameEntry); + } + + int truncatedLength = GetUtf16TruncatedTextLength(linkName, FieldLengths.LinkName); + linkName = linkName.Slice(0, truncatedLength); + } + + checksum += WriteAsUtf8String(linkName, buffer.Slice(FieldLocations.LinkName, FieldLengths.LinkName)); } return checksum; @@ -465,8 +524,8 @@ private long GetTotalDataBytesToWrite() // Writes the magic and version fields of a ustar or pax entry into the specified spans. private static int WritePosixMagicAndVersion(Span buffer) { - int checksum = WriteLeftAlignedBytesAndGetChecksum(PaxMagicBytes, buffer.Slice(FieldLocations.Magic, FieldLengths.Magic)); - checksum += WriteLeftAlignedBytesAndGetChecksum(PaxVersionBytes, buffer.Slice(FieldLocations.Version, FieldLengths.Version)); + int checksum = WriteLeftAlignedBytesAndGetChecksum(UstarMagicBytes, buffer.Slice(FieldLocations.Magic, FieldLengths.Magic)); + checksum += WriteLeftAlignedBytesAndGetChecksum(UstarVersionBytes, buffer.Slice(FieldLocations.Version, FieldLengths.Version)); return checksum; } @@ -485,22 +544,48 @@ private int WritePosixAndGnuSharedFields(Span buffer) if (!string.IsNullOrEmpty(_uName)) { - checksum += WriteAsAsciiString(_uName, buffer, FieldLocations.UName, FieldLengths.UName); + ReadOnlySpan uName = _uName; + + if (GetUtf8TextLength(uName) > FieldLengths.UName) + { + if (_format is not TarEntryFormat.Pax) + { + throw new ArgumentException(SR.Format(SR.TarEntryFieldExceedsMaxLength, nameof(PaxTarEntry.UserName)), ArgNameEntry); + } + + int truncatedLength = GetUtf16TruncatedTextLength(uName, FieldLengths.UName); + uName = uName.Slice(0, truncatedLength); + } + + checksum += WriteAsUtf8String(uName, buffer.Slice(FieldLocations.UName, FieldLengths.UName)); } if (!string.IsNullOrEmpty(_gName)) { - checksum += WriteAsAsciiString(_gName, buffer, FieldLocations.GName, FieldLengths.GName); + ReadOnlySpan gName = _gName; + + if (GetUtf8TextLength(gName) > FieldLengths.GName) + { + if (_format is not TarEntryFormat.Pax) + { + throw new ArgumentException(SR.Format(SR.TarEntryFieldExceedsMaxLength, nameof(PaxTarEntry.GroupName)), ArgNameEntry); + } + + int truncatedLength = GetUtf16TruncatedTextLength(gName, FieldLengths.GName); + gName = gName.Slice(0, truncatedLength); + } + + checksum += WriteAsUtf8String(gName, buffer.Slice(FieldLocations.GName, FieldLengths.GName)); } if (_devMajor > 0) { - checksum += WriteAsOctal(_devMajor, buffer, FieldLocations.DevMajor, FieldLengths.DevMajor); + checksum += FormatOctal(_devMajor, buffer.Slice(FieldLocations.DevMajor, FieldLengths.DevMajor)); } if (_devMinor > 0) { - checksum += WriteAsOctal(_devMinor, buffer, FieldLocations.DevMinor, FieldLengths.DevMinor); + checksum += FormatOctal(_devMinor, buffer.Slice(FieldLocations.DevMinor, FieldLengths.DevMinor)); } return checksum; @@ -509,8 +594,8 @@ private int WritePosixAndGnuSharedFields(Span buffer) // Saves the gnu-specific fields into the specified spans. private int WriteGnuFields(Span buffer) { - int checksum = WriteAsTimestamp(_aTime, buffer, FieldLocations.ATime, FieldLengths.ATime); - checksum += WriteAsTimestamp(_cTime, buffer, FieldLocations.CTime, FieldLengths.CTime); + int checksum = WriteAsTimestamp(_aTime, buffer.Slice(FieldLocations.ATime, FieldLengths.ATime)); + checksum += WriteAsTimestamp(_cTime, buffer.Slice(FieldLocations.CTime, FieldLengths.CTime)); if (_gnuUnusedBytes != null) { @@ -524,8 +609,18 @@ private int WriteGnuFields(Span buffer) private static void WriteData(Stream archiveStream, Stream dataStream, long actualLength) { dataStream.CopyTo(archiveStream); // The data gets copied from the current position + int paddingAfterData = TarHelpers.CalculatePadding(actualLength); - archiveStream.Write(new byte[paddingAfterData]); + if (paddingAfterData != 0) + { + Debug.Assert(paddingAfterData <= TarHelpers.RecordSize); + + Span padding = stackalloc byte[TarHelpers.RecordSize]; + padding = padding.Slice(0, paddingAfterData); + padding.Clear(); + + archiveStream.Write(padding); + } } // Asynchronously writes the current header's data stream into the archive stream. @@ -534,143 +629,154 @@ private static async Task WriteDataAsync(Stream archiveStream, Stream dataStream cancellationToken.ThrowIfCancellationRequested(); await dataStream.CopyToAsync(archiveStream, cancellationToken).ConfigureAwait(false); // The data gets copied from the current position + int paddingAfterData = TarHelpers.CalculatePadding(actualLength); - await archiveStream.WriteAsync(new byte[paddingAfterData], cancellationToken).ConfigureAwait(false); + if (paddingAfterData != 0) + { + byte[] buffer = ArrayPool.Shared.Rent(paddingAfterData); + Array.Clear(buffer, 0, paddingAfterData); + + await archiveStream.WriteAsync(buffer.AsMemory(0, paddingAfterData), cancellationToken).ConfigureAwait(false); + + ArrayPool.Shared.Return(buffer); + } } // Dumps into the archive stream an extended attribute entry containing metadata of the entry it precedes. private static Stream? GenerateExtendedAttributesDataStream(Dictionary extendedAttributes) { MemoryStream? dataStream = null; + + byte[]? buffer = null; + Span span = stackalloc byte[512]; + if (extendedAttributes.Count > 0) { dataStream = new MemoryStream(); + foreach ((string attribute, string value) in extendedAttributes) { - byte[] entryBytes = GenerateExtendedAttributeKeyValuePairAsByteArray(Encoding.UTF8.GetBytes(attribute), Encoding.UTF8.GetBytes(value)); - dataStream.Write(entryBytes); + // Generates an extended attribute key value pair string saved into a byte array, following the ISO/IEC 10646-1:2000 standard UTF-8 encoding format. + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html + + // The format is: + // "XX attribute=value\n" + // where "XX" is the number of characters in the entry, including those required for the count itself. + // If prepending the length digits increases the number of digits, we need to expand. + int length = 3 + Encoding.UTF8.GetByteCount(attribute) + Encoding.UTF8.GetByteCount(value); + int originalDigitCount = CountDigits(length), newDigitCount; + length += originalDigitCount; + while ((newDigitCount = CountDigits(length)) != originalDigitCount) + { + length += newDigitCount - originalDigitCount; + originalDigitCount = newDigitCount; + } + Debug.Assert(length == CountDigits(length) + 3 + Encoding.UTF8.GetByteCount(attribute) + Encoding.UTF8.GetByteCount(value)); + + // Get a large enough buffer if we don't already have one. + if (span.Length < length) + { + if (buffer is not null) + { + ArrayPool.Shared.Return(buffer); + } + span = buffer = ArrayPool.Shared.Rent(length); + } + + // Format the contents. + bool formatted = Utf8Formatter.TryFormat(length, span, out int bytesWritten); + Debug.Assert(formatted); + span[bytesWritten++] = (byte)' '; + bytesWritten += Encoding.UTF8.GetBytes(attribute, span.Slice(bytesWritten)); + span[bytesWritten++] = (byte)'='; + bytesWritten += Encoding.UTF8.GetBytes(value, span.Slice(bytesWritten)); + span[bytesWritten++] = (byte)'\n'; + + // Write it to the stream. + dataStream.Write(span.Slice(0, bytesWritten)); } - dataStream?.Seek(0, SeekOrigin.Begin); // Ensure it gets written into the archive from the beginning + + dataStream.Position = 0; // Ensure it gets written into the archive from the beginning } - return dataStream; - } - // Asynchronously dumps into the archive stream an extended attribute entry containing metadata of the entry it precedes. - private static async Task GenerateExtendedAttributesDataStreamAsync(Dictionary extendedAttributes, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); + if (buffer is not null) + { + ArrayPool.Shared.Return(buffer); + } - MemoryStream? dataStream = null; - if (extendedAttributes.Count > 0) + return dataStream; + + static int CountDigits(int value) { - dataStream = new MemoryStream(); - foreach ((string attribute, string value) in extendedAttributes) + Debug.Assert(value >= 0); + int digits = 1; + while (true) { - byte[] entryBytes = GenerateExtendedAttributeKeyValuePairAsByteArray(Encoding.UTF8.GetBytes(attribute), Encoding.UTF8.GetBytes(value)); - await dataStream.WriteAsync(entryBytes, cancellationToken).ConfigureAwait(false); + value /= 10; + if (value == 0) break; + digits++; } - dataStream?.Seek(0, SeekOrigin.Begin); // Ensure it gets written into the archive from the beginning + return digits; } - return dataStream; } // Some fields that have a reserved spot in the header, may not fit in such field anymore, but they can fit in the // extended attributes. They get collected and saved in that dictionary, with no restrictions. private void CollectExtendedAttributesFromStandardFieldsIfNeeded() { - ExtendedAttributes.Add(PaxEaName, _name); + ExtendedAttributes[PaxEaName] = _name; + ExtendedAttributes[PaxEaMTime] = TarHelpers.GetTimestampStringFromDateTimeOffset(_mTime); - if (!ExtendedAttributes.ContainsKey(PaxEaMTime)) - { - ExtendedAttributes.Add(PaxEaMTime, TarHelpers.GetTimestampStringFromDateTimeOffset(_mTime)); - } - if (!string.IsNullOrEmpty(_gName)) - { - TryAddStringField(ExtendedAttributes, PaxEaGName, _gName, FieldLengths.GName); - } - if (!string.IsNullOrEmpty(_uName)) - { - TryAddStringField(ExtendedAttributes, PaxEaUName, _uName, FieldLengths.UName); - } + TryAddStringField(ExtendedAttributes, PaxEaGName, _gName, FieldLengths.GName); + TryAddStringField(ExtendedAttributes, PaxEaUName, _uName, FieldLengths.UName); if (!string.IsNullOrEmpty(_linkName)) { - ExtendedAttributes.Add(PaxEaLinkName, _linkName); + Debug.Assert(_typeFlag is TarEntryType.SymbolicLink or TarEntryType.HardLink); + ExtendedAttributes[PaxEaLinkName] = _linkName; } - if (_size > 99_999_999) + if (_size > TarHelpers.MaxSizeLength) { - ExtendedAttributes.Add(PaxEaSize, _size.ToString()); + ExtendedAttributes[PaxEaSize] = _size.ToString(); + } + else + { + ExtendedAttributes.Remove(PaxEaSize); } - - // Adds the specified string to the dictionary if it's longer than the specified max byte length. - static void TryAddStringField(Dictionary extendedAttributes, string key, string value, int maxLength) + // Sets the specified string to the dictionary if it's longer than the specified max byte length; otherwise, remove it. + static void TryAddStringField(Dictionary extendedAttributes, string key, string? value, int maxLength) { - if (Encoding.UTF8.GetByteCount(value) > maxLength) + if (string.IsNullOrEmpty(value) || GetUtf8TextLength(value) <= maxLength) { - extendedAttributes.Add(key, value); + extendedAttributes.Remove(key); + } + else + { + extendedAttributes[key] = value; } } } - // Generates an extended attribute key value pair string saved into a byte array, following the ISO/IEC 10646-1:2000 standard UTF-8 encoding format. - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html - private static byte[] GenerateExtendedAttributeKeyValuePairAsByteArray(byte[] keyBytes, byte[] valueBytes) - { - // Assuming key="ab" and value="cdef" - - // The " ab=cdef\n" attribute string has a length of 9 chars - int suffixByteCount = 3 + // leading space, equals sign and trailing newline - keyBytes.Length + valueBytes.Length; - - // The count string "9" has a length of 1 char - string suffixByteCountString = suffixByteCount.ToString(); - int firstTotalByteCount = Encoding.ASCII.GetByteCount(suffixByteCountString); - - // If we prepend the count string length to the attribute string, - // the total length increases to 10, which has one more digit - // "9 abc=def\n" - int firstPrefixAndSuffixByteCount = firstTotalByteCount + suffixByteCount; - - // The new count string "10" has an increased length of 2 chars - string prefixAndSuffixByteCountString = firstPrefixAndSuffixByteCount.ToString(); - int realTotalCharCount = Encoding.ASCII.GetByteCount(prefixAndSuffixByteCountString); - - byte[] finalTotalCharCountBytes = Encoding.ASCII.GetBytes(prefixAndSuffixByteCountString); - - // The final string should contain the correct total length now - List bytesList = new(); - - bytesList.AddRange(finalTotalCharCountBytes); - bytesList.Add(TarHelpers.SpaceChar); - bytesList.AddRange(keyBytes); - bytesList.Add(TarHelpers.EqualsChar); - bytesList.AddRange(valueBytes); - bytesList.Add(TarHelpers.NewLineChar); - - Debug.Assert(bytesList.Count == (realTotalCharCount + suffixByteCount)); - - return bytesList.ToArray(); - } - // The checksum accumulator first adds up the byte values of eight space chars, then the final number // is written on top of those spaces on the specified span as ascii. // At the end, it's saved in the header field and the final value returned. - internal int WriteChecksum(int checksum, Span buffer) + internal static int WriteChecksum(int checksum, Span buffer) { // The checksum field is also counted towards the total sum // but as an array filled with spaces - checksum += TarHelpers.SpaceChar * 8; + checksum += (byte)' ' * 8; Span converted = stackalloc byte[FieldLengths.Checksum]; - WriteAsOctal(checksum, converted, 0, converted.Length); + converted.Clear(); + FormatOctal(checksum, converted); Span destination = buffer.Slice(FieldLocations.Checksum, FieldLengths.Checksum); // Checksum field ends with a null and a space - destination[^1] = TarHelpers.SpaceChar; // ' ' - destination[^2] = 0; // '\0' + destination[^1] = (byte)' '; + destination[^2] = (byte)'\0'; int i = destination.Length - 3; int j = converted.Length - 1; @@ -684,7 +790,7 @@ internal int WriteChecksum(int checksum, Span buffer) } else { - destination[i] = TarHelpers.ZeroChar; // Leading zero chars '0' + destination[i] = (byte)'0'; // Leading zero chars } i--; } @@ -697,67 +803,75 @@ private static int WriteLeftAlignedBytesAndGetChecksum(ReadOnlySpan bytesT { Debug.Assert(destination.Length > 1); - int checksum = 0; - - for (int i = 0, j = 0; i < destination.Length && j < bytesToWrite.Length; i++, j++) - { - destination[i] = bytesToWrite[j]; - checksum += destination[i]; - } + // Copy as many bytes as will fit + int numToCopy = Math.Min(bytesToWrite.Length, destination.Length); + bytesToWrite = bytesToWrite.Slice(0, numToCopy); + bytesToWrite.CopyTo(destination); - return checksum; + return Checksum(bytesToWrite); } // Writes the specified bytes aligned to the right, filling all the leading bytes with the zero char 0x30, // ensuring a null terminator is included at the end of the specified span. private static int WriteRightAlignedBytesAndGetChecksum(ReadOnlySpan bytesToWrite, Span destination) { - int checksum = 0; - int i = destination.Length - 1; - int j = bytesToWrite.Length - 1; + Debug.Assert(destination.Length > 1); - while (i >= 0) + // Null terminated + destination[^1] = (byte)'\0'; + + // Copy as many input bytes as will fit + int numToCopy = Math.Min(bytesToWrite.Length, destination.Length - 1); + bytesToWrite = bytesToWrite.Slice(0, numToCopy); + int copyPos = destination.Length - 1 - bytesToWrite.Length; + bytesToWrite.CopyTo(destination.Slice(copyPos)); + + // Fill all leading bytes with zeros + destination.Slice(0, copyPos).Fill((byte)'0'); + + return Checksum(destination); + } + + private static int Checksum(ReadOnlySpan bytes) + { + int checksum = 0; + foreach (byte b in bytes) { - if (i == destination.Length - 1) - { - destination[i] = 0; // null terminated - } - else if (j >= 0) - { - destination[i] = bytesToWrite[j]; - j--; - } - else - { - destination[i] = TarHelpers.ZeroChar; // leading zeros - } - checksum += destination[i]; - i--; + checksum += b; } - return checksum; } // Writes the specified decimal number as a right-aligned octal number and returns its checksum. - internal static int WriteAsOctal(long tenBaseNumber, Span destination, int location, int length) + internal static int FormatOctal(long value, Span destination) { - long octal = TarHelpers.ConvertDecimalToOctal(tenBaseNumber); - byte[] bytes = Encoding.ASCII.GetBytes(octal.ToString()); - return WriteRightAlignedBytesAndGetChecksum(bytes.AsSpan(), destination.Slice(location, length)); + ulong remaining = (ulong)value; + Span digits = stackalloc byte[32]; // longer than any possible octal formatting of a ulong + + int i = digits.Length - 1; + while (true) + { + digits[i] = (byte)('0' + (remaining % 8)); + remaining /= 8; + if (remaining == 0) break; + i--; + } + + return WriteRightAlignedBytesAndGetChecksum(digits.Slice(i), destination); } // Writes the specified DateTimeOffset's Unix time seconds as a right-aligned octal number, and returns its checksum. - private static int WriteAsTimestamp(DateTimeOffset timestamp, Span destination, int location, int length) + private static int WriteAsTimestamp(DateTimeOffset timestamp, Span destination) { long unixTimeSeconds = timestamp.ToUnixTimeSeconds(); - return WriteAsOctal(unixTimeSeconds, destination, location, length); + return FormatOctal(unixTimeSeconds, destination); } - // Writes the specified text as an ASCII string aligned to the left, and returns its checksum. - private static int WriteAsAsciiString(string str, Span buffer, int location, int length) + // Writes the specified text as an UTF8 string aligned to the left, and returns its checksum. + private static int WriteAsUtf8String(ReadOnlySpan text, Span buffer) { - byte[] bytes = Encoding.ASCII.GetBytes(str); - return WriteLeftAlignedBytesAndGetChecksum(bytes.AsSpan(), buffer.Slice(location, length)); + int encoded = Encoding.UTF8.GetBytes(text, buffer); + return WriteLeftAlignedBytesAndGetChecksum(buffer.Slice(0, encoded), buffer); } // Gets the special name for the 'name' field in an extended attribute entry. @@ -767,18 +881,15 @@ private static int WriteAsAsciiString(string str, Span buffer, int locatio // - %f: The filename of the file, equivalent to the result of the basename utility on the translated pathname. private string GenerateExtendedAttributeName() { - string? dirName = Path.GetDirectoryName(_name); - dirName = string.IsNullOrEmpty(dirName) ? "." : dirName; + ReadOnlySpan dirName = Path.GetDirectoryName(_name.AsSpan()); + dirName = dirName.IsEmpty ? "." : dirName; - int processId = Environment.ProcessId; + ReadOnlySpan fileName = Path.GetFileName(_name.AsSpan()); + fileName = fileName.IsEmpty ? "." : fileName; - string? fileName = Path.GetFileName(_name); - fileName = string.IsNullOrEmpty(fileName) ? "." : fileName; - - string trailingSeparator = (_typeFlag is TarEntryType.Directory or TarEntryType.DirectoryList) ? - $"{Path.DirectorySeparatorChar}" : string.Empty; - - return string.Format(PaxHeadersFormat, dirName, processId, fileName, trailingSeparator); + return _typeFlag is TarEntryType.Directory or TarEntryType.DirectoryList ? + $"{dirName}/PaxHeaders.{Environment.ProcessId}/{fileName}{Path.DirectorySeparatorChar}" : + $"{dirName}/PaxHeaders.{Environment.ProcessId}/{fileName}"; } // Gets the special name for the 'name' field in a global extended attribute entry. @@ -809,5 +920,32 @@ private static string GenerateGlobalExtendedAttributeName(int globalExtendedAttr return result; } + + private static int GetUtf8TextLength(ReadOnlySpan text) + => Encoding.UTF8.GetByteCount(text); + + // Returns the text's utf16 length truncated at the specified utf8 max length. + private static int GetUtf16TruncatedTextLength(ReadOnlySpan text, int utf8MaxLength) + { + Debug.Assert(GetUtf8TextLength(text) > utf8MaxLength); + + int utf8Length = 0; + int utf16TruncatedLength = 0; + + foreach (Rune rune in text.EnumerateRunes()) + { + utf8Length += rune.Utf8SequenceLength; + if (utf8Length <= utf8MaxLength) + { + utf16TruncatedLength += rune.Utf16SequenceLength; + } + else + { + break; + } + } + + return utf16TruncatedLength; + } } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Unix.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Unix.cs index f684dd78fde1c6..613816904c9c4a 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Unix.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Unix.cs @@ -47,57 +47,41 @@ public int Compare (string? x, string? y) private static UnixFileMode UMask => s_umask.Value; - /* - Tar files are usually ordered: parent directories come before their child entries. - - They may be unordered. In that case we need to create parent directories before - we know the proper permissions for these directories. - - We create these directories with restrictive permissions. If we encounter an entry for - the directory later, we store the mode to apply it later. - - If the archive doesn't have an entry for the parent directory, we use the default mask. - - The pending modes to be applied are tracked through a reverse-sorted dictionary. - The reverse order is needed to apply permissions to children before their parent. - Otherwise we may apply a restrictive mask to the parent, that prevents us from - changing a child. - */ - + // Use a reverse-sorted dictionary to apply permission to children before their parents. + // Otherwise we may apply a restrictive mask to the parent, that prevents us from changing a child. internal static SortedDictionary? CreatePendingModesDictionary() => new SortedDictionary(s_reverseStringComparer); - internal static void CreateDirectory(string fullPath, UnixFileMode? mode, bool overwriteMetadata, SortedDictionary? pendingModes) + internal static void CreateDirectory(string fullPath, UnixFileMode? mode, SortedDictionary? pendingModes) { - // Restrictive mask for creating the missing parent directories while extracting. + // Minimal permissions required for extracting. const UnixFileMode ExtractPermissions = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute; Debug.Assert(pendingModes is not null); if (Directory.Exists(fullPath)) { - // Apply permissions to an existing directory when we're overwriting metadata - // or the directory was created as a missing parent (stored in pendingModes). + // Apply permissions to an existing directory. if (mode.HasValue) { + // Ensure we have sufficient permissions to extract in the directory. bool hasExtractPermissions = (mode.Value & ExtractPermissions) == ExtractPermissions; if (hasExtractPermissions) { - bool removed = pendingModes.Remove(fullPath); - if (overwriteMetadata || removed) - { - UnixFileMode umask = UMask; - File.SetUnixFileMode(fullPath, mode.Value & ~umask); - } + pendingModes.Remove(fullPath); + + UnixFileMode umask = UMask; + File.SetUnixFileMode(fullPath, mode.Value & ~umask); } - else if (overwriteMetadata || pendingModes.ContainsKey(fullPath)) + else { - pendingModes[fullPath] = mode.Value; + pendingModes[fullPath] = mode.Value; } } return; } + // If there are missing parents, Directory.CreateDirectory will create them using default permissions. if (mode.HasValue) { // Ensure we have sufficient permissions to extract in the directory. @@ -106,28 +90,13 @@ internal static void CreateDirectory(string fullPath, UnixFileMode? mode, bool o pendingModes[fullPath] = mode.Value; mode = ExtractPermissions; } - } - else - { - pendingModes.Add(fullPath, DefaultDirectoryMode); - mode = ExtractPermissions; - } - string parentDir = Path.GetDirectoryName(fullPath)!; - string rootDir = Path.GetPathRoot(parentDir)!; - bool hasMissingParents = false; - for (string dir = parentDir; dir != rootDir && !Directory.Exists(dir); dir = Path.GetDirectoryName(dir)!) - { - pendingModes.Add(dir, DefaultDirectoryMode); - hasMissingParents = true; + Directory.CreateDirectory(fullPath, mode.Value); } - - if (hasMissingParents) + else { - Directory.CreateDirectory(parentDir, ExtractPermissions); + Directory.CreateDirectory(fullPath); } - - Directory.CreateDirectory(fullPath, mode.Value); } internal static void SetPendingModes(SortedDictionary? pendingModes) @@ -145,5 +114,25 @@ internal static void SetPendingModes(SortedDictionary? pen File.SetUnixFileMode(dir.Key, dir.Value & ~umask); } } + + internal static unsafe string EntryFromPath(ReadOnlySpan path, bool appendPathSeparator = false) + { + // Remove leading separators. + int nonSlash = path.IndexOfAnyExcept('/'); + if (nonSlash == -1) + { + nonSlash = path.Length; + } + path = path.Slice(nonSlash); + + // Append a separator if necessary. + return (path.IsEmpty, appendPathSeparator) switch + { + (false, false) => path.ToString(), + (false, true) => string.Concat(path, "/"), + (true, false) => string.Empty, + (true, true) => "/", + }; + } } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Windows.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Windows.cs index e00f6476764aba..e1025b7bdece68 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Windows.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Windows.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.IO; -using System.Text; using System.Diagnostics; +using System.Runtime.InteropServices; namespace System.Formats.Tar { @@ -13,10 +13,52 @@ internal static partial class TarHelpers internal static SortedDictionary? CreatePendingModesDictionary() => null; - internal static void CreateDirectory(string fullPath, UnixFileMode? mode, bool overwriteMetadata, SortedDictionary? pendingModes) + internal static void CreateDirectory(string fullPath, UnixFileMode? mode, SortedDictionary? pendingModes) => Directory.CreateDirectory(fullPath); internal static void SetPendingModes(SortedDictionary? pendingModes) => Debug.Assert(pendingModes is null); + + internal static unsafe string EntryFromPath(ReadOnlySpan path, bool appendPathSeparator = false) + { + // Remove leading separators. + int nonSlash = path.IndexOfAnyExcept('/', '\\'); + if (nonSlash == -1) + { + nonSlash = path.Length; + } + path = path.Slice(nonSlash); + + // Replace \ with /, and append a separator if necessary. + + if (path.IsEmpty) + { + return appendPathSeparator ? + "/" : + string.Empty; + } + + fixed (char* pathPtr = &MemoryMarshal.GetReference(path)) + { + return string.Create(appendPathSeparator ? path.Length + 1 : path.Length, (appendPathSeparator, (IntPtr)pathPtr, path.Length), static (dest, state) => + { + ReadOnlySpan path = new ReadOnlySpan((char*)state.Item2, state.Length); + path.CopyTo(dest); + if (state.appendPathSeparator) + { + dest[^1] = '/'; + } + + // To ensure tar files remain compatible with Unix, and per the ZIP File Format Specification 4.4.17.1, + // all slashes should be forward slashes. + int pos; + while ((pos = dest.IndexOf('\\')) >= 0) + { + dest[pos] = '/'; + dest = dest.Slice(pos + 1); + } + }); + } + } } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs index d4592eb85d4b5f..45cae1c2759044 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs @@ -3,8 +3,11 @@ using System.Buffers; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; +using System.Numerics; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -16,11 +19,21 @@ internal static partial class TarHelpers { internal const short RecordSize = 512; internal const int MaxBufferLength = 4096; + internal const long MaxSizeLength = (1L << 33) - 1; // Max value of 11 octal digits = 2^33 - 1 or 8 Gb. - internal const int ZeroChar = 0x30; - internal const byte SpaceChar = 0x20; - internal const byte EqualsChar = 0x3d; - internal const byte NewLineChar = 0xa; + internal const UnixFileMode ValidUnixFileModes = + UnixFileMode.UserRead | + UnixFileMode.UserWrite | + UnixFileMode.UserExecute | + UnixFileMode.GroupRead | + UnixFileMode.GroupWrite | + UnixFileMode.GroupExecute | + UnixFileMode.OtherRead | + UnixFileMode.OtherWrite | + UnixFileMode.OtherExecute | + UnixFileMode.StickyBit | + UnixFileMode.SetGroup | + UnixFileMode.SetUser; // Default mode for TarEntry created for a file-type. private const UnixFileMode DefaultFileMode = @@ -117,36 +130,6 @@ internal static int CalculatePadding(long size) return padding; } - // Returns the specified 8-base number as a 10-base number. - internal static int ConvertDecimalToOctal(int value) - { - int multiplier = 1; - int accum = value; - int actual = 0; - while (accum != 0) - { - actual += (accum % 8) * multiplier; - accum /= 8; - multiplier *= 10; - } - return actual; - } - - // Returns the specified 10-base number as an 8-base number. - internal static long ConvertDecimalToOctal(long value) - { - long multiplier = 1; - long accum = value; - long actual = 0; - while (accum != 0) - { - actual += (accum % 8) * multiplier; - accum /= 8; - multiplier *= 10; - } - return actual; - } - // Returns true if all the bytes in the specified array are nulls, false otherwise. internal static bool IsAllNullBytes(Span buffer) => buffer.IndexOfAnyExcept((byte)0) < 0; @@ -191,9 +174,10 @@ internal static bool TryGetStringAsBaseTenInteger(IReadOnlyDictionary buffer) + /// Parses a byte span that represents an ASCII string containing a number in octal base. + internal static T ParseOctal(ReadOnlySpan buffer) where T : struct, INumber { - string str = GetTrimmedAsciiString(buffer); - return string.IsNullOrEmpty(str) ? 0 : Convert.ToInt32(str, fromBase: 8); - } + buffer = TrimEndingNullsAndSpaces(buffer); + buffer = TrimLeadingNullsAndSpaces(buffer); - // Receives a byte array that represents an ASCII string containing a number in octal base. - // Converts the array to an octal base number, then transforms it to ten base and returns it. - internal static long GetTenBaseLongFromOctalAsciiChars(Span buffer) - { - string str = GetTrimmedAsciiString(buffer); - return string.IsNullOrEmpty(str) ? 0 : Convert.ToInt64(str, fromBase: 8); + if (buffer.Length == 0) + { + return T.Zero; + } + + T octalFactor = T.CreateTruncating(8u); + T value = T.Zero; + foreach (byte b in buffer) + { + uint digit = (uint)(b - '0'); + if (digit >= 8) + { + ThrowInvalidNumber(); + } + + value = checked((value * octalFactor) + T.CreateTruncating(digit)); + } + + return value; } + [DoesNotReturn] + private static void ThrowInvalidNumber() => + throw new InvalidDataException(SR.Format(SR.TarInvalidNumber)); + // Returns the string contained in the specified buffer of bytes, // in the specified encoding, removing the trailing null or space chars. private static string GetTrimmedString(ReadOnlySpan buffer, Encoding encoding) + { + buffer = TrimEndingNullsAndSpaces(buffer); + return buffer.IsEmpty ? string.Empty : encoding.GetString(buffer); + } + + internal static ReadOnlySpan TrimEndingNullsAndSpaces(ReadOnlySpan buffer) { int trimmedLength = buffer.Length; - while (trimmedLength > 0 && IsByteNullOrSpace(buffer[trimmedLength - 1])) + while (trimmedLength > 0 && buffer[trimmedLength - 1] is 0 or 32) { trimmedLength--; } - return trimmedLength == 0 ? string.Empty : encoding.GetString(buffer.Slice(0, trimmedLength)); + return buffer.Slice(0, trimmedLength); + } + + private static ReadOnlySpan TrimLeadingNullsAndSpaces(ReadOnlySpan buffer) + { + int newStart = 0; + while (newStart < buffer.Length && buffer[newStart] is 0 or 32) + { + newStart++; + } - static bool IsByteNullOrSpace(byte c) => c is 0 or 32; + return buffer.Slice(newStart); } // Returns the ASCII string contained in the specified buffer of bytes, @@ -292,7 +307,7 @@ internal static async ValueTask SkipBlockAlignmentPaddingAsync(Stream archi } // Throws if the specified entry type is not supported for the specified format. - internal static void ThrowIfEntryTypeNotSupported(TarEntryType entryType, TarEntryFormat archiveFormat) + internal static void ThrowIfEntryTypeNotSupported(TarEntryType entryType, TarEntryFormat archiveFormat, [CallerArgumentExpression("entryType")] string? paramName = null) { switch (archiveFormat) { @@ -366,10 +381,10 @@ TarEntryType.RegularFile or case TarEntryFormat.Unknown: default: - throw new FormatException(string.Format(SR.TarInvalidFormat, archiveFormat)); + throw new InvalidDataException(string.Format(SR.TarInvalidFormat, archiveFormat)); } - throw new InvalidOperationException(string.Format(SR.TarEntryTypeNotSupported, entryType, archiveFormat)); + throw new ArgumentException(string.Format(SR.TarEntryTypeNotSupportedInFormat, entryType, archiveFormat), paramName); } } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs index 7e951c5243e35c..d431b215d9db30 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs @@ -19,7 +19,6 @@ public sealed class TarReader : IDisposable, IAsyncDisposable private readonly bool _leaveOpen; private TarEntry? _previouslyReadEntry; private List? _dataStreamsToDispose; - private bool _readFirstEntry; private bool _reachedEndMarkers; internal Stream _archiveStream; @@ -28,15 +27,16 @@ public sealed class TarReader : IDisposable, IAsyncDisposable /// Initializes a instance that can read tar entries from the specified stream, and can optionally leave the stream open upon disposal of this instance. /// /// The stream to read from. - /// to dispose the when this instance is disposed; to leave the stream open. - /// is unreadable. + /// to dispose the when this instance is disposed, as well as all the non-null instances from the entries that were visited by this reader; to leave all the streams open. + /// does not support reading. + /// is . public TarReader(Stream archiveStream, bool leaveOpen = false) { ArgumentNullException.ThrowIfNull(archiveStream); if (!archiveStream.CanRead) { - throw new IOException(SR.IO_NotSupported_UnreadableStream); + throw new ArgumentException(SR.IO_NotSupported_UnreadableStream, nameof(archiveStream)); } _archiveStream = archiveStream; @@ -44,28 +44,57 @@ public TarReader(Stream archiveStream, bool leaveOpen = false) _previouslyReadEntry = null; _isDisposed = false; - _readFirstEntry = false; _reachedEndMarkers = false; } /// - /// Disposes the current instance, and disposes the streams of all the entries that were read from the archive. + /// Disposes the current instance, closes the archive stream, and disposes the non-null instances of all the entries that were read from the archive if the leaveOpen argument was set to in the constructor. /// /// The property of any entry can be replaced with a new stream. If the user decides to replace it on a instance that was obtained using a , the underlying stream gets disposed immediately, freeing the of origin from the responsibility of having to dispose it. public void Dispose() { - Dispose(disposing: true); - GC.SuppressFinalize(this); + if (!_isDisposed) + { + _isDisposed = true; + + if (!_leaveOpen) + { + if (_dataStreamsToDispose?.Count > 0) + { + foreach (Stream s in _dataStreamsToDispose) + { + s.Dispose(); + } + } + + _archiveStream.Dispose(); + } + } } /// - /// Asynchronously disposes the current instance, and disposes the streams of all the entries that were read from the archive. + /// Asynchronously disposes the current instance, and disposes the non-null instances of all the entries that were read from the archive. /// /// The property of any entry can be replaced with a new stream. If the user decides to replace it on a instance that was obtained using a , the underlying stream gets disposed immediately, freeing the of origin from the responsibility of having to dispose it. public async ValueTask DisposeAsync() { - await DisposeAsync(disposing: true).ConfigureAwait(false); - GC.SuppressFinalize(this); + if (!_isDisposed) + { + _isDisposed = true; + + if (!_leaveOpen) + { + if (_dataStreamsToDispose?.Count > 0) + { + foreach (Stream s in _dataStreamsToDispose) + { + await s.DisposeAsync().ConfigureAwait(false); + } + } + + await _archiveStream.DisposeAsync().ConfigureAwait(false); + } + } } /// @@ -75,7 +104,7 @@ public async ValueTask DisposeAsync() /// Set it to if the data should not be copied into a new stream. If the underlying stream is unseekable, the user has the responsibility of reading and processing the immediately after calling this method. /// The default value is . /// A instance if a valid entry was found, or if the end of the archive has been reached. - /// The archive is malformed. + /// The entry's data is malformed. /// -or- /// The archive contains entries in different formats. /// -or- @@ -104,11 +133,6 @@ public async ValueTask DisposeAsync() TarHeader? header = TryGetNextEntryHeader(copyData); if (header != null) { - if (!_readFirstEntry) - { - _readFirstEntry = true; - } - TarEntry entry = header._format switch { TarEntryFormat.Pax => header._typeFlag is TarEntryType.GlobalExtendedAttributes ? @@ -118,6 +142,11 @@ public async ValueTask DisposeAsync() TarEntryFormat.V7 or TarEntryFormat.Unknown or _ => new V7TarEntry(header, this), }; + if (_archiveStream.CanSeek && _archiveStream.Length == _archiveStream.Position) + { + _reachedEndMarkers = true; + } + _previouslyReadEntry = entry; PreserveDataStreamForDisposalIfNeeded(entry); return entry; @@ -135,7 +164,7 @@ public async ValueTask DisposeAsync() /// The default value is . /// The token to monitor for cancellation requests. The default value is . /// A value task containing a instance if a valid entry was found, or if the end of the archive has been reached. - /// The archive is malformed. + /// The archive is malformed. /// -or- /// The archive contains entries in different formats. /// -or- @@ -200,10 +229,10 @@ internal void AdvanceDataStreamIfNeeded() { long bytesToSkip = _previouslyReadEntry._header._size - dataStream.Position; TarHelpers.AdvanceStream(_archiveStream, bytesToSkip); - TarHelpers.SkipBlockAlignmentPadding(_archiveStream, _previouslyReadEntry._header._size); dataStream.HasReachedEnd = true; // Now the pointer is beyond the limit, so any read attempts should throw } } + TarHelpers.SkipBlockAlignmentPadding(_archiveStream, _previouslyReadEntry._header._size); } } @@ -242,56 +271,10 @@ internal async ValueTask AdvanceDataStreamIfNeededAsync(CancellationToken cancel { long bytesToSkip = _previouslyReadEntry._header._size - dataStream.Position; await TarHelpers.AdvanceStreamAsync(_archiveStream, bytesToSkip, cancellationToken).ConfigureAwait(false); - await TarHelpers.SkipBlockAlignmentPaddingAsync(_archiveStream, _previouslyReadEntry._header._size, cancellationToken).ConfigureAwait(false); dataStream.HasReachedEnd = true; // Now the pointer is beyond the limit, so any read attempts should throw } } - } - } - - // Disposes the current instance. - // If 'disposing' is 'false', the method was called from the finalizer. - private void Dispose(bool disposing) - { - if (disposing && !_isDisposed) - { - try - { - if (!_leaveOpen && _dataStreamsToDispose?.Count > 0) - { - foreach (Stream s in _dataStreamsToDispose) - { - s.Dispose(); - } - } - } - finally - { - _isDisposed = true; - } - } - } - - // Asynchronously disposes the current instance. - // If 'disposing' is 'false', the method was called from the finalizer. - private async ValueTask DisposeAsync(bool disposing) - { - if (disposing && !_isDisposed) - { - try - { - if (!_leaveOpen && _dataStreamsToDispose?.Count > 0) - { - foreach (Stream s in _dataStreamsToDispose) - { - await s.DisposeAsync().ConfigureAwait(false); - } - } - } - finally - { - _isDisposed = true; - } + await TarHelpers.SkipBlockAlignmentPaddingAsync(_archiveStream, _previouslyReadEntry._header._size, cancellationToken).ConfigureAwait(false); } } @@ -303,11 +286,6 @@ private async ValueTask DisposeAsync(bool disposing) TarHeader? header = await TryGetNextEntryHeaderAsync(copyData, cancellationToken).ConfigureAwait(false); if (header != null) { - if (!_readFirstEntry) - { - _readFirstEntry = true; - } - TarEntry entry = header._format switch { TarEntryFormat.Pax => header._typeFlag is TarEntryType.GlobalExtendedAttributes ? @@ -317,6 +295,11 @@ private async ValueTask DisposeAsync(bool disposing) TarEntryFormat.V7 or TarEntryFormat.Unknown or _ => new V7TarEntry(header, this), }; + if (_archiveStream.CanSeek && _archiveStream.Length == _archiveStream.Position) + { + _reachedEndMarkers = true; + } + _previouslyReadEntry = entry; PreserveDataStreamForDisposalIfNeeded(entry); return entry; @@ -335,7 +318,7 @@ private async ValueTask DisposeAsync(bool disposing) { Debug.Assert(!_reachedEndMarkers); - TarHeader? header = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Unknown); + TarHeader? header = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Unknown, processDataBlock: true); if (header == null) { @@ -377,7 +360,7 @@ private async ValueTask DisposeAsync(bool disposing) Debug.Assert(!_reachedEndMarkers); - TarHeader? header = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Unknown, cancellationToken).ConfigureAwait(false); + TarHeader? header = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Unknown, processDataBlock: true, cancellationToken).ConfigureAwait(false); if (header == null) { return null; @@ -413,9 +396,10 @@ private async ValueTask DisposeAsync(bool disposing) // and returns the actual entry with the processed extended attributes saved in the _extendedAttributes dictionary. private bool TryProcessExtendedAttributesHeader(TarHeader extendedAttributesHeader, bool copyData, [NotNullWhen(returnValue: true)] out TarHeader? actualHeader) { - actualHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Pax); + // Don't process the data block of the actual entry just yet, because there's a slim chance + // that the extended attributes contain a size that we need to override in the header + actualHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Pax, processDataBlock: false); - // Now get the actual entry if (actualHeader == null) { return false; @@ -427,12 +411,15 @@ TarEntryType.ExtendedAttributes or TarEntryType.LongLink or TarEntryType.LongPath) { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, actualHeader._typeFlag, TarEntryType.ExtendedAttributes)); + throw new InvalidDataException(string.Format(SR.TarUnexpectedMetadataEntry, actualHeader._typeFlag, TarEntryType.ExtendedAttributes)); } // Replace all the attributes representing standard fields with the extended ones, if any actualHeader.ReplaceNormalAttributesWithExtended(extendedAttributesHeader.ExtendedAttributes); + // We retrieved the extended attributes, now we can read the data, and always with the right size + actualHeader.ProcessDataBlock(_archiveStream, copyData); + return true; } @@ -442,8 +429,9 @@ TarEntryType.LongLink or { cancellationToken.ThrowIfCancellationRequested(); - // Now get the actual entry - TarHeader? actualHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Pax, cancellationToken).ConfigureAwait(false); + // Don't process the data block of the actual entry just yet, because there's a slim chance + // that the extended attributes contain a size that we need to override in the header + TarHeader? actualHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Pax, processDataBlock: false, cancellationToken).ConfigureAwait(false); if (actualHeader == null) { return null; @@ -455,18 +443,21 @@ TarEntryType.ExtendedAttributes or TarEntryType.LongLink or TarEntryType.LongPath) { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, actualHeader._typeFlag, TarEntryType.ExtendedAttributes)); + throw new InvalidDataException(string.Format(SR.TarUnexpectedMetadataEntry, actualHeader._typeFlag, TarEntryType.ExtendedAttributes)); } // Can't have two extended attribute metadata entries in a row if (actualHeader._typeFlag is TarEntryType.ExtendedAttributes) { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, TarEntryType.ExtendedAttributes, TarEntryType.ExtendedAttributes)); + throw new InvalidDataException(string.Format(SR.TarUnexpectedMetadataEntry, TarEntryType.ExtendedAttributes, TarEntryType.ExtendedAttributes)); } // Replace all the attributes representing standard fields with the extended ones, if any actualHeader.ReplaceNormalAttributesWithExtended(extendedAttributesHeader.ExtendedAttributes); + // We retrieved the extended attributes, now we can read the data, and always with the right size + actualHeader.ProcessDataBlock(_archiveStream, copyData); + return actualHeader; } @@ -476,7 +467,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta { finalHeader = new(TarEntryFormat.Gnu); - TarHeader? secondHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Gnu); + TarHeader? secondHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Gnu, processDataBlock: true); // Get the second entry, which is the actual entry if (secondHeader == null) @@ -487,14 +478,14 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta // Can't have two identical metadata entries in a row if (secondHeader._typeFlag == header._typeFlag) { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, secondHeader._typeFlag, header._typeFlag)); + throw new InvalidDataException(string.Format(SR.TarUnexpectedMetadataEntry, secondHeader._typeFlag, header._typeFlag)); } // It's possible to have the two different metadata entries in a row if ((header._typeFlag is TarEntryType.LongLink && secondHeader._typeFlag is TarEntryType.LongPath) || (header._typeFlag is TarEntryType.LongPath && secondHeader._typeFlag is TarEntryType.LongLink)) { - TarHeader? thirdHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Gnu); + TarHeader? thirdHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Gnu, processDataBlock: true); // Get the third entry, which is the actual entry if (thirdHeader == null) @@ -505,7 +496,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta // Can't have three GNU metadata entries in a row if (thirdHeader._typeFlag is TarEntryType.LongLink or TarEntryType.LongPath) { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, thirdHeader._typeFlag, secondHeader._typeFlag)); + throw new InvalidDataException(string.Format(SR.TarUnexpectedMetadataEntry, thirdHeader._typeFlag, secondHeader._typeFlag)); } if (header._typeFlag is TarEntryType.LongLink) @@ -553,7 +544,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta cancellationToken.ThrowIfCancellationRequested(); // Get the second entry, which is the actual entry - TarHeader? secondHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Gnu, cancellationToken).ConfigureAwait(false); + TarHeader? secondHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Gnu, processDataBlock: true, cancellationToken).ConfigureAwait(false); if (secondHeader == null) { return null; @@ -562,7 +553,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta // Can't have two identical metadata entries in a row if (secondHeader._typeFlag == header._typeFlag) { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, secondHeader._typeFlag, header._typeFlag)); + throw new InvalidDataException(string.Format(SR.TarUnexpectedMetadataEntry, secondHeader._typeFlag, header._typeFlag)); } TarHeader finalHeader; @@ -572,7 +563,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta (header._typeFlag is TarEntryType.LongPath && secondHeader._typeFlag is TarEntryType.LongLink)) { // Get the third entry, which is the actual entry - TarHeader? thirdHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Gnu, cancellationToken).ConfigureAwait(false); + TarHeader? thirdHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Gnu, processDataBlock: true, cancellationToken).ConfigureAwait(false); if (thirdHeader == null) { return null; @@ -581,7 +572,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta // Can't have three GNU metadata entries in a row if (thirdHeader._typeFlag is TarEntryType.LongLink or TarEntryType.LongPath) { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, thirdHeader._typeFlag, secondHeader._typeFlag)); + throw new InvalidDataException(string.Format(SR.TarUnexpectedMetadataEntry, thirdHeader._typeFlag, secondHeader._typeFlag)); } if (header._typeFlag is TarEntryType.LongLink) diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs index 7e678bcd918711..ccdc2b49b017c6 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs @@ -46,7 +46,7 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil TarEntryFormat.Ustar => new UstarTarEntry(entryType, entryName), TarEntryFormat.Pax => new PaxTarEntry(entryType, entryName), TarEntryFormat.Gnu => new GnuTarEntry(entryType, entryName), - _ => throw new FormatException(string.Format(SR.TarInvalidFormat, Format)), + _ => throw new InvalidDataException(string.Format(SR.TarInvalidFormat, Format)), }; if (entryType is TarEntryType.BlockDevice or TarEntryType.CharacterDevice) @@ -66,7 +66,8 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil entry._header._aTime = TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch(status.ATime); entry._header._cTime = TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch(status.CTime); - entry._header._mode = status.Mode & 4095; // First 12 bits + // This mask only keeps the least significant 12 bits valid for UnixFileModes + entry._header._mode = status.Mode & (int)TarHelpers.ValidUnixFileModes; // Uid and UName entry._header._uid = (int)status.Uid; @@ -81,8 +82,10 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil entry._header._gid = (int)status.Gid; if (!_groupIdentifiers.TryGetValue(status.Gid, out string? gName)) { - gName = Interop.Sys.GetGroupName(status.Gid); - _groupIdentifiers.Add(status.Gid, gName); + if (Interop.Sys.TryGetGroupName(status.Gid, out gName)) + { + _groupIdentifiers.Add(status.Gid, gName); + } } entry._header._gName = gName; @@ -94,16 +97,7 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil if (entry.EntryType is TarEntryType.RegularFile or TarEntryType.V7RegularFile) { Debug.Assert(entry._header._dataStream == null); - - FileStreamOptions options = new() - { - Mode = FileMode.Open, - Access = FileAccess.Read, - Share = FileShare.Read, - Options = fileOptions - }; - - entry._header._dataStream = new FileStream(fullPath, options); + entry._header._dataStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, fileOptions); } return entry; diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs index bfb6cf17f11076..7452246f742ed6 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs @@ -22,15 +22,15 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil FileAttributes attributes = File.GetAttributes(fullPath); TarEntryType entryType; - if (attributes.HasFlag(FileAttributes.ReparsePoint)) + if ((attributes & FileAttributes.ReparsePoint) != 0) { entryType = TarEntryType.SymbolicLink; } - else if (attributes.HasFlag(FileAttributes.Directory)) + else if ((attributes & FileAttributes.Directory) != 0) { entryType = TarEntryType.Directory; } - else if (attributes.HasFlag(FileAttributes.Normal) || attributes.HasFlag(FileAttributes.Archive)) + else if ((attributes & (FileAttributes.Normal | FileAttributes.Archive)) != 0) { entryType = Format is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile; } @@ -45,10 +45,10 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil TarEntryFormat.Ustar => new UstarTarEntry(entryType, entryName), TarEntryFormat.Pax => new PaxTarEntry(entryType, entryName), TarEntryFormat.Gnu => new GnuTarEntry(entryType, entryName), - _ => throw new FormatException(string.Format(SR.TarInvalidFormat, Format)), + _ => throw new InvalidDataException(string.Format(SR.TarInvalidFormat, Format)), }; - FileSystemInfo info = attributes.HasFlag(FileAttributes.Directory) ? new DirectoryInfo(fullPath) : new FileInfo(fullPath); + FileSystemInfo info = (attributes & FileAttributes.Directory) != 0 ? new DirectoryInfo(fullPath) : new FileInfo(fullPath); entry._header._mTime = info.LastWriteTimeUtc; entry._header._aTime = info.LastAccessTimeUtc; @@ -63,16 +63,8 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil if (entry.EntryType is TarEntryType.RegularFile or TarEntryType.V7RegularFile) { - FileStreamOptions options = new() - { - Mode = FileMode.Open, - Access = FileAccess.Read, - Share = FileShare.Read, - Options = fileOptions - }; - Debug.Assert(entry._header._dataStream == null); - entry._header._dataStream = new FileStream(fullPath, options); + entry._header._dataStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, fileOptions); } return entry; diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs index 94692316840235..d7b7ceceac3464 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs @@ -25,6 +25,8 @@ public sealed partial class TarWriter : IDisposable, IAsyncDisposable /// /// The stream to write to. /// When using this constructor, is used as the default format of the entries written to the archive using the method. + /// is . + /// does not support writing. public TarWriter(Stream archiveStream) : this(archiveStream, TarEntryFormat.Pax, leaveOpen: false) { @@ -35,6 +37,8 @@ public TarWriter(Stream archiveStream) /// /// The stream to write to. /// to dispose the when this instance is disposed; to leave the stream open. + /// is . + /// is unwritable. public TarWriter(Stream archiveStream, bool leaveOpen = false) : this(archiveStream, TarEntryFormat.Pax, leaveOpen) { @@ -50,7 +54,7 @@ public TarWriter(Stream archiveStream, bool leaveOpen = false) /// to leave the stream open. The default is . /// The recommended format is for its flexibility. /// is . - /// is unwritable. + /// is unwritable. /// is either , or not one of the other enum values. public TarWriter(Stream archiveStream, TarEntryFormat format = TarEntryFormat.Pax, bool leaveOpen = false) { @@ -58,7 +62,7 @@ public TarWriter(Stream archiveStream, TarEntryFormat format = TarEntryFormat.Pa if (!archiveStream.CanWrite) { - throw new IOException(SR.IO_NotSupported_UnwritableStream); + throw new ArgumentException(SR.IO_NotSupported_UnwritableStream); } if (format is not TarEntryFormat.V7 and not TarEntryFormat.Ustar and not TarEntryFormat.Pax and not TarEntryFormat.Gnu) @@ -84,8 +88,20 @@ public TarWriter(Stream archiveStream, TarEntryFormat format = TarEntryFormat.Pa /// public void Dispose() { - Dispose(disposing: true); - GC.SuppressFinalize(this); + if (!_isDisposed) + { + _isDisposed = true; + + if (_wroteEntries) + { + WriteFinalRecords(); + } + + if (!_leaveOpen) + { + _archiveStream.Dispose(); + } + } } /// @@ -93,8 +109,20 @@ public void Dispose() /// public async ValueTask DisposeAsync() { - await DisposeAsync(disposing: true).ConfigureAwait(false); - GC.SuppressFinalize(this); + if (!_isDisposed) + { + _isDisposed = true; + + if (_wroteEntries) + { + await WriteFinalRecordsAsync().ConfigureAwait(false); + } + + if (!_leaveOpen) + { + await _archiveStream.DisposeAsync().ConfigureAwait(false); + } + } } /// @@ -185,13 +213,15 @@ private async Task ReadFileFromDiskAndWriteToArchiveStreamAsEntryAsync(string fu /// /// /// + /// The entry type is or and the is or empty. /// The archive stream is disposed. - /// The entry type of the is not supported for writing. + /// is . /// An I/O problem occurred. public void WriteEntry(TarEntry entry) { ObjectDisposedException.ThrowIf(_isDisposed, this); ArgumentNullException.ThrowIfNull(entry); + ValidateEntryLinkName(entry._header._typeFlag, entry._header._linkName); WriteEntryInternal(entry); } @@ -226,8 +256,9 @@ public void WriteEntry(TarEntry entry) /// /// /// + /// The entry type is or and the is or empty. /// The archive stream is disposed. - /// The entry type of the is not supported for writing. + /// is . /// An I/O problem occurred. public Task WriteEntryAsync(TarEntry entry, CancellationToken cancellationToken = default) { @@ -238,98 +269,44 @@ public Task WriteEntryAsync(TarEntry entry, CancellationToken cancellationToken ObjectDisposedException.ThrowIf(_isDisposed, this); ArgumentNullException.ThrowIfNull(entry); + ValidateEntryLinkName(entry._header._typeFlag, entry._header._linkName); return WriteEntryAsyncInternal(entry, cancellationToken); } - // Disposes the current instance. - // If 'disposing' is 'false', the method was called from the finalizer. - private void Dispose(bool disposing) + // Portion of the WriteEntry(entry) method that rents a buffer and writes to the archive. + private void WriteEntryInternal(TarEntry entry) { - if (disposing && !_isDisposed) + Span buffer = stackalloc byte[TarHelpers.RecordSize]; + buffer.Clear(); + + switch (entry.Format) { - try - { - if (_wroteEntries) - { - WriteFinalRecords(); - } + case TarEntryFormat.V7: + entry._header.WriteAsV7(_archiveStream, buffer); + break; + case TarEntryFormat.Ustar: + entry._header.WriteAsUstar(_archiveStream, buffer); + break; - if (!_leaveOpen) + case TarEntryFormat.Pax: + if (entry._header._typeFlag is TarEntryType.GlobalExtendedAttributes) { - _archiveStream.Dispose(); + entry._header.WriteAsPaxGlobalExtendedAttributes(_archiveStream, buffer, _nextGlobalExtendedAttributesEntryNumber++); } - } - finally - { - _isDisposed = true; - } - } - } - - // Asynchronously disposes the current instance. - // If 'disposing' is 'false', the method was called from the finalizer. - private async ValueTask DisposeAsync(bool disposing) - { - if (disposing && !_isDisposed) - { - try - { - if (_wroteEntries) + else { - await WriteFinalRecordsAsync().ConfigureAwait(false); + entry._header.WriteAsPax(_archiveStream, buffer); } + break; + case TarEntryFormat.Gnu: + entry._header.WriteAsGnu(_archiveStream, buffer); + break; - if (!_leaveOpen) - { - await _archiveStream.DisposeAsync().ConfigureAwait(false); - } - } - finally - { - _isDisposed = true; - } - } - } - - // Portion of the WriteEntry(entry) method that rents a buffer and writes to the archive. - private void WriteEntryInternal(TarEntry entry) - { - byte[] rented = ArrayPool.Shared.Rent(minimumLength: TarHelpers.RecordSize); - Span buffer = rented.AsSpan(0, TarHelpers.RecordSize); // minimumLength means the array could've been larger - buffer.Clear(); // Rented arrays aren't clean - try - { - switch (entry.Format) - { - case TarEntryFormat.V7: - entry._header.WriteAsV7(_archiveStream, buffer); - break; - case TarEntryFormat.Ustar: - entry._header.WriteAsUstar(_archiveStream, buffer); - break; - case TarEntryFormat.Pax: - if (entry._header._typeFlag is TarEntryType.GlobalExtendedAttributes) - { - entry._header.WriteAsPaxGlobalExtendedAttributes(_archiveStream, buffer, _nextGlobalExtendedAttributesEntryNumber++); - } - else - { - entry._header.WriteAsPax(_archiveStream, buffer); - } - break; - case TarEntryFormat.Gnu: - entry._header.WriteAsGnu(_archiveStream, buffer); - break; - default: - Debug.Assert(entry.Format == TarEntryFormat.Unknown, "Missing format handler"); - throw new FormatException(string.Format(SR.TarInvalidFormat, Format)); - } - } - finally - { - ArrayPool.Shared.Return(rented); + default: + Debug.Assert(entry.Format == TarEntryFormat.Unknown, "Missing format handler"); + throw new InvalidDataException(string.Format(SR.TarInvalidFormat, Format)); } _wroteEntries = true; @@ -351,7 +328,7 @@ private async Task WriteEntryAsyncInternal(TarEntry entry, CancellationToken can TarEntryFormat.Pax when entry._header._typeFlag is TarEntryType.GlobalExtendedAttributes => entry._header.WriteAsPaxGlobalExtendedAttributesAsync(_archiveStream, buffer, _nextGlobalExtendedAttributesEntryNumber++, cancellationToken), TarEntryFormat.Pax => entry._header.WriteAsPaxAsync(_archiveStream, buffer, cancellationToken), TarEntryFormat.Gnu => entry._header.WriteAsGnuAsync(_archiveStream, buffer, cancellationToken), - _ => throw new FormatException(string.Format(SR.TarInvalidFormat, Format)), + _ => throw new InvalidDataException(string.Format(SR.TarInvalidFormat, Format)), }; await task.ConfigureAwait(false); @@ -364,7 +341,9 @@ private async Task WriteEntryAsyncInternal(TarEntry entry, CancellationToken can // by two records consisting entirely of zero bytes. private void WriteFinalRecords() { - byte[] emptyRecord = new byte[TarHelpers.RecordSize]; + Span emptyRecord = stackalloc byte[TarHelpers.RecordSize]; + emptyRecord.Clear(); + _archiveStream.Write(emptyRecord); _archiveStream.Write(emptyRecord); } @@ -374,9 +353,14 @@ private void WriteFinalRecords() // This method is called from DisposeAsync, so we don't want to propagate a cancelled CancellationToken. private async ValueTask WriteFinalRecordsAsync() { - byte[] emptyRecord = new byte[TarHelpers.RecordSize]; - await _archiveStream.WriteAsync(emptyRecord, cancellationToken: default).ConfigureAwait(false); - await _archiveStream.WriteAsync(emptyRecord, cancellationToken: default).ConfigureAwait(false); + const int TwoRecordSize = TarHelpers.RecordSize * 2; + + byte[] twoEmptyRecords = ArrayPool.Shared.Rent(TwoRecordSize); + Array.Clear(twoEmptyRecords, 0, TwoRecordSize); + + await _archiveStream.WriteAsync(twoEmptyRecords.AsMemory(0, TwoRecordSize), cancellationToken: default).ConfigureAwait(false); + + ArrayPool.Shared.Return(twoEmptyRecords); } private (string, string) ValidateWriteEntryArguments(string fileName, string? entryName) @@ -389,5 +373,16 @@ private async ValueTask WriteFinalRecordsAsync() return (fullPath, actualEntryName); } + + private static void ValidateEntryLinkName(TarEntryType entryType, string? linkName) + { + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + if (string.IsNullOrEmpty(linkName)) + { + throw new ArgumentException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty, "entry"); + } + } + } } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/UstarTarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/UstarTarEntry.cs index 10c5aad7325ade..9969520d96551c 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/UstarTarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/UstarTarEntry.cs @@ -19,14 +19,16 @@ internal UstarTarEntry(TarHeader header, TarReader readerOfOrigin) /// /// The type of the entry. /// A string with the path and file name of this entry. - /// is null or empty. - /// The entry type is not supported for creating an entry. /// When creating an instance using the constructor, only the following entry types are supported: /// /// In all platforms: , , , . /// In Unix platforms only: , and . /// /// + /// is . + /// is empty. + /// -or- + /// is not supported in the specified format. public UstarTarEntry(TarEntryType entryType, string entryName) : base(entryType, entryName, TarEntryFormat.Ustar, isGea: false) { @@ -36,6 +38,9 @@ public UstarTarEntry(TarEntryType entryType, string entryName) /// /// Initializes a new instance by converting the specified entry into the Ustar format. /// + /// is a and cannot be converted. + /// -or- + /// The entry type of is not supported for conversion to the Ustar format. public UstarTarEntry(TarEntry other) : base(other, TarEntryFormat.Ustar) { diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/V7TarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/V7TarEntry.cs index fd049c05d90c93..4b20f10faf4beb 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/V7TarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/V7TarEntry.cs @@ -19,9 +19,11 @@ internal V7TarEntry(TarHeader header, TarReader readerOfOrigin) /// /// The type of the entry. /// A string with the path and file name of this entry. - /// is null or empty. - /// The entry type is not supported for creating an entry. /// When creating an instance using the constructor, only the following entry types are supported: , , and . + /// is . + /// is empty. + /// -or- + /// is not supported for creating an entry. public V7TarEntry(TarEntryType entryType, string entryName) : base(entryType, entryName, TarEntryFormat.V7, isGea: false) { @@ -30,6 +32,9 @@ public V7TarEntry(TarEntryType entryType, string entryName) /// /// Initializes a new instance by converting the specified entry into the V7 format. /// + /// is a and cannot be converted. + /// -or- + /// The entry type of is not supported for conversion to the V7 format. public V7TarEntry(TarEntry other) : base(other, TarEntryFormat.V7) { diff --git a/src/libraries/System.Formats.Tar/tests/SimulatedDataStream.cs b/src/libraries/System.Formats.Tar/tests/SimulatedDataStream.cs new file mode 100644 index 00000000000000..60ba2e86be88bb --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/SimulatedDataStream.cs @@ -0,0 +1,110 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; + +namespace System.Formats.Tar.Tests +{ + // Stream that returns `length` amount of bytes with leading and trailing dummy data to verify it was correctly preserved + // e.g: + // 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x00, 0x00, 0x00, ...0x00, 0x01, 0x02, 0x03, 0x04, 0x05. + // or in decimal: + // 1, 2, 3, 4, 5, 0, 0, 0 ,0, ...0, 1, 2, 3, 4, 5. + internal class SimulatedDataStream : Stream + { + private readonly long _length; + private long _position; + internal static ReadOnlyMemory DummyData { get; } = GetDummyData(); + + private static ReadOnlyMemory GetDummyData() + { + byte[] data = new byte[5]; + new Random(42).NextBytes(data); + return data; + } + + public override bool CanRead => true; + + public override bool CanSeek => true; + + public override bool CanWrite => false; + + public override long Length => _length; + + public override long Position + { + get => _position; + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(); + } + + _position = value; + } + } + + public SimulatedDataStream(long length) + { + if (length < 10) + { + throw new ArgumentException("Length must be at least 10 to append 5 bytes of dummy data at the beginning and end."); + } + + _length = length; + } + + public override void Flush() { } + + public override int Read(byte[] buffer, int offset, int count) + => Read(buffer.AsSpan(offset, count)); + + public override int Read(Span buffer) + { + if (_position == _length || buffer.Length == 0) + { + return 0; + } + + ReadOnlySpan dummyData = DummyData.Span; + + // Write leading data and return. + if (_position < dummyData.Length - 1) + { + int bytesToWrite = Math.Min(dummyData.Length, buffer.Length); + dummyData.Slice((int)_position, bytesToWrite).CopyTo(buffer); + + _position += bytesToWrite; + return bytesToWrite; + } + + // write middle data by just zero'ing the read buffer. + int bytesToConsume = (int)Math.Min(_length - _position, buffer.Length); + Span usefulBuffer = buffer.Slice(0, bytesToConsume); + usefulBuffer.Clear(); + + _position += bytesToConsume; + long tempPos = _position; + long dummyDataTrailingLimit = _length - dummyData.Length; + + // and write trailing data at the end. + while (tempPos > dummyDataTrailingLimit) + { + int dummyDataIdx = (int)(tempPos - dummyDataTrailingLimit) - 1; + int bufferIdx = usefulBuffer.Length - 1 - (int)(_length - tempPos); + + usefulBuffer[bufferIdx] = dummyData[dummyDataIdx]; + tempPos--; + } + + return bytesToConsume; + } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + } +} diff --git a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj index f06e639b74f564..063d064a0cb8db 100644 --- a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj +++ b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj @@ -2,7 +2,7 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix true - $(LibrariesProjectRoot)/Common/tests/Resources/Strings.resx + $(MSBuildProjectDirectory)\..\src\Resources\Strings.resx true true @@ -10,12 +10,16 @@ + + + + @@ -29,6 +33,7 @@ + @@ -40,15 +45,20 @@ + + + + + @@ -61,9 +71,10 @@ - + + diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/GnuTarEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/GnuTarEntry.Tests.cs index e9bb630dd3f873..4c13273a30ceaf 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/GnuTarEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/GnuTarEntry.Tests.cs @@ -19,23 +19,23 @@ public void Constructor_InvalidEntryName() [Fact] public void Constructor_UnsupportedEntryTypes() { - Assert.Throws(() => new GnuTarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.V7RegularFile, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.V7RegularFile, InitialEntryName)); // These are specific to GNU, but currently the user cannot create them manually - Assert.Throws(() => new GnuTarEntry(TarEntryType.ContiguousFile, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.DirectoryList, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.MultiVolume, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.SparseFile, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.TapeVolume, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.ContiguousFile, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.DirectoryList, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.MultiVolume, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.SparseFile, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.TapeVolume, InitialEntryName)); // The user should not create these entries manually - Assert.Throws(() => new GnuTarEntry(TarEntryType.LongLink, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.LongPath, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.LongLink, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.LongPath, InitialEntryName)); } [Fact] diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Conversion.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Conversion.Tests.cs index 8125e45708eb30..e42f1df0ea6ea1 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Conversion.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Conversion.Tests.cs @@ -61,6 +61,15 @@ public class PaxTarEntry_Conversion_Tests : TarTestsConversionBase [Fact] public void Constructor_ConversionFromGnu_CharacterDevice() => TestConstructionConversion(TarEntryType.CharacterDevice, TarEntryFormat.Gnu, TarEntryFormat.Pax); + [Fact] + public void Constructor_ConversionFromPaxGEA_ToAny_Throw() + { + Assert.Throws(() => new V7TarEntry(new PaxGlobalExtendedAttributesTarEntry(new Dictionary()))); + Assert.Throws(() => new UstarTarEntry(new PaxGlobalExtendedAttributesTarEntry(new Dictionary()))); + Assert.Throws(() => new PaxTarEntry(new PaxGlobalExtendedAttributesTarEntry(new Dictionary()))); + Assert.Throws(() => new GnuTarEntry(new PaxGlobalExtendedAttributesTarEntry(new Dictionary()))); + } + [Theory] [InlineData(TarEntryFormat.V7)] [InlineData(TarEntryFormat.Ustar)] diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Tests.cs index 69e028ae883aa2..0e8bcc952cea5d 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Tests.cs @@ -19,21 +19,21 @@ public void Constructor_InvalidEntryName() [Fact] public void Constructor_UnsupportedEntryTypes() { - Assert.Throws(() => new PaxTarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.ContiguousFile, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.DirectoryList, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.LongLink, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.LongPath, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.MultiVolume, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.V7RegularFile, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.SparseFile, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.TapeVolume, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.ContiguousFile, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.DirectoryList, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.LongLink, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.LongPath, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.MultiVolume, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.V7RegularFile, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.SparseFile, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.TapeVolume, InitialEntryName)); // The user should not be creating these entries manually in pax - Assert.Throws(() => new PaxTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); } [Fact] diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.Conversion.Tests.Base.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.Conversion.Tests.Base.cs index 6626ca958f25d4..9fa5e57d460c19 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.Conversion.Tests.Base.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.Conversion.Tests.Base.cs @@ -184,7 +184,7 @@ protected TarEntry InvokeTarEntryConversionConstructor(TarEntryFormat targetForm TarEntryFormat.Ustar => new UstarTarEntry(other), TarEntryFormat.Pax => new PaxTarEntry(other), TarEntryFormat.Gnu => new GnuTarEntry(other), - _ => throw new FormatException($"Unexpected format: {targetFormat}") + _ => throw new InvalidDataException($"Unexpected format: {targetFormat}") }; } } diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFile.Tests.Unix.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFile.Tests.Unix.cs index cee5f6bc7dfd85..d5438928a59dd2 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFile.Tests.Unix.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFile.Tests.Unix.cs @@ -23,7 +23,7 @@ public static IEnumerable GetFormatsAndSpecialFiles() } } - [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndOnUnixAndSuperUser))] + [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] [MemberData(nameof(GetFormatsAndSpecialFiles))] public void Extract_SpecialFiles(TarEntryFormat format, TarEntryType entryType) { @@ -36,7 +36,7 @@ public void Extract_SpecialFiles(TarEntryFormat format, TarEntryType entryType) Verify_Extract_SpecialFiles(destination, entry, entryType); } - [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndOnUnixAndSuperUser))] + [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] [MemberData(nameof(GetFormatsAndSpecialFiles))] public async Task Extract_SpecialFiles_Async(TarEntryFormat format, TarEntryType entryType) { diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/UstarTarEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/UstarTarEntry.Tests.cs index 8087621f57ae1d..2fdcb34069e50b 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/UstarTarEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/UstarTarEntry.Tests.cs @@ -19,19 +19,19 @@ public void Constructor_InvalidEntryName() [Fact] public void Constructor_UnsupportedEntryTypes() { - Assert.Throws(() => new UstarTarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.ContiguousFile, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.DirectoryList, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.LongLink, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.LongPath, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.MultiVolume, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.V7RegularFile, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.SparseFile, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.TapeVolume, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.ContiguousFile, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.DirectoryList, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.LongLink, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.LongPath, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.MultiVolume, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.V7RegularFile, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.SparseFile, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.TapeVolume, InitialEntryName)); } [Fact] diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Conversion.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Conversion.Tests.cs index 3f5e41a8f3609f..8856ec4d54809a 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Conversion.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Conversion.Tests.cs @@ -16,25 +16,25 @@ public class V7TarEntry_Conversion_Tests : TarTestsConversionBase [Fact] public void Constructor_Conversion_UnsupportedEntryTypes_Ustar() { - Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.BlockDevice, InitialEntryName))); - Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); - Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.Fifo, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.BlockDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.Fifo, InitialEntryName))); } [Fact] public void Constructor_Conversion_UnsupportedEntryTypes_Pax() { - Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.BlockDevice, InitialEntryName))); - Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); - Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.Fifo, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.BlockDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.Fifo, InitialEntryName))); } [Fact] public void Constructor_Conversion_UnsupportedEntryTypes_Gnu() { - Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.BlockDevice, InitialEntryName))); - Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); - Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.Fifo, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.BlockDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.Fifo, InitialEntryName))); } [Fact] diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Tests.cs index 36dab66fb24e2f..2bf5471d4fa484 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Tests.cs @@ -20,22 +20,22 @@ public void Constructor_InvalidEntryName() [Fact] public void Constructor_UnsupportedEntryTypes() { - Assert.Throws(() => new V7TarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); + Assert.Throws(() => new V7TarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.BlockDevice, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.CharacterDevice, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.ContiguousFile, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.DirectoryList, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.Fifo, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.LongLink, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.LongPath, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.MultiVolume, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.RegularFile, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.SparseFile, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.TapeVolume, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.BlockDevice, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.CharacterDevice, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.ContiguousFile, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.DirectoryList, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.Fifo, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.LongLink, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.LongPath, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.MultiVolume, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.RegularFile, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.SparseFile, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.TapeVolume, InitialEntryName)); } [Fact] diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Roundtrip.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Roundtrip.cs new file mode 100644 index 00000000000000..460dec75780082 --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Roundtrip.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using Xunit; + +namespace System.Formats.Tar.Tests +{ + public class TarFile_CreateFromDirectory_Roundtrip_Tests : TarTestsBase + { + [ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + [InlineData("./file.txt", "subDirectory")] + [InlineData("../file.txt", "subDirectory")] + [InlineData("../file.txt", "subDirectory1/subDirectory1.1")] + [InlineData("./file.txt", "subDirectory1/subDirectory1.1")] + [InlineData("./file.txt", null)] + public void SymlinkRelativeTargets_InsideTheArchive_RoundtripsSuccessfully(string symlinkTargetPath, string subDirectory) + { + using TempDirectory root = new TempDirectory(); + + string destinationArchive = Path.Join(root.Path, "destination.tar"); + + string sourceDirectoryName = Path.Join(root.Path, "baseDirectory"); + Directory.CreateDirectory(sourceDirectoryName); + + string destinationDirectoryName = Path.Join(root.Path, "destinationDirectory"); + Directory.CreateDirectory(destinationDirectoryName); + + string sourceSubDirectory = Path.Join(sourceDirectoryName, subDirectory); + if(subDirectory != null) Directory.CreateDirectory(sourceSubDirectory); + + File.Create(Path.Join(sourceDirectoryName, subDirectory, symlinkTargetPath)).Dispose(); + File.CreateSymbolicLink(Path.Join(sourceSubDirectory, "linkToFile"), symlinkTargetPath); + + TarFile.CreateFromDirectory(sourceDirectoryName, destinationArchive, includeBaseDirectory: false); + + using FileStream archiveStream = File.OpenRead(destinationArchive); + TarFile.ExtractToDirectory(archiveStream, destinationDirectoryName, overwriteFiles: true); + + string destinationSubDirectory = Path.Join(destinationDirectoryName, subDirectory); + string symlinkPath = Path.Join(destinationSubDirectory, "linkToFile"); + Assert.True(File.Exists(symlinkPath)); + + FileInfo? fileInfo = new(symlinkPath); + Assert.Equal(symlinkTargetPath, fileInfo.LinkTarget); + + FileSystemInfo? symlinkTarget = File.ResolveLinkTarget(symlinkPath, returnFinalTarget: true); + Assert.True(File.Exists(symlinkTarget.FullName)); + } + + [ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + [InlineData("../file.txt", null)] + [InlineData("../../file.txt", "subDirectory")] + public void SymlinkRelativeTargets_OutsideTheArchive_Fails(string symlinkTargetPath, string subDirectory) + { + using TempDirectory root = new TempDirectory(); + + string destinationArchive = Path.Join(root.Path, "destination.tar"); + + string sourceDirectoryName = Path.Join(root.Path, "baseDirectory"); + Directory.CreateDirectory(sourceDirectoryName); + + string destinationDirectoryName = Path.Join(root.Path, "destinationDirectory"); + Directory.CreateDirectory(destinationDirectoryName); + + string sourceSubDirectory = Path.Join(sourceDirectoryName, subDirectory); + if(subDirectory != null) Directory.CreateDirectory(sourceSubDirectory); + + File.CreateSymbolicLink(Path.Join(sourceSubDirectory, "linkToFile"), symlinkTargetPath); + + TarFile.CreateFromDirectory(sourceDirectoryName, destinationArchive, includeBaseDirectory: false); + + using FileStream archiveStream = File.OpenRead(destinationArchive); + Exception exception = Assert.Throws(() => TarFile.ExtractToDirectory(archiveStream, destinationDirectoryName, overwriteFiles: true)); + + Assert.Equal(SR.Format(SR.TarExtractingResultsLinkOutside, symlinkTargetPath, destinationDirectoryName), exception.Message); + } + } +} diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs index 75a3495d94e5e5..308a9b68ba4def 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs @@ -193,5 +193,65 @@ public void IncludeAllSegmentsOfPath(bool includeBaseDirectory) Assert.Null(reader.GetNextEntry()); } + + [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + public void SkipRecursionIntoDirectorySymlinks() + { + using TempDirectory root = new TempDirectory(); + + string destinationArchive = Path.Join(root.Path, "destination.tar"); + + string externalDirectory = Path.Join(root.Path, "externalDirectory"); + Directory.CreateDirectory(externalDirectory); + + File.Create(Path.Join(externalDirectory, "file.txt")).Dispose(); + + string sourceDirectoryName = Path.Join(root.Path, "baseDirectory"); + Directory.CreateDirectory(sourceDirectoryName); + + string subDirectory = Path.Join(sourceDirectoryName, "subDirectory"); + Directory.CreateSymbolicLink(subDirectory, externalDirectory); // Should not recurse here + + TarFile.CreateFromDirectory(sourceDirectoryName, destinationArchive, includeBaseDirectory: false); + + using FileStream archiveStream = File.OpenRead(destinationArchive); + using TarReader reader = new(archiveStream, leaveOpen: false); + + TarEntry entry = reader.GetNextEntry(); + Assert.NotNull(entry); + Assert.Equal("subDirectory/", entry.Name); + Assert.Equal(TarEntryType.SymbolicLink, entry.EntryType); + + Assert.Null(reader.GetNextEntry()); // file.txt should not be found + } + + [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + public void SkipRecursionIntoBaseDirectorySymlink() + { + using TempDirectory root = new TempDirectory(); + + string destinationArchive = Path.Join(root.Path, "destination.tar"); + + string externalDirectory = Path.Join(root.Path, "externalDirectory"); + Directory.CreateDirectory(externalDirectory); + + string subDirectory = Path.Join(externalDirectory, "subDirectory"); + Directory.CreateDirectory(subDirectory); + + string sourceDirectoryName = Path.Join(root.Path, "baseDirectory"); + Directory.CreateSymbolicLink(sourceDirectoryName, externalDirectory); + + TarFile.CreateFromDirectory(sourceDirectoryName, destinationArchive, includeBaseDirectory: true); // Base directory is a symlink, do not recurse + + using FileStream archiveStream = File.OpenRead(destinationArchive); + using TarReader reader = new(archiveStream, leaveOpen: false); + + TarEntry entry = reader.GetNextEntry(); + Assert.NotNull(entry); + Assert.Equal("baseDirectory/", entry.Name); + Assert.Equal(TarEntryType.SymbolicLink, entry.EntryType); + + Assert.Null(reader.GetNextEntry()); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.Stream.Tests.cs index ca0cb57fab783b..33b550960ef563 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.Stream.Tests.cs @@ -28,7 +28,7 @@ public void UnwritableStream_Throws() { using MemoryStream archive = new MemoryStream(); using WrappedStream unwritable = new WrappedStream(archive, canRead: true, canWrite: false, canSeek: true); - Assert.Throws(() => TarFile.CreateFromDirectory(sourceDirectoryName: "path",destination: unwritable, includeBaseDirectory: false)); + Assert.Throws(() => TarFile.CreateFromDirectory(sourceDirectoryName: "path",destination: unwritable, includeBaseDirectory: false)); } [Fact] diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Roundtrip.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Roundtrip.cs new file mode 100644 index 00000000000000..7908d459ced984 --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Roundtrip.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.Threading.Tasks; +using Xunit; + +namespace System.Formats.Tar.Tests +{ + public class TarFile_CreateFromDirectoryAsync_Roundtrip_Tests : TarTestsBase + { + [ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + [InlineData("./file.txt", "subDirectory")] + [InlineData("../file.txt", "subDirectory")] + [InlineData("../file.txt", "subDirectory1/subDirectory1.1")] + [InlineData("./file.txt", "subDirectory1/subDirectory1.1")] + [InlineData("./file.txt", null)] + public async Task SymlinkRelativeTargets_InsideTheArchive_RoundtripsSuccessfully_Async(string symlinkTargetPath, string subDirectory) + { + using TempDirectory root = new TempDirectory(); + + string destinationArchive = Path.Join(root.Path, "destination.tar"); + + string sourceDirectoryName = Path.Join(root.Path, "baseDirectory"); + Directory.CreateDirectory(sourceDirectoryName); + + string destinationDirectoryName = Path.Join(root.Path, "destinationDirectory"); + Directory.CreateDirectory(destinationDirectoryName); + + string sourceSubDirectory = Path.Join(sourceDirectoryName, subDirectory); + if (subDirectory != null) Directory.CreateDirectory(sourceSubDirectory); + + File.Create(Path.Join(sourceDirectoryName, subDirectory, symlinkTargetPath)).Dispose(); + File.CreateSymbolicLink(Path.Join(sourceSubDirectory, "linkToFile"), symlinkTargetPath); + + await TarFile.CreateFromDirectoryAsync(sourceDirectoryName, destinationArchive, includeBaseDirectory: false); + + await using FileStream archiveStream = File.OpenRead(destinationArchive); + await TarFile.ExtractToDirectoryAsync(archiveStream, destinationDirectoryName, overwriteFiles: true); + + string destinationSubDirectory = Path.Join(destinationDirectoryName, subDirectory); + string symlinkPath = Path.Join(destinationSubDirectory, "linkToFile"); + Assert.True(File.Exists(symlinkPath)); + + FileInfo? fileInfo = new(symlinkPath); + Assert.Equal(symlinkTargetPath, fileInfo.LinkTarget); + + FileSystemInfo? symlinkTarget = File.ResolveLinkTarget(symlinkPath, returnFinalTarget: true); + Assert.True(File.Exists(symlinkTarget.FullName)); + } + + [ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + [InlineData("../file.txt", null)] + [InlineData("../../file.txt", "subDirectory")] + public async Task SymlinkRelativeTargets_OutsideTheArchive_Fails_Async(string symlinkTargetPath, string subDirectory) + { + using TempDirectory root = new TempDirectory(); + + string destinationArchive = Path.Join(root.Path, "destination.tar"); + + string sourceDirectoryName = Path.Join(root.Path, "baseDirectory"); + Directory.CreateDirectory(sourceDirectoryName); + + string destinationDirectoryName = Path.Join(root.Path, "destinationDirectory"); + Directory.CreateDirectory(destinationDirectoryName); + + string sourceSubDirectory = Path.Join(sourceDirectoryName, subDirectory); + if (subDirectory != null) Directory.CreateDirectory(sourceSubDirectory); + + File.CreateSymbolicLink(Path.Join(sourceSubDirectory, "linkToFile"), symlinkTargetPath); + + await TarFile.CreateFromDirectoryAsync(sourceDirectoryName, destinationArchive, includeBaseDirectory: false); + + using FileStream archiveStream = File.OpenRead(destinationArchive); + Exception exception = await Assert.ThrowsAsync(() => TarFile.ExtractToDirectoryAsync(archiveStream, destinationDirectoryName, overwriteFiles: true)); + + Assert.Equal(SR.Format(SR.TarExtractingResultsLinkOutside, symlinkTargetPath, destinationDirectoryName), exception.Message); + } + } +} diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs index c9b4377cd03285..78b051ae33b08e 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs @@ -237,5 +237,65 @@ public async Task IncludeAllSegmentsOfPath_Async(bool includeBaseDirectory) } } } + + [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + public async Task SkipRecursionIntoDirectorySymlinksAsync() + { + using TempDirectory root = new TempDirectory(); + + string destinationArchive = Path.Join(root.Path, "destination.tar"); + + string externalDirectory = Path.Join(root.Path, "externalDirectory"); + Directory.CreateDirectory(externalDirectory); + + File.Create(Path.Join(externalDirectory, "file.txt")).Dispose(); + + string sourceDirectoryName = Path.Join(root.Path, "baseDirectory"); + Directory.CreateDirectory(sourceDirectoryName); + + string subDirectory = Path.Join(sourceDirectoryName, "subDirectory"); + Directory.CreateSymbolicLink(subDirectory, externalDirectory); // Should not recurse here + + await TarFile.CreateFromDirectoryAsync(sourceDirectoryName, destinationArchive, includeBaseDirectory: false); + + await using FileStream archiveStream = File.OpenRead(destinationArchive); + await using TarReader reader = new(archiveStream, leaveOpen: false); + + TarEntry entry = await reader.GetNextEntryAsync(); + Assert.NotNull(entry); + Assert.Equal("subDirectory/", entry.Name); + Assert.Equal(TarEntryType.SymbolicLink, entry.EntryType); + + Assert.Null(await reader.GetNextEntryAsync()); // file.txt should not be found + } + + [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + public async Task SkipRecursionIntoBaseDirectorySymlinkAsync() + { + using TempDirectory root = new TempDirectory(); + + string destinationArchive = Path.Join(root.Path, "destination.tar"); + + string externalDirectory = Path.Join(root.Path, "externalDirectory"); + Directory.CreateDirectory(externalDirectory); + + string subDirectory = Path.Join(externalDirectory, "subDirectory"); + Directory.CreateDirectory(subDirectory); + + string sourceDirectoryName = Path.Join(root.Path, "baseDirectory"); + Directory.CreateSymbolicLink(sourceDirectoryName, externalDirectory); + + await TarFile.CreateFromDirectoryAsync(sourceDirectoryName, destinationArchive, includeBaseDirectory: true); // Base directory is a symlink, do not recurse + + await using FileStream archiveStream = File.OpenRead(destinationArchive); + await using TarReader reader = new(archiveStream, leaveOpen: false); + + TarEntry entry = await reader.GetNextEntryAsync(); + Assert.NotNull(entry); + Assert.Equal("baseDirectory/", entry.Name); + Assert.Equal(TarEntryType.SymbolicLink, entry.EntryType); + + Assert.Null(await reader.GetNextEntryAsync()); // subDirectory should not be found + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.Stream.Tests.cs index 7a67aac64508f7..8dbde4461290a6 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.Stream.Tests.cs @@ -48,7 +48,7 @@ public async Task UnwritableStream_Throws_Async() { await using (WrappedStream unwritable = new WrappedStream(archiveStream, canRead: true, canWrite: false, canSeek: true)) { - await Assert.ThrowsAsync(() => TarFile.CreateFromDirectoryAsync(sourceDirectoryName: "path", destination: unwritable, includeBaseDirectory: false)); + await Assert.ThrowsAsync(() => TarFile.CreateFromDirectoryAsync(sourceDirectoryName: "path", destination: unwritable, includeBaseDirectory: false)); } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Base.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Base.cs new file mode 100644 index 00000000000000..b5a1f111aa6a46 --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Base.cs @@ -0,0 +1,80 @@ +// 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.Formats.Tar.Tests; + +public abstract class TarFile_ExtractToDirectory_Tests : TarTestsBase +{ + // TarEntryFormat, TarEntryType, string fileName + public static IEnumerable GetExactRootDirMatchCases() + { + var allValidFormats = new TarEntryFormat[] { TarEntryFormat.V7, TarEntryFormat.Ustar, TarEntryFormat.Pax, TarEntryFormat.Gnu }; + + foreach (TarEntryFormat format in allValidFormats) + { + yield return new object[] + { + format, + TarEntryType.Directory, + "" // Root directory + }; + yield return new object[] + { + format, + TarEntryType.Directory, + "./" // Slash dot root directory + }; + yield return new object[] + { + format, + TarEntryType.Directory, + "directory", + }; + yield return new object[] + { + format, + GetTarEntryTypeForTarEntryFormat(TarEntryType.RegularFile, format), + "file.txt" + }; + } + + var formatsThatHandleLongFileNames = new TarEntryFormat[] { TarEntryFormat.Pax, TarEntryFormat.Gnu }; + var longFileNames = new string[] + { + // Long path with many short segment names and a filename + "folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/file.txt", + // Long path with single long segment name and a filename + "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryverylongfoldername/file.txt", + // Long path with single long leaf filename + "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryverylongfilename.txt", + }; + + foreach (TarEntryFormat format in formatsThatHandleLongFileNames) + { + foreach (string filePath in longFileNames) + { + yield return new object[] { format, TarEntryType.RegularFile, filePath }; + } + } + + var longFolderNames = new string[] + { + // Long path with many short segment names and a filename + "folderfolder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder", + // Long path with single long segment name and a filename + "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryverylongfoldername/folder", + // Long path with single long leaf filename + "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryverylongfoldername" + }; + + foreach (TarEntryFormat format in formatsThatHandleLongFileNames) + { + foreach (string folderPath in longFolderNames) + { + yield return new object[] { format, TarEntryType.Directory, folderPath }; + } + } + } +} diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs index f741926cdd8ef1..90c5eba6b9ff97 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs @@ -44,6 +44,33 @@ public void NonExistentDirectory_Throws() Assert.Throws(() => TarFile.ExtractToDirectory(sourceFileName: filePath, destinationDirectoryName: dirPath, overwriteFiles: false)); } + [Fact] + public void SetsLastModifiedTimeOnExtractedFiles() + { + using TempDirectory root = new TempDirectory(); + + string inDir = Path.Join(root.Path, "indir"); + string inFile = Path.Join(inDir, "file"); + + string tarFile = Path.Join(root.Path, "file.tar"); + + string outDir = Path.Join(root.Path, "outdir"); + string outFile = Path.Join(outDir, "file"); + + Directory.CreateDirectory(inDir); + File.Create(inFile).Dispose(); + var dt = new DateTime(2001, 1, 2, 3, 4, 5, DateTimeKind.Local); + File.SetLastWriteTime(inFile, dt); + + TarFile.CreateFromDirectory(sourceDirectoryName: inDir, destinationFileName: tarFile, includeBaseDirectory: false); + + Directory.CreateDirectory(outDir); + TarFile.ExtractToDirectory(sourceFileName: tarFile, destinationDirectoryName: outDir, overwriteFiles: false); + + Assert.True(File.Exists(outFile)); + Assert.InRange(File.GetLastWriteTime(outFile).Ticks, dt.AddSeconds(-3).Ticks, dt.AddSeconds(3).Ticks); // include some slop for filesystem granularity + } + [Theory] [InlineData(TestTarFormat.v7)] [InlineData(TestTarFormat.ustar)] @@ -216,13 +243,9 @@ public void UnixFileModes(bool overwrite) Assert.True(File.Exists(filePath), $"{filePath}' does not exist."); AssertFileModeEquals(filePath, TestPermission2); - // Missing parents are created with DefaultDirectoryMode. - // The mode is not set when overwrite == true if there is no entry and the directory exists before extracting. + // Missing parents are created with CreateDirectoryDefaultMode. Assert.True(Directory.Exists(missingParentPath), $"{missingParentPath}' does not exist."); - if (!overwrite) - { - AssertFileModeEquals(missingParentPath, DefaultDirectoryMode); - } + AssertFileModeEquals(missingParentPath, CreateDirectoryDefaultMode); Assert.True(Directory.Exists(missingParentDirPath), $"{missingParentDirPath}' does not exist."); AssertFileModeEquals(missingParentDirPath, TestPermission3); diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs index 5a68d5a95d5d26..3cec81f62a501f 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs @@ -3,12 +3,13 @@ using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; using Xunit; namespace System.Formats.Tar.Tests { - public class TarFile_ExtractToDirectory_Stream_Tests : TarTestsBase + public class TarFile_ExtractToDirectory_Stream_Tests : TarFile_ExtractToDirectory_Tests { [Fact] public void NullStream_Throws() @@ -29,7 +30,7 @@ public void UnreadableStream_Throws() { using MemoryStream archive = new MemoryStream(); using WrappedStream unreadable = new WrappedStream(archive, canRead: false, canWrite: true, canSeek: true); - Assert.Throws(() => TarFile.ExtractToDirectory(unreadable, destinationDirectoryName: "path", overwriteFiles: false)); + Assert.Throws(() => TarFile.ExtractToDirectory(unreadable, destinationDirectoryName: "path", overwriteFiles: false)); } [Fact] @@ -145,5 +146,228 @@ private void Extract_LinkEntry_TargetInsideDirectory_Internal(TarEntryType entry Assert.Equal(2, Directory.GetFileSystemEntries(baseDir).Count()); } + + [Theory] + [InlineData(512)] + [InlineData(512 + 1)] + [InlineData(512 + 512 - 1)] + public void Extract_UnseekableStream_BlockAlignmentPadding_DoesNotAffectNextEntries(int contentSize) + { + byte[] fileContents = new byte[contentSize]; + Array.Fill(fileContents, 0x1); + + using var archive = new MemoryStream(); + using (var compressor = new GZipStream(archive, CompressionMode.Compress, leaveOpen: true)) + { + using var writer = new TarWriter(compressor); + var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file"); + entry1.DataStream = new MemoryStream(fileContents); + writer.WriteEntry(entry1); + + var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); + writer.WriteEntry(entry2); + } + + archive.Position = 0; + using var decompressor = new GZipStream(archive, CompressionMode.Decompress); + using var reader = new TarReader(decompressor); + + using TempDirectory destination = new TempDirectory(); + TarFile.ExtractToDirectory(decompressor, destination.Path, overwriteFiles: true); + + Assert.Equal(2, Directory.GetFileSystemEntries(destination.Path, "*", SearchOption.AllDirectories).Count()); + } + + [Fact] + public void PaxNameCollision_DedupInExtendedAttributes() + { + using TempDirectory root = new(); + + string sharedRootFolders = Path.Join(root.Path, "folder with spaces", new string('a', 100)); + string path1 = Path.Join(sharedRootFolders, "entry 1 with spaces.txt"); + string path2 = Path.Join(sharedRootFolders, "entry 2 with spaces.txt"); + + using MemoryStream stream = new(); + using (TarWriter writer = new(stream, TarEntryFormat.Pax, leaveOpen: true)) + { + // Paths don't fit in the standard 'name' field, but they differ in the filename, + // which is fully stored as an extended attribute + PaxTarEntry entry1 = new(TarEntryType.RegularFile, path1); + writer.WriteEntry(entry1); + PaxTarEntry entry2 = new(TarEntryType.RegularFile, path2); + writer.WriteEntry(entry2); + } + stream.Position = 0; + + TarFile.ExtractToDirectory(stream, root.Path, overwriteFiles: true); + + Assert.True(File.Exists(path1)); + Assert.True(Path.Exists(path2)); + } + + [Theory] + [MemberData(nameof(GetExactRootDirMatchCases))] + public void ExtractToDirectory_ExactRootDirMatch_RegularFile_And_Directory_Throws(TarEntryFormat format, TarEntryType entryType, string fileName) + { + ExtractToDirectory_ExactRootDirMatch_RegularFile_And_Directory_Throws_Internal(format, entryType, fileName, inverted: false); + ExtractToDirectory_ExactRootDirMatch_RegularFile_And_Directory_Throws_Internal(format, entryType, fileName, inverted: true); + } + + [Fact] + public void ExtractToDirectory_ExactRootDirMatch_Directory_Relative_Throws() + { + string entryFolderName = "folder"; + string destinationFolderName = "folderSibling"; + + using TempDirectory root = new TempDirectory(); + + string entryFolderPath = Path.Join(root.Path, entryFolderName); + string destinationFolderPath = Path.Join(root.Path, destinationFolderName); + + Directory.CreateDirectory(entryFolderPath); + Directory.CreateDirectory(destinationFolderPath); + + // Relative segments should not change the final destination folder + string dirPath1 = Path.Join(entryFolderPath, "..", "folder"); + string dirPath2 = Path.Join(entryFolderPath, "..", "folder" + Path.DirectorySeparatorChar); + + ExtractRootDirMatch_Verify_Throws(TarEntryFormat.Ustar, TarEntryType.Directory, destinationFolderPath, dirPath1, linkTargetPath: null); + ExtractRootDirMatch_Verify_Throws(TarEntryFormat.Ustar, TarEntryType.Directory, destinationFolderPath, dirPath2, linkTargetPath: null); + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.SupportsHardLinkCreation))] + [InlineData(TarEntryFormat.V7)] + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public void ExtractToDirectory_ExactRootDirMatch_HardLinks_Throws(TarEntryFormat format) + { + ExtractToDirectory_ExactRootDirMatch_Links_Throws(format, TarEntryType.HardLink, inverted: false); + ExtractToDirectory_ExactRootDirMatch_Links_Throws(format, TarEntryType.HardLink, inverted: true); + } + + [ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + [InlineData(TarEntryFormat.V7)] + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public void ExtractToDirectory_ExactRootDirMatch_SymLinks_Throws(TarEntryFormat format) + { + ExtractToDirectory_ExactRootDirMatch_Links_Throws(format, TarEntryType.SymbolicLink, inverted: false); + ExtractToDirectory_ExactRootDirMatch_Links_Throws(format, TarEntryType.SymbolicLink, inverted: true); + } + + [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + public void ExtractToDirectory_ExactRootDirMatch_SymLinks_TargetOutside_Throws() + { + string entryFolderName = "folder"; + string destinationFolderName = "folderSibling"; + + using TempDirectory root = new TempDirectory(); + + string entryFolderPath = Path.Join(root.Path, entryFolderName); + string destinationFolderPath = Path.Join(root.Path, destinationFolderName); + + Directory.CreateDirectory(entryFolderPath); + Directory.CreateDirectory(destinationFolderPath); + + string linkPath = Path.Join(entryFolderPath, "link"); + + // Links target outside the destination path should not be allowed + // Ensure relative segments do not go around this restriction + string linkTargetPath1 = Path.Join(entryFolderPath, "..", entryFolderName); + string linkTargetPath2 = Path.Join(entryFolderPath, "..", entryFolderName + Path.DirectorySeparatorChar); + + ExtractRootDirMatch_Verify_Throws(TarEntryFormat.Ustar, TarEntryType.Directory, destinationFolderPath, linkPath, linkTargetPath1); + ExtractRootDirMatch_Verify_Throws(TarEntryFormat.Ustar, TarEntryType.Directory, destinationFolderPath, linkPath, linkTargetPath2); + } + + private void ExtractToDirectory_ExactRootDirMatch_RegularFile_And_Directory_Throws_Internal(TarEntryFormat format, TarEntryType entryType, string fileName, bool inverted) + { + // inverted == false: + // destination: folderSibling/ + // entry folder: folder/ (does not match destination) + + // inverted == true: + // destination: folder/ + // entry folder: folderSibling/ (does not match destination) + + string entryFolderName = inverted ? "folderSibling" : "folder"; + string destinationFolderName = inverted ? "folder" : "folderSibling"; + + using TempDirectory root = new TempDirectory(); + + string entryFolderPath = Path.Join(root.Path, entryFolderName); + string destinationFolderPath = Path.Join(root.Path, destinationFolderName); + + Directory.CreateDirectory(entryFolderPath); + Directory.CreateDirectory(destinationFolderPath); + + string filePath = Path.Join(entryFolderPath, fileName); + + ExtractRootDirMatch_Verify_Throws(format, entryType, destinationFolderPath, filePath, linkTargetPath: null); + } + + private void ExtractToDirectory_ExactRootDirMatch_Links_Throws(TarEntryFormat format, TarEntryType entryType, bool inverted) + { + // inverted == false: + // destination: folderSibling/ + // entry folder: folder/ (does not match destination) + // link entry file path: folder/link (does not match destination, should not be extracted) + + // inverted == true: + // destination: folder/ + // entry folder: folderSibling/ (does not match destination) + // link entry file path: folderSibling/link (does not match destination, should not be extracted) + + string entryFolderName = inverted ? "folderSibling" : "folder"; + string destinationFolderName = inverted ? "folder" : "folderSibling"; + + string linkTargetFileName = "file.txt"; + string linkFileName = "link"; + + using TempDirectory root = new TempDirectory(); + + string entryFolderPath = Path.Join(root.Path, entryFolderName); + string destinationFolderPath = Path.Join(root.Path, destinationFolderName); + + string linkPath = Path.Join(entryFolderPath, linkFileName); + string linkTargetPath = Path.Join(destinationFolderPath, linkTargetFileName); + + Directory.CreateDirectory(entryFolderPath); + Directory.CreateDirectory(destinationFolderPath); + File.Create(linkTargetPath).Dispose(); + + ExtractRootDirMatch_Verify_Throws(format, entryType, destinationFolderPath, linkPath, linkTargetPath); + } + + private void ExtractRootDirMatch_Verify_Throws(TarEntryFormat format, TarEntryType entryType, string destinationFolderPath, string entryFilePath, string linkTargetPath) + { + using MemoryStream archive = new(); + using (TarWriter writer = new TarWriter(archive, format, leaveOpen: true)) + { + TarEntry entry = InvokeTarEntryCreationConstructor(format, entryType, entryFilePath); + MemoryStream dataStream = null; + if (entryType is TarEntryType.RegularFile or TarEntryType.V7RegularFile) + { + dataStream = new MemoryStream(); + dataStream.Write(new byte[] { 0x1 }); + entry.DataStream = dataStream; + } + if (entryType is TarEntryType.SymbolicLink or TarEntryType.HardLink) + { + entry.LinkName = linkTargetPath; + } + writer.WriteEntry(entry); + if (dataStream != null) + { + dataStream.Dispose(); + } + } + archive.Position = 0; + + Assert.Throws(() => TarFile.ExtractToDirectory(archive, destinationFolderPath, overwriteFiles: false)); + Assert.False(File.Exists(entryFilePath), $"File should not exist: {entryFilePath}"); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs index 01d2457018ce24..19a28d7f59ce39 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs @@ -56,6 +56,33 @@ public async Task NonExistentDirectory_Throws_Async() } } + [Fact] + public async Task SetsLastModifiedTimeOnExtractedFiles() + { + using TempDirectory root = new TempDirectory(); + + string inDir = Path.Join(root.Path, "indir"); + string inFile = Path.Join(inDir, "file"); + + string tarFile = Path.Join(root.Path, "file.tar"); + + string outDir = Path.Join(root.Path, "outdir"); + string outFile = Path.Join(outDir, "file"); + + Directory.CreateDirectory(inDir); + File.Create(inFile).Dispose(); + var dt = new DateTime(2001, 1, 2, 3, 4, 5, DateTimeKind.Local); + File.SetLastWriteTime(inFile, dt); + + await TarFile.CreateFromDirectoryAsync(sourceDirectoryName: inDir, destinationFileName: tarFile, includeBaseDirectory: false); + + Directory.CreateDirectory(outDir); + await TarFile.ExtractToDirectoryAsync(sourceFileName: tarFile, destinationDirectoryName: outDir, overwriteFiles: false); + + Assert.True(File.Exists(outFile)); + Assert.InRange(File.GetLastWriteTime(outFile).Ticks, dt.AddSeconds(-3).Ticks, dt.AddSeconds(3).Ticks); // include some slop for filesystem granularity + } + [Theory] [InlineData(TestTarFormat.v7)] [InlineData(TestTarFormat.ustar)] @@ -191,8 +218,10 @@ public async Task ExtractArchiveWithEntriesThatStartWithSlashDotPrefix_Async() } } - [Fact] - public async Task UnixFileModes_Async() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task UnixFileModes_Async(bool overwrite) { using TempDirectory source = new TempDirectory(); using TempDirectory destination = new TempDirectory(); @@ -223,27 +252,36 @@ public async Task UnixFileModes_Async() writer.WriteEntry(outOfOrderDir); } - await TarFile.ExtractToDirectoryAsync(archivePath, destination.Path, overwriteFiles: false); - string dirPath = Path.Join(destination.Path, "dir"); + string filePath = Path.Join(destination.Path, "file"); + string missingParentPath = Path.Join(destination.Path, "missing_parent"); + string missingParentDirPath = Path.Join(missingParentPath, "dir"); + string outOfOrderDirPath = Path.Join(destination.Path, "out_of_order_parent"); + + if (overwrite) + { + File.OpenWrite(filePath).Dispose(); + Directory.CreateDirectory(dirPath); + Directory.CreateDirectory(missingParentDirPath); + Directory.CreateDirectory(outOfOrderDirPath); + } + + await TarFile.ExtractToDirectoryAsync(archivePath, destination.Path, overwriteFiles: overwrite); + Assert.True(Directory.Exists(dirPath), $"{dirPath}' does not exist."); AssertFileModeEquals(dirPath, TestPermission1); - string filePath = Path.Join(destination.Path, "file"); Assert.True(File.Exists(filePath), $"{filePath}' does not exist."); AssertFileModeEquals(filePath, TestPermission2); - // Missing parents are created with DefaultDirectoryMode. - string missingParentPath = Path.Join(destination.Path, "missing_parent"); + // Missing parents are created with CreateDirectoryDefaultMode. Assert.True(Directory.Exists(missingParentPath), $"{missingParentPath}' does not exist."); - AssertFileModeEquals(missingParentPath, DefaultDirectoryMode); + AssertFileModeEquals(missingParentPath, CreateDirectoryDefaultMode); - string missingParentDirPath = Path.Join(missingParentPath, "dir"); Assert.True(Directory.Exists(missingParentDirPath), $"{missingParentDirPath}' does not exist."); AssertFileModeEquals(missingParentDirPath, TestPermission3); // Directory modes that are out-of-order are still applied. - string outOfOrderDirPath = Path.Join(destination.Path, "out_of_order_parent"); Assert.True(Directory.Exists(outOfOrderDirPath), $"{outOfOrderDirPath}' does not exist."); AssertFileModeEquals(outOfOrderDirPath, TestPermission4); } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs index a35a22c1050f56..a765f4f911eb77 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -10,7 +11,7 @@ namespace System.Formats.Tar.Tests { - public class TarFile_ExtractToDirectoryAsync_Stream_Tests : TarTestsBase + public class TarFile_ExtractToDirectoryAsync_Stream_Tests : TarFile_ExtractToDirectory_Tests { [Fact] public async Task ExtractToDirectoryAsync_Cancel() @@ -44,7 +45,7 @@ public async Task UnreadableStream_Throws_Async() { using (WrappedStream unreadable = new WrappedStream(archive, canRead: false, canWrite: true, canSeek: true)) { - await Assert.ThrowsAsync(() => TarFile.ExtractToDirectoryAsync(unreadable, destinationDirectoryName: "path", overwriteFiles: false)); + await Assert.ThrowsAsync(() => TarFile.ExtractToDirectoryAsync(unreadable, destinationDirectoryName: "path", overwriteFiles: false)); } } } @@ -96,6 +97,42 @@ public async Task ExtractEntry_ManySubfolderSegments_NoPrecedingDirectoryEntries } } + [Fact] + public async Task ExtractEntry_DockerImageTarWithFileTypeInDirectoriesInMode_SuccessfullyExtracts_Async() + { + using (TempDirectory root = new TempDirectory()) + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "misc", "docker-hello-world"); + await TarFile.ExtractToDirectoryAsync(archiveStream, root.Path, overwriteFiles: true); + + Assert.True(File.Exists(Path.Join(root.Path, "manifest.json"))); + Assert.True(File.Exists(Path.Join(root.Path, "repositories"))); + } + } + + [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + public async Task ExtractEntry_PodmanImageTarWithRelativeSymlinksPointingInExtractDirectory_SuccessfullyExtracts_Async() + { + using (TempDirectory root = new TempDirectory()) + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "misc", "podman-hello-world"); + await TarFile.ExtractToDirectoryAsync(archiveStream, root.Path, overwriteFiles: true); + + Assert.True(File.Exists(Path.Join(root.Path, "manifest.json"))); + Assert.True(File.Exists(Path.Join(root.Path, "repositories"))); + Assert.True(File.Exists(Path.Join(root.Path, "efb53921da3394806160641b72a2cbd34ca1a9a8345ac670a85a04ad3d0e3507.tar"))); + + string symlinkPath = Path.Join(root.Path, "e7fc2b397c1ab5af9938f18cc9a80d526cccd1910e4678390157d8cc6c94410d/layer.tar"); + Assert.True(File.Exists(symlinkPath)); + + FileInfo? fileInfo = new(symlinkPath); + Assert.Equal("../efb53921da3394806160641b72a2cbd34ca1a9a8345ac670a85a04ad3d0e3507.tar", fileInfo.LinkTarget); + + FileSystemInfo? symlinkTarget = File.ResolveLinkTarget(symlinkPath, returnFinalTarget: true); + Assert.True(File.Exists(symlinkTarget.FullName)); + } + } + [Theory] [InlineData(TarEntryType.SymbolicLink)] [InlineData(TarEntryType.HardLink)] @@ -174,5 +211,228 @@ private async Task Extract_LinkEntry_TargetInsideDirectory_Internal_Async(TarEnt } } } + + [Theory] + [InlineData(512)] + [InlineData(512 + 1)] + [InlineData(512 + 512 - 1)] + public async Task Extract_UnseekableStream_BlockAlignmentPadding_DoesNotAffectNextEntries_Async(int contentSize) + { + byte[] fileContents = new byte[contentSize]; + Array.Fill(fileContents, 0x1); + + using var archive = new MemoryStream(); + using (var compressor = new GZipStream(archive, CompressionMode.Compress, leaveOpen: true)) + { + using var writer = new TarWriter(compressor); + var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file"); + entry1.DataStream = new MemoryStream(fileContents); + await writer.WriteEntryAsync(entry1); + + var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); + await writer.WriteEntryAsync(entry2); + } + + archive.Position = 0; + using var decompressor = new GZipStream(archive, CompressionMode.Decompress); + using var reader = new TarReader(decompressor); + + using TempDirectory destination = new TempDirectory(); + await TarFile.ExtractToDirectoryAsync(decompressor, destination.Path, overwriteFiles: true); + + Assert.Equal(2, Directory.GetFileSystemEntries(destination.Path, "*", SearchOption.AllDirectories).Count()); + } + + [Fact] + public async Task PaxNameCollision_DedupInExtendedAttributesAsync() + { + using TempDirectory root = new(); + + string sharedRootFolders = Path.Join(root.Path, "folder with spaces", new string('a', 100)); + string path1 = Path.Join(sharedRootFolders, "entry 1 with spaces.txt"); + string path2 = Path.Join(sharedRootFolders, "entry 2 with spaces.txt"); + + await using MemoryStream stream = new(); + await using (TarWriter writer = new(stream, TarEntryFormat.Pax, leaveOpen: true)) + { + // Paths don't fit in the standard 'name' field, but they differ in the filename, + // which is fully stored as an extended attribute + PaxTarEntry entry1 = new(TarEntryType.RegularFile, path1); + await writer.WriteEntryAsync(entry1); + PaxTarEntry entry2 = new(TarEntryType.RegularFile, path2); + await writer.WriteEntryAsync(entry2); + } + stream.Position = 0; + + await TarFile.ExtractToDirectoryAsync(stream, root.Path, overwriteFiles: true); + + Assert.True(File.Exists(path1)); + Assert.True(Path.Exists(path2)); + } + + [Theory] + [MemberData(nameof(GetExactRootDirMatchCases))] + public async Task ExtractToDirectory_ExactRootDirMatch_RegularFile_And_Directory_Throws_Async(TarEntryFormat format, TarEntryType entryType, string fileName) + { + await ExtractToDirectory_ExactRootDirMatch_RegularFile_And_Directory_Throws_Internal_Async(format, entryType, fileName, inverted: false); + await ExtractToDirectory_ExactRootDirMatch_RegularFile_And_Directory_Throws_Internal_Async(format, entryType, fileName, inverted: true); + } + + [Fact] + public async Task ExtractToDirectory_ExactRootDirMatch_Directory_Relative_Throws_Async() + { + string entryFolderName = "folder"; + string destinationFolderName = "folderSibling"; + + using TempDirectory root = new TempDirectory(); + + string entryFolderPath = Path.Join(root.Path, entryFolderName); + string destinationFolderPath = Path.Join(root.Path, destinationFolderName); + + Directory.CreateDirectory(entryFolderPath); + Directory.CreateDirectory(destinationFolderPath); + + // Relative segments should not change the final destination folder + string dirPath1 = Path.Join(entryFolderPath, "..", "folder"); + string dirPath2 = Path.Join(entryFolderPath, "..", "folder" + Path.DirectorySeparatorChar); + + await ExtractRootDirMatch_Verify_Throws_Async(TarEntryFormat.Ustar, TarEntryType.Directory, destinationFolderPath, dirPath1, linkTargetPath: null); + await ExtractRootDirMatch_Verify_Throws_Async(TarEntryFormat.Ustar, TarEntryType.Directory, destinationFolderPath, dirPath2, linkTargetPath: null); + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.SupportsHardLinkCreation))] + [InlineData(TarEntryFormat.V7)] + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public async Task ExtractToDirectory_ExactRootDirMatch_HardLinks_Throws_Async(TarEntryFormat format) + { + await ExtractToDirectory_ExactRootDirMatch_Links_Throws_Async(format, TarEntryType.HardLink, inverted: false); + await ExtractToDirectory_ExactRootDirMatch_Links_Throws_Async(format, TarEntryType.HardLink, inverted: true); + } + + [ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + [InlineData(TarEntryFormat.V7)] + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public async Task ExtractToDirectory_ExactRootDirMatch_SymLinks_Throws_Async(TarEntryFormat format) + { + await ExtractToDirectory_ExactRootDirMatch_Links_Throws_Async(format, TarEntryType.SymbolicLink, inverted: false); + await ExtractToDirectory_ExactRootDirMatch_Links_Throws_Async(format, TarEntryType.SymbolicLink, inverted: true); + } + + [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + public async Task ExtractToDirectory_ExactRootDirMatch_SymLinks_TargetOutside_Throws_Async() + { + string entryFolderName = "folder"; + string destinationFolderName = "folderSibling"; + + using TempDirectory root = new TempDirectory(); + + string entryFolderPath = Path.Join(root.Path, entryFolderName); + string destinationFolderPath = Path.Join(root.Path, destinationFolderName); + + Directory.CreateDirectory(entryFolderPath); + Directory.CreateDirectory(destinationFolderPath); + + string linkPath = Path.Join(entryFolderPath, "link"); + + // Links target outside the destination path should not be allowed + // Ensure relative segments do not go around this restriction + string linkTargetPath1 = Path.Join(entryFolderPath, "..", entryFolderName); + string linkTargetPath2 = Path.Join(entryFolderPath, "..", entryFolderName + Path.DirectorySeparatorChar); + + await ExtractRootDirMatch_Verify_Throws_Async(TarEntryFormat.Ustar, TarEntryType.Directory, destinationFolderPath, linkPath, linkTargetPath1); + await ExtractRootDirMatch_Verify_Throws_Async(TarEntryFormat.Ustar, TarEntryType.Directory, destinationFolderPath, linkPath, linkTargetPath2); + } + + private async Task ExtractToDirectory_ExactRootDirMatch_RegularFile_And_Directory_Throws_Internal_Async(TarEntryFormat format, TarEntryType entryType, string fileName, bool inverted) + { + // inverted == false: + // destination: folderSibling/ + // entry folder: folder/ (does not match destination) + + // inverted == true: + // destination: folder/ + // entry folder: folderSibling/ (does not match destination) + + string entryFolderName = inverted ? "folderSibling" : "folder"; + string destinationFolderName = inverted ? "folder" : "folderSibling"; + + using TempDirectory root = new TempDirectory(); + + string entryFolderPath = Path.Join(root.Path, entryFolderName); + string destinationFolderPath = Path.Join(root.Path, destinationFolderName); + + Directory.CreateDirectory(entryFolderPath); + Directory.CreateDirectory(destinationFolderPath); + + string filePath = Path.Join(entryFolderPath, fileName); + + await ExtractRootDirMatch_Verify_Throws_Async(format, entryType, destinationFolderPath, filePath, linkTargetPath: null); + } + + private Task ExtractToDirectory_ExactRootDirMatch_Links_Throws_Async(TarEntryFormat format, TarEntryType entryType, bool inverted) + { + // inverted == false: + // destination: folderSibling/ + // entry folder: folder/ (does not match destination) + // link entry file path: folder/link (does not match destination, should not be extracted) + + // inverted == true: + // destination: folder/ + // entry folder: folderSibling/ (does not match destination) + // link entry file path: folderSibling/link (does not match destination, should not be extracted) + + string entryFolderName = inverted ? "folderSibling" : "folder"; + string destinationFolderName = inverted ? "folder" : "folderSibling"; + + string linkTargetFileName = "file.txt"; + string linkFileName = "link"; + + using TempDirectory root = new TempDirectory(); + + string entryFolderPath = Path.Join(root.Path, entryFolderName); + string destinationFolderPath = Path.Join(root.Path, destinationFolderName); + + string linkPath = Path.Join(entryFolderPath, linkFileName); + string linkTargetPath = Path.Join(destinationFolderPath, linkTargetFileName); + + Directory.CreateDirectory(entryFolderPath); + Directory.CreateDirectory(destinationFolderPath); + File.Create(linkTargetPath).Dispose(); + + return ExtractRootDirMatch_Verify_Throws_Async(format, entryType, destinationFolderPath, linkPath, linkTargetPath); + } + + private async Task ExtractRootDirMatch_Verify_Throws_Async(TarEntryFormat format, TarEntryType entryType, string destinationFolderPath, string entryFilePath, string linkTargetPath) + { + await using MemoryStream archive = new(); + await using (TarWriter writer = new TarWriter(archive, format, leaveOpen: true)) + { + TarEntry entry = InvokeTarEntryCreationConstructor(format, entryType, entryFilePath); + MemoryStream dataStream = null; + if (entryType is TarEntryType.RegularFile or TarEntryType.V7RegularFile) + { + dataStream = new MemoryStream(); + await dataStream.WriteAsync(new byte[] { 0x1 }); + entry.DataStream = dataStream; + } + if (entryType is TarEntryType.SymbolicLink or TarEntryType.HardLink) + { + entry.LinkName = linkTargetPath; + } + await writer.WriteEntryAsync(entry); + if (dataStream != null) + { + await dataStream.DisposeAsync(); + } + } + archive.Position = 0; + + await Assert.ThrowsAsync(() => TarFile.ExtractToDirectoryAsync(archive, destinationFolderPath, overwriteFiles: false)); + Assert.False(File.Exists(entryFilePath), $"File should not exist: {entryFilePath}"); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.Async.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.Async.Tests.cs new file mode 100644 index 00000000000000..50216ed04ccbcb --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.Async.Tests.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace System.Formats.Tar.Tests +{ + public partial class TarReader_Tests : TarTestsBase + { + [Fact] + public async Task TarReader_LeaveOpen_False_Async() + { + await using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax, "many_small_files"); + List dataStreams = new List(); + await using (TarReader reader = new TarReader(ms, leaveOpen: false)) + { + TarEntry entry; + while ((entry = await reader.GetNextEntryAsync()) != null) + { + if (entry.DataStream != null) + { + dataStreams.Add(entry.DataStream); + } + } + } + + Assert.Throws(() => ms.ReadByte()); + + Assert.True(dataStreams.Any()); + foreach (Stream ds in dataStreams) + { + Assert.Throws(() => ds.ReadByte()); + } + } + + [Fact] + public async Task TarReader_LeaveOpen_True_Async() + { + await using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax, "many_small_files"); + List dataStreams = new List(); + await using (TarReader reader = new TarReader(ms, leaveOpen: true)) + { + TarEntry entry; + while ((entry = await reader.GetNextEntryAsync()) != null) + { + if (entry.DataStream != null) + { + dataStreams.Add(entry.DataStream); + } + } + } + + ms.ReadByte(); // Should not throw + + Assert.True(dataStreams.Any()); + foreach (Stream ds in dataStreams) + { + ds.ReadByte(); // Should not throw + ds.Dispose(); + } + } + } +} diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.cs index 14087fb030d020..d86cfa4e34dd47 100644 --- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.IO.Compression; using System.Linq; using System.Threading.Tasks; using Xunit; @@ -125,5 +126,245 @@ public Task Read_Archive_LongFileName_Over100_Under255_Async(TarEntryFormat form [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] public Task Read_Archive_LongPath_Over255_Async(TarEntryFormat format, TestTarFormat testFormat) => Read_Archive_LongPath_Over255_Async_Internal(format, testFormat); + + [Theory] + [MemberData(nameof(GetV7TestCaseNames))] + public Task ReadDataStreamOfTarGzV7Async(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.v7, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetUstarTestCaseNames))] + public Task ReadDataStreamOfTarGzUstarAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.ustar, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadDataStreamOfTarGzPaxAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.pax, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadDataStreamOfTarGzPaxGeaAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.pax_gea, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadDataStreamOfTarGzOldGnuAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.oldgnu, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadDataStreamOfTarGzGnuAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.gnu, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetV7TestCaseNames))] + public Task ReadCopiedDataStreamOfTarGzV7Async(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.v7, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetUstarTestCaseNames))] + public Task ReadCopiedDataStreamOfTarGzUstarAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.ustar, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadCopiedDataStreamOfTarGzPaxAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.pax, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadCopiedDataStreamOfTarGzPaxGeaAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.pax_gea, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadCopiedDataStreamOfTarGzOldGnuAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.oldgnu, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadCopiedDataStreamOfTarGzGnuAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.gnu, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetGoLangTarTestCaseNames))] + public Task ReadDataStreamOfExternalAssetsGoLangAsync(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternalAsync("golang_tar", testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetNodeTarTestCaseNames))] + public Task ReadDataStreamOfExternalAssetsNodeAsync(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternalAsync("node-tar", testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetRsTarTestCaseNames))] + public Task ReadDataStreamOfExternalAssetsRsAsync(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternalAsync("tar-rs", testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetGoLangTarTestCaseNames))] + public Task ReadCopiedDataStreamOfExternalAssetsGoLangAsync(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternalAsync("golang_tar", testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetNodeTarTestCaseNames))] + public Task ReadCopiedDataStreamOfExternalAssetsNodeAsync(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternalAsync("node-tar", testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetRsTarTestCaseNames))] + public Task ReadCopiedDataStreamOfExternalAssetsRsAsync(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternalAsync("tar-rs", testCaseName, copyData: true); + + [Fact] + public async Task Throw_FifoContainsNonZeroDataSectionAsync() + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "hdr-only"); + await using TarReader reader = new TarReader(archiveStream); + Assert.NotNull(await reader.GetNextEntryAsync()); + Assert.NotNull(await reader.GetNextEntryAsync()); + Assert.NotNull(await reader.GetNextEntryAsync()); + Assert.NotNull(await reader.GetNextEntryAsync()); + Assert.NotNull(await reader.GetNextEntryAsync()); + Assert.NotNull(await reader.GetNextEntryAsync()); + Assert.NotNull(await reader.GetNextEntryAsync()); + Assert.NotNull(await reader.GetNextEntryAsync()); + await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); + } + + [Fact] + public async Task Throw_SingleExtendedAttributesEntryWithNoActualEntryAsync() + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "pax-path-hdr"); + await using TarReader reader = new TarReader(archiveStream); + await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); + } + + [Theory] + [InlineData("tar-rs", "spaces")] + [InlineData("golang_tar", "v7")] + public async Task AllowSpacesInOctalFieldsAsync(string folderName, string testCaseName) + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, folderName, testCaseName); + await using TarReader reader = new TarReader(archiveStream); + TarEntry entry; + while ((entry = await reader.GetNextEntryAsync()) != null) + { + AssertExtensions.GreaterThan(entry.Checksum, 0); + AssertExtensions.GreaterThan((int)entry.Mode, 0); + } + } + + [Theory] + [InlineData("pax-multi-hdrs")] // Multiple consecutive PAX metadata entries + [InlineData("gnu-multi-hdrs")] // Multiple consecutive GNU metadata entries + [InlineData("neg-size")] // Garbage chars + [InlineData("invalid-go17")] // Many octal fields are all zero chars + [InlineData("issue11169")] // Checksum with null in the middle + [InlineData("issue10968")] // Garbage chars + [InlineData("writer-big")] // The size field contains an euro char + public async Task Throw_ArchivesWithRandomCharsAsync(string testCaseName) + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", testCaseName); + await using TarReader reader = new TarReader(archiveStream); + await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); + } + + [Fact] + public async Task GarbageEntryChecksumZeroReturnNullAsync() + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "issue12435"); + await using TarReader reader = new TarReader(archiveStream); + Assert.Null(await reader.GetNextEntryAsync()); + } + + [Theory] + [InlineData("golang_tar", "gnu-nil-sparse-data")] + [InlineData("golang_tar", "gnu-nil-sparse-hole")] + [InlineData("golang_tar", "gnu-sparse-big")] + [InlineData("golang_tar", "sparse-formats")] + [InlineData("tar-rs", "sparse-1")] + [InlineData("tar-rs", "sparse")] + public async Task SparseEntryNotSupportedAsync(string testFolderName, string testCaseName) + { + // Currently sparse entries are not supported. + + // There are PAX archives archives in the golang folder that have extended attributes for treating a regular file as a sparse file. + // Sparse entries were created for the GNU format, so they are very rare entry types which are excluded from this test method: + // pax-nil-sparse-data, pax-nil-sparse-hole, pax-sparse-big + + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, testFolderName, testCaseName); + await using TarReader reader = new TarReader(archiveStream); + await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); + } + + [Fact] + public async Task DirectoryListRegularFileAndSparseAsync() + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "gnu-incremental"); + await using TarReader reader = new TarReader(archiveStream); + TarEntry directoryList = await reader.GetNextEntryAsync(); + + Assert.Equal(TarEntryType.DirectoryList, directoryList.EntryType); + Assert.NotNull(directoryList.DataStream); + Assert.Equal(14, directoryList.Length); + + Assert.NotNull(await reader.GetNextEntryAsync()); // Just a regular file + + await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); // Sparse + } + + [Fact] + public async Task PaxSizeLargerThanMaxAllowedByStreamAsync() + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "writer-big-long"); + await using TarReader reader = new TarReader(archiveStream); + // The extended attribute 'size' has the value 17179869184 + // Exception message: Stream length must be non-negative and less than 2^31 - 1 - origin + await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); + } + + private static async Task VerifyDataStreamOfTarUncompressedInternalAsync(string testFolderName, string testCaseName, bool copyData) + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, testFolderName, testCaseName); + await VerifyDataStreamOfTarInternalAsync(archiveStream, copyData); + } + + private static async Task VerifyDataStreamOfTarGzInternalAsync(TestTarFormat testTarFormat, string testCaseName, bool copyData) + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.GZip, testTarFormat, testCaseName); + await using GZipStream decompressor = new GZipStream(archiveStream, CompressionMode.Decompress); + await VerifyDataStreamOfTarInternalAsync(decompressor, copyData); + } + + private static async Task VerifyDataStreamOfTarInternalAsync(Stream archiveStream, bool copyData) + { + await using TarReader reader = new TarReader(archiveStream); + + TarEntry entry; + + await using MemoryStream ms = new MemoryStream(); + while ((entry = await reader.GetNextEntryAsync(copyData)) != null) + { + if (entry.EntryType is TarEntryType.V7RegularFile or TarEntryType.RegularFile) + { + if (entry.Length == 0) + { + Assert.Null(entry.DataStream); + } + else + { + Assert.NotNull(entry.DataStream); + Assert.Equal(entry.DataStream.Length, entry.Length); + if (copyData) + { + Assert.True(entry.DataStream.CanSeek); + Assert.Equal(0, entry.DataStream.Position); + } + } + } + } + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.GlobalExtendedAttributes.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.GlobalExtendedAttributes.Tests.cs index 2661c542ae4c0e..7c25ba67b657f4 100644 --- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.GlobalExtendedAttributes.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.GlobalExtendedAttributes.Tests.cs @@ -82,5 +82,25 @@ public void ExtractGlobalExtendedAttributesEntry_Throws() Assert.Throws(() => entry.ExtractToFile(Path.Join(root.Path, "file"), overwrite: true)); } } + + [Theory] + [MemberData(nameof(GetPaxExtendedAttributesRoundtripTestData))] + public void GlobalExtendedAttribute_Roundtrips(string key, string value) + { + var stream = new MemoryStream(); + using (var writer = new TarWriter(stream, leaveOpen: true)) + { + writer.WriteEntry(new PaxGlobalExtendedAttributesTarEntry(new Dictionary() { { key, value } })); + } + + stream.Position = 0; + using (var reader = new TarReader(stream)) + { + PaxGlobalExtendedAttributesTarEntry entry = Assert.IsType(reader.GetNextEntry()); + Assert.Equal(1, entry.GlobalExtendedAttributes.Count); + Assert.Equal(KeyValuePair.Create(key, value), entry.GlobalExtendedAttributes.First()); + Assert.Null(reader.GetNextEntry()); + } + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs index ffc537917fc7af..17c67423c390b2 100644 --- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.IO.Compression; using System.Linq; using Xunit; +using static System.Formats.Tar.Tests.TarTestsBase; namespace System.Formats.Tar.Tests { @@ -124,5 +126,244 @@ public void Read_Archive_LongFileName_Over100_Under255(TarEntryFormat format, Te [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] public void Read_Archive_LongPath_Over255(TarEntryFormat format, TestTarFormat testFormat) => Read_Archive_LongPath_Over255_Internal(format, testFormat); + + [Theory] + [MemberData(nameof(GetV7TestCaseNames))] + public void ReadDataStreamOfTarGzV7(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.v7, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetUstarTestCaseNames))] + public void ReadDataStreamOfTarGzUstar(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.ustar, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadDataStreamOfTarGzPax(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.pax, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadDataStreamOfTarGzPaxGea(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.pax_gea, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadDataStreamOfTarGzOldGnu(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.oldgnu, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadDataStreamOfTarGzGnu(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.gnu, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetV7TestCaseNames))] + public void ReadCopiedDataStreamOfTarGzV7(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.v7, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetUstarTestCaseNames))] + public void ReadCopiedDataStreamOfTarGzUstar(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.ustar, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadCopiedDataStreamOfTarGzPax(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.pax, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadCopiedDataStreamOfTarGzPaxGea(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.pax_gea, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadCopiedDataStreamOfTarGzOldGnu(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.oldgnu, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadCopiedDataStreamOfTarGzGnu(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.gnu, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetGoLangTarTestCaseNames))] + public void ReadDataStreamOfExternalAssetsGoLang(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternal("golang_tar", testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetNodeTarTestCaseNames))] + public void ReadDataStreamOfExternalAssetsNode(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternal("node-tar", testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetRsTarTestCaseNames))] + public void ReadDataStreamOfExternalAssetsRs(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternal("tar-rs", testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetGoLangTarTestCaseNames))] + public void ReadCopiedDataStreamOfExternalAssetsGoLang(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternal("golang_tar", testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetNodeTarTestCaseNames))] + public void ReadCopiedDataStreamOfExternalAssetsNode(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternal("node-tar", testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetRsTarTestCaseNames))] + public void ReadCopiedDataStreamOfExternalAssetsRs(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternal("tar-rs", testCaseName, copyData: true); + + [Fact] + public void Throw_FifoContainsNonZeroDataSection() + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "hdr-only"); + using TarReader reader = new TarReader(archiveStream); + Assert.NotNull(reader.GetNextEntry()); + Assert.NotNull(reader.GetNextEntry()); + Assert.NotNull(reader.GetNextEntry()); + Assert.NotNull(reader.GetNextEntry()); + Assert.NotNull(reader.GetNextEntry()); + Assert.NotNull(reader.GetNextEntry()); + Assert.NotNull(reader.GetNextEntry()); + Assert.NotNull(reader.GetNextEntry()); + Assert.Throws(() => reader.GetNextEntry()); + } + + [Fact] + public void Throw_SingleExtendedAttributesEntryWithNoActualEntry() + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "pax-path-hdr"); + using TarReader reader = new TarReader(archiveStream); + Assert.Throws(() => reader.GetNextEntry()); + } + + [Theory] + [InlineData("tar-rs", "spaces")] + [InlineData("golang_tar", "v7")] + public void AllowSpacesInOctalFields(string folderName, string testCaseName) + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, folderName, testCaseName); + using TarReader reader = new TarReader(archiveStream); + TarEntry entry; + while ((entry = reader.GetNextEntry()) != null) + { + AssertExtensions.GreaterThan(entry.Checksum, 0); + AssertExtensions.GreaterThan((int)entry.Mode, 0); + } + } + + [Theory] + [InlineData("pax-multi-hdrs")] // Multiple consecutive PAX metadata entries + [InlineData("gnu-multi-hdrs")] // Multiple consecutive GNU metadata entries + [InlineData("neg-size")] // Garbage chars + [InlineData("invalid-go17")] // Many octal fields are all zero chars + [InlineData("issue11169")] // Checksum with null in the middle + [InlineData("issue10968")] // Garbage chars + [InlineData("writer-big")] // The size field contains an euro char + public void Throw_ArchivesWithRandomChars(string testCaseName) + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", testCaseName); + using TarReader reader = new TarReader(archiveStream); + Assert.Throws(() => reader.GetNextEntry()); + } + + [Fact] + public void GarbageEntryChecksumZeroReturnNull() + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "issue12435"); + using TarReader reader = new TarReader(archiveStream); + Assert.Null(reader.GetNextEntry()); + } + + [Theory] + [InlineData("golang_tar", "gnu-nil-sparse-data")] + [InlineData("golang_tar", "gnu-nil-sparse-hole")] + [InlineData("golang_tar", "gnu-sparse-big")] + [InlineData("golang_tar", "sparse-formats")] + [InlineData("tar-rs", "sparse-1")] + [InlineData("tar-rs", "sparse")] + public void SparseEntryNotSupported(string testFolderName, string testCaseName) + { + // Currently sparse entries are not supported. + + // There are PAX archives archives in the golang folder that have extended attributes for treating a regular file as a sparse file. + // Sparse entries were created for the GNU format, so they are very rare entry types which are excluded from this test method: + // pax-nil-sparse-data, pax-nil-sparse-hole, pax-sparse-big + + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, testFolderName, testCaseName); + using TarReader reader = new TarReader(archiveStream); + Assert.Throws(() => reader.GetNextEntry()); + } + + [Fact] + public void DirectoryListRegularFileAndSparse() + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "gnu-incremental"); + using TarReader reader = new TarReader(archiveStream); + TarEntry directoryList = reader.GetNextEntry(); + + Assert.Equal(TarEntryType.DirectoryList, directoryList.EntryType); + Assert.NotNull(directoryList.DataStream); + Assert.Equal(14, directoryList.Length); + + Assert.NotNull(reader.GetNextEntry()); // Just a regular file + + Assert.Throws(() => reader.GetNextEntry()); // Sparse + } + + [Fact] + public void PaxSizeLargerThanMaxAllowedByStream() + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "writer-big-long"); + using TarReader reader = new TarReader(archiveStream); + // The extended attribute 'size' has the value 17179869184 + // Exception message: Stream length must be non-negative and less than 2^31 - 1 - origin + Assert.Throws(() => reader.GetNextEntry()); + } + + private static void VerifyDataStreamOfTarUncompressedInternal(string testFolderName, string testCaseName, bool copyData) + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, testFolderName, testCaseName); + VerifyDataStreamOfTarInternal(archiveStream, copyData); + } + + private static void VerifyDataStreamOfTarGzInternal(TestTarFormat testTarFormat, string testCaseName, bool copyData) + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.GZip, testTarFormat, testCaseName); + using GZipStream decompressor = new GZipStream(archiveStream, CompressionMode.Decompress); + VerifyDataStreamOfTarInternal(decompressor, copyData); + } + + private static void VerifyDataStreamOfTarInternal(Stream archiveStream, bool copyData) + { + using TarReader reader = new TarReader(archiveStream); + + TarEntry entry; + + while ((entry = reader.GetNextEntry(copyData)) != null) + { + if (entry.EntryType is TarEntryType.V7RegularFile or TarEntryType.RegularFile) + { + if (entry.Length == 0) + { + Assert.Null(entry.DataStream); + } + else + { + Assert.NotNull(entry.DataStream); + Assert.Equal(entry.DataStream.Length, entry.Length); + if (copyData) + { + Assert.True(entry.DataStream.CanSeek); + Assert.Equal(0, entry.DataStream.Position); + } + } + } + } + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs index bad9bf8fa179bf..2cede3a350c825 100644 --- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs @@ -30,7 +30,7 @@ public void MalformedArchive_HeaderSize() malformed.Seek(0, SeekOrigin.Begin); using TarReader reader = new TarReader(malformed); - Assert.Throws(() => reader.GetNextEntry()); + Assert.Throws(() => reader.GetNextEntry()); } [Fact] @@ -250,5 +250,46 @@ public void GetNextEntry_UnseekableArchive_ReplaceDataStream_ExcludeFromDisposin Assert.Equal("Substituted", streamReader.ReadLine()); } } + + [Theory] + [InlineData(512, false)] + [InlineData(512, true)] + [InlineData(512 + 1, false)] + [InlineData(512 + 1, true)] + [InlineData(512 + 512 - 1, false)] + [InlineData(512 + 512 - 1, true)] + public void BlockAlignmentPadding_DoesNotAffectNextEntries(int contentSize, bool copyData) + { + byte[] fileContents = new byte[contentSize]; + Array.Fill(fileContents, 0x1); + + using var archive = new MemoryStream(); + using (var writer = new TarWriter(archive, leaveOpen: true)) + { + var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file"); + entry1.DataStream = new MemoryStream(fileContents); + writer.WriteEntry(entry1); + + var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); + writer.WriteEntry(entry2); + } + + archive.Position = 0; + using var unseekable = new WrappedStream(archive, archive.CanRead, archive.CanWrite, canSeek: false); + using var reader = new TarReader(unseekable); + + TarEntry e = reader.GetNextEntry(copyData); + Assert.Equal(contentSize, e.Length); + + byte[] buffer = new byte[contentSize]; + while (e.DataStream.Read(buffer) > 0) ; + AssertExtensions.SequenceEqual(fileContents, buffer); + + e = reader.GetNextEntry(copyData); + Assert.Equal(0, e.Length); + + e = reader.GetNextEntry(copyData); + Assert.Null(e); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntryAsync.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntryAsync.Tests.cs index 77ddf1f1320621..f99e5853ebeaad 100644 --- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntryAsync.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntryAsync.Tests.cs @@ -51,7 +51,7 @@ public async Task MalformedArchive_HeaderSize_Async() await using (TarReader reader = new TarReader(malformed)) { - await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); + await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); } } } @@ -288,5 +288,46 @@ public async Task GetNextEntry_UnseekableArchive_ReplaceDataStream_ExcludeFromDi } } } + + [Theory] + [InlineData(512, false)] + [InlineData(512, true)] + [InlineData(512 + 1, false)] + [InlineData(512 + 1, true)] + [InlineData(512 + 512 - 1, false)] + [InlineData(512 + 512 - 1, true)] + public async Task BlockAlignmentPadding_DoesNotAffectNextEntries_Async(int contentSize, bool copyData) + { + byte[] fileContents = new byte[contentSize]; + Array.Fill(fileContents, 0x1); + + using var archive = new MemoryStream(); + using (var writer = new TarWriter(archive, leaveOpen: true)) + { + var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file"); + entry1.DataStream = new MemoryStream(fileContents); + await writer.WriteEntryAsync(entry1); + + var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); + await writer.WriteEntryAsync(entry2); + } + + archive.Position = 0; + using var unseekable = new WrappedStream(archive, archive.CanRead, archive.CanWrite, canSeek: false); + using var reader = new TarReader(unseekable); + + TarEntry e = await reader.GetNextEntryAsync(copyData); + Assert.Equal(contentSize, e.Length); + + byte[] buffer = new byte[contentSize]; + while (e.DataStream.Read(buffer) > 0) ; + AssertExtensions.SequenceEqual(fileContents, buffer); + + e = await reader.GetNextEntryAsync(copyData); + Assert.Equal(0, e.Length); + + e = await reader.GetNextEntryAsync(copyData); + Assert.Null(e); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.Tests.cs new file mode 100644 index 00000000000000..f95a22335a1418 --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.Tests.cs @@ -0,0 +1,122 @@ +// 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.IO; +using System.Linq; +using Xunit; + +namespace System.Formats.Tar.Tests +{ + public partial class TarReader_Tests : TarTestsBase + { + [Fact] + public void TarReader_NullArchiveStream() => Assert.Throws(() => new TarReader(archiveStream: null)); + + [Fact] + public void TarReader_UnreadableStream() + { + using MemoryStream ms = new MemoryStream(); + using WrappedStream ws = new WrappedStream(ms, canRead: false, canWrite: true, canSeek: true); + Assert.Throws(() => new TarReader(ws)); + } + + [Fact] + public void TarReader_LeaveOpen_False() + { + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax, "many_small_files"); + List dataStreams = new List(); + using (TarReader reader = new TarReader(ms, leaveOpen: false)) + { + TarEntry entry; + while ((entry = reader.GetNextEntry()) != null) + { + if (entry.DataStream != null) + { + dataStreams.Add(entry.DataStream); + } + } + } + + Assert.Throws(() => ms.ReadByte()); + + Assert.True(dataStreams.Any()); + foreach (Stream ds in dataStreams) + { + Assert.Throws(() => ds.ReadByte()); + } + } + + [Fact] + public void TarReader_LeaveOpen_True() + { + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax, "many_small_files"); + List dataStreams = new List(); + using (TarReader reader = new TarReader(ms, leaveOpen: true)) + { + TarEntry entry; + while ((entry = reader.GetNextEntry()) != null) + { + if (entry.DataStream != null) + { + dataStreams.Add(entry.DataStream); + } + } + } + + ms.ReadByte(); // Should not throw + + Assert.True(dataStreams.Any()); + foreach (Stream ds in dataStreams) + { + ds.ReadByte(); // Should not throw + ds.Dispose(); + } + } + + [Fact] + public void TarReader_LeaveOpen_False_CopiedDataNotDisposed() + { + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax, "many_small_files"); + List dataStreams = new List(); + using (TarReader reader = new TarReader(ms, leaveOpen: false)) + { + TarEntry entry; + while ((entry = reader.GetNextEntry(copyData: true)) != null) + { + if (entry.DataStream != null) + { + dataStreams.Add(entry.DataStream); + } + } + } + + Assert.True(dataStreams.Any()); + foreach (Stream ds in dataStreams) + { + ds.ReadByte(); // Should not throw, copied streams, user should dispose + ds.Dispose(); + } + } + + [Theory] + [MemberData(nameof(GetPaxExtendedAttributesRoundtripTestData))] + public void PaxExtendedAttribute_Roundtrips(string key, string value) + { + var stream = new MemoryStream(); + using (var writer = new TarWriter(stream, leaveOpen: true)) + { + writer.WriteEntry(new PaxTarEntry(TarEntryType.Directory, "entryName", new Dictionary() { { key, value } })); + } + + stream.Position = 0; + using (var reader = new TarReader(stream)) + { + PaxTarEntry entry = Assert.IsType(reader.GetNextEntry()); + Assert.Equal(5, entry.ExtendedAttributes.Count); + Assert.Contains(KeyValuePair.Create(key, value), entry.ExtendedAttributes); + Assert.Null(reader.GetNextEntry()); + } + } + } +} diff --git a/src/libraries/System.Formats.Tar/tests/TarTestsBase.Pax.cs b/src/libraries/System.Formats.Tar/tests/TarTestsBase.Pax.cs index dd71b157963962..c1776753512cfd 100644 --- a/src/libraries/System.Formats.Tar/tests/TarTestsBase.Pax.cs +++ b/src/libraries/System.Formats.Tar/tests/TarTestsBase.Pax.cs @@ -124,5 +124,21 @@ protected void VerifyExtendedAttributeTimestamps(PaxTarEntry pax) VerifyExtendedAttributeTimestamp(pax, PaxEaATime, MinimumTime); VerifyExtendedAttributeTimestamp(pax, PaxEaCTime, MinimumTime); } + + public static IEnumerable GetPaxExtendedAttributesRoundtripTestData() + { + yield return new object[] { "key", "value" }; + yield return new object[] { "key ", "value " }; + yield return new object[] { " key", " value" }; + yield return new object[] { " key ", " value " }; + yield return new object[] { " key spaced ", " value spaced " }; + yield return new object[] { "many sla/s\\hes", "/////////////\\\\\\///////////" }; + yield return new object[] { "key", "=" }; + yield return new object[] { "key", "=value" }; + yield return new object[] { "key", "va=lue" }; + yield return new object[] { "key", "value=" }; + // real world scenario + yield return new object[] { "MSWINDOWS.rawsd", "AQAAgBQAAAAkAAAAAAAAAAAAAAABAgAAAAAABSAAAAAhAgAAAQIAAAAAAAUgAAAAIQIAAA==" }; + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs b/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs index b167a980acd2b0..48b7e23df4681c 100644 --- a/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs +++ b/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs @@ -1,8 +1,10 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.IO; +using System.Linq; +using System.Runtime.CompilerServices; using Microsoft.DotNet.RemoteExecutor; using Xunit; @@ -10,6 +12,8 @@ namespace System.Formats.Tar.Tests { public abstract partial class TarTestsBase : FileCleanupTestBase { + protected static bool IsRemoteExecutorSupportedAndPrivilegedProcess => RemoteExecutor.IsSupported && PlatformDetection.IsUnixAndSuperUser; + protected const string InitialEntryName = "InitialEntryName.ext"; protected readonly string ModifiedEntryName = "ModifiedEntryName.ext"; @@ -17,6 +21,9 @@ public abstract partial class TarTestsBase : FileCleanupTestBase protected const UnixFileMode DefaultFileMode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead; // 644 in octal, internally used as default protected const UnixFileMode DefaultDirectoryMode = DefaultFileMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; // 755 in octal, internally used as default + protected readonly UnixFileMode CreateDirectoryDefaultMode; // Mode of directories created using Directory.CreateDirectory(string). + protected readonly UnixFileMode UMask; + // Mode assumed for files and directories on Windows. protected const UnixFileMode DefaultWindowsMode = DefaultFileMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; // 755 in octal, internally used as default @@ -84,6 +91,100 @@ public abstract partial class TarTestsBase : FileCleanupTestBase protected const string PaxEaSize = "size"; protected const string PaxEaDevMajor = "devmajor"; protected const string PaxEaDevMinor = "devminor"; + internal const char OneByteCharacter = 'a'; + internal const char TwoBytesCharacter = '\u00F6'; + internal const string FourBytesCharacter = "\uD83D\uDE12"; + internal const char Separator = '/'; + internal const int MaxPathComponent = 255; + internal const long LegacyMaxFileSize = (1L << 33) - 1; // Max value of 11 octal digits = 2^33 - 1 or 8 Gb. + + private static readonly string[] V7TestCaseNames = new[] + { + "file", + "file_hardlink", + "file_symlink", + "folder_file", + "folder_file_utf8", + "folder_subfolder_file", + "foldersymlink_folder_subfolder_file", + "many_small_files" + }; + + private static readonly string[] UstarTestCaseNames = new[] + { + "longpath_splitable_under255", + "specialfiles" }; + + private static readonly string[] PaxAndGnuTestCaseNames = new[] + { + "file_longsymlink", + "longfilename_over100_under255", + "longpath_over255" + }; + + private static readonly string[] GoLangTestCaseNames = new[] + { + "empty", + "file-and-dir", + "gnu-long-nul", + "gnu-not-utf8", + "gnu-utf8", + "gnu", + "hardlink", + "nil-uid", + "pax-bad-hdr-file", + "pax-bad-mtime-file", + "pax-global-records", + "pax-nul-path", + "pax-nul-xattrs", + "pax-pos-size-file", + "pax-records", + "pax", + "star", + "trailing-slash", + "ustar-file-devs", + "ustar-file-reg", + "ustar", + "writer", + "xattrs" + }; + + private static readonly string[] NodeTarTestCaseNames = new[] + { + "bad-cksum", + "body-byte-counts", + "dir", + "emptypax", + "file", + "global-header", + "links-invalid", + "links-strip", + "links", + "long-paths", + "long-pax", + "next-file-has-long", + "null-byte", + "path-missing", + "trailing-slash-corner-case", + "utf8" + }; + + private static readonly string[] RsTarTestCaseNames = new[] + { + "7z_long_path", + "directory", + "duplicate_dirs", + "empty_filename", + "file_times", + "link", + "pax_size", + "pax", + "pax2", + "reading_files", + "simple_missing_last_header", + "simple", + "xattrs" + }; protected enum CompressionMethod { @@ -109,33 +210,49 @@ public enum TestTarFormat // GNU formatted files. Format used by GNU tar versions up to 1.13.25. gnu } - protected static bool IsRemoteExecutorSupportedAndOnUnixAndSuperUser => RemoteExecutor.IsSupported && PlatformDetection.IsUnixAndSuperUser; protected static bool IsUnixButNotSuperUser => !PlatformDetection.IsWindows && !PlatformDetection.IsSuperUser; protected static bool IsNotLinuxBionic => !PlatformDetection.IsLinuxBionic; + protected TarTestsBase() + { + CreateDirectoryDefaultMode = Directory.CreateDirectory(GetRandomDirPath()).UnixFileMode; // '0777 & ~umask' + UMask = ~CreateDirectoryDefaultMode & (UnixFileMode)Convert.ToInt32("777", + 8); + } + protected static string GetTestCaseUnarchivedFolderPath(string testCaseName) => - Path.Join(Directory.GetCurrentDirectory(), "unarchived", testCaseName); + Path.Join(Directory.GetCurrentDirectory(), "unarchived", + testCaseName); protected static string GetTarFilePath(CompressionMethod compressionMethod, TestTarFormat format, string testCaseName) + => GetTarFilePath(compressionMethod, format.ToString(), testCaseName); + + protected static string GetTarFilePath(CompressionMethod compressionMethod, string testFolderName, string testCaseName) { (string compressionMethodFolder, string fileExtension) = compressionMethod switch { - CompressionMethod.Uncompressed => ("tar", ".tar"), - CompressionMethod.GZip => ("targz", ".tar.gz"), + CompressionMethod.Uncompressed => ("tar", + ".tar"), + CompressionMethod.GZip => ("targz", + ".tar.gz"), _ => throw new InvalidOperationException($"Unexpected compression method: {compressionMethod}"), }; - return Path.Join(Directory.GetCurrentDirectory(), compressionMethodFolder, format.ToString(), testCaseName + fileExtension); + return Path.Join(Directory.GetCurrentDirectory(), compressionMethodFolder, testFolderName, testCaseName + fileExtension); } // MemoryStream containing the copied contents of the specified file. Meant for reading and writing. protected static MemoryStream GetTarMemoryStream(CompressionMethod compressionMethod, TestTarFormat format, string testCaseName) => - GetMemoryStream(GetTarFilePath(compressionMethod, format, testCaseName)); + GetTarMemoryStream(compressionMethod, format.ToString(), testCaseName); + + protected static MemoryStream GetTarMemoryStream(CompressionMethod compressionMethod, string testFolderName, string testCaseName) => + GetMemoryStream(GetTarFilePath(compressionMethod, testFolderName, testCaseName)); protected static string GetStrangeTarFilePath(string testCaseName) => - Path.Join(Directory.GetCurrentDirectory(), "strange", testCaseName + ".tar"); + Path.Join(Directory.GetCurrentDirectory(), "strange", + testCaseName + ".tar"); protected static MemoryStream GetStrangeTarMemoryStream(string testCaseName) => GetMemoryStream(GetStrangeTarFilePath(testCaseName)); @@ -178,6 +295,8 @@ protected void SetCommonHardLink(TarEntry hardLink) // LinkName Assert.Equal(DefaultLinkName, hardLink.LinkName); + Assert.Throws(() => hardLink.LinkName = null); + Assert.Throws(() => hardLink.LinkName = string.Empty); hardLink.LinkName = TestLinkName; } @@ -189,6 +308,8 @@ protected void SetCommonSymbolicLink(TarEntry symbolicLink) // LinkName Assert.Equal(DefaultLinkName, symbolicLink.LinkName); + Assert.Throws(() => symbolicLink.LinkName = null); + Assert.Throws(() => symbolicLink.LinkName = string.Empty); symbolicLink.LinkName = TestLinkName; } @@ -291,6 +412,13 @@ protected void VerifyDataStream(TarEntry entry, bool isFromWriter) if (isFromWriter) { Assert.Null(entry.DataStream); + + using (MemoryStream ms = new MemoryStream()) + using (WrappedStream ws = new WrappedStream(ms, canRead: false, canWrite: true, canSeek: true)) + { + Assert.Throws(() => entry.DataStream = ws); + } + entry.DataStream = new MemoryStream(); // Verify it is not modified or wrapped in any way Assert.True(entry.DataStream.CanRead); @@ -326,7 +454,7 @@ protected Type GetTypeForFormat(TarEntryFormat expectedFormat) TarEntryFormat.Ustar => typeof(UstarTarEntry), TarEntryFormat.Pax => typeof(PaxTarEntry), TarEntryFormat.Gnu => typeof(GnuTarEntry), - _ => throw new FormatException($"Unrecognized format: {expectedFormat}"), + _ => throw new InvalidDataException($"Unrecognized format: {expectedFormat}"), }; } @@ -336,7 +464,7 @@ protected void CheckConversionType(TarEntry entry, TarEntryFormat expectedFormat Assert.Equal(expectedType, entry.GetType()); } - protected TarEntryType GetTarEntryTypeForTarEntryFormat(TarEntryType entryType, TarEntryFormat format) + protected static TarEntryType GetTarEntryTypeForTarEntryFormat(TarEntryType entryType, TarEntryFormat format) { if (format is TarEntryFormat.V7) { @@ -355,14 +483,14 @@ protected TarEntryType GetTarEntryTypeForTarEntryFormat(TarEntryType entryType, return entryType; } - protected TarEntry InvokeTarEntryCreationConstructor(TarEntryFormat targetFormat, TarEntryType entryType, string entryName) + protected static TarEntry InvokeTarEntryCreationConstructor(TarEntryFormat targetFormat, TarEntryType entryType, string entryName) => targetFormat switch { TarEntryFormat.V7 => new V7TarEntry(entryType, entryName), TarEntryFormat.Ustar => new UstarTarEntry(entryType, entryName), TarEntryFormat.Pax => new PaxTarEntry(entryType, entryName), TarEntryFormat.Gnu => new GnuTarEntry(entryType, entryName), - _ => throw new FormatException($"Unexpected format: {targetFormat}") + _ => throw new InvalidDataException($"Unexpected format: {targetFormat}") }; public static IEnumerable GetFormatsAndLinks() @@ -407,11 +535,20 @@ protected static void AssertEntryModeFromFileSystemEquals(TarEntry entry, UnixFi Assert.Equal(fileMode, entry.Mode); } - protected static void AssertFileModeEquals(string path, UnixFileMode mode) + protected void AssertFileModeEquals(string path, UnixFileMode archiveMode) { if (!PlatformDetection.IsWindows) { - Assert.Equal(mode, File.GetUnixFileMode(path)); + UnixFileMode expectedMode = archiveMode & ~UMask; + + UnixFileMode actualMode = File.GetUnixFileMode(path); + // Ignore SetGroup: it may have been added to preserve group ownership. + if ((expectedMode & UnixFileMode.SetGroup) == 0) + { + actualMode &= ~UnixFileMode.SetGroup; + } + + Assert.Equal(expectedMode, actualMode); } } @@ -444,5 +581,235 @@ protected void Verify_Extract(string destination, TarEntry entry, TarEntryType e AssertFileModeEquals(destination, TestPermission1); } + + public static IEnumerable GetNodeTarTestCaseNames() + { + foreach (string name in NodeTarTestCaseNames) + { + yield return new object[] { name }; + } + } + + public static IEnumerable GetGoLangTarTestCaseNames() + { + foreach (string name in GoLangTestCaseNames) + { + yield return new object[] { name }; + } + } + + public static IEnumerable GetRsTarTestCaseNames() + { + foreach (string name in RsTarTestCaseNames) + { + yield return new object[] { name }; + } + } + + public static IEnumerable GetV7TestCaseNames() + { + foreach (string name in V7TestCaseNames) + { + yield return new object[] { name }; + } + } + + public static IEnumerable GetUstarTestCaseNames() + { + foreach (string name in UstarTestCaseNames.Concat(V7TestCaseNames)) + { + yield return new object[] { name }; + } + } + + public static IEnumerable GetPaxAndGnuTestCaseNames() + { + foreach (string name in UstarTestCaseNames.Concat(V7TestCaseNames).Concat(PaxAndGnuTestCaseNames)) + { + yield return new object[] { name }; + } + } + + private static List GetPrefixes() + { + List prefixes = new() { "", "/a/", "./", "../" }; + + if (OperatingSystem.IsWindows()) + prefixes.Add("C:/"); + + return prefixes; + } + + internal static IEnumerable GetNamesPrefixedTestData(NameCapabilities max) + { + Assert.True(Enum.IsDefined(max)); + List prefixes = GetPrefixes(); + + foreach (string prefix in prefixes) + { + // prefix + name of length 100 + int nameLength = 100 - prefix.Length; + yield return prefix + Repeat(OneByteCharacter, nameLength); + yield return prefix + Repeat(OneByteCharacter, nameLength - 2) + TwoBytesCharacter; + yield return prefix + Repeat(OneByteCharacter, nameLength - 4) + FourBytesCharacter; + + // prefix alone + if (prefix != string.Empty) + yield return prefix; + } + + if (max == NameCapabilities.Name) + yield break; + + // maxed out name. + foreach (string prefix in prefixes) + { + yield return prefix + Repeat(OneByteCharacter, 100); + yield return prefix + Repeat(OneByteCharacter, 100 - 2) + TwoBytesCharacter; + yield return prefix + Repeat(OneByteCharacter, 100 - 4) + FourBytesCharacter; + } + + // maxed out prefix and name. + foreach (string prefix in prefixes) + { + int directoryLength = 155 - prefix.Length; + yield return prefix + Repeat(OneByteCharacter, directoryLength) + Separator + Repeat(OneByteCharacter, 100); + yield return prefix + Repeat(OneByteCharacter, directoryLength - 2) + TwoBytesCharacter + Separator + Repeat(OneByteCharacter, 100); + yield return prefix + Repeat(OneByteCharacter, directoryLength - 4) + FourBytesCharacter + Separator + Repeat(OneByteCharacter, 100); + } + + if (max == NameCapabilities.NameAndPrefix) + yield break; + + foreach (string prefix in prefixes) + { + int directoryLength = MaxPathComponent - prefix.Length; + yield return prefix + Repeat(OneByteCharacter, directoryLength) + Separator + Repeat(OneByteCharacter, MaxPathComponent); + yield return prefix + Repeat(OneByteCharacter, directoryLength - 2) + TwoBytesCharacter + Separator + Repeat(OneByteCharacter, MaxPathComponent); + yield return prefix + Repeat(OneByteCharacter, directoryLength - 4) + FourBytesCharacter + Separator + Repeat(OneByteCharacter, MaxPathComponent); + } + } + + internal static IEnumerable GetNamesNonAsciiTestData(NameCapabilities max) + { + Assert.True(Enum.IsDefined(max)); + + yield return Repeat(OneByteCharacter, 100); + yield return Repeat(TwoBytesCharacter, 100 / 2); + yield return Repeat(OneByteCharacter, 2) + Repeat(TwoBytesCharacter, 49); + + yield return Repeat(FourBytesCharacter, 100 / 4); + yield return Repeat(OneByteCharacter, 4) + Repeat(FourBytesCharacter, 24); + + if (max == NameCapabilities.Name) + yield break; + + // prefix + name + // this is 256 but is supported because prefix is not required to end in separator. + yield return Repeat(OneByteCharacter, 155) + Separator + Repeat(OneByteCharacter, 100); + + // non-ascii prefix + name + yield return Repeat(TwoBytesCharacter, 155 / 2) + Separator + Repeat(OneByteCharacter, 100); + yield return Repeat(FourBytesCharacter, 155 / 4) + Separator + Repeat(OneByteCharacter, 100); + + // prefix + non-ascii name + yield return Repeat(OneByteCharacter, 155) + Separator + Repeat(TwoBytesCharacter, 100 / 2); + yield return Repeat(OneByteCharacter, 155) + Separator + Repeat(FourBytesCharacter, 100 / 4); + + // non-ascii prefix + non-ascii name + yield return Repeat(TwoBytesCharacter, 155 / 2) + Separator + Repeat(TwoBytesCharacter, 100 / 2); + yield return Repeat(FourBytesCharacter, 155 / 4) + Separator + Repeat(FourBytesCharacter, 100 / 4); + + if (max == NameCapabilities.NameAndPrefix) + yield break; + + // Pax and Gnu support unlimited paths. + yield return Repeat(OneByteCharacter, MaxPathComponent); + yield return Repeat(TwoBytesCharacter, MaxPathComponent / 2); + yield return Repeat(FourBytesCharacter, MaxPathComponent / 4); + + yield return Repeat(OneByteCharacter, MaxPathComponent) + Separator + Repeat(OneByteCharacter, MaxPathComponent); + yield return Repeat(TwoBytesCharacter, MaxPathComponent / 2) + Separator + Repeat(TwoBytesCharacter, MaxPathComponent / 2); + yield return Repeat(FourBytesCharacter, MaxPathComponent / 4) + Separator + Repeat(FourBytesCharacter, MaxPathComponent / 4); + } + + internal static IEnumerable GetTooLongNamesTestData(NameCapabilities max) + { + Assert.True(max is NameCapabilities.Name or NameCapabilities.NameAndPrefix); + + // root directory can't be saved as prefix + yield return "/" + Repeat(OneByteCharacter, 100); + + List prefixes = GetPrefixes(); + + // 1. non-ascii last character doesn't fit in name. + foreach (string prefix in prefixes) + { + // 1.1. last character doesn't fit fully. + yield return prefix + Repeat(OneByteCharacter, 100 + 1); + yield return prefix + Repeat(OneByteCharacter, 100 - 2) + Repeat(TwoBytesCharacter, 2); + yield return prefix + Repeat(OneByteCharacter, 100 - 4) + Repeat(FourBytesCharacter, 2); + + // 1.2. last character doesn't fit by one byte. + yield return prefix + Repeat(OneByteCharacter, 100 - 2 + 1) + Repeat(TwoBytesCharacter, 1); + yield return prefix + Repeat(OneByteCharacter, 100 - 4 + 1) + Repeat(FourBytesCharacter, 1); + } + + // 2. non-ascii last character doesn't fit in prefix. + string maxedOutName = Repeat(OneByteCharacter, 100); + + // 2.1. last char doesn't fit fully. + yield return Repeat(OneByteCharacter, 155 + 1) + Separator + maxedOutName; + yield return Repeat(OneByteCharacter, 155 - 2) + Repeat(TwoBytesCharacter, 2) + Separator + maxedOutName; + yield return Repeat(OneByteCharacter, 155 - 4) + Repeat(FourBytesCharacter, 2) + Separator + maxedOutName; + + // 2.2 last char doesn't fit by one byte. + yield return Repeat(OneByteCharacter, 155 - 2 + 1) + TwoBytesCharacter + Separator + maxedOutName; + yield return Repeat(OneByteCharacter, 155 - 4 + 1) + FourBytesCharacter + Separator + maxedOutName; + + if (max is NameCapabilities.NameAndPrefix) + yield break; + + // Next cases only apply for V7 which only allows 100 length names. + foreach (string prefix in prefixes) + { + if (prefix.Length == 0) + continue; + + yield return prefix + Repeat(OneByteCharacter, 100); + yield return prefix + Repeat(TwoBytesCharacter, 100 / 2); + yield return prefix + Repeat(FourBytesCharacter, 100 / 4); + } + } + + internal static string Repeat(char c, int count) + { + return new string(c, count); + } + + internal static string Repeat(string c, int count) + { + return string.Concat(Enumerable.Repeat(c, count)); + } + + internal enum NameCapabilities + { + Name, + NameAndPrefix, + Unlimited + } + + internal static void WriteTarArchiveWithOneEntry(Stream s, TarEntryFormat entryFormat, TarEntryType entryType) + { + using TarWriter writer = new(s, leaveOpen: true); + + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, entryType, "foo"); + if (entryType == TarEntryType.SymbolicLink) + { + entry.LinkName = "bar"; + } + + writer.WriteEntry(entry); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.Unix.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.Unix.cs index 8b613191b0ef87..ccd3404d2eef6f 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.Unix.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.Unix.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.IO; using Xunit; @@ -20,7 +21,7 @@ protected void VerifyPlatformSpecificMetadata(string filePath, TarEntry entry) if (entry is PosixTarEntry posix) { - string gname = Interop.Sys.GetGroupName(status.Gid); + Assert.True(Interop.Sys.TryGetGroupName(status.Gid, out string gname)); string uname = Interop.Sys.GetUserNameFromPasswd(status.Uid); Assert.Equal(gname, posix.GroupName); @@ -51,5 +52,82 @@ protected void VerifyPlatformSpecificMetadata(string filePath, TarEntry entry) } } } + + protected int CreateGroup(string groupName) + { + Execute("groupadd", groupName); + return GetGroupId(groupName); + } + + protected int CreateUser(string userName) + { + Execute("useradd", userName); + return GetUserId(userName); + } + + protected int GetGroupId(string groupName) + { + string standardOutput = Execute("getent", $"group {groupName}"); + string[] values = standardOutput.Split(':', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + return int.Parse(values[^1]); + } + + protected int GetUserId(string userName) + { + string standardOutput = Execute("id", $"-u {userName}"); + return int.Parse(standardOutput); + } + + protected void SetGroupAsOwnerOfFile(string groupName, string filePath) => + Execute("chgrp", $"{groupName} {filePath}"); + + protected void SetUserAsOwnerOfFile(string userName, string filePath) => + Execute("chown", $"{userName} {filePath}"); + + protected void DeleteGroup(string groupName) + { + Execute("groupdel", groupName); + Threading.Thread.Sleep(250); + Assert.Throws(() => GetGroupId(groupName)); + } + + protected void DeleteUser(string userName) + { + Execute("userdel", $"-f {userName}"); + Threading.Thread.Sleep(250); + Assert.Throws(() => GetUserId(userName)); + } + + private string Execute(string command, string arguments) + { + using Process p = new Process(); + + p.StartInfo.FileName = command; + p.StartInfo.Arguments = arguments; + + p.StartInfo.UseShellExecute = false; + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.RedirectStandardError = true; + + string standardError = string.Empty; + p.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => { standardError += e.Data; }); + + string standardOutput = string.Empty; + p.OutputDataReceived += new DataReceivedEventHandler((sender, e) => { standardOutput += e.Data; }); + + p.Start(); + + p.BeginOutputReadLine(); + p.BeginErrorReadLine(); + + p.WaitForExit(); + + if (p.ExitCode != 0) + { + throw new IOException($"Error '{p.ExitCode}' when executing '{command} {arguments}'. Message: {standardError}"); + } + + return standardOutput; + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs index 894498934f0177..b46816844b44cb 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; using System.IO; using Xunit; @@ -63,8 +62,8 @@ public void Constructors_UnwritableStream_Throws() { using MemoryStream archiveStream = new MemoryStream(); using WrappedStream wrappedStream = new WrappedStream(archiveStream, canRead: true, canWrite: false, canSeek: false); - Assert.Throws(() => new TarWriter(wrappedStream)); - Assert.Throws(() => new TarWriter(wrappedStream, TarEntryFormat.V7)); + Assert.Throws(() => new TarWriter(wrappedStream)); + Assert.Throws(() => new TarWriter(wrappedStream, TarEntryFormat.V7)); } [Fact] @@ -174,7 +173,7 @@ private TarEntry CreateTarEntryAndGetExpectedChecksum(TarEntryFormat format, Tar if (entryType is TarEntryType.SymbolicLink) { - expectedChecksum += GetLinkChecksum(longLink, out string linkName); + expectedChecksum += GetLinkChecksum(format, longLink, out string linkName); entry.LinkName = linkName; } @@ -195,23 +194,26 @@ private int GetNameChecksum(TarEntryFormat format, bool longPath, out string ent } else { - entryName = new string('a', 150); - // 100 * 97 = 9700 (first 100 bytes go into 'name' field) - expectedChecksum += 9700; + entryName = new string('a', 100); + expectedChecksum += 9700; // 100 * 97 = 9700 (first 100 bytes go into 'name' field) + + // V7 does not support name fields larger than 100 + if (format is not TarEntryFormat.V7) + { + entryName += "/" + new string('a', 50); + } - // - V7 does not support name fields larger than 100, writes what it can - // - Gnu writes first 100 bytes in 'name' field, then the full name is written in a LonPath entry - // that precedes this one. - if (format is TarEntryFormat.Ustar or TarEntryFormat.Pax) + // Gnu and Pax writes first 100 bytes in 'name' field, then the full name is written in a metadata entry that precedes this one. + if (format is TarEntryFormat.Ustar) { - // 50 * 97 = 4850 (rest of bytes go into 'prefix' field) - expectedChecksum += 4850; + // Ustar can write the directory into prefix. + expectedChecksum += 4850; // 50 * 97 = 4850 } } return expectedChecksum; } - private int GetLinkChecksum(bool longLink, out string linkName) + private int GetLinkChecksum(TarEntryFormat format, bool longLink, out string linkName) { int expectedChecksum = 0; if (!longLink) @@ -222,12 +224,16 @@ private int GetLinkChecksum(bool longLink, out string linkName) } else { - linkName = new string('a', 150); - // 100 * 97 = 9700 (first 100 bytes go into 'linkName' field) + linkName = new string('a', 100); // 100 * 97 = 9700 (first 100 bytes go into 'linkName' field) expectedChecksum += 9700; - // - V7 and Ustar ignore the rest of the bytes - // - Pax and Gnu write first 100 bytes in 'linkName' field, then the full link name is written in the + + // V7 and Ustar does not support name fields larger than 100 + // Pax and Gnu write first 100 bytes in 'linkName' field, then the full link name is written in the // preceding metadata entry (extended attributes for PAX, LongLink for GNU). + if (format is not TarEntryFormat.V7 and not TarEntryFormat.Ustar) + { + linkName += "/" + new string('a', 50); + } } return expectedChecksum; diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs index d591ba9b6542a6..925daef99d5167 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs @@ -167,6 +167,10 @@ public void Write_Long_Name(TarEntryType entryType) using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry entry = new GnuTarEntry(entryType, longName); + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + entry.LinkName = "linktarget"; + } writer.WriteEntry(entry); } @@ -231,5 +235,15 @@ public void Write_LongName_And_LongLinkName(TarEntryType entryType) Assert.Equal(longLinkName, entry.LinkName); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public void Write_LinkEntry_EmptyLinkName_Throws(TarEntryType entryType) + { + using MemoryStream archiveStream = new MemoryStream(); + using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + Assert.Throws("entry", () => writer.WriteEntry(new GnuTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs index 6c9e79d83a1eff..1e81fb7b1e8a01 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs @@ -485,5 +485,15 @@ public void WriteTimestampsBeyondOctalLimitInPax() Assert.Equal(overLimitTimestamp, actualCTime); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public void Write_LinkEntry_EmptyLinkName_Throws(TarEntryType entryType) + { + using MemoryStream archiveStream = new MemoryStream(); + using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + Assert.Throws("entry", () => writer.WriteEntry(new PaxTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Roundtrip.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Roundtrip.Tests.cs new file mode 100644 index 00000000000000..650c31b1f86734 --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Roundtrip.Tests.cs @@ -0,0 +1,253 @@ +// 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.IO; +using System.Linq; +using Xunit; + +namespace System.Formats.Tar.Tests +{ + public class TarWriter_WriteEntry_Roundtrip_Tests : TarTestsBase + { + public static IEnumerable NameRoundtripsTheoryData() + { + foreach (bool unseekableStream in new[] { false, true }) + { + foreach (TarEntryType entryType in new[] { TarEntryType.RegularFile, TarEntryType.Directory }) + { + foreach (string name in GetNamesNonAsciiTestData(NameCapabilities.Name).Concat(GetNamesPrefixedTestData(NameCapabilities.Name))) + { + TarEntryType v7EntryType = entryType is TarEntryType.RegularFile ? TarEntryType.V7RegularFile : entryType; + yield return new object[] { TarEntryFormat.V7, v7EntryType, unseekableStream, name }; + } + + foreach (string name in GetNamesNonAsciiTestData(NameCapabilities.NameAndPrefix).Concat(GetNamesPrefixedTestData(NameCapabilities.NameAndPrefix))) + { + yield return new object[] { TarEntryFormat.Ustar, entryType, unseekableStream, name }; + } + + foreach (string name in GetNamesNonAsciiTestData(NameCapabilities.Unlimited).Concat(GetNamesPrefixedTestData(NameCapabilities.Unlimited))) + { + yield return new object[] { TarEntryFormat.Pax, entryType, unseekableStream, name }; + yield return new object[] { TarEntryFormat.Gnu, entryType, unseekableStream, name }; + } + } + } + } + + [Theory] + [MemberData(nameof(NameRoundtripsTheoryData))] + public void NameRoundtrips(TarEntryFormat entryFormat, TarEntryType entryType, bool unseekableStream, string name) + { + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, entryType, name); + entry.Name = name; + + MemoryStream ms = new(); + Stream s = unseekableStream ? new WrappedStream(ms, ms.CanRead, ms.CanWrite, canSeek: false) : ms; + + using (TarWriter writer = new(s, leaveOpen: true)) + { + writer.WriteEntry(entry); + } + + ms.Position = 0; + using TarReader reader = new(s); + + entry = reader.GetNextEntry(); + Assert.Null(reader.GetNextEntry()); + Assert.Equal(name, entry.Name); + } + + public static IEnumerable LinkNameRoundtripsTheoryData() + { + foreach (bool unseekableStream in new[] { false, true }) + { + foreach (TarEntryType entryType in new[] { TarEntryType.SymbolicLink, TarEntryType.HardLink }) + { + foreach (string name in GetNamesNonAsciiTestData(NameCapabilities.Name).Concat(GetNamesPrefixedTestData(NameCapabilities.Name))) + { + yield return new object[] { TarEntryFormat.V7, entryType, unseekableStream, name }; + yield return new object[] { TarEntryFormat.Ustar, entryType, unseekableStream, name }; + } + + foreach (string name in GetNamesNonAsciiTestData(NameCapabilities.Unlimited).Concat(GetNamesPrefixedTestData(NameCapabilities.Unlimited))) + { + yield return new object[] { TarEntryFormat.Pax, entryType, unseekableStream, name }; + yield return new object[] { TarEntryFormat.Gnu, entryType, unseekableStream, name }; + } + } + } + } + + [Theory] + [MemberData(nameof(LinkNameRoundtripsTheoryData))] + public void LinkNameRoundtrips(TarEntryFormat entryFormat, TarEntryType entryType, bool unseekableStream, string linkName) + { + string name = "foo"; + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, entryType, name); + entry.LinkName = linkName; + + MemoryStream ms = new(); + Stream s = unseekableStream ? new WrappedStream(ms, ms.CanRead, ms.CanWrite, canSeek: false) : ms; + + using (TarWriter writer = new(s, leaveOpen: true)) + { + writer.WriteEntry(entry); + } + + ms.Position = 0; + using TarReader reader = new(s); + + entry = reader.GetNextEntry(); + Assert.Null(reader.GetNextEntry()); + Assert.Equal(name, entry.Name); + Assert.Equal(linkName, entry.LinkName); + } + + public static IEnumerable UserNameGroupNameRoundtripsTheoryData() + { + foreach (bool unseekableStream in new[] { false, true }) + { + foreach (TarEntryFormat entryFormat in new[] { TarEntryFormat.Ustar, TarEntryFormat.Pax, TarEntryFormat.Gnu }) + { + yield return new object[] { entryFormat, unseekableStream, Repeat(OneByteCharacter, 32) }; + yield return new object[] { entryFormat, unseekableStream, Repeat(TwoBytesCharacter, 32 / 2) }; + yield return new object[] { entryFormat, unseekableStream, Repeat(FourBytesCharacter, 32 / 4) }; + } + } + } + + [Theory] + [MemberData(nameof(UserNameGroupNameRoundtripsTheoryData))] + public void UserNameGroupNameRoundtrips(TarEntryFormat entryFormat, bool unseekableStream, string userGroupName) + { + string name = "foo"; + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, TarEntryType.RegularFile, name); + PosixTarEntry posixEntry = Assert.IsAssignableFrom(entry); + posixEntry.UserName = userGroupName; + posixEntry.GroupName = userGroupName; + + MemoryStream ms = new(); + Stream s = unseekableStream ? new WrappedStream(ms, ms.CanRead, ms.CanWrite, canSeek: false) : ms; + + using (TarWriter writer = new(s, leaveOpen: true)) + { + writer.WriteEntry(posixEntry); + } + + ms.Position = 0; + using TarReader reader = new(s); + + entry = reader.GetNextEntry(); + posixEntry = Assert.IsAssignableFrom(entry); + Assert.Null(reader.GetNextEntry()); + + Assert.Equal(name, posixEntry.Name); + Assert.Equal(userGroupName, posixEntry.UserName); + Assert.Equal(userGroupName, posixEntry.GroupName); + } + + [Theory] + [InlineData(TarEntryType.RegularFile)] + [InlineData(TarEntryType.Directory)] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public void PaxExtendedAttributes_DoNotOverwritePublicProperties_WhenTheyFitOnLegacyFields(TarEntryType entryType) + { + Dictionary extendedAttributes = new(); + extendedAttributes[PaxEaName] = "ea_name"; + extendedAttributes[PaxEaGName] = "ea_gname"; + extendedAttributes[PaxEaUName] = "ea_uname"; + extendedAttributes[PaxEaMTime] = GetTimestampStringFromDateTimeOffset(TestModificationTime); + extendedAttributes[PaxEaSize] = 42.ToString(); + + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + extendedAttributes[PaxEaLinkName] = "ea_linkname"; + } + + PaxTarEntry writeEntry = new PaxTarEntry(entryType, "name", extendedAttributes); + writeEntry.Name = new string('a', 100); + // GName and UName must be longer than 32 to be written as extended attribute. + writeEntry.GroupName = new string('b', 32); + writeEntry.UserName = new string('c', 32); + // There's no limit on MTime, we just ensure it roundtrips. + writeEntry.ModificationTime = TestModificationTime.AddDays(1); + + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + writeEntry.LinkName = new string('d', 100); + } + + MemoryStream ms = new(); + using (TarWriter w = new(ms, leaveOpen: true)) + { + w.WriteEntry(writeEntry); + } + ms.Position = 0; + + using TarReader r = new(ms); + PaxTarEntry readEntry = Assert.IsType(r.GetNextEntry()); + Assert.Null(r.GetNextEntry()); + + Assert.Equal(writeEntry.Name, readEntry.Name); + Assert.Equal(writeEntry.GroupName, readEntry.GroupName); + Assert.Equal(writeEntry.UserName, readEntry.UserName); + Assert.Equal(writeEntry.ModificationTime, readEntry.ModificationTime); + Assert.Equal(writeEntry.LinkName, readEntry.LinkName); + + Assert.Equal(0, writeEntry.Length); + Assert.Equal(0, readEntry.Length); + } + + [Theory] + [InlineData(TarEntryType.RegularFile)] + [InlineData(TarEntryType.Directory)] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public void PaxExtendedAttributes_DoNotOverwritePublicProperties_WhenLargerThanLegacyFields(TarEntryType entryType) + { + Dictionary extendedAttributes = new(); + extendedAttributes[PaxEaName] = "ea_name"; + extendedAttributes[PaxEaGName] = "ea_gname"; + extendedAttributes[PaxEaUName] = "ea_uname"; + extendedAttributes[PaxEaMTime] = GetTimestampStringFromDateTimeOffset(TestModificationTime); + + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + extendedAttributes[PaxEaLinkName] = "ea_linkname"; + } + + PaxTarEntry writeEntry = new PaxTarEntry(entryType, "name", extendedAttributes); + writeEntry.Name = new string('a', MaxPathComponent); + // GName and UName must be longer than 32 to be written as extended attribute. + writeEntry.GroupName = new string('b', 32 + 1); + writeEntry.UserName = new string('c', 32 + 1); + // There's no limit on MTime, we just ensure it roundtrips. + writeEntry.ModificationTime = TestModificationTime.AddDays(1); + + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + writeEntry.LinkName = new string('d', 100 + 1); + } + + MemoryStream ms = new(); + using (TarWriter w = new(ms, leaveOpen: true)) + { + w.WriteEntry(writeEntry); + } + ms.Position = 0; + + using TarReader r = new(ms); + PaxTarEntry readEntry = Assert.IsType(r.GetNextEntry()); + Assert.Null(r.GetNextEntry()); + + Assert.Equal(writeEntry.Name, readEntry.Name); + Assert.Equal(writeEntry.GroupName, readEntry.GroupName); + Assert.Equal(writeEntry.UserName, readEntry.UserName); + Assert.Equal(writeEntry.ModificationTime, readEntry.ModificationTime); + Assert.Equal(writeEntry.LinkName, readEntry.LinkName); + } + } +} diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs index da3b69051ab347..c1f2d07562d388 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs @@ -152,5 +152,15 @@ public void WriteFifo() VerifyFifo(fifo); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public void Write_LinkEntry_EmptyLinkName_Throws(TarEntryType entryType) + { + using MemoryStream archiveStream = new MemoryStream(); + using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + Assert.Throws("entry", () => writer.WriteEntry(new UstarTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs index fb0cbb980ee6e2..51be90c74b07c9 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs @@ -92,5 +92,15 @@ public void WriteDirectory() VerifyDirectory(directory); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public void Write_LinkEntry_EmptyLinkName_Throws(TarEntryType entryType) + { + using MemoryStream archiveStream = new MemoryStream(); + using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + Assert.Throws("entry", () => writer.WriteEntry(new V7TarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.Unix.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.Unix.cs index 5ca600f992f932..6423fa830c3f2c 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.Unix.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.Unix.cs @@ -1,15 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.DotNet.RemoteExecutor; using System.IO; +using Microsoft.DotNet.RemoteExecutor; using Xunit; namespace System.Formats.Tar.Tests { public partial class TarWriter_WriteEntry_File_Tests : TarWriter_File_Base { - [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndOnUnixAndSuperUser))] + [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] [InlineData(TarEntryFormat.Ustar)] [InlineData(TarEntryFormat.Pax)] [InlineData(TarEntryFormat.Gnu)] @@ -51,7 +51,7 @@ public void Add_Fifo(TarEntryFormat format) }, format.ToString(), new RemoteInvokeOptions { RunAsSudo = true }).Dispose(); } - [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndOnUnixAndSuperUser))] + [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] [InlineData(TarEntryFormat.Ustar)] [InlineData(TarEntryFormat.Pax)] [InlineData(TarEntryFormat.Gnu)] @@ -96,7 +96,7 @@ public void Add_BlockDevice(TarEntryFormat format) }, format.ToString(), new RemoteInvokeOptions { RunAsSudo = true }).Dispose(); } - [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndOnUnixAndSuperUser))] + [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] [InlineData(TarEntryFormat.Ustar)] [InlineData(TarEntryFormat.Pax)] [InlineData(TarEntryFormat.Gnu)] @@ -139,5 +139,170 @@ public void Add_CharacterDevice(TarEntryFormat format) }, format.ToString(), new RemoteInvokeOptions { RunAsSudo = true }).Dispose(); } + + [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public void CreateEntryFromFileOwnedByNonExistentGroup(TarEntryFormat f) + { + RemoteExecutor.Invoke((string strFormat) => + { + using TempDirectory root = new TempDirectory(); + + string fileName = "file.txt"; + string filePath = Path.Join(root.Path, fileName); + File.Create(filePath).Dispose(); + + string groupName = Path.GetRandomFileName()[0..6]; + int groupId = CreateGroup(groupName); + + try + { + SetGroupAsOwnerOfFile(groupName, filePath); + } + finally + { + DeleteGroup(groupName); + } + + using MemoryStream archive = new MemoryStream(); + using (TarWriter writer = new TarWriter(archive, Enum.Parse(strFormat), leaveOpen: true)) + { + writer.WriteEntry(filePath, fileName); // Should not throw + } + archive.Seek(0, SeekOrigin.Begin); + + using (TarReader reader = new TarReader(archive, leaveOpen: false)) + { + PosixTarEntry entry = reader.GetNextEntry() as PosixTarEntry; + Assert.NotNull(entry); + + Assert.Equal(string.Empty, entry.GroupName); + Assert.Equal(groupId, entry.Gid); + + string extractedPath = Path.Join(root.Path, "extracted.txt"); + entry.ExtractToFile(extractedPath, overwrite: false); + Assert.True(File.Exists(extractedPath)); + + Assert.Null(reader.GetNextEntry()); + } + }, f.ToString(), new RemoteInvokeOptions { RunAsSudo = true }).Dispose(); + } + + [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public void CreateEntryFromFileOwnedByNonExistentUser(TarEntryFormat f) + { + RemoteExecutor.Invoke((string strFormat) => + { + using TempDirectory root = new TempDirectory(); + + string fileName = "file.txt"; + string filePath = Path.Join(root.Path, fileName); + File.Create(filePath).Dispose(); + + string userName = Path.GetRandomFileName()[0..6]; + int userId = CreateUser(userName); + + try + { + SetUserAsOwnerOfFile(userName, filePath); + } + finally + { + DeleteUser(userName); + } + + using MemoryStream archive = new MemoryStream(); + using (TarWriter writer = new TarWriter(archive, Enum.Parse(strFormat), leaveOpen: true)) + { + writer.WriteEntry(filePath, fileName); // Should not throw + } + archive.Seek(0, SeekOrigin.Begin); + + using (TarReader reader = new TarReader(archive, leaveOpen: false)) + { + PosixTarEntry entry = reader.GetNextEntry() as PosixTarEntry; + Assert.NotNull(entry); + + Assert.Equal(string.Empty, entry.UserName); + Assert.Equal(userId, entry.Uid); + + string extractedPath = Path.Join(root.Path, "extracted.txt"); + entry.ExtractToFile(extractedPath, overwrite: false); + Assert.True(File.Exists(extractedPath)); + + Assert.Null(reader.GetNextEntry()); + } + }, f.ToString(), new RemoteInvokeOptions { RunAsSudo = true }).Dispose(); + } + + [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public void CreateEntryFromFileOwnedByNonExistentGroupAndUser(TarEntryFormat f) + { + RemoteExecutor.Invoke((string strFormat) => + { + using TempDirectory root = new TempDirectory(); + + string fileName = "file.txt"; + string filePath = Path.Join(root.Path, fileName); + File.Create(filePath).Dispose(); + + string groupName = Path.GetRandomFileName()[0..6]; + int groupId = CreateGroup(groupName); + + string userName = Path.GetRandomFileName()[0..6]; + int userId = CreateUser(userName); + + try + { + SetGroupAsOwnerOfFile(groupName, filePath); + } + finally + { + DeleteGroup(groupName); + } + + try + { + SetUserAsOwnerOfFile(userName, filePath); + } + finally + { + DeleteUser(userName); + } + + using MemoryStream archive = new MemoryStream(); + using (TarWriter writer = new TarWriter(archive, Enum.Parse(strFormat), leaveOpen: true)) + { + writer.WriteEntry(filePath, fileName); // Should not throw + } + archive.Seek(0, SeekOrigin.Begin); + + using (TarReader reader = new TarReader(archive, leaveOpen: false)) + { + PosixTarEntry entry = reader.GetNextEntry() as PosixTarEntry; + Assert.NotNull(entry); + + Assert.Equal(string.Empty, entry.GroupName); + Assert.Equal(groupId, entry.Gid); + + Assert.Equal(string.Empty, entry.UserName); + Assert.Equal(userId, entry.Uid); + + string extractedPath = Path.Join(root.Path, "extracted.txt"); + entry.ExtractToFile(extractedPath, overwrite: false); + Assert.True(File.Exists(extractedPath)); + + Assert.Null(reader.GetNextEntry()); + } + }, f.ToString(), new RemoteInvokeOptions { RunAsSudo = true }).Dispose(); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.LongFile.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.LongFile.Tests.cs new file mode 100644 index 00000000000000..613e30c541d1c4 --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.LongFile.Tests.cs @@ -0,0 +1,91 @@ +// 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.IO; +using Xunit; + +namespace System.Formats.Tar.Tests +{ + [OuterLoop] + [Collection(nameof(DisableParallelization))] // don't create multiple large files at the same time + public class TarWriter_WriteEntry_LongFile_Tests : TarTestsBase + { + public static IEnumerable WriteEntry_LongFileSize_TheoryData() + { + foreach (bool unseekableStream in new[] { false, true }) + { + foreach (TarEntryFormat entryFormat in new[] { TarEntryFormat.V7, TarEntryFormat.Ustar, TarEntryFormat.Gnu, TarEntryFormat.Pax }) + { + yield return new object[] { entryFormat, LegacyMaxFileSize, unseekableStream }; + } + + // Pax supports unlimited size files. + yield return new object[] { TarEntryFormat.Pax, LegacyMaxFileSize + 1, unseekableStream }; + } + } + + [Theory] + [MemberData(nameof(WriteEntry_LongFileSize_TheoryData))] + public void WriteEntry_LongFileSize(TarEntryFormat entryFormat, long size, bool unseekableStream) + { + // Write archive with a 8 Gb long entry. + using FileStream tarFile = File.Open(GetTestFilePath(), new FileStreamOptions { Access = FileAccess.ReadWrite, Mode = FileMode.Create, Options = FileOptions.DeleteOnClose }); + Stream s = unseekableStream ? new WrappedStream(tarFile, tarFile.CanRead, tarFile.CanWrite, canSeek: false) : tarFile; + + using (TarWriter writer = new(s, leaveOpen: true)) + { + TarEntry writeEntry = InvokeTarEntryCreationConstructor(entryFormat, entryFormat is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile, "foo"); + writeEntry.DataStream = new SimulatedDataStream(size); + writer.WriteEntry(writeEntry); + } + + tarFile.Position = 0; + + // Read archive back. + using TarReader reader = new TarReader(s); + TarEntry entry = reader.GetNextEntry(); + Assert.Equal(size, entry.Length); + + Stream dataStream = entry.DataStream; + Assert.Equal(size, dataStream.Length); + Assert.Equal(0, dataStream.Position); + + ReadOnlySpan dummyData = SimulatedDataStream.DummyData.Span; + + // Read the first bytes. + Span buffer = new byte[dummyData.Length]; + Assert.Equal(buffer.Length, dataStream.Read(buffer)); + AssertExtensions.SequenceEqual(dummyData, buffer); + Assert.Equal(0, dataStream.ReadByte()); // check next byte is correct. + buffer.Clear(); + + // Read the last bytes. + long dummyDataOffset = size - dummyData.Length - 1; + if (dataStream.CanSeek) + { + Assert.False(unseekableStream); + dataStream.Seek(dummyDataOffset, SeekOrigin.Begin); + } + else + { + Assert.True(unseekableStream); + Span seekBuffer = new byte[4_096]; + + while (dataStream.Position < dummyDataOffset) + { + int bufSize = (int)Math.Min(seekBuffer.Length, dummyDataOffset - dataStream.Position); + int res = dataStream.Read(seekBuffer.Slice(0, bufSize)); + Assert.True(res > 0, "Unseekable stream finished before expected - Something went very wrong"); + } + } + + Assert.Equal(0, dataStream.ReadByte()); // check previous byte is correct. + Assert.Equal(buffer.Length, dataStream.Read(buffer)); + AssertExtensions.SequenceEqual(dummyData, buffer); + Assert.Equal(size, dataStream.Position); + + Assert.Null(reader.GetNextEntry()); + } + } +} diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs index 6d19503c4fc25d..3b36432e43311c 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using Xunit; namespace System.Formats.Tar.Tests @@ -101,7 +102,7 @@ public void Write_RegularFileEntry_In_V7Writer(TarEntryFormat entryFormat) TarEntryFormat.Ustar => new UstarTarEntry(TarEntryType.RegularFile, InitialEntryName), TarEntryFormat.Pax => new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName), TarEntryFormat.Gnu => new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName), - _ => throw new FormatException($"Unexpected format: {entryFormat}") + _ => throw new InvalidDataException($"Unexpected format: {entryFormat}") }; // Should be written in the format of the entry @@ -299,5 +300,215 @@ public void WriteTimestampsBeyondOctalLimit(TarEntryFormat format) } } } + + [Theory] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public void WriteLongName(TarEntryFormat format) + { + var r = new Random(); + foreach (int length in new[] { 99, 100, 101, 199, 200, 201, 254, 255, 256 }) + { + string name = string.Concat(Enumerable.Range(0, length).Select(_ => (char)('a' + r.Next(26)))); + WriteLongNameCore(format, name); + } + } + + private void WriteLongNameCore(TarEntryFormat format, string maxPathComponent) + { + TarEntry entry; + MemoryStream ms = new(); + using (TarWriter writer = new(ms, true)) + { + TarEntryType entryType = format == TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile; + entry = InvokeTarEntryCreationConstructor(format, entryType, maxPathComponent); + writer.WriteEntry(entry); + + entry = InvokeTarEntryCreationConstructor(format, entryType, Path.Join(maxPathComponent, maxPathComponent)); + writer.WriteEntry(entry); + } + + ms.Position = 0; + using TarReader reader = new(ms); + + entry = reader.GetNextEntry(); + string expectedName = GetExpectedNameForFormat(format, maxPathComponent); + Assert.Equal(expectedName, entry.Name); + + entry = reader.GetNextEntry(); + expectedName = GetExpectedNameForFormat(format, Path.Join(maxPathComponent, maxPathComponent)); + Assert.Equal(expectedName, entry.Name); + + Assert.Null(reader.GetNextEntry()); + + string GetExpectedNameForFormat(TarEntryFormat format, string expectedName) + { + if (format is TarEntryFormat.V7 && expectedName.Length > 100) // V7 truncates names at 100 characters. + { + return expectedName.Substring(0, 100); + } + return expectedName; + } + } + + public static IEnumerable WriteEntry_TooLongName_Throws_TheoryData() + { + foreach (TarEntryType entryType in new[] { TarEntryType.RegularFile, TarEntryType.Directory }) + { + foreach (string name in GetTooLongNamesTestData(NameCapabilities.Name)) + { + TarEntryType v7EntryType = entryType is TarEntryType.RegularFile ? TarEntryType.V7RegularFile : entryType; + yield return new object[] { TarEntryFormat.V7, v7EntryType, name }; + } + + foreach (string name in GetTooLongNamesTestData(NameCapabilities.NameAndPrefix)) + { + yield return new object[] { TarEntryFormat.Ustar, entryType, name }; + } + } + } + + [Theory] + [MemberData(nameof(WriteEntry_TooLongName_Throws_TheoryData))] + public void WriteEntry_TooLongName_Throws(TarEntryFormat entryFormat, TarEntryType entryType, string name) + { + using TarWriter writer = new(new MemoryStream()); + + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, entryType, name); + Assert.Throws("entry", () => writer.WriteEntry(entry)); + } + + public static IEnumerable WriteEntry_TooLongLinkName_Throws_TheoryData() + { + foreach (TarEntryType entryType in new[] { TarEntryType.SymbolicLink, TarEntryType.HardLink }) + { + foreach (string name in GetTooLongNamesTestData(NameCapabilities.Name)) + { + yield return new object[] { TarEntryFormat.V7, entryType, name }; + } + + foreach (string name in GetTooLongNamesTestData(NameCapabilities.NameAndPrefix)) + { + yield return new object[] { TarEntryFormat.Ustar, entryType, name }; + } + } + } + + [Theory] + [MemberData(nameof(WriteEntry_TooLongLinkName_Throws_TheoryData))] + public void WriteEntry_TooLongLinkName_Throws(TarEntryFormat entryFormat, TarEntryType entryType, string linkName) + { + using TarWriter writer = new(new MemoryStream()); + + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, entryType, "foo"); + entry.LinkName = linkName; + + Assert.Throws("entry", () => writer.WriteEntry(entry)); + } + + public static IEnumerable WriteEntry_TooLongUserGroupName_Throws_TheoryData() + { + // Not testing Pax as it supports unlimited size uname/gname. + foreach (TarEntryFormat entryFormat in new[] { TarEntryFormat.Ustar, TarEntryFormat.Gnu }) + { + // Last character doesn't fit fully. + yield return new object[] { entryFormat, Repeat(OneByteCharacter, 32 + 1) }; + yield return new object[] { entryFormat, Repeat(TwoBytesCharacter, 32 / 2 + 1) }; + yield return new object[] { entryFormat, Repeat(FourBytesCharacter, 32 / 4 + 1) }; + + // Last character doesn't fit by one byte. + yield return new object[] { entryFormat, Repeat(TwoBytesCharacter, 32 - 2 + 1) + TwoBytesCharacter }; + yield return new object[] { entryFormat, Repeat(FourBytesCharacter, 32 - 4 + 1) + FourBytesCharacter }; + } + } + + [Theory] + [MemberData(nameof(WriteEntry_TooLongUserGroupName_Throws_TheoryData))] + public void WriteEntry_TooLongUserName_Throws(TarEntryFormat entryFormat, string userName) + { + using TarWriter writer = new(new MemoryStream()); + + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, TarEntryType.RegularFile, "foo"); + PosixTarEntry posixEntry = Assert.IsAssignableFrom(entry); + posixEntry.UserName = userName; + + Assert.Throws("entry", () => writer.WriteEntry(entry)); + } + + [Theory] + [MemberData(nameof(WriteEntry_TooLongUserGroupName_Throws_TheoryData))] + public void WriteEntry_TooLongGroupName_Throws(TarEntryFormat entryFormat, string groupName) + { + using TarWriter writer = new(new MemoryStream()); + + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, TarEntryType.RegularFile, "foo"); + PosixTarEntry posixEntry = Assert.IsAssignableFrom(entry); + posixEntry.GroupName = groupName; + + Assert.Throws("entry", () => writer.WriteEntry(entry)); + } + + public static IEnumerable WriteEntry_UsingTarEntry_FromTarReader_IntoTarWriter_TheoryData() + { + foreach (var entryFormat in new[] { TarEntryFormat.V7, TarEntryFormat.Ustar, TarEntryFormat.Pax, TarEntryFormat.Gnu }) + { + foreach (var entryType in new[] { entryFormat == TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile, TarEntryType.Directory, TarEntryType.SymbolicLink }) + { + foreach (bool unseekableStream in new[] { false, true }) + { + yield return new object[] { entryFormat, entryType, unseekableStream }; + } + } + } + } + + [Theory] + [MemberData(nameof(WriteEntry_UsingTarEntry_FromTarReader_IntoTarWriter_TheoryData))] + public void WriteEntry_UsingTarEntry_FromTarReader_IntoTarWriter(TarEntryFormat entryFormat, TarEntryType entryType, bool unseekableStream) + { + MemoryStream msSource = new(); + MemoryStream msDestination = new(); + + WriteTarArchiveWithOneEntry(msSource, entryFormat, entryType); + msSource.Position = 0; + + Stream source = new WrappedStream(msSource, msSource.CanRead, msSource.CanWrite, canSeek: !unseekableStream); + Stream destination = new WrappedStream(msDestination, msDestination.CanRead, msDestination.CanWrite, canSeek: !unseekableStream); + + using (TarReader reader = new(source)) + using (TarWriter writer = new(destination)) + { + TarEntry entry; + while ((entry = reader.GetNextEntry()) != null) + { + writer.WriteEntry(entry); + } + } + + AssertExtensions.SequenceEqual(msSource.ToArray(), msDestination.ToArray()); + } + + [Theory] + [InlineData(TarEntryFormat.V7, false)] + [InlineData(TarEntryFormat.Ustar, false)] + [InlineData(TarEntryFormat.Gnu, false)] + [InlineData(TarEntryFormat.V7, true)] + [InlineData(TarEntryFormat.Ustar, true)] + [InlineData(TarEntryFormat.Gnu, true)] + public void WriteEntry_FileSizeOverLegacyLimit_Throws(TarEntryFormat entryFormat, bool unseekableStream) + { + const long FileSizeOverLimit = LegacyMaxFileSize + 1; + + MemoryStream ms = new(); + Stream s = unseekableStream ? new WrappedStream(ms, ms.CanRead, ms.CanWrite, canSeek: false) : ms; + + using TarWriter writer = new(s); + TarEntry writeEntry = InvokeTarEntryCreationConstructor(entryFormat, entryFormat is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile, "foo"); + writeEntry.DataStream = new SimulatedDataStream(FileSizeOverLimit); + + Assert.Equal(FileSizeOverLimit, writeEntry.Length); + + Assert.Throws(() => writer.WriteEntry(writeEntry)); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Gnu.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Gnu.Tests.cs index b33e2bf7add22a..1e77e548d4a6f7 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Gnu.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Gnu.Tests.cs @@ -183,6 +183,10 @@ public async Task Write_Long_Name_Async(TarEntryType entryType) await using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry entry = new GnuTarEntry(entryType, longName); + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + entry.LinkName = "linktarget"; + } await writer.WriteEntryAsync(entry); } @@ -252,5 +256,15 @@ public async Task Write_LongName_And_LongLinkName_Async(TarEntryType entryType) } } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public async Task Write_LinkEntry_EmptyLinkName_Throws_Async(TarEntryType entryType) + { + await using MemoryStream archiveStream = new MemoryStream(); + await using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(new GnuTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Pax.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Pax.Tests.cs index 688c918baedf15..b0c9d636420b52 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Pax.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Pax.Tests.cs @@ -505,5 +505,15 @@ public async Task WriteTimestampsBeyondOctalLimitInPax_Async() Assert.Equal(overLimitTimestamp, actualCTime); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public async Task Write_LinkEntry_EmptyLinkName_Throws_Async(TarEntryType entryType) + { + await using MemoryStream archiveStream = new MemoryStream(); + await using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(new PaxTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Roundtrip.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Roundtrip.Tests.cs new file mode 100644 index 00000000000000..e1ba5ef7819cac --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Roundtrip.Tests.cs @@ -0,0 +1,202 @@ +// 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.IO; +using System.Threading.Tasks; +using Xunit; + +namespace System.Formats.Tar.Tests +{ + public class TarWriter_WriteEntryAsync_Roundtrip_Tests : TarTestsBase + { + public static IEnumerable NameRoundtripsAsyncTheoryData() + => TarWriter_WriteEntry_Roundtrip_Tests.NameRoundtripsTheoryData(); + + [Theory] + [MemberData(nameof(NameRoundtripsAsyncTheoryData))] + public async Task NameRoundtripsAsync(TarEntryFormat entryFormat, TarEntryType entryType, bool unseekableStream, string name) + { + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, entryType, name); + entry.Name = name; + + MemoryStream ms = new(); + Stream s = unseekableStream ? new WrappedStream(ms, ms.CanRead, ms.CanWrite, canSeek: false) : ms; + + await using (TarWriter writer = new(s, leaveOpen: true)) + { + await writer.WriteEntryAsync(entry); + } + + ms.Position = 0; + await using TarReader reader = new(s); + + entry = await reader.GetNextEntryAsync(); + Assert.Null(await reader.GetNextEntryAsync()); + Assert.Equal(name, entry.Name); + } + + public static IEnumerable LinkNameRoundtripsAsyncTheoryData() + => TarWriter_WriteEntry_Roundtrip_Tests.LinkNameRoundtripsTheoryData(); + + [Theory] + [MemberData(nameof(LinkNameRoundtripsAsyncTheoryData))] + public async Task LinkNameRoundtripsAsync(TarEntryFormat entryFormat, TarEntryType entryType, bool unseekableStream, string linkName) + { + string name = "foo"; + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, entryType, name); + entry.LinkName = linkName; + + MemoryStream ms = new(); + Stream s = unseekableStream ? new WrappedStream(ms, ms.CanRead, ms.CanWrite, canSeek: false) : ms; + + await using (TarWriter writer = new(s, leaveOpen: true)) + { + await writer.WriteEntryAsync(entry); + } + + ms.Position = 0; + await using TarReader reader = new(s); + + entry = await reader.GetNextEntryAsync(); + Assert.Null(await reader.GetNextEntryAsync()); + Assert.Equal(name, entry.Name); + Assert.Equal(linkName, entry.LinkName); + } + + public static IEnumerable UserNameGroupNameRoundtripsAsyncTheoryData() + => TarWriter_WriteEntry_Roundtrip_Tests.UserNameGroupNameRoundtripsTheoryData(); + + [Theory] + [MemberData(nameof(UserNameGroupNameRoundtripsAsyncTheoryData))] + public async Task UserNameGroupNameRoundtripsAsync(TarEntryFormat entryFormat, bool unseekableStream, string userGroupName) + { + string name = "foo"; + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, TarEntryType.RegularFile, name); + PosixTarEntry posixEntry = Assert.IsAssignableFrom(entry); + posixEntry.UserName = userGroupName; + posixEntry.GroupName = userGroupName; + + MemoryStream ms = new(); + Stream s = unseekableStream ? new WrappedStream(ms, ms.CanRead, ms.CanWrite, canSeek: false) : ms; + + await using (TarWriter writer = new(s, leaveOpen: true)) + { + await writer.WriteEntryAsync(posixEntry); + } + + ms.Position = 0; + await using TarReader reader = new(s); + + entry = await reader.GetNextEntryAsync(); + posixEntry = Assert.IsAssignableFrom(entry); + Assert.Null(await reader.GetNextEntryAsync()); + + Assert.Equal(name, posixEntry.Name); + Assert.Equal(userGroupName, posixEntry.UserName); + Assert.Equal(userGroupName, posixEntry.GroupName); + } + + [Theory] + [InlineData(TarEntryType.RegularFile)] + [InlineData(TarEntryType.Directory)] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public async Task PaxExtendedAttributes_DoNotOverwritePublicProperties_WhenTheyFitOnLegacyFieldsAsync(TarEntryType entryType) + { + Dictionary extendedAttributes = new(); + extendedAttributes[PaxEaName] = "ea_name"; + extendedAttributes[PaxEaGName] = "ea_gname"; + extendedAttributes[PaxEaUName] = "ea_uname"; + extendedAttributes[PaxEaMTime] = GetTimestampStringFromDateTimeOffset(TestModificationTime); + extendedAttributes[PaxEaSize] = 42.ToString(); + + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + extendedAttributes[PaxEaLinkName] = "ea_linkname"; + } + + PaxTarEntry writeEntry = new PaxTarEntry(entryType, "name", extendedAttributes); + writeEntry.Name = new string('a', 100); + // GName and UName must be longer than 32 to be written as extended attribute. + writeEntry.GroupName = new string('b', 32); + writeEntry.UserName = new string('c', 32); + // There's no limit on MTime, we just ensure it roundtrips. + writeEntry.ModificationTime = TestModificationTime.AddDays(1); + + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + writeEntry.LinkName = new string('d', 100); + } + + MemoryStream ms = new(); + await using (TarWriter w = new(ms, leaveOpen: true)) + { + await w.WriteEntryAsync(writeEntry); + } + ms.Position = 0; + + await using TarReader r = new(ms); + PaxTarEntry readEntry = Assert.IsType(await r.GetNextEntryAsync()); + Assert.Null(await r.GetNextEntryAsync()); + + Assert.Equal(writeEntry.Name, readEntry.Name); + Assert.Equal(writeEntry.GroupName, readEntry.GroupName); + Assert.Equal(writeEntry.UserName, readEntry.UserName); + Assert.Equal(writeEntry.ModificationTime, readEntry.ModificationTime); + Assert.Equal(writeEntry.LinkName, readEntry.LinkName); + + Assert.Equal(0, writeEntry.Length); + Assert.Equal(0, readEntry.Length); + } + + [Theory] + [InlineData(TarEntryType.RegularFile)] + [InlineData(TarEntryType.Directory)] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public async Task PaxExtendedAttributes_DoNotOverwritePublicProperties_WhenLargerThanLegacyFieldsAsync(TarEntryType entryType) + { + Dictionary extendedAttributes = new(); + extendedAttributes[PaxEaName] = "ea_name"; + extendedAttributes[PaxEaGName] = "ea_gname"; + extendedAttributes[PaxEaUName] = "ea_uname"; + extendedAttributes[PaxEaMTime] = GetTimestampStringFromDateTimeOffset(TestModificationTime); + + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + extendedAttributes[PaxEaLinkName] = "ea_linkname"; + } + + PaxTarEntry writeEntry = new PaxTarEntry(entryType, "name", extendedAttributes); + writeEntry.Name = new string('a', MaxPathComponent); + // GName and UName must be longer than 32 to be written as extended attribute. + writeEntry.GroupName = new string('b', 32 + 1); + writeEntry.UserName = new string('c', 32 + 1); + // There's no limit on MTime, we just ensure it roundtrips. + writeEntry.ModificationTime = TestModificationTime.AddDays(1); + + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + writeEntry.LinkName = new string('d', 100 + 1); + } + + MemoryStream ms = new(); + await using (TarWriter w = new(ms, leaveOpen: true)) + { + await w.WriteEntryAsync(writeEntry); + } + ms.Position = 0; + + await using TarReader r = new(ms); + PaxTarEntry readEntry = Assert.IsType(await r.GetNextEntryAsync()); + Assert.Null(await r.GetNextEntryAsync()); + + Assert.Equal(writeEntry.Name, readEntry.Name); + Assert.Equal(writeEntry.GroupName, readEntry.GroupName); + Assert.Equal(writeEntry.UserName, readEntry.UserName); + Assert.Equal(writeEntry.ModificationTime, readEntry.ModificationTime); + Assert.Equal(writeEntry.LinkName, readEntry.LinkName); + } + } +} diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Ustar.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Ustar.Tests.cs index 1266e7216a0126..b3e207d434f78e 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Ustar.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Ustar.Tests.cs @@ -153,5 +153,15 @@ public async Task WriteFifo_Async() VerifyFifo(fifo); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public async Task Write_LinkEntry_EmptyLinkName_Throws_Async(TarEntryType entryType) + { + await using MemoryStream archiveStream = new MemoryStream(); + await using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(new UstarTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.V7.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.V7.Tests.cs index 4408477ee00006..3e41f263ef02e4 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.V7.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.V7.Tests.cs @@ -93,5 +93,15 @@ public async Task WriteDirectory_Async() VerifyDirectory(directory); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public async Task Write_LinkEntry_EmptyLinkName_Throws_Async(TarEntryType entryType) + { + await using MemoryStream archiveStream = new MemoryStream(); + await using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(new V7TarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.File.Tests.Unix.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.File.Tests.Unix.cs index ba4a5600ae81d4..d53503f34c2849 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.File.Tests.Unix.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.File.Tests.Unix.cs @@ -1,16 +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 Microsoft.DotNet.RemoteExecutor; using System.IO; using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; using Xunit; namespace System.Formats.Tar.Tests { public partial class TarWriter_WriteEntryAsync_File_Tests : TarWriter_File_Base { - [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndOnUnixAndSuperUser))] + [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] [InlineData(TarEntryFormat.Ustar)] [InlineData(TarEntryFormat.Pax)] [InlineData(TarEntryFormat.Gnu)] @@ -55,7 +55,7 @@ public void Add_Fifo_Async(TarEntryFormat format) }, format.ToString(), new RemoteInvokeOptions { RunAsSudo = true }).Dispose(); } - [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndOnUnixAndSuperUser))] + [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] [InlineData(TarEntryFormat.Ustar)] [InlineData(TarEntryFormat.Pax)] [InlineData(TarEntryFormat.Gnu)] @@ -103,7 +103,7 @@ public void Add_BlockDevice_Async(TarEntryFormat format) }, format.ToString(), new RemoteInvokeOptions { RunAsSudo = true }).Dispose(); } - [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndOnUnixAndSuperUser))] + [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] [InlineData(TarEntryFormat.Ustar)] [InlineData(TarEntryFormat.Pax)] [InlineData(TarEntryFormat.Gnu)] @@ -149,5 +149,170 @@ public void Add_CharacterDevice_Async(TarEntryFormat format) } }, format.ToString(), new RemoteInvokeOptions { RunAsSudo = true }).Dispose(); } + + [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public void CreateEntryFromFileOwnedByNonExistentGroup_Async(TarEntryFormat f) + { + RemoteExecutor.Invoke(async (string strFormat) => + { + using TempDirectory root = new TempDirectory(); + + string fileName = "file.txt"; + string filePath = Path.Join(root.Path, fileName); + File.Create(filePath).Dispose(); + + string groupName = Path.GetRandomFileName()[0..6]; + int groupId = CreateGroup(groupName); + + try + { + SetGroupAsOwnerOfFile(groupName, filePath); + } + finally + { + DeleteGroup(groupName); + } + + await using MemoryStream archive = new MemoryStream(); + await using (TarWriter writer = new TarWriter(archive, Enum.Parse(strFormat), leaveOpen: true)) + { + await writer.WriteEntryAsync(filePath, fileName); // Should not throw + } + archive.Seek(0, SeekOrigin.Begin); + + await using (TarReader reader = new TarReader(archive, leaveOpen: false)) + { + PosixTarEntry entry = await reader.GetNextEntryAsync() as PosixTarEntry; + Assert.NotNull(entry); + + Assert.Equal(string.Empty, entry.GroupName); + Assert.Equal(groupId, entry.Gid); + + string extractedPath = Path.Join(root.Path, "extracted.txt"); + await entry.ExtractToFileAsync(extractedPath, overwrite: false); + Assert.True(File.Exists(extractedPath)); + + Assert.Null(await reader.GetNextEntryAsync()); + } + }, f.ToString(), new RemoteInvokeOptions { RunAsSudo = true }).Dispose(); + } + + [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public void CreateEntryFromFileOwnedByNonExistentUser_Async(TarEntryFormat f) + { + RemoteExecutor.Invoke(async (string strFormat) => + { + using TempDirectory root = new TempDirectory(); + + string fileName = "file.txt"; + string filePath = Path.Join(root.Path, fileName); + File.Create(filePath).Dispose(); + + string userName = Path.GetRandomFileName()[0..6]; + int userId = CreateUser(userName); + + try + { + SetUserAsOwnerOfFile(userName, filePath); + } + finally + { + DeleteUser(userName); + } + + await using MemoryStream archive = new MemoryStream(); + await using (TarWriter writer = new TarWriter(archive, Enum.Parse(strFormat), leaveOpen: true)) + { + await writer.WriteEntryAsync(filePath, fileName); // Should not throw + } + archive.Seek(0, SeekOrigin.Begin); + + await using (TarReader reader = new TarReader(archive, leaveOpen: false)) + { + PosixTarEntry entry = await reader.GetNextEntryAsync() as PosixTarEntry; + Assert.NotNull(entry); + + Assert.Equal(string.Empty, entry.UserName); + Assert.Equal(userId, entry.Uid); + + string extractedPath = Path.Join(root.Path, "extracted.txt"); + await entry.ExtractToFileAsync(extractedPath, overwrite: false); + Assert.True(File.Exists(extractedPath)); + + Assert.Null(await reader.GetNextEntryAsync()); + } + }, f.ToString(), new RemoteInvokeOptions { RunAsSudo = true }).Dispose(); + } + + [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndPrivilegedProcess))] + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public void CreateEntryFromFileOwnedByNonExistentGroupAndUser_Async(TarEntryFormat f) + { + RemoteExecutor.Invoke(async (string strFormat) => + { + using TempDirectory root = new TempDirectory(); + + string fileName = "file.txt"; + string filePath = Path.Join(root.Path, fileName); + File.Create(filePath).Dispose(); + + string groupName = Path.GetRandomFileName()[0..6]; + int groupId = CreateGroup(groupName); + + string userName = Path.GetRandomFileName()[0..6]; + int userId = CreateUser(userName); + + try + { + SetGroupAsOwnerOfFile(groupName, filePath); + } + finally + { + DeleteGroup(groupName); + } + + try + { + SetUserAsOwnerOfFile(userName, filePath); + } + finally + { + DeleteUser(userName); + } + + await using MemoryStream archive = new MemoryStream(); + await using (TarWriter writer = new TarWriter(archive, Enum.Parse(strFormat), leaveOpen: true)) + { + await writer.WriteEntryAsync(filePath, fileName); // Should not throw + } + archive.Seek(0, SeekOrigin.Begin); + + await using (TarReader reader = new TarReader(archive, leaveOpen: false)) + { + PosixTarEntry entry = await reader.GetNextEntryAsync() as PosixTarEntry; + Assert.NotNull(entry); + + Assert.Equal(string.Empty, entry.GroupName); + Assert.Equal(groupId, entry.Gid); + + Assert.Equal(string.Empty, entry.UserName); + Assert.Equal(userId, entry.Uid); + + string extractedPath = Path.Join(root.Path, "extracted.txt"); + await entry.ExtractToFileAsync(extractedPath, overwrite: false); + Assert.True(File.Exists(extractedPath)); + + Assert.Null(await reader.GetNextEntryAsync()); + } + }, f.ToString(), new RemoteInvokeOptions { RunAsSudo = true }).Dispose(); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.LongFile.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.LongFile.Tests.cs new file mode 100644 index 00000000000000..6d260cc9ab7bdc --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.LongFile.Tests.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Xunit; + +namespace System.Formats.Tar.Tests +{ + [OuterLoop] + [Collection(nameof(DisableParallelization))] // don't create multiple large files at the same time + public class TarWriter_WriteEntryAsync_LongFile_Tests : TarTestsBase + { + public static IEnumerable WriteEntry_LongFileSize_TheoryDataAsync() + => TarWriter_WriteEntry_LongFile_Tests.WriteEntry_LongFileSize_TheoryData(); + + [Theory] + [MemberData(nameof(WriteEntry_LongFileSize_TheoryDataAsync))] + public async Task WriteEntry_LongFileSizeAsync(TarEntryFormat entryFormat, long size, bool unseekableStream) + { + // Write archive with a 8 Gb long entry. + await using FileStream tarFile = File.Open(GetTestFilePath(), new FileStreamOptions { Access = FileAccess.ReadWrite, Mode = FileMode.Create, Options = FileOptions.DeleteOnClose }); + Stream s = unseekableStream ? new WrappedStream(tarFile, tarFile.CanRead, tarFile.CanWrite, canSeek: false) : tarFile; + + await using (TarWriter writer = new(s, leaveOpen: true)) + { + TarEntry writeEntry = InvokeTarEntryCreationConstructor(entryFormat, entryFormat is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile, "foo"); + writeEntry.DataStream = new SimulatedDataStream(size); + await writer.WriteEntryAsync(writeEntry); + } + + tarFile.Position = 0; + + // Read the archive back. + await using TarReader reader = new TarReader(s); + TarEntry entry = await reader.GetNextEntryAsync(); + Assert.Equal(size, entry.Length); + + Stream dataStream = entry.DataStream; + Assert.Equal(size, dataStream.Length); + Assert.Equal(0, dataStream.Position); + + ReadOnlyMemory dummyData = SimulatedDataStream.DummyData; + + // Read the first bytes. + byte[] buffer = new byte[dummyData.Length]; + Assert.Equal(buffer.Length, dataStream.Read(buffer)); + AssertExtensions.SequenceEqual(dummyData.Span, buffer); + Assert.Equal(0, dataStream.ReadByte()); // check next byte is correct. + buffer.AsSpan().Clear(); + + // Read the last bytes. + long dummyDataOffset = size - dummyData.Length - 1; + if (dataStream.CanSeek) + { + Assert.False(unseekableStream); + dataStream.Seek(dummyDataOffset, SeekOrigin.Begin); + } + else + { + Assert.True(unseekableStream); + Memory seekBuffer = new byte[4_096]; + + while (dataStream.Position < dummyDataOffset) + { + int bufSize = (int)Math.Min(seekBuffer.Length, dummyDataOffset - dataStream.Position); + int res = await dataStream.ReadAsync(seekBuffer.Slice(0, bufSize)); + Assert.True(res > 0, "Unseekable stream finished before expected - Something went very wrong"); + } + } + + Assert.Equal(0, dataStream.ReadByte()); // check previous byte is correct. + Assert.Equal(buffer.Length, dataStream.Read(buffer)); + AssertExtensions.SequenceEqual(dummyData.Span, buffer); + Assert.Equal(size, dataStream.Position); + + Assert.Null(await reader.GetNextEntryAsync()); + } + } +} diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Tests.cs index 8f6637408a8c9b..f0d4d238b2f8e8 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Tests.cs @@ -125,7 +125,7 @@ public async Task Write_RegularFileEntry_In_V7Writer_Async(TarEntryFormat entryF TarEntryFormat.Ustar => new UstarTarEntry(TarEntryType.RegularFile, InitialEntryName), TarEntryFormat.Pax => new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName), TarEntryFormat.Gnu => new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName), - _ => throw new FormatException($"Unexpected format: {entryFormat}") + _ => throw new InvalidDataException($"Unexpected format: {entryFormat}") }; // Should be written in the format of the entry @@ -322,5 +322,115 @@ public async Task WriteTimestampsBeyondOctalLimit_Async(TarEntryFormat format) } } } + + public static IEnumerable WriteEntry_TooLongName_Throws_Async_TheoryData() + => TarWriter_WriteEntry_Tests.WriteEntry_TooLongName_Throws_TheoryData(); + + [Theory] + [MemberData(nameof(WriteEntry_TooLongName_Throws_Async_TheoryData))] + public async Task WriteEntry_TooLongName_Throws_Async(TarEntryFormat entryFormat, TarEntryType entryType, string name) + { + await using TarWriter writer = new(new MemoryStream()); + + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, entryType, name); + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(entry)); + } + + public static IEnumerable WriteEntry_TooLongLinkName_Throws_Async_TheoryData() + => TarWriter_WriteEntry_Tests.WriteEntry_TooLongLinkName_Throws_TheoryData(); + + [Theory] + [MemberData(nameof(WriteEntry_TooLongLinkName_Throws_Async_TheoryData))] + public async Task WriteEntry_TooLongLinkName_Throws_Async(TarEntryFormat entryFormat, TarEntryType entryType, string linkName) + { + await using TarWriter writer = new(new MemoryStream()); + + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, entryType, "foo"); + entry.LinkName = linkName; + + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(entry)); + } + + public static IEnumerable WriteEntry_TooLongUserGroupName_Throws_Async_TheoryData() + => TarWriter_WriteEntry_Tests.WriteEntry_TooLongUserGroupName_Throws_TheoryData(); + + [Theory] + [MemberData(nameof(WriteEntry_TooLongUserGroupName_Throws_Async_TheoryData))] + public async Task WriteEntry_TooLongUserName_Throws_Async(TarEntryFormat entryFormat, string userName) + { + await using TarWriter writer = new(new MemoryStream()); + + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, TarEntryType.RegularFile, "foo"); + PosixTarEntry posixEntry = Assert.IsAssignableFrom(entry); + posixEntry.UserName = userName; + + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(entry)); + } + + [Theory] + [MemberData(nameof(WriteEntry_TooLongUserGroupName_Throws_Async_TheoryData))] + public async Task WriteEntry_TooLongGroupName_Throws_Async(TarEntryFormat entryFormat, string groupName) + { + await using TarWriter writer = new(new MemoryStream()); + + TarEntry entry = InvokeTarEntryCreationConstructor(entryFormat, TarEntryType.RegularFile, "foo"); + PosixTarEntry posixEntry = Assert.IsAssignableFrom(entry); + posixEntry.GroupName = groupName; + + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(entry)); + } + + public static IEnumerable WriteEntry_UsingTarEntry_FromTarReader_IntoTarWriter_Async_TheoryData() + => TarWriter_WriteEntry_Tests.WriteEntry_UsingTarEntry_FromTarReader_IntoTarWriter_TheoryData(); + + [Theory] + [MemberData(nameof(WriteEntry_UsingTarEntry_FromTarReader_IntoTarWriter_Async_TheoryData))] + public async Task WriteEntry_UsingTarEntry_FromTarReader_IntoTarWriter_Async(TarEntryFormat entryFormat, TarEntryType entryType, bool unseekableStream) + { + using MemoryStream msSource = new(); + using MemoryStream msDestination = new(); + + WriteTarArchiveWithOneEntry(msSource, entryFormat, entryType); + msSource.Position = 0; + + Stream source = new WrappedStream(msSource, msSource.CanRead, msSource.CanWrite, canSeek: !unseekableStream); + Stream destination = new WrappedStream(msDestination, msDestination.CanRead, msDestination.CanWrite, canSeek: !unseekableStream); + + await using (TarReader reader = new(source)) + await using (TarWriter writer = new(destination)) + { + TarEntry entry; + while ((entry = await reader.GetNextEntryAsync()) != null) + { + await writer.WriteEntryAsync(entry); + } + } + + AssertExtensions.SequenceEqual(msSource.ToArray(), msDestination.ToArray()); + } + + [Theory] + [InlineData(TarEntryFormat.V7, false)] + [InlineData(TarEntryFormat.Ustar, false)] + [InlineData(TarEntryFormat.Gnu, false)] + [InlineData(TarEntryFormat.V7, true)] + [InlineData(TarEntryFormat.Ustar, true)] + [InlineData(TarEntryFormat.Gnu, true)] + public async Task WriteEntry_FileSizeOverLegacyLimit_Throws_Async(TarEntryFormat entryFormat, bool unseekableStream) + { + const long FileSizeOverLimit = LegacyMaxFileSize + 1; + + MemoryStream ms = new(); + Stream s = unseekableStream ? new WrappedStream(ms, ms.CanRead, ms.CanWrite, canSeek: false) : ms; + + string tarFilePath = GetTestFilePath(); + await using TarWriter writer = new(File.Create(tarFilePath)); + TarEntry writeEntry = InvokeTarEntryCreationConstructor(entryFormat, entryFormat is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile, "foo"); + writeEntry.DataStream = new SimulatedDataStream(FileSizeOverLimit); + + Assert.Equal(FileSizeOverLimit, writeEntry.Length); + + await Assert.ThrowsAsync(() => writer.WriteEntryAsync(writeEntry)); + } } } diff --git a/src/libraries/System.Globalization.Extensions/tests/NlsTests/System.Globalization.Extensions.Nls.Tests.csproj b/src/libraries/System.Globalization.Extensions/tests/NlsTests/System.Globalization.Extensions.Nls.Tests.csproj index 573565c9decac6..61a409ffedc326 100644 --- a/src/libraries/System.Globalization.Extensions/tests/NlsTests/System.Globalization.Extensions.Nls.Tests.csproj +++ b/src/libraries/System.Globalization.Extensions/tests/NlsTests/System.Globalization.Extensions.Nls.Tests.csproj @@ -3,6 +3,9 @@ $(NetCoreAppCurrent)-windows true + + + diff --git a/src/libraries/System.Globalization.Extensions/tests/NlsTests/runtimeconfig.template.json b/src/libraries/System.Globalization.Extensions/tests/NlsTests/runtimeconfig.template.json deleted file mode 100644 index ec1e96166f3b3c..00000000000000 --- a/src/libraries/System.Globalization.Extensions/tests/NlsTests/runtimeconfig.template.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "configProperties": { - "System.Globalization.UseNls": true - } -} diff --git a/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoCtor.cs b/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoCtor.cs index 83a3bd57cbfe1b..2481138415e1e9 100644 --- a/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoCtor.cs +++ b/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoCtor.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 Microsoft.DotNet.RemoteExecutor; using System.Collections.Generic; using Xunit; @@ -233,8 +234,8 @@ public static IEnumerable Ctor_String_TestData() yield return new object[] { "ms-MY", new [] { "ms-MY" } }; yield return new object[] { "mt", new [] { "mt" }, true }; yield return new object[] { "mt-MT", new [] { "mt-MT" }, true }; - yield return new object[] { "nb", new [] { "nb" }, true }; - yield return new object[] { "nb-NO", new [] { "nb-NO" }, true }; + yield return new object[] { "nb", new [] { "nb" } }; + yield return new object[] { "nb-NO", new [] { "nb-NO" } }; yield return new object[] { "ne", new [] { "ne" }, true }; yield return new object[] { "ne-NP", new [] { "ne-NP" }, true }; yield return new object[] { "nl", new [] { "nl" } }; @@ -242,7 +243,7 @@ public static IEnumerable Ctor_String_TestData() yield return new object[] { "nl-NL", new [] { "nl-NL" } }; yield return new object[] { "nn", new [] { "nn" }, true }; yield return new object[] { "nn-NO", new [] { "nn-NO" }, true }; - yield return new object[] { "no", new [] { "no" }, true }; + yield return new object[] { "no", new [] { "no" } }; yield return new object[] { "nso", new [] { "nso" }, true }; yield return new object[] { "nso-ZA", new [] { "nso-ZA" }, true }; yield return new object[] { "oc", new [] { "oc" }, true }; @@ -253,8 +254,8 @@ public static IEnumerable Ctor_String_TestData() yield return new object[] { "pa-IN", new [] { "pa-IN" }, true }; yield return new object[] { "pl", new [] { "pl" } }; yield return new object[] { "pl-PL", new [] { "pl-PL" } }; - yield return new object[] { "prs", new [] { "prs" }, true }; - yield return new object[] { "prs-AF", new [] { "prs-AF" }, true }; + yield return new object[] { "prs", new [] { "prs", "fa" }, true }; + yield return new object[] { "prs-AF", new [] { "prs-AF", "fa-AF" }, true}; yield return new object[] { "ps", new [] { "ps" }, true }; yield return new object[] { "ps-AF", new [] { "ps-AF" }, true }; yield return new object[] { "pt", new [] { "pt" } }; @@ -440,13 +441,62 @@ public void TestCreationWithTemporaryLCID(int lcid) Assert.NotEqual(lcid, new CultureInfo(lcid).LCID); } + private static bool NotWasmWithIcu => PlatformDetection.IsNotBrowser && PlatformDetection.IsIcuGlobalization; + + [InlineData("zh-TW-u-co-zhuyin", "zh-TW", "zh-TW_zhuyin")] + [InlineData("de-DE-u-co-phonebk", "de-DE", "de-DE_phoneboo")] + [InlineData("de-DE-u-co-phonebk-u-xx", "de-DE-u-xx", "de-DE-u-xx_phoneboo")] + [InlineData("de-DE-u-xx-u-co-phonebk", "de-DE-u-xx-u-co-phonebk", "de-DE-u-xx-u-co-phonebk")] + [InlineData("de-DE-t-xx-u-co-phonebk", "de-DE-t-xx-u-co-phonebk", "de-DE-t-xx-u-co-phonebk_phoneboo")] + [InlineData("de-DE-u-co-phonebk-t-xx", "de-DE-t-xx", "de-DE-t-xx_phoneboo")] + [InlineData("de-DE-u-co-phonebk-t-xx-u-yy", "de-DE-t-xx-u-yy", "de-DE-t-xx-u-yy_phoneboo")] + [InlineData("de-DE", "de-DE", "de-DE")] + [ConditionalTheory(nameof(NotWasmWithIcu))] + public void TestCreationWithMangledSortName(string cultureName, string expectedCultureName, string expectedSortName) + { + CultureInfo ci = CultureInfo.GetCultureInfo(cultureName); + + Assert.Equal(expectedCultureName, ci.Name); + Assert.Equal(expectedSortName, ci.CompareInfo.Name); + } + + private static bool SupportRemoteExecutionWithIcu => RemoteExecutor.IsSupported && PlatformDetection.IsIcuGlobalization && PlatformDetection.IsNotWindowsServerCore; + + [InlineData("xx-u-XX", "xx-u-xx")] + [InlineData("xx-u-XX-u-yy", "xx-u-xx-u-yy")] + [InlineData("xx-t-ja-JP", "xx-t-ja-jp")] + [InlineData("qps-plocm", "qps-PLOCM")] // ICU normalize this name to "qps--plocm" which we normalize it back to "qps-plocm" + [InlineData("zh_CN", "zh_cn")] + [InlineData("km_KH", "km_kh")] + [ConditionalTheory(nameof(SupportRemoteExecutionWithIcu))] + public void TestCreationWithICUNormalizedNames(string cultureName, string expectedCultureName) + { + CultureInfo ci = CultureInfo.GetCultureInfo(cultureName); + Assert.Equal(expectedCultureName, ci.Name); + } + + [InlineData("xx-u-XX")] + [InlineData("xx-u-XX-u-yy")] + [InlineData("xx-t-ja-JP")] + [InlineData("qps-plocm")] [InlineData("zh-TW-u-co-zhuyin")] - [InlineData("de-DE-u-co-phoneb")] - [InlineData("de-u-co-phonebk")] - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization))] - public void TestCreationWithMangledSortName(string cultureName) + [InlineData("de-DE-u-co-phonebk")] + [InlineData("de-DE-u-co-phonebk-u-xx")] + [InlineData("de-DE-u-xx-u-co-phonebk")] + [InlineData("de-DE-t-xx-u-co-phonebk")] + [InlineData("de-DE-u-co-phonebk-t-xx")] + [InlineData("de-DE-u-co-phonebk-t-xx-u-yy")] + [InlineData("de-DE")] + [ConditionalTheory(nameof(SupportRemoteExecutionWithIcu))] + public void TestWithResourceLookup(string cultureName) { - Assert.True(CultureInfo.GetCultureInfo(cultureName).CompareInfo.Name.Equals(cultureName, StringComparison.OrdinalIgnoreCase)); + RemoteExecutor.Invoke(name => { + CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(name); + int Zero = 0; + + // This should go through the resource manager to get the localized exception message using the current UI culture + Assert.Throws(() => 1 / Zero); + }, cultureName).Dispose(); } } } diff --git a/src/libraries/System.Globalization/tests/IcuAppLocal/IcuAppLocal.Tests.csproj b/src/libraries/System.Globalization/tests/IcuAppLocal/IcuAppLocal.Tests.csproj new file mode 100644 index 00000000000000..3b9800574da644 --- /dev/null +++ b/src/libraries/System.Globalization/tests/IcuAppLocal/IcuAppLocal.Tests.csproj @@ -0,0 +1,25 @@ + + + $(NetCoreAppCurrent) + true + true + + + + + + + + + + + \ No newline at end of file diff --git a/src/libraries/System.Globalization/tests/IcuAppLocal/IcuAppLocal.cs b/src/libraries/System.Globalization/tests/IcuAppLocal/IcuAppLocal.cs new file mode 100644 index 00000000000000..aa0a0df938c407 --- /dev/null +++ b/src/libraries/System.Globalization/tests/IcuAppLocal/IcuAppLocal.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.DotNet.RemoteExecutor; +using System.Diagnostics; +using System.Reflection; +using Xunit; + +namespace System.Globalization.Tests +{ + public class IcuAppLocalTests + { + private static bool SupportsIcuPackageDownload => RemoteExecutor.IsSupported && + ((PlatformDetection.IsWindows && !PlatformDetection.IsArmProcess) || + (PlatformDetection.IsLinux && (PlatformDetection.IsX64Process || PlatformDetection.IsArm64Process) && + !PlatformDetection.IsAlpine && !PlatformDetection.IsLinuxBionic)); + + + [ConditionalFact(nameof(SupportsIcuPackageDownload))] + public void TestIcuAppLocal() + { + // We define this switch dynamically during the runtime using RemoteExecutor. + // The reason is, if we enable ICU app-local here, this test will compile and run + // on all supported OSs even the ICU NuGet package not have native bits support such OSs. + // Note, it doesn't matter if we have test case conditioned to not run on such OSs, because + // the test has to start running first before filtering the test cases and the globalization + // code will run and fail fast at that time. + + ProcessStartInfo psi = new ProcessStartInfo(); + psi.Environment.Add("DOTNET_SYSTEM_GLOBALIZATION_APPLOCALICU", "68.2.0.9"); + + RemoteExecutor.Invoke(() => + { + // Ensure initializing globalization code before checking the ICU version. + CultureInfo ci = CultureInfo.GetCultureInfo("en-US"); + + Type? interopGlobalization = Type.GetType("Interop+Globalization, System.Private.CoreLib"); + Assert.NotNull(interopGlobalization); + + MethodInfo? methodInfo = interopGlobalization.GetMethod("GetICUVersion", BindingFlags.NonPublic | BindingFlags.Static); + Assert.NotNull(methodInfo); + + // Assert the ICU version 0x44020009 is 68.2.0.9 + Assert.Equal(0x44020009, (int)methodInfo.Invoke(null, null)); + + // Now call globalization API to ensure the binding working without any problem. + Assert.Equal(-1, ci.CompareInfo.Compare("sample\u0000", "Sample\u0000", CompareOptions.IgnoreSymbols)); + }, new RemoteInvokeOptions { StartInfo = psi }).Dispose(); + } + } +} diff --git a/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoCurrencyGroupSizes.cs b/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoCurrencyGroupSizes.cs index 34fd7bc00cb2b7..079c355bdc594c 100644 --- a/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoCurrencyGroupSizes.cs +++ b/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoCurrencyGroupSizes.cs @@ -10,20 +10,21 @@ public class NumberFormatInfoCurrencyGroupSizes { public static IEnumerable CurrencyGroupSizes_TestData() { - yield return new object[] { NumberFormatInfo.InvariantInfo, new int[] { 3 } }; - yield return new object[] { CultureInfo.GetCultureInfo("en-US").NumberFormat, new int[] { 3 } }; + yield return new object[] { NumberFormatInfo.InvariantInfo, new int[] { 3 }, null }; + yield return new object[] { CultureInfo.GetCultureInfo("en-US").NumberFormat, new int[] { 3 }, null }; if (PlatformDetection.IsNotUsingLimitedCultures && !PlatformDetection.IsUbuntu && !PlatformDetection.IsWindows7 && !PlatformDetection.IsWindows8x && !PlatformDetection.IsFedora) { - yield return new object[] { CultureInfo.GetCultureInfo("ur-IN").NumberFormat, new int[] { 3, 2 } }; + yield return new object[] { CultureInfo.GetCultureInfo("ur-IN").NumberFormat, new int[] { 3, 2 }, new int[] { 3 }}; } } [Theory] [MemberData(nameof(CurrencyGroupSizes_TestData))] - public void CurrencyGroupSizes_Get_ReturnsExpected(NumberFormatInfo format, int[] expected) + public void CurrencyGroupSizes_Get_ReturnsExpected(NumberFormatInfo format, int[] expected, int [] expectedAlternative) { - Assert.Equal(expected, format.CurrencyGroupSizes); + Assert.True(format.CurrencyGroupSizes.AsSpan().SequenceEqual(expected.AsSpan()) || format.CurrencyGroupSizes.AsSpan().SequenceEqual(expectedAlternative.AsSpan()), + $"Expected {string.Join(", ", expected)} or {string.Join(", ", expectedAlternative ?? Array.Empty())}, got {string.Join(", ", format.CurrencyGroupSizes)}"); } [Theory] diff --git a/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoTests.cs b/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoTests.cs index 0fe17597a799ac..86f74e9f35a91d 100644 --- a/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoTests.cs +++ b/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoTests.cs @@ -54,8 +54,6 @@ public static IEnumerable DigitSubstitution_TestData() yield return new object[] { "nqo-GN" , DigitShapes.NativeNational }; yield return new object[] { "pa-Arab" , DigitShapes.NativeNational }; yield return new object[] { "pa-Arab-PK", DigitShapes.NativeNational }; - yield return new object[] { "prs" , DigitShapes.NativeNational }; - yield return new object[] { "prs-AF" , DigitShapes.NativeNational }; yield return new object[] { "ps" , DigitShapes.NativeNational }; yield return new object[] { "ps-AF" , DigitShapes.NativeNational }; yield return new object[] { "sd" , DigitShapes.NativeNational }; @@ -115,6 +113,24 @@ public void DigitSubstitutionListTest(string cultureName, DigitShapes shape) } } + [Theory] + [InlineData("prs")] + [InlineData("prs-AF")] + public void PrsNativeDigitsTest(string cultureName) + { + try + { + CultureInfo ci = CultureInfo.GetCultureInfo(cultureName); + + // Some OS's set the DigitSubstitution to Context for the culture "prs" and "prs-AF". Majority of Os's set it to NativeNational. + Assert.True(ci.NumberFormat.DigitSubstitution == DigitShapes.Context || ci.NumberFormat.DigitSubstitution == DigitShapes.NativeNational); + } + catch (CultureNotFoundException) + { + // ignore the cultures that we cannot create as it is not supported on the platforms + } + } + public static IEnumerable NativeDigitTestData() { yield return new object[] { "ccp-Cakm-BD", new string[] { "\U0001E950", "\U0001E951", "\U0001E952", "\U0001E953", "\U0001E954", "\U0001E955", "\U0001E956", "\U0001E957", "\U0001E958", "\U0001E959" }}; diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/Deflater.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/Deflater.cs index 8ceb38f13eb8c2..f2d02cde7296bf 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/Deflater.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/Deflater.cs @@ -64,21 +64,17 @@ internal Deflater(CompressionLevel compressionLevel, int windowBits) ZLibNative.CompressionStrategy strategy = ZLibNative.CompressionStrategy.DefaultStrategy; - ZLibNative.ZLibStreamHandle? zlibStream = null; ZErrorCode errC; try { - errC = ZLibNative.CreateZLibStreamForDeflate(out zlibStream, zlibCompressionLevel, + errC = ZLibNative.CreateZLibStreamForDeflate(out _zlibStream, zlibCompressionLevel, windowBits, memLevel, strategy); } catch (Exception cause) { - zlibStream?.Dispose(); throw new ZLibException(SR.ZLibErrorDLLLoadError, cause); } - _zlibStream = zlibStream; - switch (errC) { case ZErrorCode.Ok: diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/Inflater.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/Inflater.cs index 493a6f47d8cb29..4544353ac5406b 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/Inflater.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/Inflater.cs @@ -234,20 +234,16 @@ public void Dispose() [MemberNotNull(nameof(_zlibStream))] private void InflateInit(int windowBits) { - ZLibNative.ZLibStreamHandle? zlibStream = null; ZLibNative.ErrorCode error; try { - error = ZLibNative.CreateZLibStreamForInflate(out zlibStream, windowBits); + error = ZLibNative.CreateZLibStreamForInflate(out _zlibStream, windowBits); } catch (Exception exception) // could not load the ZLib dll { - zlibStream?.Dispose(); throw new ZLibException(SR.ZLibErrorDLLLoadError, exception); } - _zlibStream = zlibStream; - switch (error) { case ZLibNative.ErrorCode.Ok: // Successful initialization diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs index 44511243b5e9f5..fb186dfcfc9f37 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs @@ -199,30 +199,55 @@ private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField e if (extraField.Size < sizeof(long)) return true; - long value64 = reader.ReadInt64(); + // Advancing the stream (by reading from it) is possible only when: + // 1. There is an explicit ask to do that (valid files, corresponding boolean flag(s) set to true). + // 2. When the size indicates that all the information is available ("slightly invalid files"). + bool readAllFields = extraField.Size >= sizeof(long) + sizeof(long) + sizeof(long) + sizeof(int); + if (readUncompressedSize) - zip64Block._uncompressedSize = value64; + { + zip64Block._uncompressedSize = reader.ReadInt64(); + } + else if (readAllFields) + { + _ = reader.ReadInt64(); + } if (ms.Position > extraField.Size - sizeof(long)) return true; - value64 = reader.ReadInt64(); if (readCompressedSize) - zip64Block._compressedSize = value64; + { + zip64Block._compressedSize = reader.ReadInt64(); + } + else if (readAllFields) + { + _ = reader.ReadInt64(); + } if (ms.Position > extraField.Size - sizeof(long)) return true; - value64 = reader.ReadInt64(); if (readLocalHeaderOffset) - zip64Block._localHeaderOffset = value64; + { + zip64Block._localHeaderOffset = reader.ReadInt64(); + } + else if (readAllFields) + { + _ = reader.ReadInt64(); + } if (ms.Position > extraField.Size - sizeof(int)) return true; - int value32 = reader.ReadInt32(); if (readStartDiskNumber) - zip64Block._startDiskNumber = value32; + { + zip64Block._startDiskNumber = reader.ReadInt32(); + } + else if (readAllFields) + { + _ = reader.ReadInt32(); + } // original values are unsigned, so implies value is too big to fit in signed integer if (zip64Block._uncompressedSize < 0) throw new InvalidDataException(SR.FieldTooBigUncompressedSize); diff --git a/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj b/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj index 229119e3aa37cf..3adc39904dd46d 100644 --- a/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj +++ b/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj @@ -17,13 +17,13 @@ - + @@ -35,7 +35,9 @@ + + diff --git a/src/libraries/System.IO.Compression/tests/Utilities/WrappedStream.cs b/src/libraries/System.IO.Compression/tests/Utilities/WrappedStream.cs deleted file mode 100644 index c93e6662be3bee..00000000000000 --- a/src/libraries/System.IO.Compression/tests/Utilities/WrappedStream.cs +++ /dev/null @@ -1,123 +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.IO; - -internal class WrappedStream : Stream -{ - private readonly Stream _baseStream; - private readonly EventHandler _onClosed; - private bool _canRead, _canWrite, _canSeek; - - internal WrappedStream(Stream baseStream, bool canRead, bool canWrite, bool canSeek, EventHandler onClosed) - { - _baseStream = baseStream; - _onClosed = onClosed; - _canRead = canRead; - _canSeek = canSeek; - _canWrite = canWrite; - } - - internal WrappedStream(Stream baseStream, EventHandler onClosed) - : this(baseStream, true, true, true, onClosed) { } - - internal WrappedStream(Stream baseStream) : this(baseStream, null) { } - - public override void Flush() => _baseStream.Flush(); - - public override int Read(byte[] buffer, int offset, int count) - { - if (CanRead) - { - try - { - return _baseStream.Read(buffer, offset, count); - } - catch (ObjectDisposedException ex) - { - throw new NotSupportedException("This stream does not support reading", ex); - } - } - else throw new NotSupportedException("This stream does not support reading"); - } - - public override long Seek(long offset, SeekOrigin origin) - { - if (CanSeek) - { - try - { - return _baseStream.Seek(offset, origin); - } - catch (ObjectDisposedException ex) - { - throw new NotSupportedException("This stream does not support seeking", ex); - } - } - else throw new NotSupportedException("This stream does not support seeking"); - } - - public override void SetLength(long value) { _baseStream.SetLength(value); } - - public override void Write(byte[] buffer, int offset, int count) - { - if (CanWrite) - { - try - { - _baseStream.Write(buffer, offset, count); - } - catch (ObjectDisposedException ex) - { - throw new NotSupportedException("This stream does not support writing", ex); - } - } - else throw new NotSupportedException("This stream does not support writing"); - } - - public override bool CanRead => _canRead && _baseStream.CanRead; - - public override bool CanSeek => _canSeek && _baseStream.CanSeek; - - public override bool CanWrite => _canWrite && _baseStream.CanWrite; - - public override long Length => _baseStream.Length; - - public override long Position - { - get - { - if (CanSeek) - return _baseStream.Position; - throw new NotSupportedException("This stream does not support seeking"); - } - set - { - if (CanSeek) - { - try - { - _baseStream.Position = value; - } - catch (ObjectDisposedException ex) - { - throw new NotSupportedException("This stream does not support seeking", ex); - } - } - else throw new NotSupportedException("This stream does not support seeking"); - } - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - _onClosed?.Invoke(this, null); - _canRead = false; - _canWrite = false; - _canSeek = false; - } - base.Dispose(disposing); - } -} diff --git a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_LargeFiles.cs b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_LargeFiles.cs new file mode 100644 index 00000000000000..d240a176b2b7a2 --- /dev/null +++ b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_LargeFiles.cs @@ -0,0 +1,48 @@ +// 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.IO.Compression.Tests +{ + [Collection(nameof(DisableParallelization))] + public class zip_LargeFiles : ZipFileTestBase + { + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes + [OuterLoop("It requires almost 12 GB of free disk space")] + public static void UnzipOver4GBZipFile() + { + byte[] buffer = GC.AllocateUninitializedArray(1_000_000_000); // 1 GB + + string zipArchivePath = Path.Combine(Path.GetTempPath(), "over4GB.zip"); + DirectoryInfo tempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "over4GB")); + + try + { + for (byte i = 0; i < 6; i++) + { + File.WriteAllBytes(Path.Combine(tempDir.FullName, $"{i}.test"), buffer); + } + + ZipFile.CreateFromDirectory(tempDir.FullName, zipArchivePath, CompressionLevel.NoCompression, includeBaseDirectory: false); + + using ZipArchive zipArchive = ZipFile.OpenRead(zipArchivePath); + foreach (ZipArchiveEntry entry in zipArchive.Entries) + { + using Stream entryStream = entry.Open(); + + Assert.True(entryStream.CanRead); + Assert.Equal(buffer.Length, entryStream.Length); + } + } + finally + { + File.Delete(zipArchivePath); + + tempDir.Delete(recursive: true); + } + } + } +} diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs index 96d601de841b8b..ebf8f5facb3d2f 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs +++ b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs @@ -158,7 +158,7 @@ private unsafe void Monitor(AsyncReadState state) continueExecuting = Interop.Kernel32.ReadDirectoryChangesW( state.DirectoryHandle, state.Buffer, // the buffer is kept pinned for the duration of the sync and async operation by the PreAllocatedOverlapped - _internalBufferSize, + (uint)state.Buffer.Length, _includeSubdirectories, (uint)_notifyFilters, null, diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs index 73c8f587bccede..5627974f47420a 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs @@ -39,6 +39,21 @@ public void CreateDoesntChangeExistingMode() Assert.Equal(initialMode, sameDir.UnixFileMode); } + [Fact] + public void MissingParentsHaveDefaultPermissions() + { + string parent = GetRandomDirPath(); + string child = Path.Combine(parent, "child"); + + const UnixFileMode childMode = UnixFileMode.UserRead | UnixFileMode.UserExecute; + DirectoryInfo childDir = Directory.CreateDirectory(child, childMode); + + Assert.Equal(childMode, childDir.UnixFileMode); + + UnixFileMode defaultPermissions = Directory.CreateDirectory(GetRandomDirPath()).UnixFileMode; + Assert.Equal(defaultPermissions, File.GetUnixFileMode(parent)); + } + [Theory] [InlineData((UnixFileMode)(1 << 12), false)] [InlineData((UnixFileMode)(1 << 12), true)] diff --git a/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetTimes.cs b/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetTimes.cs index 78c2781eac6613..943c3867f948c5 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetTimes.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetTimes.cs @@ -153,7 +153,12 @@ public void CopyToNanosecondsPresent_LowTempRes() output.Directory.Create(); output = input.CopyTo(output.FullName, true); - Assert.Equal(input.LastWriteTime.Ticks, output.LastWriteTime.Ticks); + // On Browser, we sometimes see a difference of exactly 10M, eg., + // Expected: 637949564520000000 + // Actual: 637949564530000000 + double tolerance = PlatformDetection.IsBrowser ? 10_000_000 : 0; + + Assert.Equal(input.LastWriteTime.Ticks, output.LastWriteTime.Ticks, tolerance); Assert.False(HasNonZeroNanoseconds(output.LastWriteTime)); } diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ReadAsync.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ReadAsync.cs index 8d676da9b542e2..268224ab9b9f75 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ReadAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ReadAsync.cs @@ -11,7 +11,7 @@ namespace System.IO.Tests { public abstract class FileStream_AsyncReads : FileSystemTest { - protected abstract Task ReadAsync(FileStream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken = default); + protected abstract Task ReadAsync(Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken = default); [Fact] public async Task EmptyFileReadAsyncSucceedSynchronously() @@ -132,17 +132,72 @@ public async Task IncompleteReadCantSetPositionBeyondEndOfFile(FileShare fileSha Assert.Equal(fileSize, fs.Position); } } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, false)] + [InlineData(false, true)] + public async Task BypassingCacheInvalidatesCachedData(bool fsIsAsync, bool asyncReads) + { + const int BufferSize = 4096; + const int FileSize = BufferSize * 4; + string filePath = GetTestFilePath(); + byte[] content = RandomNumberGenerator.GetBytes(FileSize); + File.WriteAllBytes(filePath, content); + + await Test(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, BufferSize, fsIsAsync)); + await Test(new BufferedStream(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 0, fsIsAsync), BufferSize)); + + async Task Test(Stream stream) + { + try + { + // 1. Populates the private stream buffer, leaves bufferSize - 1 bytes available for next read. + await ReadAndAssertAsync(stream, 1); + // 2. Consumes all available data from the buffer, reads another bufferSize-many bytes from the disk and copies the 1 missing byte. + await ReadAndAssertAsync(stream, BufferSize); + // 3. Seek back by the number of bytes consumed from the buffer, all buffered data is now available for next read. + stream.Position -= 1; + // 4. Consume all buffered data. + await ReadAndAssertAsync(stream, BufferSize); + // 5. Bypass the cache (all buffered data has been consumed and we need bufferSize-many bytes). + // The cache should get invalidated now!! + await ReadAndAssertAsync(stream,BufferSize); + // 6. Seek back by just a few bytes. + stream.Position -= 9; + // 7. Perform a read, which should not use outdated buffered data. + await ReadAndAssertAsync(stream,BufferSize); + } + finally + { + await stream.DisposeAsync(); + } + } + + async Task ReadAndAssertAsync(Stream stream, int size) + { + var initialPosition = stream.Position; + var buffer = new byte[size]; + + var count = asyncReads + ? await ReadAsync(stream, buffer, 0, size) + : stream.Read(buffer); + + Assert.Equal(content.Skip((int)initialPosition).Take(count), buffer.Take(count)); + } + } } public class FileStream_ReadAsync_AsyncReads : FileStream_AsyncReads { - protected override Task ReadAsync(FileStream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) => + protected override Task ReadAsync(Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) => stream.ReadAsync(buffer, offset, count, cancellationToken); } public class FileStream_BeginEndRead_AsyncReads : FileStream_AsyncReads { - protected override Task ReadAsync(FileStream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) => + protected override Task ReadAsync(Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) => Task.Factory.FromAsync( (callback, state) => stream.BeginRead(buffer, offset, count, callback, state), iar => stream.EndRead(iar), diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/GetLength.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/GetLength.cs index 0bb405c487f02c..e4b482c675db41 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/GetLength.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/GetLength.cs @@ -41,8 +41,9 @@ public void ReturnsExactSizeForNonEmptyFiles(FileOptions options) [MemberData(nameof(GetSyncAsyncOptions))] public void ReturnsActualLengthForDevices(FileOptions options) { - // both File.Exists and Path.Exists return false when "\\?\PhysicalDrive0" exists - // that is why we just try and swallow the exception when it occurs + // Both File.Exists and Path.Exists return false when "\\?\PhysicalDrive0" exists + // that is why we just try and swallow the exception when it occurs. + // Exception can be also thrown when the file is in use (#73925). try { using (SafeFileHandle handle = File.OpenHandle(@"\\?\PhysicalDrive0", FileMode.Open, options: options)) @@ -51,7 +52,7 @@ public void ReturnsActualLengthForDevices(FileOptions options) Assert.True(length > 0); } } - catch (FileNotFoundException) { } + catch (IOException) { } } } } diff --git a/src/libraries/System.IO.IsolatedStorage/src/System.IO.IsolatedStorage.csproj b/src/libraries/System.IO.IsolatedStorage/src/System.IO.IsolatedStorage.csproj index 9b5284960f9ae3..2309a8c007ab3f 100644 --- a/src/libraries/System.IO.IsolatedStorage/src/System.IO.IsolatedStorage.csproj +++ b/src/libraries/System.IO.IsolatedStorage/src/System.IO.IsolatedStorage.csproj @@ -1,6 +1,6 @@ - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent) + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-MacCatalyst;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-tvOS;$(NetCoreAppCurrent)-Android;$(NetCoreAppCurrent) @@ -19,10 +19,18 @@ + + + + + + + + - + diff --git a/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.AnyMobile.cs b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.AnyMobile.cs new file mode 100644 index 00000000000000..4121814515fe67 --- /dev/null +++ b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.AnyMobile.cs @@ -0,0 +1,10 @@ +// 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.IsolatedStorage +{ + internal static partial class Helper + { + public const string IsolatedStorageDirectoryName = ".isolated-storage"; + } +} diff --git a/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.NonMobile.cs b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.NonMobile.cs new file mode 100644 index 00000000000000..cde27b6c5e27c4 --- /dev/null +++ b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.NonMobile.cs @@ -0,0 +1,10 @@ +// 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.IsolatedStorage +{ + internal static partial class Helper + { + public const string IsolatedStorageDirectoryName = "IsolatedStorage"; + } +} diff --git a/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.Win32Unix.cs b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.Win32Unix.cs index 5a55e866789ee1..7577ea7ee0495b 100644 --- a/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.Win32Unix.cs +++ b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.Win32Unix.cs @@ -12,7 +12,7 @@ internal static partial class Helper { internal static string GetDataDirectory(IsolatedStorageScope scope) { - // This is the relevant special folder for the given scope plus "IsolatedStorage". + // This is the relevant special folder for the given scope plus IsolatedStorageDirectoryName. // It is meant to replicate the behavior of the VM ComIsolatedStorage::GetRootDir(). // (note that Silverlight used "CoreIsolatedStorage" for a directory name and did not support machine scope) diff --git a/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.cs b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.cs index 3b5d6b6afbc7c5..984eeed4046067 100644 --- a/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.cs +++ b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.cs @@ -5,14 +5,12 @@ namespace System.IO.IsolatedStorage { internal static partial class Helper { - private const string IsolatedStorageDirectoryName = "IsolatedStorage"; - private static string? s_machineRootDirectory; private static string? s_roamingUserRootDirectory; private static string? s_userRootDirectory; /// - /// The full root directory is the relevant special folder from Environment.GetFolderPath() plus "IsolatedStorage" + /// The full root directory is the relevant special folder from Environment.GetFolderPath() plus IsolatedStorageDirectoryName /// and a set of random directory names if not roaming. (The random directories aren't created for WinRT as /// the FolderPath locations for WinRT are app isolated already.) /// @@ -21,6 +19,8 @@ internal static partial class Helper /// User: @"C:\Users\jerem\AppData\Local\IsolatedStorage\10v31ho4.bo2\eeolfu22.f2w\" /// User|Roaming: @"C:\Users\jerem\AppData\Roaming\IsolatedStorage\" /// Machine: @"C:\ProgramData\IsolatedStorage\nin03cyc.wr0\o3j0urs3.0sn\" + /// Android path: "/data/user/0/net.dot.System.IO.IsolatedStorage.Tests/files/.config/.isolated-storage/" + /// iOS path: "/var/mobile/Containers/Data/Application/A323CBB9-A2B3-4432-9449-48CC20C07A7D/Documents/.config/.isolated-storage/" /// /// Identity for the current store gets tacked on after this. /// diff --git a/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorageFile.AnyMobile.cs b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorageFile.AnyMobile.cs new file mode 100644 index 00000000000000..69a2f706779084 --- /dev/null +++ b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorageFile.AnyMobile.cs @@ -0,0 +1,13 @@ +// 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.IsolatedStorage +{ + public sealed partial class IsolatedStorageFile : IsolatedStorage, IDisposable + { + private string GetIsolatedStorageRoot() + { + return Helper.GetRootDirectory(Scope); + } + } +} diff --git a/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorageFile.NonMobile.cs b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorageFile.NonMobile.cs new file mode 100644 index 00000000000000..4f547d55cff2f6 --- /dev/null +++ b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorageFile.NonMobile.cs @@ -0,0 +1,19 @@ +// 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; + +namespace System.IO.IsolatedStorage +{ + public sealed partial class IsolatedStorageFile : IsolatedStorage, IDisposable + { + private string GetIsolatedStorageRoot() + { + StringBuilder root = new StringBuilder(Helper.GetRootDirectory(Scope)); + root.Append(SeparatorExternal); + root.Append(IdentityHash); + + return root.ToString(); + } + } +} diff --git a/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorageFile.cs b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorageFile.cs index 0f024ca64fb23f..4afd6468ceaa3c 100644 --- a/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorageFile.cs +++ b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorageFile.cs @@ -43,9 +43,7 @@ internal IsolatedStorageFile(IsolatedStorageScope scope) // InitStore will set up the IdentityHash InitStore(scope, null, null); - StringBuilder sb = new StringBuilder(Helper.GetRootDirectory(scope)); - sb.Append(SeparatorExternal); - sb.Append(IdentityHash); + StringBuilder sb = new StringBuilder(GetIsolatedStorageRoot()); sb.Append(SeparatorExternal); if (Helper.IsApplication(scope)) diff --git a/src/libraries/System.IO.IsolatedStorage/tests/System.IO.IsolatedStorage.Tests.csproj b/src/libraries/System.IO.IsolatedStorage/tests/System.IO.IsolatedStorage.Tests.csproj index b075376fa8bf89..0410513c46ceb1 100644 --- a/src/libraries/System.IO.IsolatedStorage/tests/System.IO.IsolatedStorage.Tests.csproj +++ b/src/libraries/System.IO.IsolatedStorage/tests/System.IO.IsolatedStorage.Tests.csproj @@ -1,6 +1,6 @@ - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent)-MacCatalyst;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-tvOS;$(NetCoreAppCurrent)-Android true @@ -17,7 +17,7 @@ - + @@ -52,6 +52,14 @@ + + + + + + + + diff --git a/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/HelperTests.cs b/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/HelperTests.cs index 2fe50e59055320..60d65949da7ee2 100644 --- a/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/HelperTests.cs +++ b/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/HelperTests.cs @@ -45,7 +45,7 @@ public void GetDataDirectory(IsolatedStorageScope scope) return; string path = Helper.GetDataDirectory(scope); - Assert.Equal("IsolatedStorage", Path.GetFileName(path)); + Assert.Equal(Helper.IsolatedStorageDirectoryName, Path.GetFileName(path)); } } } diff --git a/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/TestHelper.AnyMobile.cs b/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/TestHelper.AnyMobile.cs new file mode 100644 index 00000000000000..63709fd41ab837 --- /dev/null +++ b/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/TestHelper.AnyMobile.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.Reflection; +using System.Collections.Generic; + +namespace System.IO.IsolatedStorage +{ + public static partial class TestHelper + { + private static List GetRoots() + { + List roots = new List(); + string userRoot = Helper.GetDataDirectory(IsolatedStorageScope.User); + string randomUserRoot = Helper.GetRandomDirectory(userRoot, IsolatedStorageScope.User); + roots.Add(randomUserRoot); + + // Application scope doesn't go under a random dir + roots.Add(userRoot); + return roots; + } + } +} diff --git a/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/TestHelper.NonMobile.cs b/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/TestHelper.NonMobile.cs new file mode 100644 index 00000000000000..e0217dc241a5a9 --- /dev/null +++ b/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/TestHelper.NonMobile.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.Reflection; +using System.Collections.Generic; + +namespace System.IO.IsolatedStorage +{ + public static partial class TestHelper + { + private static List GetRoots() + { + string hash; + object identity; + Helper.GetDefaultIdentityAndHash(out identity, out hash, '.'); + List roots = new List(); + string userRoot = Helper.GetDataDirectory(IsolatedStorageScope.User); + string randomUserRoot = Helper.GetRandomDirectory(userRoot, IsolatedStorageScope.User); + + roots.Add(Path.Combine(randomUserRoot, hash)); + // Application scope doesn't go under a random dir + roots.Add(Path.Combine(userRoot, hash)); + + // https://github.com/dotnet/runtime/issues/2092 + // https://github.com/dotnet/runtime/issues/21742 + if (OperatingSystem.IsWindows() + && !PlatformDetection.IsInAppContainer) + { + roots.Add(Helper.GetDataDirectory(IsolatedStorageScope.Machine)); + } + + return roots; + } + } +} + \ No newline at end of file diff --git a/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/TestHelper.cs b/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/TestHelper.cs index e3e7f423a7b734..06339504237ddd 100644 --- a/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/TestHelper.cs +++ b/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/TestHelper.cs @@ -8,7 +8,7 @@ namespace System.IO.IsolatedStorage { - public static class TestHelper + public static partial class TestHelper { private static PropertyInfo s_rootDirectoryProperty; private static List s_roots; @@ -17,27 +17,8 @@ static TestHelper() { s_rootDirectoryProperty = typeof(IsolatedStorageFile).GetProperty("RootDirectory", BindingFlags.NonPublic | BindingFlags.Instance); - s_roots = new List(); - - string hash; - object identity; - Helper.GetDefaultIdentityAndHash(out identity, out hash, '.'); - - string userRoot = Helper.GetDataDirectory(IsolatedStorageScope.User); - string randomUserRoot = Helper.GetRandomDirectory(userRoot, IsolatedStorageScope.User); - s_roots.Add(Path.Combine(randomUserRoot, hash)); - - // Application scope doesn't go under a random dir - s_roots.Add(Path.Combine(userRoot, hash)); - - // https://github.com/dotnet/runtime/issues/2092 - // https://github.com/dotnet/runtime/issues/21742 - if (OperatingSystem.IsWindows() - && !PlatformDetection.IsInAppContainer) - { - s_roots.Add(Helper.GetDataDirectory(IsolatedStorageScope.Machine)); - } - + s_roots = GetRoots(); + // We don't expose Roaming yet // Helper.GetDataDirectory(IsolatedStorageScope.Roaming); } diff --git a/src/libraries/System.IO.Packaging/src/System.IO.Packaging.csproj b/src/libraries/System.IO.Packaging/src/System.IO.Packaging.csproj index c5d5c39d226db7..6ef8658addebcd 100644 --- a/src/libraries/System.IO.Packaging/src/System.IO.Packaging.csproj +++ b/src/libraries/System.IO.Packaging/src/System.IO.Packaging.csproj @@ -5,6 +5,9 @@ $(NoWarn);CA1847 true + + false + 0 Provides classes that support storage of multiple data objects in a single container. diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs index 67ec9825de3063..b5d182a28b55c5 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs @@ -12,6 +12,7 @@ public class NamedPipeTest_UnixDomainSockets [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public void NamedPipeServer_Connects_With_UnixDomainSocketEndPointClient() { string pipeName = Path.Combine(Path.GetTempPath(), "pipe-tests-corefx-" + Path.GetRandomFileName()); @@ -30,6 +31,7 @@ public void NamedPipeServer_Connects_With_UnixDomainSocketEndPointClient() [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public async Task NamedPipeClient_Connects_With_UnixDomainSocketEndPointServer() { string pipeName = Path.Combine(Path.GetTempPath(), "pipe-tests-corefx-" + Path.GetRandomFileName()); diff --git a/src/libraries/System.IO.Ports/src/System.IO.Ports.csproj b/src/libraries/System.IO.Ports/src/System.IO.Ports.csproj index c341d6c3ba6ffb..ad36bee6d63439 100644 --- a/src/libraries/System.IO.Ports/src/System.IO.Ports.csproj +++ b/src/libraries/System.IO.Ports/src/System.IO.Ports.csproj @@ -6,6 +6,9 @@ true annotations true + + false + 0 Provides classes for controlling serial ports. Commonly Used Types: diff --git a/src/libraries/System.Management/Directory.Build.props b/src/libraries/System.Management/Directory.Build.props index bfa544ca6f649e..709a22a7537088 100644 --- a/src/libraries/System.Management/Directory.Build.props +++ b/src/libraries/System.Management/Directory.Build.props @@ -1,10 +1,6 @@  - - 4.0.0.0 Microsoft windows diff --git a/src/libraries/System.Management/Directory.Build.targets b/src/libraries/System.Management/Directory.Build.targets new file mode 100644 index 00000000000000..69fc811d007beb --- /dev/null +++ b/src/libraries/System.Management/Directory.Build.targets @@ -0,0 +1,10 @@ + + + + + 4.0.0.0 + + diff --git a/src/libraries/System.Management/src/System.Management.csproj b/src/libraries/System.Management/src/System.Management.csproj index 5b66be0b573566..66172649390b56 100644 --- a/src/libraries/System.Management/src/System.Management.csproj +++ b/src/libraries/System.Management/src/System.Management.csproj @@ -9,6 +9,9 @@ annotations true true + + false + 2 true true Provides access to a rich set of management information and management events about the system, devices, and applications instrumented to the Windows Management Instrumentation (WMI) infrastructure. diff --git a/src/libraries/System.Management/src/System/Management/ManagementScope.cs b/src/libraries/System.Management/src/System/Management/ManagementScope.cs index 99ad4b32f59d34..052d7605cd8cd4 100644 --- a/src/libraries/System.Management/src/System/Management/ManagementScope.cs +++ b/src/libraries/System.Management/src/System/Management/ManagementScope.cs @@ -290,7 +290,9 @@ internal enum APTTYPE static WmiNetUtilsHelper() { RegistryKey netFrameworkSubKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\.NETFramework\"); - string netFrameworkInstallRoot = (string)netFrameworkSubKey?.GetValue("InstallRoot"); + string netFrameworkInstallRoot = (string)netFrameworkSubKey?.GetValue(RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? + "InstallRootArm64" : + "InstallRoot"); if (netFrameworkInstallRoot == null) { diff --git a/src/libraries/System.Management/tests/WmiTestHelper.cs b/src/libraries/System.Management/tests/WmiTestHelper.cs index 0496626b5b1863..75460d826d125e 100644 --- a/src/libraries/System.Management/tests/WmiTestHelper.cs +++ b/src/libraries/System.Management/tests/WmiTestHelper.cs @@ -11,7 +11,7 @@ public static class WmiTestHelper private static readonly bool s_isElevated = AdminHelpers.IsProcessElevated(); private static readonly bool s_isWmiSupported = PlatformDetection.IsWindows && - PlatformDetection.IsNotArmNorArm64Process && + PlatformDetection.IsNotArmProcess && PlatformDetection.IsNotWindowsNanoServer && PlatformDetection.IsNotWindowsIoTCore && !PlatformDetection.IsInAppContainer; diff --git a/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs b/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs index 1b447f8faa7dfc..4e11de38490591 100644 --- a/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs +++ b/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs @@ -272,6 +272,23 @@ public void BasicDecodingWithFinalBlockTrueKnownInputInvalid(string inputString, Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, decodedBytes.Length, source, decodedBytes)); } + [Theory] + [InlineData("\u00ecz/T", 0, 0)] // scalar code-path + [InlineData("z/Ta123\u00ec", 4, 3)] + [InlineData("\u00ecz/TpH7sqEkerqMweH1uSw==", 0, 0)] // Vector128 code-path + [InlineData("z/TpH7sqEkerqMweH1uSw\u00ec==", 20, 15)] + [InlineData("\u00ecz/TpH7sqEkerqMweH1uSw1a5ebaAF9xa8B0ze1wet4epo==", 0, 0)] // Vector256 / AVX code-path + [InlineData("z/TpH7sqEkerqMweH1uSw1a5ebaAF9xa8B0ze1wet4epo\u00ec==", 44, 33)] + public void BasicDecodingNonAsciiInputInvalid(string inputString, int expectedConsumed, int expectedWritten) + { + Span source = Encoding.UTF8.GetBytes(inputString); + Span decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)]; + + Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount)); + Assert.Equal(expectedConsumed, consumed); + Assert.Equal(expectedWritten, decodedByteCount); + } + [Theory] [InlineData("AQID", 3)] [InlineData("AQIDBAUG", 6)] diff --git a/src/libraries/System.Memory/tests/Span/IndexOf.T.cs b/src/libraries/System.Memory/tests/Span/IndexOf.T.cs index 67b6e896bd59fa..e2fb8a0e64e48a 100644 --- a/src/libraries/System.Memory/tests/Span/IndexOf.T.cs +++ b/src/libraries/System.Memory/tests/Span/IndexOf.T.cs @@ -192,5 +192,60 @@ public static void IndexOfNull_String(string[] spanInput, int expected) Span theStrings = spanInput; Assert.Equal(expected, theStrings.IndexOf((string)null)); } + + [Fact] + public static void NotBitwiseEquatableUsesCustomIEquatableImplementationForActualComparison() + { + const byte Ten = 10, NotTen = 11; + for (int length = 1; length < 100; length++) + { + TwoBytes[] array = new TwoBytes[length]; + for (int i = 0; i < length; i++) + { + array[i] = new TwoBytes(Ten, (byte)i); + } + + Span span = new Span(array); + ReadOnlySpan ros = new ReadOnlySpan(array); + + ReadOnlySpan noMatch2 = new TwoBytes[2] { new TwoBytes(10, NotTen), new TwoBytes(10, NotTen) }; + Assert.Equal(-1, span.IndexOfAny(noMatch2)); + Assert.Equal(-1, ros.IndexOfAny(noMatch2)); + Assert.Equal(-1, span.LastIndexOfAny(noMatch2)); + Assert.Equal(-1, ros.LastIndexOfAny(noMatch2)); + + ReadOnlySpan noMatch3 = new TwoBytes[3] { new TwoBytes(10, NotTen), new TwoBytes(10, NotTen), new TwoBytes(10, NotTen) }; + Assert.Equal(-1, span.IndexOfAny(noMatch3)); + Assert.Equal(-1, ros.IndexOfAny(noMatch3)); + Assert.Equal(-1, span.LastIndexOfAny(noMatch3)); + Assert.Equal(-1, ros.LastIndexOfAny(noMatch3)); + + ReadOnlySpan match2 = new TwoBytes[2] { new TwoBytes(0, Ten), new TwoBytes(0, Ten) }; + Assert.Equal(0, span.IndexOfAny(match2)); + Assert.Equal(0, ros.IndexOfAny(match2)); + Assert.Equal(0, span.LastIndexOfAny(match2)); + Assert.Equal(0, ros.LastIndexOfAny(match2)); + + ReadOnlySpan match3 = new TwoBytes[3] { new TwoBytes(0, Ten), new TwoBytes(0, Ten), new TwoBytes(0, Ten) }; + Assert.Equal(0, span.IndexOfAny(match3)); + Assert.Equal(0, ros.IndexOfAny(match3)); + Assert.Equal(0, span.LastIndexOfAny(match3)); + Assert.Equal(0, ros.LastIndexOfAny(match3)); + } + } + + private readonly struct TwoBytes : IEquatable + { + private readonly byte _first, _second; + + public TwoBytes(byte first, byte second) + { + _first = first; + _second = second; + } + + // it compares different fields on purpose + public bool Equals(TwoBytes other) => _first == other._second && _second == other._first; + } } } diff --git a/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj b/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj index 067db94007d3b9..20a3397f8c3d3c 100644 --- a/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj +++ b/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj @@ -2,6 +2,8 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true + false + 1 Provides extension methods for System.Net.Http.HttpClient and System.Net.Http.HttpContent that perform automatic serialization and deserialization using System.Text.Json. Commonly Used Types: diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.cs index 3df50de00fa83d..1f4c01573c2e67 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.cs @@ -24,7 +24,7 @@ public static partial class HttpClientJsonExtensions throw new ArgumentNullException(nameof(client)); } - Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken); return GetFromJsonAsyncCore(taskResponse, type, options, cancellationToken); } @@ -37,7 +37,7 @@ public static partial class HttpClientJsonExtensions throw new ArgumentNullException(nameof(client)); } - Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken); return GetFromJsonAsyncCore(taskResponse, type, options, cancellationToken); } @@ -50,7 +50,7 @@ public static partial class HttpClientJsonExtensions throw new ArgumentNullException(nameof(client)); } - Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken); return GetFromJsonAsyncCore(taskResponse, options, cancellationToken); } @@ -63,7 +63,7 @@ public static partial class HttpClientJsonExtensions throw new ArgumentNullException(nameof(client)); } - Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken); return GetFromJsonAsyncCore(taskResponse, options, cancellationToken); } @@ -74,7 +74,7 @@ public static partial class HttpClientJsonExtensions throw new ArgumentNullException(nameof(client)); } - Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken); return GetFromJsonAsyncCore(taskResponse, type, context, cancellationToken); } @@ -85,7 +85,7 @@ public static partial class HttpClientJsonExtensions throw new ArgumentNullException(nameof(client)); } - Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken); return GetFromJsonAsyncCore(taskResponse, type, context, cancellationToken); } @@ -96,7 +96,7 @@ public static partial class HttpClientJsonExtensions throw new ArgumentNullException(nameof(client)); } - Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken); return GetFromJsonAsyncCore(taskResponse, jsonTypeInfo, cancellationToken); } @@ -107,7 +107,7 @@ public static partial class HttpClientJsonExtensions throw new ArgumentNullException(nameof(client)); } - Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + Task taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken); return GetFromJsonAsyncCore(taskResponse, jsonTypeInfo, cancellationToken); } diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs index 076c223877384d..6c142dfe28ac3a 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs @@ -354,5 +354,92 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( await server.HandleRequestAsync(content: json, headers: headers); }); } + + public static IEnumerable GetFromJsonAsync_EnforcesMaxResponseContentBufferSize_MemberData() => + from useDeleteAsync in new[] { true, false } + from limit in new[] { 2, 100, 100000 } + from contentLength in new[] { limit, limit + 1 } + from chunked in new[] { true, false } + select new object[] { useDeleteAsync, limit, contentLength, chunked }; + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] // No Socket support + [MemberData(nameof(GetFromJsonAsync_EnforcesMaxResponseContentBufferSize_MemberData))] + public async Task GetFromJsonAsync_EnforcesMaxResponseContentBufferSize(bool useDeleteAsync, int limit, int contentLength, bool chunked) + { + await LoopbackServer.CreateClientAndServerAsync(async uri => + { + using var client = new HttpClient { MaxResponseContentBufferSize = limit }; + + Func testMethod = () => useDeleteAsync ? client.DeleteFromJsonAsync(uri) : client.GetFromJsonAsync(uri); + + if (contentLength > limit) + { + Exception ex = await Assert.ThrowsAsync(testMethod); + Assert.Contains(limit.ToString(), ex.Message); + } + else + { + await testMethod(); + } + }, + async server => + { + List headers = new(); + string content = $"\"{new string('a', contentLength - 2)}\""; + + if (chunked) + { + headers.Add(new HttpHeaderData("Transfer-Encoding", "chunked")); + content = $"{Convert.ToString(contentLength, 16)}\r\n{content}\r\n0\r\n\r\n"; + } + + await server.HandleRequestAsync(headers: headers, content: content); + }); + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] // No Socket support + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public async Task GetFromJsonAsync_EnforcesTimeout(bool useDeleteAsync, bool slowHeaders) + { + TaskCompletionSource exceptionThrown = new(TaskCreationOptions.RunContinuationsAsynchronously); + + await LoopbackServer.CreateClientAndServerAsync(async uri => + { + using var client = new HttpClient { Timeout = TimeSpan.FromMilliseconds(100) }; + + Exception ex = await Assert.ThrowsAsync(() => + useDeleteAsync ? client.DeleteFromJsonAsync(uri) : client.GetFromJsonAsync(uri)); + +#if NETCORE + Assert.Contains("HttpClient.Timeout", ex.Message); + Assert.IsType(ex.InnerException); +#endif + + exceptionThrown.SetResult(0); + }, + async server => + { + // The client may timeout before even connecting the server + await Task.WhenAny(exceptionThrown.Task, Task.Run(async () => + { + try + { + await server.AcceptConnectionAsync(async connection => + { + if (!slowHeaders) + { + await connection.SendPartialResponseHeadersAsync(headers: new[] { new HttpHeaderData("Content-Length", "42") }); + } + + await exceptionThrown.Task; + }); + } + catch { } + })); + }); + } } } diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/System.Net.Http.Json.Functional.Tests.csproj b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/System.Net.Http.Json.Functional.Tests.csproj index 144cbb55fd2b63..25c6170e271007 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/System.Net.Http.Json.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/System.Net.Http.Json.Functional.Tests.csproj @@ -1,4 +1,4 @@ - + $(NetCoreAppCurrent);net48 @@ -13,11 +13,13 @@ + + diff --git a/src/libraries/System.Net.Http/src/Resources/Strings.resx b/src/libraries/System.Net.Http/src/Resources/Strings.resx index 5a250aaa58488d..969a7022a3b3be 100644 --- a/src/libraries/System.Net.Http/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Http/src/Resources/Strings.resx @@ -354,6 +354,9 @@ The buffer was not long enough. + + The HTTP request headers length exceeded the server limit of {0} bytes. + The HTTP response headers length exceeded the set limit of {0} bytes. 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 eb072b1d63cd4f..0f1758689cd87b 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -470,7 +470,6 @@ - diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs index ccca155d4bc8aa..c140c2ccb9e404 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs @@ -133,9 +133,6 @@ private static async Task CallFetch(HttpRequestMessage reques int headerCount = request.Headers.Count + request.Content?.Headers.Count ?? 0; List headerNames = new List(headerCount); List headerValues = new List(headerCount); - List optionNames = new List(); - List optionValues = new List(); - JSObject abortController = BrowserHttpInterop.CreateAbortController(); CancellationTokenRegistration? abortRegistration = cancellationToken.Register(() => { @@ -147,12 +144,27 @@ private static async Task CallFetch(HttpRequestMessage reques }); try { - optionNames.Add("method"); - optionValues.Add(request.Method.Method); + if (request.RequestUri == null) + { + throw new ArgumentNullException(nameof(request.RequestUri)); + } + + string uri = request.RequestUri.IsAbsoluteUri ? request.RequestUri.AbsoluteUri : request.RequestUri.ToString(); + + bool hasFetchOptions = request.Options.TryGetValue(FetchOptions, out IDictionary? fetchOptions); + int optionCount = 1 + (allowAutoRedirect.HasValue ? 1 : 0) + (hasFetchOptions && fetchOptions != null ? fetchOptions.Count : 0); + int optionIndex = 0; + string[] optionNames = new string[optionCount]; + object?[] optionValues = new object?[optionCount]; + + optionNames[optionIndex] = "method"; + optionValues[optionIndex] = request.Method.Method; + optionIndex++; if (allowAutoRedirect.HasValue) { - optionNames.Add("redirect"); - optionValues.Add(allowAutoRedirect.Value ? "follow" : "manual"); + optionNames[optionIndex] = "redirect"; + optionValues[optionIndex] = allowAutoRedirect.Value ? "follow" : "manual"; + optionIndex++; } foreach (KeyValuePair> header in request.Headers) @@ -176,21 +188,16 @@ private static async Task CallFetch(HttpRequestMessage reques } } - if (request.Options.TryGetValue(FetchOptions, out IDictionary? fetchOptions)) + if (hasFetchOptions && fetchOptions != null) { foreach (KeyValuePair item in fetchOptions) { - optionNames.Add(item.Key); - optionValues.Add(item.Value); + optionNames[optionIndex] = item.Key; + optionValues[optionIndex] = item.Value; + optionIndex++; } } - if (request.RequestUri == null) - { - throw new ArgumentNullException(nameof(request.RequestUri)); - } - - string uri = request.RequestUri.IsAbsoluteUri ? request.RequestUri.AbsoluteUri : request.RequestUri.ToString(); Task? promise; cancellationToken.ThrowIfCancellationRequested(); if (request.Content != null) @@ -201,7 +208,7 @@ private static async Task CallFetch(HttpRequestMessage reques .ConfigureAwait(true); cancellationToken.ThrowIfCancellationRequested(); - promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames.ToArray(), optionValues.ToArray(), abortController, body); + promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames, optionValues, abortController, body); } else { @@ -209,13 +216,14 @@ private static async Task CallFetch(HttpRequestMessage reques .ConfigureAwait(true); cancellationToken.ThrowIfCancellationRequested(); - promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames.ToArray(), optionValues.ToArray(), abortController, buffer); + promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames, optionValues, abortController, buffer); } } else { - promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames.ToArray(), optionValues.ToArray(), abortController); + promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames, optionValues, abortController); } + cancellationToken.ThrowIfCancellationRequested(); ValueTask wrappedTask = BrowserHttpInterop.CancelationHelper(promise, cancellationToken, abortController); JSObject fetchResponse = await wrappedTask.ConfigureAwait(true); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs index 83f4ab8db11660..c5e8ce8860465a 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs @@ -229,7 +229,7 @@ private async Task GetStringAsyncCore(HttpRequestMessage request, Cancel } finally { - FinishSend(cts, disposeCts, telemetryStarted, responseContentTelemetryStarted); + FinishSend(response, cts, disposeCts, telemetryStarted, responseContentTelemetryStarted); } } @@ -308,7 +308,7 @@ private async Task GetByteArrayAsyncCore(HttpRequestMessage request, Can } finally { - FinishSend(cts, disposeCts, telemetryStarted, responseContentTelemetryStarted); + FinishSend(response, cts, disposeCts, telemetryStarted, responseContentTelemetryStarted); } } @@ -354,7 +354,7 @@ private async Task GetStreamAsyncCore(HttpRequestMessage request, Cancel } finally { - FinishSend(cts, disposeCts, telemetryStarted, responseContentTelemetryStarted: false); + FinishSend(response, cts, disposeCts, telemetryStarted, responseContentTelemetryStarted: false); } } @@ -498,7 +498,7 @@ public HttpResponseMessage Send(HttpRequestMessage request, HttpCompletionOption } finally { - FinishSend(cts, disposeCts, telemetryStarted, responseContentTelemetryStarted); + FinishSend(response, cts, disposeCts, telemetryStarted, responseContentTelemetryStarted); } } @@ -553,7 +553,7 @@ async Task Core( } finally { - FinishSend(cts, disposeCts, telemetryStarted, responseContentTelemetryStarted); + FinishSend(response, cts, disposeCts, telemetryStarted, responseContentTelemetryStarted); } } } @@ -585,8 +585,6 @@ private static bool ShouldBufferResponse(HttpCompletionOption completionOption, private void HandleFailure(Exception e, bool telemetryStarted, HttpResponseMessage? response, CancellationTokenSource cts, CancellationToken cancellationToken, CancellationTokenSource pendingRequestsCts) { - LogRequestFailed(telemetryStarted); - response?.Dispose(); Exception? toThrow = null; @@ -625,6 +623,8 @@ private void HandleFailure(Exception e, bool telemetryStarted, HttpResponseMessa e = toThrow = new OperationCanceledException(cancellationToken.IsCancellationRequested ? cancellationToken : cts.Token); } + LogRequestFailed(e, telemetryStarted); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, e); if (toThrow != null) @@ -644,7 +644,7 @@ private static bool StartSend(HttpRequestMessage request) return false; } - private static void FinishSend(CancellationTokenSource cts, bool disposeCts, bool telemetryStarted, bool responseContentTelemetryStarted) + private static void FinishSend(HttpResponseMessage? response, CancellationTokenSource cts, bool disposeCts, bool telemetryStarted, bool responseContentTelemetryStarted) { // Log completion. if (HttpTelemetry.Log.IsEnabled() && telemetryStarted) @@ -654,7 +654,7 @@ private static void FinishSend(CancellationTokenSource cts, bool disposeCts, boo HttpTelemetry.Log.ResponseContentStop(); } - HttpTelemetry.Log.RequestStop(); + HttpTelemetry.Log.RequestStop(response); } // Dispose of the CancellationTokenSource if it was created specially for this request diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpMessageInvoker.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpMessageInvoker.cs index a06da5b9ed5a27..ceb93c06a2aae2 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpMessageInvoker.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpMessageInvoker.cs @@ -41,18 +41,20 @@ public virtual HttpResponseMessage Send(HttpRequestMessage request, Cancellation { HttpTelemetry.Log.RequestStart(request); + HttpResponseMessage? response = null; try { - return _handler.Send(request, cancellationToken); + response = _handler.Send(request, cancellationToken); + return response; } - catch when (LogRequestFailed(telemetryStarted: true)) + catch (Exception ex) when (LogRequestFailed(ex, telemetryStarted: true)) { // Unreachable as LogRequestFailed will return false throw; } finally { - HttpTelemetry.Log.RequestStop(); + HttpTelemetry.Log.RequestStop(response); } } else @@ -78,18 +80,20 @@ static async Task SendAsyncWithTelemetry(HttpMessageHandler { HttpTelemetry.Log.RequestStart(request); + HttpResponseMessage? response = null; try { - return await handler.SendAsync(request, cancellationToken).ConfigureAwait(false); + response = await handler.SendAsync(request, cancellationToken).ConfigureAwait(false); + return response; } - catch when (LogRequestFailed(telemetryStarted: true)) + catch (Exception ex) when (LogRequestFailed(ex, telemetryStarted: true)) { // Unreachable as LogRequestFailed will return false throw; } finally { - HttpTelemetry.Log.RequestStop(); + HttpTelemetry.Log.RequestStop(response); } } } @@ -100,11 +104,11 @@ private static bool ShouldSendWithTelemetry(HttpRequestMessage request) => request.RequestUri is Uri requestUri && requestUri.IsAbsoluteUri; - internal static bool LogRequestFailed(bool telemetryStarted) + internal static bool LogRequestFailed(Exception exception, bool telemetryStarted) { if (HttpTelemetry.Log.IsEnabled() && telemetryStarted) { - HttpTelemetry.Log.RequestFailed(); + HttpTelemetry.Log.RequestFailed(exception); } return false; } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpTelemetry.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpTelemetry.cs index 131748bddf5a8c..d85862dbfb7825 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpTelemetry.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpTelemetry.cs @@ -50,18 +50,34 @@ public void RequestStart(HttpRequestMessage request) request.VersionPolicy); } - [Event(2, Level = EventLevel.Informational)] - public void RequestStop() + [NonEvent] + public void RequestStop(HttpResponseMessage? response) + { + RequestStop(response is null ? -1 : (int)response.StatusCode); + } + + [Event(2, Level = EventLevel.Informational, Version = 1)] + private void RequestStop(int statusCode) { Interlocked.Increment(ref _stoppedRequests); - WriteEvent(eventId: 2); + WriteEvent(eventId: 2, statusCode); } - [Event(3, Level = EventLevel.Error)] - public void RequestFailed() + [NonEvent] + public void RequestFailed(Exception exception) { Interlocked.Increment(ref _failedRequests); - WriteEvent(eventId: 3); + + if (IsEnabled(EventLevel.Error, EventKeywords.None)) + { + RequestFailed(exceptionMessage: exception.Message); + } + } + + [Event(3, Level = EventLevel.Error, Version = 1)] + private void RequestFailed(string exceptionMessage) + { + WriteEvent(eventId: 3, exceptionMessage); } [Event(4, Level = EventLevel.Informational)] @@ -112,10 +128,10 @@ public void ResponseHeadersStart() WriteEvent(eventId: 11); } - [Event(12, Level = EventLevel.Informational)] - public void ResponseHeadersStop() + [Event(12, Level = EventLevel.Informational, Version = 1)] + public void ResponseHeadersStop(int statusCode) { - WriteEvent(eventId: 12); + WriteEvent(eventId: 12, statusCode); } [Event(13, Level = EventLevel.Informational)] diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index e513bce81b0afc..45dc67d7342c52 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -56,6 +56,10 @@ internal sealed partial class Http2Connection : HttpConnectionBase private readonly Channel _writeChannel; private bool _lastPendingWriterShouldFlush; + // Server-advertised SETTINGS_MAX_HEADER_LIST_SIZE + // https://www.rfc-editor.org/rfc/rfc9113.html#section-6.5.2-2.12.1 + private uint _maxHeaderListSize = uint.MaxValue; // Defaults to infinite + // This flag indicates that the connection is shutting down and cannot accept new requests, because of one of the following conditions: // (1) We received a GOAWAY frame from the server // (2) We have exhaustead StreamIds (i.e. _nextStream == MaxStreamId) @@ -162,6 +166,14 @@ public Http2Connection(HttpConnectionPool pool, Stream stream) _nextPingRequestTimestamp = Environment.TickCount64 + _keepAlivePingDelay; _keepAlivePingPolicy = _pool.Settings._keepAlivePingPolicy; + uint maxHeaderListSize = _pool._lastSeenHttp2MaxHeaderListSize; + if (maxHeaderListSize > 0) + { + // Previous connections to the same host advertised a limit. + // Use this as an initial value before we receive the SETTINGS frame. + _maxHeaderListSize = maxHeaderListSize; + } + if (HttpTelemetry.Log.IsEnabled()) { HttpTelemetry.Log.Http20ConnectionEstablished(); @@ -822,6 +834,8 @@ private void ProcessSettingsFrame(FrameHeader frameHeader, bool initialFrame = f uint settingValue = BinaryPrimitives.ReadUInt32BigEndian(settings); settings = settings.Slice(4); + if (NetEventSource.Log.IsEnabled()) Trace($"Applying setting {(SettingId)settingId}={settingValue}"); + switch ((SettingId)settingId) { case SettingId.MaxConcurrentStreams: @@ -861,6 +875,11 @@ private void ProcessSettingsFrame(FrameHeader frameHeader, bool initialFrame = f } break; + case SettingId.MaxHeaderListSize: + _maxHeaderListSize = settingValue; + _pool._lastSeenHttp2MaxHeaderListSize = _maxHeaderListSize; + break; + default: // All others are ignored because we don't care about them. // Note, per RFC, unknown settings IDs should be ignored. @@ -1379,14 +1398,18 @@ private void WriteBytes(ReadOnlySpan bytes, ref ArrayBuffer headerBuffer) headerBuffer.Commit(bytes.Length); } - private void WriteHeaderCollection(HttpRequestMessage request, HttpHeaders headers, ref ArrayBuffer headerBuffer) + private int WriteHeaderCollection(HttpRequestMessage request, HttpHeaders headers, ref ArrayBuffer headerBuffer) { if (NetEventSource.Log.IsEnabled()) Trace(""); HeaderEncodingSelector? encodingSelector = _pool.Settings._requestHeaderEncodingSelector; ref string[]? tmpHeaderValuesArray = ref t_headerValues; - foreach (HeaderEntry header in headers.GetEntries()) + + ReadOnlySpan entries = headers.GetEntries(); + int headerListSize = entries.Length * HeaderField.RfcOverhead; + + foreach (HeaderEntry header in entries) { int headerValuesCount = HttpHeaders.GetStoreValuesIntoStringArray(header.Key, header.Value, ref tmpHeaderValuesArray); Debug.Assert(headerValuesCount > 0, "No values for header??"); @@ -1402,6 +1425,10 @@ private void WriteHeaderCollection(HttpRequestMessage request, HttpHeaders heade // The Connection, Upgrade and ProxyConnection headers are also not supported in HTTP2. if (knownHeader != KnownHeaders.Host && knownHeader != KnownHeaders.Connection && knownHeader != KnownHeaders.Upgrade && knownHeader != KnownHeaders.ProxyConnection) { + // The length of the encoded name may be shorter than the actual name. + // Ensure that headerListSize is always >= of the actual size. + headerListSize += knownHeader.Name.Length; + if (knownHeader == KnownHeaders.TE) { // HTTP/2 allows only 'trailers' TE header. rfc7540 8.1.2.2 @@ -1442,6 +1469,8 @@ private void WriteHeaderCollection(HttpRequestMessage request, HttpHeaders heade WriteLiteralHeader(header.Key.Name, headerValues, valueEncoding, ref headerBuffer); } } + + return headerListSize; } private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuffer) @@ -1472,9 +1501,9 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff WriteIndexedHeader(_pool.IsSecure ? H2StaticTable.SchemeHttps : H2StaticTable.SchemeHttp, ref headerBuffer); - if (request.HasHeaders && request.Headers.Host != null) + if (request.HasHeaders && request.Headers.Host is string host) { - WriteIndexedHeader(H2StaticTable.Authority, request.Headers.Host, ref headerBuffer); + WriteIndexedHeader(H2StaticTable.Authority, host, ref headerBuffer); } else { @@ -1492,6 +1521,8 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff WriteIndexedHeader(H2StaticTable.PathSlash, pathAndQuery, ref headerBuffer); } + int headerListSize = 3 * HeaderField.RfcOverhead; // Method, Authority, Path + if (request.HasHeaders) { if (request.Headers.Protocol != null) @@ -1499,9 +1530,10 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff WriteBytes(ProtocolLiteralHeaderBytes, ref headerBuffer); Encoding? protocolEncoding = _pool.Settings._requestHeaderEncodingSelector?.Invoke(":protocol", request); WriteLiteralHeaderValue(request.Headers.Protocol, protocolEncoding, ref headerBuffer); + headerListSize += HeaderField.RfcOverhead; } - WriteHeaderCollection(request, request.Headers, ref headerBuffer); + headerListSize += WriteHeaderCollection(request, request.Headers, ref headerBuffer); } // Determine cookies to send. @@ -1511,9 +1543,9 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff if (cookiesFromContainer != string.Empty) { WriteBytes(KnownHeaders.Cookie.Http2EncodedName, ref headerBuffer); - Encoding? cookieEncoding = _pool.Settings._requestHeaderEncodingSelector?.Invoke(KnownHeaders.Cookie.Name, request); WriteLiteralHeaderValue(cookiesFromContainer, cookieEncoding, ref headerBuffer); + headerListSize += HttpKnownHeaderNames.Cookie.Length + HeaderField.RfcOverhead; } } @@ -1525,11 +1557,24 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff { WriteBytes(KnownHeaders.ContentLength.Http2EncodedName, ref headerBuffer); WriteLiteralHeaderValue("0", valueEncoding: null, ref headerBuffer); + headerListSize += HttpKnownHeaderNames.ContentLength.Length + HeaderField.RfcOverhead; } } else { - WriteHeaderCollection(request, request.Content.Headers, ref headerBuffer); + headerListSize += WriteHeaderCollection(request, request.Content.Headers, ref headerBuffer); + } + + // The headerListSize is an approximation of the total header length. + // This is acceptable as long as the value is always >= the actual length. + // We must avoid ever sending more than the server allowed. + // This approach must be revisted if we ever support the dynamic table or compression when sending requests. + headerListSize += headerBuffer.ActiveLength; + + uint maxHeaderListSize = _maxHeaderListSize; + if ((uint)headerListSize > maxHeaderListSize) + { + throw new HttpRequestException(SR.Format(SR.net_http_request_headers_exceeded_length, maxHeaderListSize)); } } @@ -1602,10 +1647,10 @@ private async ValueTask SendHeadersAsync(HttpRequestMessage request // streams are created and started in order. await PerformWriteAsync(totalSize, (thisRef: this, http2Stream, headerBytes, endStream: (request.Content == null && !request.IsExtendedConnectRequest), mustFlush), static (s, writeBuffer) => { - if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.http2Stream.StreamId, $"Started writing. Total header bytes={s.headerBytes.Length}"); - s.thisRef.AddStream(s.http2Stream); + if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.http2Stream.StreamId, $"Started writing. Total header bytes={s.headerBytes.Length}"); + Span span = writeBuffer.Span; // Copy the HEADERS frame. diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index 7557ece3e264e0..89cacb066ece5b 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -1024,7 +1024,8 @@ public async Task ReadResponseHeadersAsync(CancellationToken cancellationToken) Debug.Assert(!wait); } - if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStop(); + Debug.Assert(_response is not null); + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStop((int)_response.StatusCode); } catch { 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 ee9732416a38d7..ae970632e384c9 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 @@ -37,8 +37,9 @@ internal sealed class Http3Connection : HttpConnectionBase // Our control stream. private QuicStream? _clientControl; - // Current SETTINGS from the server. - private int _maximumHeadersLength = int.MaxValue; // TODO: this is not yet observed by Http3Stream when buffering headers. + // Server-advertised SETTINGS_MAX_FIELD_SECTION_SIZE + // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4.1-2.2.1 + private uint _maxHeaderListSize = uint.MaxValue; // Defaults to infinite // Once the server's streams are received, these are set to 1. Further receipt of these streams results in a connection error. private int _haveServerControlStream; @@ -54,7 +55,7 @@ internal sealed class Http3Connection : HttpConnectionBase public HttpAuthority Authority => _authority; public HttpConnectionPool Pool => _pool; - public int MaximumRequestHeadersLength => _maximumHeadersLength; + public uint MaxHeaderListSize => _maxHeaderListSize; public byte[]? AltUsedEncodedHeaderBytes => _altUsedEncodedHeader; public Exception? AbortException => Volatile.Read(ref _abortException); private object SyncObj => _activeRequests; @@ -85,6 +86,13 @@ public Http3Connection(HttpConnectionPool pool, HttpAuthority? origin, HttpAutho _altUsedEncodedHeader = QPack.QPackEncoder.EncodeLiteralHeaderFieldWithoutNameReferenceToArray(KnownHeaders.AltUsed.Name, altUsedValue); } + uint maxHeaderListSize = _pool._lastSeenHttp3MaxHeaderListSize; + if (maxHeaderListSize > 0) + { + // Previous connections to the same host advertised a limit. + // Use this as an initial value before we receive the SETTINGS frame. + _maxHeaderListSize = maxHeaderListSize; + } if (HttpTelemetry.Log.IsEnabled()) { @@ -240,7 +248,7 @@ public async Task SendAsync(HttpRequestMessage request, lon catch (QuicException ex) when (ex.QuicError == QuicError.OperationAborted) { // This will happen if we aborted _connection somewhere and we have pending OpenOutboundStreamAsync call. - Debug.Assert(_abortException is not null); + // note that _abortException may be null if we closed the connection in response to a GOAWAY frame throw new HttpRequestException(SR.net_http_client_execution_error, _abortException, RequestRetryType.RetryOnConnectionFailure); } finally @@ -720,10 +728,13 @@ async ValueTask ProcessSettingsFrameAsync(long settingsPayloadLength) buffer.Discard(bytesRead); + if (NetEventSource.Log.IsEnabled()) Trace($"Applying setting {(Http3SettingType)settingId}={settingValue}"); + switch ((Http3SettingType)settingId) { case Http3SettingType.MaxHeaderListSize: - _maximumHeadersLength = (int)Math.Min(settingValue, int.MaxValue); + _maxHeaderListSize = (uint)Math.Min((ulong)settingValue, uint.MaxValue); + _pool._lastSeenHttp3MaxHeaderListSize = _maxHeaderListSize; break; case Http3SettingType.ReservedHttp2EnablePush: case Http3SettingType.ReservedHttp2MaxConcurrentStreams: diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs index 2f0558a5c9c750..56fe374b3bef2b 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs @@ -352,7 +352,7 @@ private async Task ReadResponseAsync(CancellationToken cancellationToken) _headerState = HeaderState.TrailingHeaders; - if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStop(); + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStop((int)_response.StatusCode); } private async Task SendContentAsync(HttpContent content, CancellationToken cancellationToken) @@ -575,9 +575,9 @@ private void BufferHeaders(HttpRequestMessage request) BufferBytes(normalizedMethod.Http3EncodedBytes); BufferIndexedHeader(H3StaticTable.SchemeHttps); - if (request.HasHeaders && request.Headers.Host != null) + if (request.HasHeaders && request.Headers.Host is string host) { - BufferLiteralHeaderWithStaticNameReference(H3StaticTable.Authority, request.Headers.Host); + BufferLiteralHeaderWithStaticNameReference(H3StaticTable.Authority, host); } else { @@ -598,6 +598,8 @@ private void BufferHeaders(HttpRequestMessage request) // The only way to reach H3 is to upgrade via an Alt-Svc header, so we can encode Alt-Used for every connection. BufferBytes(_connection.AltUsedEncodedHeaderBytes); + int headerListSize = 4 * HeaderField.RfcOverhead; // Scheme, Method, Authority, Path + if (request.HasHeaders) { // H3 does not support Transfer-Encoding: chunked. @@ -606,7 +608,7 @@ private void BufferHeaders(HttpRequestMessage request) request.Headers.TransferEncodingChunked = false; } - BufferHeaderCollection(request.Headers); + headerListSize += BufferHeaderCollection(request.Headers); } if (_connection.Pool.Settings._useCookies) @@ -616,6 +618,7 @@ private void BufferHeaders(HttpRequestMessage request) { Encoding? valueEncoding = _connection.Pool.Settings._requestHeaderEncodingSelector?.Invoke(HttpKnownHeaderNames.Cookie, request); BufferLiteralHeaderWithStaticNameReference(H3StaticTable.Cookie, cookiesFromContainer, valueEncoding); + headerListSize += HttpKnownHeaderNames.Cookie.Length + HeaderField.RfcOverhead; } } @@ -624,11 +627,12 @@ private void BufferHeaders(HttpRequestMessage request) if (normalizedMethod.MustHaveRequestBody) { BufferIndexedHeader(H3StaticTable.ContentLength0); + headerListSize += HttpKnownHeaderNames.ContentLength.Length + HeaderField.RfcOverhead; } } else { - BufferHeaderCollection(request.Content.Headers); + headerListSize += BufferHeaderCollection(request.Content.Headers); } // Determine our header envelope size. @@ -642,15 +646,30 @@ private void BufferHeaders(HttpRequestMessage request) int actualHeadersLengthEncodedSize = VariableLengthIntegerHelper.WriteInteger(_sendBuffer.ActiveSpan.Slice(1, headersLengthEncodedSize), headersLength); Debug.Assert(actualHeadersLengthEncodedSize == headersLengthEncodedSize); + // The headerListSize is an approximation of the total header length. + // This is acceptable as long as the value is always >= the actual length. + // We must avoid ever sending more than the server allowed. + // This approach must be revisted if we ever support the dynamic table or compression when sending requests. + headerListSize += headersLength; + + uint maxHeaderListSize = _connection.MaxHeaderListSize; + if ((uint)headerListSize > maxHeaderListSize) + { + throw new HttpRequestException(SR.Format(SR.net_http_request_headers_exceeded_length, maxHeaderListSize)); + } + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestHeadersStop(); } // TODO: special-case Content-Type for static table values values? - private void BufferHeaderCollection(HttpHeaders headers) + private int BufferHeaderCollection(HttpHeaders headers) { HeaderEncodingSelector? encodingSelector = _connection.Pool.Settings._requestHeaderEncodingSelector; - foreach (HeaderEntry header in headers.GetEntries()) + ReadOnlySpan entries = headers.GetEntries(); + int headerListSize = entries.Length * HeaderField.RfcOverhead; + + foreach (HeaderEntry header in entries) { int headerValuesCount = HttpHeaders.GetStoreValuesIntoStringArray(header.Key, header.Value, ref _headerValues); Debug.Assert(headerValuesCount > 0, "No values for header??"); @@ -666,6 +685,10 @@ private void BufferHeaderCollection(HttpHeaders headers) // The Connection, Upgrade and ProxyConnection headers are also not supported in HTTP/3. if (knownHeader != KnownHeaders.Host && knownHeader != KnownHeaders.Connection && knownHeader != KnownHeaders.Upgrade && knownHeader != KnownHeaders.ProxyConnection) { + // The length of the encoded name may be shorter than the actual name. + // Ensure that headerListSize is always >= of the actual size. + headerListSize += knownHeader.Name.Length; + if (knownHeader == KnownHeaders.TE) { // HTTP/2 allows only 'trailers' TE header. rfc7540 8.1.2.2 @@ -706,6 +729,8 @@ private void BufferHeaderCollection(HttpHeaders headers) BufferLiteralHeaderWithoutNameReference(header.Key.Name, headerValues, HttpHeaderParser.DefaultSeparator, valueEncoding); } } + + return headerListSize; } private void BufferIndexedHeader(int index) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs index d0bef3bc27a23a..95749c4fa950c1 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs @@ -666,7 +666,7 @@ public async Task SendAsyncCore(HttpRequestMessage request, ParseHeaderNameValue(this, line.Span, response, isFromTrailer: false); } - if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStop(); + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStop((int)response.StatusCode); if (allowExpect100ToContinue != null) { 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 e4afdd294fca3c..0bc317d7e09f9b 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 @@ -98,6 +98,15 @@ internal sealed class HttpConnectionPool : IDisposable private SemaphoreSlim? _http3ConnectionCreateLock; internal readonly byte[]? _http3EncodedAuthorityHostHeader; + // These settings are advertised by the server via SETTINGS_MAX_HEADER_LIST_SIZE and SETTINGS_MAX_FIELD_SECTION_SIZE. + // If we had previous connections to the same host in this pool, memorize the last value seen. + // This value is used as an initial value for new connections before they have a chance to observe the SETTINGS frame. + // Doing so avoids immediately exceeding the server limit on the first request, potentially causing the connection to be torn down. + // 0 means there were no previous connections, or they hadn't advertised this limit. + // There is no need to lock when updating these values - we're only interested in saving _a_ value, not necessarily the min/max/last. + internal uint _lastSeenHttp2MaxHeaderListSize; + internal uint _lastSeenHttp3MaxHeaderListSize; + /// For non-proxy connection pools, this is the host name in bytes; for proxies, null. private readonly byte[]? _hostHeaderValueBytes; /// Options specialized and cached for this pool and its key. diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/StringContent.cs b/src/libraries/System.Net.Http/src/System/Net/Http/StringContent.cs index b9ce8f271464dd..519d15a60012fa 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/StringContent.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/StringContent.cs @@ -45,7 +45,7 @@ public StringContent(string content, Encoding? encoding) /// The encoding to use for the content. /// The media type to use for the content. public StringContent(string content, Encoding? encoding, string mediaType) - : this(content, encoding, new MediaTypeHeaderValue(mediaType, (encoding ?? DefaultStringEncoding).WebName)) + : this(content, encoding, new MediaTypeHeaderValue(mediaType ?? DefaultMediaType, (encoding ?? DefaultStringEncoding).WebName)) { } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index d2bc4873b6ad85..12c3ebceb1ab07 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -1227,14 +1227,156 @@ public void Expect100ContinueTimeout_SetAfterUse_Throws() } } + public abstract class SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength : HttpClientHandler_MaxResponseHeadersLength_Test + { + public SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength(ITestOutputHelper output) : base(output) { } + + [Fact] + public async Task ServerAdvertisedMaxHeaderListSize_IsHonoredByClient() + { + if (UseVersion.Major == 1) + { + // HTTP/1.X doesn't have a concept of SETTINGS_MAX_HEADER_LIST_SIZE. + return; + } + + // On HTTP/3 there is no synchronization between regular requests and the acknowledgement of the SETTINGS frame. + // Retry the test with increasing delays to give the client connection a chance to observe the settings. + int retry = 0; + await RetryHelper.ExecuteAsync(async () => + { + retry++; + + const int Limit = 10_000; + + using HttpClientHandler handler = CreateHttpClientHandler(); + using HttpClient client = CreateHttpClient(handler); + + // We want to test that the client remembered the setting it received from the previous connection. + // To do this, we trick the client into using the same HttpConnectionPool for both server connections. + // We only have control over the ConnectCallback on HTTP/2. + bool fakeRequestHost = UseVersion.Major == 2; + Uri lastServerUri = null; + + GetUnderlyingSocketsHttpHandler(handler).ConnectCallback = async (context, ct) => + { + Assert.Equal("foo", context.DnsEndPoint.Host); + + Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true }; + try + { + await socket.ConnectAsync(lastServerUri.IdnHost, lastServerUri.Port); + return new NetworkStream(socket, ownsSocket: true); + } + catch + { + socket.Dispose(); + throw; + } + }; + + TaskCompletionSource waitingForLastRequest = new(TaskCreationOptions.RunContinuationsAsynchronously); + + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + if (fakeRequestHost) + { + lastServerUri = uri; + uri = new UriBuilder(uri) { Host = "foo", Port = 42 }.Uri; + } + + // Send a dummy request to ensure the SETTINGS frame has been received. + Assert.Equal("Hello world", await client.GetStringAsync(uri)); + + if (retry > 1) + { + // Give the client HTTP/3 connection a chance to observe the SETTINGS frame. + await Task.Delay(100 * retry); + } + + HttpRequestMessage request = CreateRequest(HttpMethod.Get, uri, UseVersion, exactVersion: true); + request.Headers.Add("Foo", new string('a', Limit)); + + Exception ex = await Assert.ThrowsAsync(() => client.SendAsync(request)); + Assert.Contains(Limit.ToString(), ex.Message); + + request = CreateRequest(HttpMethod.Get, uri, UseVersion, exactVersion: true); + for (int i = 0; i < Limit / 40; i++) + { + request.Headers.Add($"Foo-{i}", ""); + } + + ex = await Assert.ThrowsAsync(() => client.SendAsync(request)); + Assert.Contains(Limit.ToString(), ex.Message); + + await waitingForLastRequest.Task.WaitAsync(TimeSpan.FromSeconds(10)); + + // Ensure that the connection is still usable for requests that don't hit the limit. + Assert.Equal("Hello world", await client.GetStringAsync(uri)); + }, + async server => + { + var setting = new SettingsEntry { SettingId = SettingId.MaxHeaderListSize, Value = Limit }; + + await using GenericLoopbackConnection connection = UseVersion.Major == 2 + ? await ((Http2LoopbackServer)server).EstablishConnectionAsync(setting) + : await ((Http3LoopbackServer)server).EstablishConnectionAsync(setting); + + await connection.ReadRequestDataAsync(); + await connection.SendResponseAsync(content: "Hello world"); + + // On HTTP/3, the client will establish a request stream before buffering the headers. + // Swallow two streams to account for the client creating and closing them before reporting the error. + if (connection is Http3LoopbackConnection http3Connection) + { + await http3Connection.AcceptRequestStreamAsync().WaitAsync(TimeSpan.FromSeconds(10)); + await http3Connection.AcceptRequestStreamAsync().WaitAsync(TimeSpan.FromSeconds(10)); + } + + waitingForLastRequest.SetResult(); + + // HandleRequestAsync will close the connection + await connection.HandleRequestAsync(content: "Hello world"); + + if (UseVersion.Major == 3) + { + await ((Http3LoopbackConnection)connection).ShutdownAsync(); + } + }); + + if (fakeRequestHost) + { + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + lastServerUri = uri; + uri = new UriBuilder(uri) { Host = "foo", Port = 42 }.Uri; + + HttpRequestMessage request = CreateRequest(HttpMethod.Get, uri, UseVersion, exactVersion: true); + request.Headers.Add("Foo", new string('a', Limit)); + + Exception ex = await Assert.ThrowsAsync(() => client.SendAsync(request)); + Assert.Contains(Limit.ToString(), ex.Message); + + // Ensure that the connection is still usable for requests that don't hit the limit. + Assert.Equal("Hello world", await client.GetStringAsync(uri)); + }, + async server => + { + await server.HandleRequestAsync(content: "Hello world"); + }); + } + }, maxAttempts: UseVersion.Major == 3 ? 5 : 1); + } + } + [ConditionalClass(typeof(SocketsHttpHandler), nameof(SocketsHttpHandler.IsSupported))] - public sealed class SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Http11 : HttpClientHandler_MaxResponseHeadersLength_Test + public sealed class SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Http11 : SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength { public SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Http11(ITestOutputHelper output) : base(output) { } } [ConditionalClass(typeof(SocketsHttpHandler), nameof(SocketsHttpHandler.IsSupported))] - public sealed class SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Http2 : HttpClientHandler_MaxResponseHeadersLength_Test + public sealed class SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Http2 : SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength { public SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Http2(ITestOutputHelper output) : base(output) { } protected override Version UseVersion => HttpVersion.Version20; @@ -1242,7 +1384,7 @@ public SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Http2(ITest [ActiveIssue("https://github.com/dotnet/runtime/issues/73930")] [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsQuicSupported))] - public sealed class SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Http3 : HttpClientHandler_MaxResponseHeadersLength_Test + public sealed class SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Http3 : SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength { public SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Http3(ITestOutputHelper output) : base(output) { } protected override Version UseVersion => HttpVersion.Version30; @@ -3751,6 +3893,7 @@ public abstract class SocketsHttpHandler_SecurityTest : HttpClientHandlerTestBas public SocketsHttpHandler_SecurityTest(ITestOutputHelper output) : base(output) { } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")] public async Task SslOptions_CustomTrust_Ok() { X509Certificate2Collection caCerts = new X509Certificate2Collection(); @@ -3787,6 +3930,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync( } [Fact] + [SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")] public async Task SslOptions_InvalidName_Throws() { X509Certificate2Collection caCerts = new X509Certificate2Collection(); @@ -3817,6 +3961,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync( } [Fact] + [SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")] public async Task SslOptions_CustomPolicy_IgnoresNameMismatch() { X509Certificate2Collection caCerts = new X509Certificate2Collection(); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/StringContentTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/StringContentTest.cs index 73e33fde4fa9ba..cb69b3e4e7259b 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/StringContentTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/StringContentTest.cs @@ -71,6 +71,17 @@ public async Task Ctor_DefineNoEncoding_DefaultEncodingUsed() Assert.Equal(sourceString, roundTrip); } + [Fact] + public void Ctor_PassNullForMediaType_DefaultMediaTypeUsed() + { + string sourceString = "\u00C4\u00E4\u00FC\u00DC"; + Encoding defaultStringEncoding = Encoding.GetEncoding("utf-8"); + var content = new StringContent(sourceString, defaultStringEncoding, ((string)null)!); + + // If no media is passed-in, the default is used + Assert.Equal("text/plain", content.Headers.ContentType.MediaType); + } + [Fact] public async Task Ctor_UseCustomMediaTypeHeaderValue_SpecificEncoding() { 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 4cbf8ecec267a7..b5a0ebaa38931a 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,7 @@ true true + true diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/TelemetryTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/TelemetryTest.cs index 38785e38f45591..e3b79bd00d465e 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/TelemetryTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/TelemetryTest.cs @@ -6,7 +6,6 @@ using System.Diagnostics.Tracing; using System.IO; using System.Linq; -using System.Net.Quic; using System.Net.Test.Common; using System.Text; using System.Threading; @@ -412,14 +411,25 @@ private static void ValidateStartFailedStopEvents(ConcurrentQueue<(EventWrittenE Assert.Equal(count, starts.Length); (EventWrittenEventArgs Event, Guid ActivityId)[] stops = events.Where(e => e.Event.EventName == "RequestStop").ToArray(); - Assert.All(stops, stopEvent => Assert.Empty(stopEvent.Event.Payload)); + foreach (EventWrittenEventArgs stopEvent in stops.Select(e => e.Event)) + { + object payload = Assert.Single(stopEvent.Payload); + int statusCode = Assert.IsType(payload); + Assert.Equal(shouldHaveFailures ? -1 : 200, statusCode); + } ValidateSameActivityIds(starts, stops); (EventWrittenEventArgs Event, Guid ActivityId)[] failures = events.Where(e => e.Event.EventName == "RequestFailed").ToArray(); - Assert.All(failures, failedEvent => Assert.Empty(failedEvent.Event.Payload)); if (shouldHaveFailures) { + foreach (EventWrittenEventArgs failedEvent in failures.Select(e => e.Event)) + { + object payload = Assert.Single(failedEvent.Payload); + string exceptionMessage = Assert.IsType(payload); + Assert.Equal(new OperationCanceledException().Message, exceptionMessage); + } + ValidateSameActivityIds(starts, failures); } else @@ -470,8 +480,8 @@ private static void ValidateRequestResponseStartStopEvents(ConcurrentQueue<(Even foreach (EventWrittenEventArgs requestContentStop in requestContentStops.Select(e => e.Event)) { object payload = Assert.Single(requestContentStop.Payload); - Assert.True(payload is long); - Assert.Equal(requestContentLength.Value, (long)payload); + long contentLength = Assert.IsType(payload); + Assert.Equal(requestContentLength.Value, contentLength); } ValidateSameActivityIds(requestContentStarts, requestContentStops); @@ -482,7 +492,12 @@ private static void ValidateRequestResponseStartStopEvents(ConcurrentQueue<(Even (EventWrittenEventArgs Event, Guid ActivityId)[] responseHeadersStops = events.Where(e => e.Event.EventName == "ResponseHeadersStop").ToArray(); Assert.Equal(count, responseHeadersStops.Length); - Assert.All(responseHeadersStops, r => Assert.Empty(r.Event.Payload)); + foreach (EventWrittenEventArgs responseHeadersStop in responseHeadersStops.Select(e => e.Event)) + { + object payload = Assert.Single(responseHeadersStop.Payload); + int statusCode = Assert.IsType(payload); + Assert.Equal(200, statusCode); + } ValidateSameActivityIds(responseHeadersStarts, responseHeadersStops); diff --git a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/docker-compose.yml b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/docker-compose.yml index cbb12a6f6033b1..c22be392756f26 100644 --- a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/docker-compose.yml +++ b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/docker-compose.yml @@ -7,8 +7,6 @@ services: image: httpstress volumes: - "${CLIENT_DUMPS_SHARE}:${DUMPS_SHARE_MOUNT_ROOT}" - cap_add: - - SYS_PTRACE links: - server environment: @@ -20,8 +18,6 @@ services: image: httpstress volumes: - "${SERVER_DUMPS_SHARE}:${DUMPS_SHARE_MOUNT_ROOT}" - cap_add: - - SYS_PTRACE ports: - "5001:5001" environment: diff --git a/src/libraries/System.Net.Http/tests/UnitTests/Fakes/HttpTelemetry.cs b/src/libraries/System.Net.Http/tests/UnitTests/Fakes/HttpTelemetry.cs index 5667687e632e2f..26ac057e31bd8d 100644 --- a/src/libraries/System.Net.Http/tests/UnitTests/Fakes/HttpTelemetry.cs +++ b/src/libraries/System.Net.Http/tests/UnitTests/Fakes/HttpTelemetry.cs @@ -11,9 +11,9 @@ public class HttpTelemetry public void RequestStart(HttpRequestMessage request) { } - public void RequestStop() { } + public void RequestStop(HttpResponseMessage response) { } - public void RequestFailed() { } + public void RequestFailed(Exception exception) { } public void ResponseContentStart() { } diff --git a/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj b/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj index 85139c5391ff85..0c06f6eb21f194 100644 --- a/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj @@ -322,6 +322,8 @@ Link="HPack\HPackIntegerTest.cs" /> + @@ -393,8 +395,12 @@ Link="Common\System\Net\Http\aspnetcore\Http3\QPack\HeaderField.cs" /> + + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-Android true true + true + true @@ -24,6 +26,8 @@ Link="Common\System\Diagnostics\Tracing\TestEventListener.cs" /> + diff --git a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/System.Net.NameResolution.Functional.Tests.csproj b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/System.Net.NameResolution.Functional.Tests.csproj index 982ca8491f706c..f4588a2b4a1125 100644 --- a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/System.Net.NameResolution.Functional.Tests.csproj +++ b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/System.Net.NameResolution.Functional.Tests.csproj @@ -3,6 +3,7 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser true true + true diff --git a/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj b/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj index d36ad67504ed4f..3f57620675a3bc 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj +++ b/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj @@ -153,8 +153,9 @@ - + + @@ -188,7 +189,7 @@ - + diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/LinuxIPv4InterfaceProperties.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/LinuxIPv4InterfaceProperties.cs index 1a02bbe05fb0d8..de7b309e48022f 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/LinuxIPv4InterfaceProperties.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/LinuxIPv4InterfaceProperties.cs @@ -52,8 +52,7 @@ private bool GetIsForwardingEnabled() { return StringParsingHelpers.ParseRawIntFile(paths[i]) == 1; } - catch (IOException) { } - catch (UnauthorizedAccessException) { } + catch (NetworkInformationException ex) when (ex.InnerException is IOException or UnauthorizedAccessException) { } } return false; diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs new file mode 100644 index 00000000000000..3bca7060682f66 --- /dev/null +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs @@ -0,0 +1,251 @@ +// 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.Runtime.Versioning; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.NetworkInformation +{ + public partial class NetworkChange + { + private static readonly TimeSpan s_timerInterval = TimeSpan.FromSeconds(2); + private static readonly object s_lockObj = new(); + + private static Task? s_loopTask; + private static CancellationTokenSource? s_cancellationTokenSource; + private static IPAddress[]? s_lastIpAddresses; + + [UnsupportedOSPlatform("illumos")] + [UnsupportedOSPlatform("solaris")] + public static event NetworkAddressChangedEventHandler? NetworkAddressChanged + { + add + { + if (value != null) + { + lock (s_lockObj) + { + if (s_addressChangedSubscribers.Count == 0 && + s_availabilityChangedSubscribers.Count == 0) + { + CreateAndStartLoop(); + } + else + { + Debug.Assert(s_loopTask is not null); + } + + s_addressChangedSubscribers.TryAdd(value, ExecutionContext.Capture()); + } + } + } + remove + { + if (value != null) + { + lock (s_lockObj) + { + bool hadAddressChangedSubscribers = s_addressChangedSubscribers.Count != 0; + s_addressChangedSubscribers.Remove(value); + + if (hadAddressChangedSubscribers && s_addressChangedSubscribers.Count == 0 && + s_availabilityChangedSubscribers.Count == 0) + { + StopLoop(); + } + } + } + } + } + + [UnsupportedOSPlatform("illumos")] + [UnsupportedOSPlatform("solaris")] + public static event NetworkAvailabilityChangedEventHandler? NetworkAvailabilityChanged + { + add + { + if (value != null) + { + lock (s_lockObj) + { + if (s_addressChangedSubscribers.Count == 0 && + s_availabilityChangedSubscribers.Count == 0) + { + CreateAndStartLoop(); + } + else + { + Debug.Assert(s_loopTask is not null); + } + + s_availabilityChangedSubscribers.TryAdd(value, ExecutionContext.Capture()); + } + } + } + remove + { + if (value != null) + { + lock (s_lockObj) + { + bool hadSubscribers = s_addressChangedSubscribers.Count != 0 || + s_availabilityChangedSubscribers.Count != 0; + s_availabilityChangedSubscribers.Remove(value); + + if (hadSubscribers && s_addressChangedSubscribers.Count == 0 && + s_availabilityChangedSubscribers.Count == 0) + { + StopLoop(); + } + } + } + } + } + + private static void CreateAndStartLoop() + { + Debug.Assert(s_cancellationTokenSource is null); + Debug.Assert(s_loopTask is null); + + s_cancellationTokenSource = new CancellationTokenSource(); + s_loopTask = Task.Run(() => PeriodicallyCheckIfNetworkChanged(s_cancellationTokenSource.Token)); + } + + private static void StopLoop() + { + Debug.Assert(s_cancellationTokenSource is not null); + Debug.Assert(s_loopTask is not null); + + s_cancellationTokenSource.Cancel(); + + s_loopTask = null; + s_cancellationTokenSource = null; + s_lastIpAddresses = null; + } + + private static async Task PeriodicallyCheckIfNetworkChanged(CancellationToken cancellationToken) + { + using var timer = new PeriodicTimer(s_timerInterval); + + try + { + while (await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false) + && !cancellationToken.IsCancellationRequested) + { + CheckIfNetworkChanged(); + } + } + catch (OperationCanceledException) + { + } + } + + private static void CheckIfNetworkChanged() + { + var newAddresses = GetIPAddresses(); + if (s_lastIpAddresses is IPAddress[] oldAddresses && NetworkChanged(oldAddresses, newAddresses)) + { + OnNetworkChanged(); + } + + s_lastIpAddresses = newAddresses; + } + + private static IPAddress[] GetIPAddresses() + { + var addresses = new List(); + + var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); + foreach (var networkInterface in networkInterfaces) + { + var properties = networkInterface.GetIPProperties(); + foreach (var addressInformation in properties.UnicastAddresses) + { + addresses.Add(addressInformation.Address); + } + } + + return addresses.ToArray(); + } + + private static bool NetworkChanged(IPAddress[] oldAddresses, IPAddress[] newAddresses) + { + if (oldAddresses.Length != newAddresses.Length) + { + return true; + } + + for (int i = 0; i < newAddresses.Length; i++) + { + if (Array.IndexOf(oldAddresses, newAddresses[i]) == -1) + { + return true; + } + } + + return false; + } + + private static void OnNetworkChanged() + { + Dictionary? addressChangedSubscribers = null; + Dictionary? availabilityChangedSubscribers = null; + + lock (s_lockObj) + { + if (s_addressChangedSubscribers.Count > 0) + { + addressChangedSubscribers = new Dictionary(s_addressChangedSubscribers); + } + if (s_availabilityChangedSubscribers.Count > 0) + { + availabilityChangedSubscribers = new Dictionary(s_availabilityChangedSubscribers); + } + } + + if (addressChangedSubscribers != null) + { + foreach (KeyValuePair + subscriber in addressChangedSubscribers) + { + NetworkAddressChangedEventHandler handler = subscriber.Key; + ExecutionContext? ec = subscriber.Value; + + if (ec == null) // Flow suppressed + { + handler(null, EventArgs.Empty); + } + else + { + ExecutionContext.Run(ec, s_runAddressChangedHandler, handler); + } + } + } + + if (availabilityChangedSubscribers != null) + { + bool isAvailable = NetworkInterface.GetIsNetworkAvailable(); + NetworkAvailabilityEventArgs args = isAvailable ? s_availableEventArgs : s_notAvailableEventArgs; + ContextCallback callbackContext = isAvailable ? s_runHandlerAvailable : s_runHandlerNotAvailable; + foreach (KeyValuePair + subscriber in availabilityChangedSubscribers) + { + NetworkAvailabilityChangedEventHandler handler = subscriber.Key; + ExecutionContext? ec = subscriber.Value; + + if (ec == null) // Flow suppressed + { + handler(null, args); + } + else + { + ExecutionContext.Run(ec, callbackContext, handler); + } + } + } + } + } +} diff --git a/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/System.Net.NetworkInformation.Functional.Tests.csproj b/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/System.Net.NetworkInformation.Functional.Tests.csproj index 345e1f518da3b4..d0d92ab8682ce7 100644 --- a/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/System.Net.NetworkInformation.Functional.Tests.csproj +++ b/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/System.Net.NetworkInformation.Functional.Tests.csproj @@ -5,6 +5,7 @@ $(NetCoreAppCurrent) true $(DefineConstants);NETWORKINFORMATION_TEST + true diff --git a/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.PingUtility.cs b/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.PingUtility.cs index 7bdc4120cdb537..23299bccfede0e 100644 --- a/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.PingUtility.cs +++ b/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.PingUtility.cs @@ -131,9 +131,11 @@ private static bool TryParseTtlExceeded(string stdout, out PingReply? reply) return false; } - // look for address in "From 172.21.64.1 icmp_seq=1 Time to live exceeded" + // look for address in: + // - "From 172.21.64.1 icmp_seq=1 Time to live exceeded" + // - "From 172.21.64.1: icmp_seq=1 Time to live exceeded" int addressStart = stdout.IndexOf("From ", StringComparison.Ordinal) + 5; - int addressLength = stdout.IndexOf(' ', Math.Max(addressStart, 0)) - addressStart; + int addressLength = stdout.AsSpan(Math.Max(addressStart, 0)).IndexOfAny(' ', ':'); IPAddress? address; if (addressStart < 5 || addressLength <= 0 || !IPAddress.TryParse(stdout.AsSpan(addressStart, addressLength), out address)) { 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 a19e8592056f62..257af8dbb8808b 100644 --- a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj +++ b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj @@ -11,6 +11,7 @@ $(DefineConstants);TARGET_WINDOWS SR.SystemNetQuic_PlatformNotSupported ExcludeApiList.PNSE.txt + false @@ -137,17 +138,15 @@ - - + + - + diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.NativeMethods.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.NativeMethods.cs new file mode 100644 index 00000000000000..206eac76ac7878 --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.NativeMethods.cs @@ -0,0 +1,378 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Quic; + +namespace System.Net.Quic; + +internal sealed unsafe partial class MsQuicApi +{ + public void SetContext(MsQuicSafeHandle handle, void* context) + { + bool success = false; + try + { + handle.DangerousAddRef(ref success); + ApiTable->SetContext(handle.QuicHandle, context); + } + finally + { + if (success) + { + handle.DangerousRelease(); + } + } + } + + public void* GetContext(MsQuicSafeHandle handle) + { + bool success = false; + try + { + handle.DangerousAddRef(ref success); + return ApiTable->GetContext(handle.QuicHandle); + } + finally + { + if (success) + { + handle.DangerousRelease(); + } + } + } + + public void SetCallbackHandler(MsQuicSafeHandle handle, void* callback, void* context) + { + bool success = false; + try + { + handle.DangerousAddRef(ref success); + ApiTable->SetCallbackHandler(handle.QuicHandle, callback, context); + } + finally + { + if (success) + { + handle.DangerousRelease(); + } + } + } + + public int SetParam(MsQuicSafeHandle handle, uint param, uint bufferLength, void* buffer) + { + bool success = false; + try + { + handle.DangerousAddRef(ref success); + return ApiTable->SetParam(handle.QuicHandle, param, bufferLength, buffer); + } + finally + { + if (success) + { + handle.DangerousRelease(); + } + } + } + + public int GetParam(MsQuicSafeHandle handle, uint param, uint* bufferLength, void* buffer) + { + bool success = false; + try + { + handle.DangerousAddRef(ref success); + return ApiTable->GetParam(handle.QuicHandle, param, bufferLength, buffer); + } + finally + { + if (success) + { + handle.DangerousRelease(); + } + } + } + + public void RegistrationShutdown(MsQuicSafeHandle registration, QUIC_CONNECTION_SHUTDOWN_FLAGS flags, ulong code) + { + bool success = false; + try + { + registration.DangerousAddRef(ref success); + ApiTable->RegistrationShutdown(registration.QuicHandle, flags, code); + } + finally + { + if (success) + { + registration.DangerousRelease(); + } + } + } + + public int ConfigurationOpen(MsQuicSafeHandle registration, QUIC_BUFFER* alpnBuffers, uint alpnBuffersCount, QUIC_SETTINGS* settings, uint settingsSize, void* context, QUIC_HANDLE** configuration) + { + bool success = false; + try + { + registration.DangerousAddRef(ref success); + return ApiTable->ConfigurationOpen(registration.QuicHandle, alpnBuffers, alpnBuffersCount, settings, settingsSize, context, configuration); + } + finally + { + if (success) + { + registration.DangerousRelease(); + } + } + } + + public int ConfigurationLoadCredential(MsQuicSafeHandle configuration, QUIC_CREDENTIAL_CONFIG* config) + { + bool success = false; + try + { + configuration.DangerousAddRef(ref success); + return ApiTable->ConfigurationLoadCredential(configuration.QuicHandle, config); + } + finally + { + if (success) + { + configuration.DangerousRelease(); + } + } + } + + public int ListenerOpen(MsQuicSafeHandle registration, delegate* unmanaged[Cdecl] callback, void* context, QUIC_HANDLE** listener) + { + bool success = false; + try + { + registration.DangerousAddRef(ref success); + return ApiTable->ListenerOpen(registration.QuicHandle, callback, context, listener); + } + finally + { + if (success) + { + registration.DangerousRelease(); + } + } + } + + public int ListenerStart(MsQuicSafeHandle listener, QUIC_BUFFER* alpnBuffers, uint alpnBuffersCount, QuicAddr* localAddress) + { + bool success = false; + try + { + listener.DangerousAddRef(ref success); + return ApiTable->ListenerStart(listener.QuicHandle, alpnBuffers, alpnBuffersCount, localAddress); + } + finally + { + if (success) + { + listener.DangerousRelease(); + } + } + } + + public void ListenerStop(MsQuicSafeHandle listener) + { + bool success = false; + try + { + listener.DangerousAddRef(ref success); + ApiTable->ListenerStop(listener.QuicHandle); + } + finally + { + if (success) + { + listener.DangerousRelease(); + } + } + } + + public int ConnectionOpen(MsQuicSafeHandle registration, delegate* unmanaged[Cdecl] callback, void* context, QUIC_HANDLE** connection) + { + bool success = false; + try + { + registration.DangerousAddRef(ref success); + return ApiTable->ConnectionOpen(registration.QuicHandle, callback, context, connection); + } + finally + { + if (success) + { + registration.DangerousRelease(); + } + } + } + + public void ConnectionShutdown(MsQuicSafeHandle connection, QUIC_CONNECTION_SHUTDOWN_FLAGS flags, ulong code) + { + bool success = false; + try + { + connection.DangerousAddRef(ref success); + ApiTable->ConnectionShutdown(connection.QuicHandle, flags, code); + } + finally + { + if (success) + { + connection.DangerousRelease(); + } + } + } + + public int ConnectionStart(MsQuicSafeHandle connection, MsQuicSafeHandle configuration, ushort family, sbyte* serverName, ushort serverPort) + { + bool connectionSuccess = false; + bool configurationSuccess = false; + try + { + connection.DangerousAddRef(ref connectionSuccess); + configuration.DangerousAddRef(ref configurationSuccess); + return ApiTable->ConnectionStart(connection.QuicHandle, configuration.QuicHandle, family, serverName, serverPort); + } + finally + { + if (connectionSuccess) + { + connection.DangerousRelease(); + } + if (configurationSuccess) + { + configuration.DangerousRelease(); + } + } + } + + public int ConnectionSetConfiguration(MsQuicSafeHandle connection, MsQuicSafeHandle configuration) + { + bool connectionSuccess = false; + bool configurationSuccess = false; + try + { + connection.DangerousAddRef(ref connectionSuccess); + configuration.DangerousAddRef(ref configurationSuccess); + return ApiTable->ConnectionSetConfiguration(connection.QuicHandle, configuration.QuicHandle); + } + finally + { + if (connectionSuccess) + { + connection.DangerousRelease(); + } + if (configurationSuccess) + { + configuration.DangerousRelease(); + } + } + } + + public int StreamOpen(MsQuicSafeHandle connection, QUIC_STREAM_OPEN_FLAGS flags, delegate* unmanaged[Cdecl] callback, void* context, QUIC_HANDLE** stream) + { + bool success = false; + try + { + connection.DangerousAddRef(ref success); + return ApiTable->StreamOpen(connection.QuicHandle, flags, callback, context, stream); + } + finally + { + if (success) + { + connection.DangerousRelease(); + } + } + } + + public int StreamStart(MsQuicSafeHandle stream, QUIC_STREAM_START_FLAGS flags) + { + bool success = false; + try + { + stream.DangerousAddRef(ref success); + return ApiTable->StreamStart(stream.QuicHandle, flags); + } + finally + { + if (success) + { + stream.DangerousRelease(); + } + } + } + + public int StreamShutdown(MsQuicSafeHandle stream, QUIC_STREAM_SHUTDOWN_FLAGS flags, ulong code) + { + bool success = false; + try + { + stream.DangerousAddRef(ref success); + return ApiTable->StreamShutdown(stream.QuicHandle, flags, code); + } + finally + { + if (success) + { + stream.DangerousRelease(); + } + } + } + + public int StreamSend(MsQuicSafeHandle stream, QUIC_BUFFER* buffers, uint buffersCount, QUIC_SEND_FLAGS flags, void* context) + { + bool success = false; + try + { + stream.DangerousAddRef(ref success); + return ApiTable->StreamSend(stream.QuicHandle, buffers, buffersCount, flags, context); + } + finally + { + if (success) + { + stream.DangerousRelease(); + } + } + } + + public void StreamReceiveComplete(MsQuicSafeHandle stream, ulong length) + { + bool success = false; + try + { + stream.DangerousAddRef(ref success); + ApiTable->StreamReceiveComplete(stream.QuicHandle, length); + } + finally + { + if (success) + { + stream.DangerousRelease(); + } + } + } + + public int StreamReceiveSetEnabled(MsQuicSafeHandle stream, byte enabled) + { + bool success = false; + try + { + stream.DangerousAddRef(ref success); + return ApiTable->StreamReceiveSetEnabled(stream.QuicHandle, enabled); + } + finally + { + if (success) + { + stream.DangerousRelease(); + } + } + } +} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs index e2866454356dd2..63b4a827d6790b 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.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.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Microsoft.Quic; @@ -13,11 +14,14 @@ namespace System.Net.Quic; -internal sealed unsafe class MsQuicApi +internal sealed unsafe partial class MsQuicApi { private static readonly Version MinWindowsVersion = new Version(10, 0, 20145, 1000); - private static readonly Version MsQuicVersion = new Version(2, 1); + private static readonly Version MinMsQuicVersion = new Version(2, 1); + + private static readonly delegate* unmanaged[Cdecl] MsQuicOpenVersion; + private static readonly delegate* unmanaged[Cdecl] MsQuicClose; public MsQuicSafeHandle Registration { get; } @@ -47,7 +51,8 @@ private MsQuicApi(QUIC_API_TABLE* apiTable) } } - internal static MsQuicApi Api { get; } = null!; + private static readonly Lazy s_api = new Lazy(AllocateMsQuicApi); + internal static MsQuicApi Api => s_api.Value; internal static bool IsQuicSupported { get; } @@ -56,92 +61,123 @@ private MsQuicApi(QUIC_API_TABLE* apiTable) internal static bool Tls13ServerMayBeDisabled { get; } internal static bool Tls13ClientMayBeDisabled { get; } +#pragma warning disable CA1810 // Initialize all static fields in 'MsQuicApi' when those fields are declared and remove the explicit static constructor static MsQuicApi() { + bool loaded = false; IntPtr msQuicHandle; - if (!NativeLibrary.TryLoad($"{Interop.Libraries.MsQuic}.{MsQuicVersion.Major}", typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle) && - !NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle)) + if (OperatingSystem.IsWindows()) + { + // Windows ships msquic in the assembly directory. + loaded = NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle); + } + else + { + // Non-Windows relies on the package being installed on the system and may include the version in its name + loaded = NativeLibrary.TryLoad($"{Interop.Libraries.MsQuic}.{MinMsQuicVersion.Major}", typeof(MsQuicApi).Assembly, null, out msQuicHandle) || + NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, null, out msQuicHandle); + } + + if (!loaded) { + // MsQuic library not loaded + return; + } + + MsQuicOpenVersion = (delegate* unmanaged[Cdecl])NativeLibrary.GetExport(msQuicHandle, nameof(MsQuicOpenVersion)); + MsQuicClose = (delegate* unmanaged[Cdecl])NativeLibrary.GetExport(msQuicHandle, nameof(MsQuicClose)); + + if (!TryOpenMsQuic(out QUIC_API_TABLE* apiTable, out _)) + { + // Too low version of the library (likely pre-2.0) return; } try { - if (!NativeLibrary.TryGetExport(msQuicHandle, "MsQuicOpenVersion", out IntPtr msQuicOpenVersionAddress)) + // Check version + const int ArraySize = 4; + uint* libVersion = stackalloc uint[ArraySize]; + uint size = (uint)ArraySize * sizeof(uint); + if (StatusFailed(apiTable->GetParam(null, QUIC_PARAM_GLOBAL_LIBRARY_VERSION, &size, libVersion))) { return; } - QUIC_API_TABLE* apiTable = null; - delegate* unmanaged[Cdecl] msQuicOpenVersion = (delegate* unmanaged[Cdecl])msQuicOpenVersionAddress; - if (StatusFailed(msQuicOpenVersion((uint)MsQuicVersion.Major, &apiTable))) + var version = new Version((int)libVersion[0], (int)libVersion[1], (int)libVersion[2], (int)libVersion[3]); + if (version < MinMsQuicVersion) { + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info(null, $"Incompatible MsQuic library version '{version}', expecting at least '{MinMsQuicVersion}'"); + } return; } - try - { - int arraySize = 4; - uint* libVersion = stackalloc uint[arraySize]; - uint size = (uint)arraySize * sizeof(uint); - if (StatusFailed(apiTable->GetParam(null, QUIC_PARAM_GLOBAL_LIBRARY_VERSION, &size, libVersion))) - { - return; - } + // Assume SChannel is being used on windows and query for the actual provider from the library if querying is supported + QUIC_TLS_PROVIDER provider = OperatingSystem.IsWindows() ? QUIC_TLS_PROVIDER.SCHANNEL : QUIC_TLS_PROVIDER.OPENSSL; + size = sizeof(QUIC_TLS_PROVIDER); + apiTable->GetParam(null, QUIC_PARAM_GLOBAL_TLS_PROVIDER, &size, &provider); + UsesSChannelBackend = provider == QUIC_TLS_PROVIDER.SCHANNEL; - var version = new Version((int)libVersion[0], (int)libVersion[1], (int)libVersion[2], (int)libVersion[3]); - if (version < MsQuicVersion) + if (UsesSChannelBackend) + { + // Implies windows platform, check TLS1.3 availability + if (!IsWindowsVersionSupported()) { if (NetEventSource.Log.IsEnabled()) { - NetEventSource.Info(null, $"Incompatible MsQuic library version '{version}', expecting '{MsQuicVersion}'"); + NetEventSource.Info(null, $"Current Windows version ({Environment.OSVersion}) is not supported by QUIC. Minimal supported version is {MinWindowsVersion}"); } + return; } - // Assume SChannel is being used on windows and query for the actual provider from the library - QUIC_TLS_PROVIDER provider = OperatingSystem.IsWindows() ? QUIC_TLS_PROVIDER.SCHANNEL : QUIC_TLS_PROVIDER.OPENSSL; - size = sizeof(QUIC_TLS_PROVIDER); - apiTable->GetParam(null, QUIC_PARAM_GLOBAL_TLS_PROVIDER, &size, &provider); - UsesSChannelBackend = provider == QUIC_TLS_PROVIDER.SCHANNEL; + Tls13ServerMayBeDisabled = IsTls13Disabled(isServer: true); + Tls13ClientMayBeDisabled = IsTls13Disabled(isServer: false); + } - if (UsesSChannelBackend) - { - // Implies windows platform, check TLS1.3 availability - if (!IsWindowsVersionSupported()) - { - if (NetEventSource.Log.IsEnabled()) - { - NetEventSource.Info(null, $"Current Windows version ({Environment.OSVersion}) is not supported by QUIC. Minimal supported version is {MinWindowsVersion}"); - } + IsQuicSupported = true; + } + finally + { + // Gracefully close the API table to free resources. The API table will be allocated lazily again if needed + MsQuicClose(apiTable); + } + } +#pragma warning restore CA1810 - return; - } + private static MsQuicApi AllocateMsQuicApi() + { + Debug.Assert(IsQuicSupported); - Tls13ServerMayBeDisabled = IsTls13Disabled(isServer: true); - Tls13ClientMayBeDisabled = IsTls13Disabled(isServer: false); - } + if (!TryOpenMsQuic(out QUIC_API_TABLE* apiTable, out int openStatus)) + { + throw ThrowHelper.GetExceptionForMsQuicStatus(openStatus); + } - Api = new MsQuicApi(apiTable); - IsQuicSupported = true; - } - finally - { - if (!IsQuicSupported && NativeLibrary.TryGetExport(msQuicHandle, "MsQuicClose", out IntPtr msQuicClose)) - { - // Gracefully close the API table - ((delegate* unmanaged[Cdecl])msQuicClose)(apiTable); - } - } + return new MsQuicApi(apiTable); + } - } - finally + private static bool TryOpenMsQuic(out QUIC_API_TABLE* apiTable, out int openStatus) + { + Debug.Assert(MsQuicOpenVersion != null); + + QUIC_API_TABLE* table = null; + openStatus = MsQuicOpenVersion((uint)MinMsQuicVersion.Major, &table); + if (StatusFailed(openStatus)) { - if (!IsQuicSupported) + if (NetEventSource.Log.IsEnabled()) { - NativeLibrary.Free(msQuicHandle); + NetEventSource.Info(null, $"MsQuicOpenVersion returned {openStatus} status code."); } + + apiTable = null; + return false; } + + apiTable = table; + return true; } private static bool IsWindowsVersionSupported() => OperatingSystem.IsWindowsVersionAtLeast(MinWindowsVersion.Major, diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicConfiguration.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicConfiguration.cs index b84cb6cce4267a..ddec979aade1b5 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicConfiguration.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicConfiguration.cs @@ -131,8 +131,8 @@ private static unsafe MsQuicSafeHandle Create(QuicConnectionOptions options, QUI using MsQuicBuffers msquicBuffers = new MsQuicBuffers(); msquicBuffers.Initialize(alpnProtocols, alpnProtocol => alpnProtocol.Protocol); - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ConfigurationOpen( - MsQuicApi.Api.Registration.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ConfigurationOpen( + MsQuicApi.Api.Registration, msquicBuffers.Buffers, (uint)alpnProtocols.Count, &settings, @@ -140,7 +140,7 @@ private static unsafe MsQuicSafeHandle Create(QuicConnectionOptions options, QUI (void*)IntPtr.Zero, &handle), "ConfigurationOpen failed"); - MsQuicSafeHandle configurationHandle = new MsQuicSafeHandle(handle, MsQuicApi.Api.ApiTable->ConfigurationClose, SafeHandleType.Configuration); + MsQuicSafeHandle configurationHandle = new MsQuicSafeHandle(handle, SafeHandleType.Configuration); try { @@ -157,13 +157,13 @@ private static unsafe MsQuicSafeHandle Create(QuicConnectionOptions options, QUI if (certificate is null) { config.Type = QUIC_CREDENTIAL_TYPE.NONE; - status = MsQuicApi.Api.ApiTable->ConfigurationLoadCredential(configurationHandle.QuicHandle, &config); + status = MsQuicApi.Api.ConfigurationLoadCredential(configurationHandle, &config); } else if (MsQuicApi.UsesSChannelBackend) { config.Type = QUIC_CREDENTIAL_TYPE.CERTIFICATE_CONTEXT; config.CertificateContext = (void*)certificate.Handle; - status = MsQuicApi.Api.ApiTable->ConfigurationLoadCredential(configurationHandle.QuicHandle, &config); + status = MsQuicApi.Api.ConfigurationLoadCredential(configurationHandle, &config); } else { @@ -192,7 +192,7 @@ private static unsafe MsQuicSafeHandle Create(QuicConnectionOptions options, QUI PrivateKeyPassword = (sbyte*)IntPtr.Zero }; config.CertificatePkcs12 = &pkcs12Certificate; - status = MsQuicApi.Api.ApiTable->ConfigurationLoadCredential(configurationHandle.QuicHandle, &config); + status = MsQuicApi.Api.ConfigurationLoadCredential(configurationHandle, &config); } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicHelpers.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicHelpers.cs index 04beebc6a2fc13..683e8bb62473e4 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicHelpers.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicHelpers.cs @@ -61,8 +61,8 @@ internal static unsafe T GetMsQuicParameter(MsQuicSafeHandle handle, uint par T value; uint length = (uint)sizeof(T); - int status = MsQuicApi.Api.ApiTable->GetParam( - handle.QuicHandle, + int status = MsQuicApi.Api.GetParam( + handle, parameter, &length, (byte*)&value); @@ -78,8 +78,8 @@ internal static unsafe T GetMsQuicParameter(MsQuicSafeHandle handle, uint par internal static unsafe void SetMsQuicParameter(MsQuicSafeHandle handle, uint parameter, T value) where T : unmanaged { - int status = MsQuicApi.Api.ApiTable->SetParam( - handle.QuicHandle, + int status = MsQuicApi.Api.SetParam( + handle, parameter, (uint)sizeof(T), (byte*)&value); diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicSafeHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicSafeHandle.cs index 6e247ef9937376..58b41443694a1d 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicSafeHandle.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicSafeHandle.cs @@ -39,10 +39,25 @@ public MsQuicSafeHandle(QUIC_HANDLE* handle, delegate* unmanaged[Cdecl] MsQuicApi.Api.ApiTable->RegistrationClose, + SafeHandleType.Configuration => MsQuicApi.Api.ApiTable->ConfigurationClose, + SafeHandleType.Listener => MsQuicApi.Api.ApiTable->ListenerClose, + SafeHandleType.Connection => MsQuicApi.Api.ApiTable->ConnectionClose, + SafeHandleType.Stream => MsQuicApi.Api.ApiTable->StreamClose, + _ => throw new ArgumentException($"Unexpected value: {safeHandleType}", nameof(safeHandleType)) + }, + safeHandleType) { } + protected override bool ReleaseHandle() { - _releaseAction(QuicHandle); + QUIC_HANDLE* quicHandle = QuicHandle; SetHandle(IntPtr.Zero); + _releaseAction(quicHandle); if (NetEventSource.Log.IsEnabled()) { @@ -77,8 +92,8 @@ internal sealed class MsQuicContextSafeHandle : MsQuicSafeHandle /// private readonly MsQuicSafeHandle? _parent; - public unsafe MsQuicContextSafeHandle(QUIC_HANDLE* handle, GCHandle context, delegate* unmanaged[Cdecl] releaseAction, SafeHandleType safeHandleType, MsQuicSafeHandle? parent = null) - : base(handle, releaseAction, safeHandleType) + public unsafe MsQuicContextSafeHandle(QUIC_HANDLE* handle, GCHandle context, SafeHandleType safeHandleType, MsQuicSafeHandle? parent = null) + : base(handle, safeHandleType) { _context = context; if (parent is not null) diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs index 3aa439a25af03a..83073bcfab5022 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs @@ -179,13 +179,13 @@ private unsafe QuicConnection() try { QUIC_HANDLE* handle; - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ConnectionOpen( - MsQuicApi.Api.Registration.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ConnectionOpen( + MsQuicApi.Api.Registration, &NativeCallback, (void*)GCHandle.ToIntPtr(context), &handle), "ConnectionOpen failed"); - _handle = new MsQuicContextSafeHandle(handle, context, MsQuicApi.Api.ApiTable->ConnectionClose, SafeHandleType.Connection); + _handle = new MsQuicContextSafeHandle(handle, context, SafeHandleType.Connection); } catch { @@ -204,12 +204,12 @@ internal unsafe QuicConnection(QUIC_HANDLE* handle, QUIC_NEW_CONNECTION_INFO* in GCHandle context = GCHandle.Alloc(this, GCHandleType.Weak); try { + _handle = new MsQuicContextSafeHandle(handle, context, SafeHandleType.Connection); delegate* unmanaged[Cdecl] nativeCallback = &NativeCallback; - MsQuicApi.Api.ApiTable->SetCallbackHandler( - handle, + MsQuicApi.Api.SetCallbackHandler( + _handle, nativeCallback, (void*)GCHandle.ToIntPtr(context)); - _handle = new MsQuicContextSafeHandle(handle, context, MsQuicApi.Api.ApiTable->ConnectionClose, SafeHandleType.Connection); } catch { @@ -294,9 +294,9 @@ private async ValueTask FinishConnectAsync(QuicClientConnectionOptions options, { unsafe { - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ConnectionStart( - _handle.QuicHandle, - _configuration.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ConnectionStart( + _handle, + _configuration, (ushort)addressFamily, (sbyte*)targetHostPtr, (ushort)port), @@ -334,9 +334,9 @@ internal ValueTask FinishHandshakeAsync(QuicServerConnectionOptions options, str unsafe { - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ConnectionSetConfiguration( - _handle.QuicHandle, - _configuration.QuicHandle), + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ConnectionSetConfiguration( + _handle, + _configuration), "ConnectionSetConfiguration failed"); } } @@ -392,6 +392,7 @@ public async ValueTask AcceptInboundStreamAsync(CancellationToken ca throw new InvalidOperationException(SR.net_quic_accept_not_allowed); } + GCHandle keepObject = GCHandle.Alloc(this); try { return await _acceptQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false); @@ -401,6 +402,10 @@ public async ValueTask AcceptInboundStreamAsync(CancellationToken ca ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); throw; } + finally + { + keepObject.Free(); + } } /// @@ -425,8 +430,8 @@ public ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken { unsafe { - MsQuicApi.Api.ApiTable->ConnectionShutdown( - _handle.QuicHandle, + MsQuicApi.Api.ConnectionShutdown( + _handle, QUIC_CONNECTION_SHUTDOWN_FLAGS.NONE, (ulong)errorCode); } @@ -469,8 +474,8 @@ private unsafe int HandleEventShutdownInitiatedByPeer(ref SHUTDOWN_INITIATED_BY_ } private unsafe int HandleEventShutdownComplete(ref SHUTDOWN_COMPLETE_DATA data) { - _shutdownTcs.TrySetResult(); _acceptQueue.Writer.TryComplete(ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetOperationAbortedException())); + _shutdownTcs.TrySetResult(); return QUIC_STATUS_SUCCESS; } private unsafe int HandleEventLocalAddressChanged(ref LOCAL_ADDRESS_CHANGED_DATA data) @@ -577,8 +582,8 @@ public async ValueTask DisposeAsync() { unsafe { - MsQuicApi.Api.ApiTable->ConnectionShutdown( - _handle.QuicHandle, + MsQuicApi.Api.ConnectionShutdown( + _handle, QUIC_CONNECTION_SHUTDOWN_FLAGS.NONE, (ulong)_defaultCloseErrorCode); } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs index 11c4d77731e033..a99e82159eceb4 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs @@ -106,13 +106,13 @@ private unsafe QuicListener(QuicListenerOptions options) try { QUIC_HANDLE* handle; - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ListenerOpen( - MsQuicApi.Api.Registration.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ListenerOpen( + MsQuicApi.Api.Registration, &NativeCallback, (void*)GCHandle.ToIntPtr(context), &handle), "ListenerOpen failed"); - _handle = new MsQuicContextSafeHandle(handle, context, MsQuicApi.Api.ApiTable->ListenerClose, SafeHandleType.Listener); + _handle = new MsQuicContextSafeHandle(handle, context, SafeHandleType.Listener); } catch { @@ -135,8 +135,8 @@ private unsafe QuicListener(QuicListenerOptions options) // Using the Unspecified family makes MsQuic handle connections from all IP addresses. address.Family = QUIC_ADDRESS_FAMILY_UNSPEC; } - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ListenerStart( - _handle.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ListenerStart( + _handle, alpnBuffers.Buffers, (uint)alpnBuffers.Count, &address), @@ -162,6 +162,7 @@ public async ValueTask AcceptConnectionAsync(CancellationToken c { ObjectDisposedException.ThrowIf(_disposed == 1, this); + GCHandle keepObject = GCHandle.Alloc(this); try { PendingConnection pendingConnection = await _acceptQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false); @@ -175,6 +176,10 @@ public async ValueTask AcceptConnectionAsync(CancellationToken c ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); throw; } + finally + { + keepObject.Free(); + } } private unsafe int HandleEventNewConnection(ref NEW_CONNECTION_DATA data) @@ -261,7 +266,7 @@ public async ValueTask DisposeAsync() { unsafe { - MsQuicApi.Api.ApiTable->ListenerStop(_handle.QuicHandle); + MsQuicApi.Api.ListenerStop(_handle); } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs index c11f3029a2136c..4c2633963477e9 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs @@ -70,9 +70,18 @@ public sealed partial class QuicStream { CancellationAction = target => { - if (target is QuicStream stream) + try { - stream.Abort(QuicAbortDirection.Read, stream._defaultErrorCode); + if (target is QuicStream stream) + { + stream.Abort(QuicAbortDirection.Read, stream._defaultErrorCode); + } + } + catch (ObjectDisposedException) + { + // We collided with a Dispose in another thread. This can happen + // when using CancellationTokenSource.CancelAfter. + // Ignore the exception } } }; @@ -83,13 +92,23 @@ public sealed partial class QuicStream { CancellationAction = target => { - if (target is QuicStream stream) + try + { + if (target is QuicStream stream) + { + stream.Abort(QuicAbortDirection.Write, stream._defaultErrorCode); + } + } + catch (ObjectDisposedException) { - stream.Abort(QuicAbortDirection.Write, stream._defaultErrorCode); + // We collided with a Dispose in another thread. This can happen + // when using CancellationTokenSource.CancelAfter. + // Ignore the exception } } }; private MsQuicBuffers _sendBuffers = new MsQuicBuffers(); + private object _sendBuffersLock = new object(); private readonly long _defaultErrorCode; @@ -141,14 +160,14 @@ internal unsafe QuicStream(MsQuicContextSafeHandle connectionHandle, QuicStreamT try { QUIC_HANDLE* handle; - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->StreamOpen( - connectionHandle.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.StreamOpen( + connectionHandle, type == QuicStreamType.Unidirectional ? QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL : QUIC_STREAM_OPEN_FLAGS.NONE, &NativeCallback, (void*)GCHandle.ToIntPtr(context), &handle), "StreamOpen failed"); - _handle = new MsQuicContextSafeHandle(handle, context, MsQuicApi.Api.ApiTable->StreamClose, SafeHandleType.Stream, connectionHandle); + _handle = new MsQuicContextSafeHandle(handle, context, SafeHandleType.Stream, connectionHandle); } catch { @@ -179,12 +198,12 @@ internal unsafe QuicStream(MsQuicContextSafeHandle connectionHandle, QUIC_HANDLE GCHandle context = GCHandle.Alloc(this, GCHandleType.Weak); try { + _handle = new MsQuicContextSafeHandle(handle, context, SafeHandleType.Stream, connectionHandle); delegate* unmanaged[Cdecl] nativeCallback = &NativeCallback; - MsQuicApi.Api.ApiTable->SetCallbackHandler( - handle, + MsQuicApi.Api.SetCallbackHandler( + _handle, nativeCallback, (void*)GCHandle.ToIntPtr(context)); - _handle = new MsQuicContextSafeHandle(handle, context, MsQuicApi.Api.ApiTable->StreamClose, SafeHandleType.Stream, connectionHandle); } catch { @@ -220,8 +239,8 @@ internal ValueTask StartAsync(CancellationToken cancellationToken = default) { unsafe { - int status = MsQuicApi.Api.ApiTable->StreamStart( - _handle.QuicHandle, + int status = MsQuicApi.Api.StreamStart( + _handle, QUIC_STREAM_START_FLAGS.SHUTDOWN_ON_FAIL | QUIC_STREAM_START_FLAGS.INDICATE_PEER_ACCEPT); if (ThrowHelper.TryGetStreamExceptionForMsQuicStatus(status, out Exception? exception)) { @@ -297,8 +316,8 @@ public override async ValueTask ReadAsync(Memory buffer, Cancellation { unsafe { - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->StreamReceiveSetEnabled( - _handle.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.StreamReceiveSetEnabled( + _handle, 1), "StreamReceivedSetEnabled failed"); } @@ -360,19 +379,34 @@ public ValueTask WriteAsync(ReadOnlyMemory buffer, bool completeWrites, Ca return valueTask; } - _sendBuffers.Initialize(buffer); - unsafe + lock (_sendBuffersLock) { - int status = MsQuicApi.Api.ApiTable->StreamSend( - _handle.QuicHandle, - _sendBuffers.Buffers, - (uint)_sendBuffers.Count, - completeWrites ? QUIC_SEND_FLAGS.FIN : QUIC_SEND_FLAGS.NONE, - null); - if (ThrowHelper.TryGetStreamExceptionForMsQuicStatus(status, out Exception? exception)) + ObjectDisposedException.ThrowIf(_disposed == 1, this); // TODO: valueTask is left unobserved + unsafe { - _sendBuffers.Reset(); - _sendTcs.TrySetException(exception, final: true); + if (_sendBuffers.Count > 0 && _sendBuffers.Buffers[0].Buffer != null) + { + // _sendBuffers are not reset, meaning SendComplete for the previous WriteAsync call didn't arrive yet. + // In case of cancellation, the task from _sendTcs is finished before the aborting. It is technically possible for subsequent + // WriteAsync to grab the next task from _sendTcs and start executing before SendComplete event occurs for the previous (canceled) write. + // This is not an "invalid nested call", because the previous task has finished. Best guess is to mimic OperationAborted as it will be from Abort + // that would execute soon enough, if not already. Not final, because Abort should be the one to set final exception. + _sendTcs.TrySetException(ThrowHelper.GetOperationAbortedException(SR.net_quic_writing_aborted), final: false); + return valueTask; + } + + _sendBuffers.Initialize(buffer); + int status = MsQuicApi.Api.StreamSend( + _handle, + _sendBuffers.Buffers, + (uint)_sendBuffers.Count, + completeWrites ? QUIC_SEND_FLAGS.FIN : QUIC_SEND_FLAGS.NONE, + null); + if (ThrowHelper.TryGetStreamExceptionForMsQuicStatus(status, out Exception? exception)) + { + _sendBuffers.Reset(); + _sendTcs.TrySetException(exception, final: true); + } } } @@ -419,8 +453,8 @@ public void Abort(QuicAbortDirection abortDirection, long errorCode) unsafe { - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->StreamShutdown( - _handle.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.StreamShutdown( + _handle, flags, (ulong)errorCode), "StreamShutdown failed"); @@ -442,8 +476,8 @@ public void CompleteWrites() { unsafe { - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->StreamShutdown( - _handle.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.StreamShutdown( + _handle, QUIC_STREAM_SHUTDOWN_FLAGS.GRACEFUL, default), "StreamShutdown failed"); @@ -475,8 +509,8 @@ private unsafe int HandleEventStartComplete(ref START_COMPLETE data) private unsafe int HandleEventReceive(ref RECEIVE data) { ulong totalCopied = (ulong)_receiveBuffers.CopyFrom( - new ReadOnlySpan(data.Buffers, (int) data.BufferCount), - (int) data.TotalBufferLength, + new ReadOnlySpan(data.Buffers, (int)data.BufferCount), + (int)data.TotalBufferLength, data.Flags.HasFlag(QUIC_RECEIVE_FLAGS.FIN)); if (totalCopied < data.TotalBufferLength) { @@ -490,7 +524,12 @@ private unsafe int HandleEventReceive(ref RECEIVE data) } private unsafe int HandleEventSendComplete(ref SEND_COMPLETE data) { - _sendBuffers.Reset(); + // In case of cancellation, the task from _sendTcs is finished before the aborting. It is technically possible for subsequent WriteAsync to grab the next task + // from _sendTcs and start executing before SendComplete event occurs for the previous (canceled) write + lock (_sendBuffersLock) + { + _sendBuffers.Reset(); + } if (data.Canceled == 0) { _sendTcs.TrySetResult(); @@ -653,13 +692,16 @@ public override async ValueTask DisposeAsync() await valueTask.ConfigureAwait(false); _handle.Dispose(); - // TODO: memory leak if not disposed - _sendBuffers.Dispose(); + lock (_sendBuffersLock) + { + // TODO: memory leak if not disposed + _sendBuffers.Dispose(); + } unsafe void StreamShutdown(QUIC_STREAM_SHUTDOWN_FLAGS flags, long errorCode) { - int status = MsQuicApi.Api.ApiTable->StreamShutdown( - _handle.QuicHandle, + int status = MsQuicApi.Api.StreamShutdown( + _handle, flags, (ulong)errorCode); if (StatusFailed(status)) diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs index 3831c8e6e9be9d..a12a14e8c9eb0f 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs @@ -336,5 +336,33 @@ public async Task Connect_PeerCertificateDisposed(bool useGetter) } peerCertificate.Dispose(); } + + [Fact] + public async Task Connection_AwaitsStream_ConnectionSurvivesGC() + { + const byte data = 0xDC; + + TaskCompletionSource listenerEndpointTcs = new TaskCompletionSource(); + await Task.WhenAll( + Task.Run(async () => + { + await using var listener = await CreateQuicListener(); + listenerEndpointTcs.SetResult(listener.LocalEndPoint); + await using var connection = await listener.AcceptConnectionAsync(); + await using var stream = await connection.AcceptInboundStreamAsync(); + var buffer = new byte[1]; + Assert.Equal(1, await stream.ReadAsync(buffer)); + Assert.Equal(data, buffer[0]); + }).WaitAsync(TimeSpan.FromSeconds(5)), + Task.Run(async () => + { + var endpoint = await listenerEndpointTcs.Task; + await using var connection = await CreateQuicConnection(endpoint); + await Task.Delay(TimeSpan.FromSeconds(0.5)); + GC.Collect(); + await using var stream = await connection.OpenOutboundStreamAsync(QuicStreamType.Unidirectional); + await stream.WriteAsync(new byte[1] { data }, completeWrites: true); + }).WaitAsync(TimeSpan.FromSeconds(5))); + } } } diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs index aa78c72c0b6e4b..643436a0d3f0f6 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs @@ -106,7 +106,7 @@ public async Task TwoListenersOnSamePort_DisjointAlpn_Success() QuicListenerOptions listenerOptions = CreateQuicListenerOptions(); listenerOptions.ListenEndPoint = listener1.LocalEndPoint; listenerOptions.ApplicationProtocols[0] = new SslApplicationProtocol("someprotocol"); - listenerOptions.ConnectionOptionsCallback = (_, _, _) => + listenerOptions.ConnectionOptionsCallback = (_, _, _) => { var options = CreateQuicServerOptions(); options.ServerAuthenticationOptions.ApplicationProtocols[0] = listenerOptions.ApplicationProtocols[0]; @@ -144,5 +144,27 @@ public async Task TwoListenersOnSamePort_SameAlpn_Throws() // await AssertThrowsQuicExceptionAsync(QuicError.InternalError, async () => await CreateQuicListener(listener.LocalEndPoint)); } + + [Fact] + public async Task Listener_AwaitsConnection_ListenerSurvivesGC() + { + TaskCompletionSource listenerEndpointTcs = new TaskCompletionSource(); + await Task.WhenAll( + Task.Run(async () => + { + await using var listener = await CreateQuicListener(); + listenerEndpointTcs.SetResult(listener.LocalEndPoint); + var connection = await listener.AcceptConnectionAsync(); + await connection.DisposeAsync(); + }).WaitAsync(TimeSpan.FromSeconds(5)), + Task.Run(async () => + { + var endpoint = await listenerEndpointTcs.Task; + await Task.Delay(TimeSpan.FromSeconds(0.5)); + GC.Collect(); + var connection = await CreateQuicConnection(endpoint); + await connection.DisposeAsync(); + }).WaitAsync(TimeSpan.FromSeconds(5))); + } } } diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs index 5dafe8f6266a32..762fc4230abc0e 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs @@ -102,7 +102,7 @@ await WhenAllOrAnyFailed( } catch (Exception ex) { - _output?.WriteLine($"Failed to {ex.Message}"); + _output?.WriteLine($"Failed to connect: {ex.Message}"); throw; } })); @@ -153,14 +153,5 @@ public override void Dispose() } } } - - [OuterLoop("May take several seconds")] - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] - [ActiveIssue("https://github.com/dotnet/runtime/issues/73377")] - public override Task Parallel_ReadWriteMultipleStreamsConcurrently() - { - return Task.CompletedTask; - } } } diff --git a/src/libraries/System.Net.Requests/tests/System.Net.Requests.Tests.csproj b/src/libraries/System.Net.Requests/tests/System.Net.Requests.Tests.csproj index 868d3582b442a1..8e509bd356a8d3 100644 --- a/src/libraries/System.Net.Requests/tests/System.Net.Requests.Tests.csproj +++ b/src/libraries/System.Net.Requests/tests/System.Net.Requests.Tests.csproj @@ -9,6 +9,8 @@ $(NoWarn);SYSLIB0014 true + true + true @@ -26,6 +28,8 @@ Link="Common\System\Net\Configuration.Http.cs" /> + EnsureFullTlsFrameAsync(CancellationTok if (frameSize < int.MaxValue) { + // make sure we have space for the whole frame _buffer.EnsureAvailableSpace(frameSize - _buffer.EncryptedLength); } + else + { + // move existing data to the beginning of the buffer (they will + // be couple of bytes only, otherwise we would have entire + // header and know exact size) + _buffer.EnsureAvailableSpace(_buffer.Capacity - _buffer.EncryptedLength); + } while (_buffer.EncryptedLength < frameSize) { 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 fdc98705d426b2..20574597af706a 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 @@ -73,11 +73,12 @@ partial void SetNoOcspFetch(bool noOcspFetch) _staplingForbidden = noOcspFetch; } - partial void AddRootCertificate(X509Certificate2? rootCertificate) + partial void AddRootCertificate(X509Certificate2? rootCertificate, ref bool transferredOwnership) { if (IntermediateCertificates.Length == 0) { _ca = rootCertificate; + transferredOwnership = true; } else { @@ -197,6 +198,17 @@ partial void AddRootCertificate(X509Certificate2? rootCertificate) IntPtr subject = Certificate.Handle; IntPtr issuer = caCert.Handle; + Debug.Assert(subject != 0); + Debug.Assert(issuer != 0); + + // This should not happen - but in the event that it does, we can't give null pointers when building the + // request, so skip stapling, and set it as forbidden so we don't bother looking for new stapled responses + // in the future. + if (subject == 0 || issuer == 0) + { + _staplingForbidden = true; + return null; + } using (SafeOcspRequestHandle ocspRequest = Interop.Crypto.X509BuildOcspRequest(subject, issuer)) { 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 9ea3971f0444b5..515794271ccb55 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 @@ -96,8 +96,13 @@ internal static SslStreamCertificateContext Create( // Dispose the copy of the target cert. chain.ChainElements[0].Certificate.Dispose(); - // Dispose the last cert, if we didn't include it. - for (int i = count + 1; i < chain.ChainElements.Count; i++) + // Dispose of the certificates that we do not need. If we are holding on to the root, + // don't dispose of it. + int stopDisposingChainPosition = root is null ? + chain.ChainElements.Count : + chain.ChainElements.Count - 1; + + for (int i = count + 1; i < stopDisposingChainPosition; i++) { chain.ChainElements[i].Certificate.Dispose(); } @@ -109,12 +114,19 @@ internal static SslStreamCertificateContext Create( // On Linux, AddRootCertificate will start a background download of an OCSP response, // unless this context was built "offline", or this came from the internal Create(X509Certificate2) ctx.SetNoOcspFetch(offline || noOcspFetch); - ctx.AddRootCertificate(root); + + bool transferredOwnership = false; + ctx.AddRootCertificate(root, ref transferredOwnership); + + if (!transferredOwnership) + { + root?.Dispose(); + } return ctx; } - partial void AddRootCertificate(X509Certificate2? rootCertificate); + partial void AddRootCertificate(X509Certificate2? rootCertificate, ref bool transferredOwnership); partial void SetNoOcspFetch(bool noOcspFetch); internal SslStreamCertificateContext Duplicate() diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationRemoteServer.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationRemoteServer.cs index ade1bcb23a940f..7b536e6e60738c 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationRemoteServer.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationRemoteServer.cs @@ -96,6 +96,7 @@ public async Task DefaultConnect_EndToEnd_Ok(string host) [InlineData(true)] [InlineData(false)] [SkipOnPlatform(TestPlatforms.Android, "The invalid certificate is rejected by Android and the .NET validation code isn't reached")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/70981", TestPlatforms.OSX)] public Task ConnectWithRevocation_WithCallback(bool checkRevocation) { X509RevocationMode mode = checkRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck; @@ -105,9 +106,11 @@ public Task ConnectWithRevocation_WithCallback(bool checkRevocation) [PlatformSpecific(TestPlatforms.Linux)] [ConditionalTheory] [OuterLoop("Subject to system load race conditions")] - [InlineData(false)] - [InlineData(true)] - public Task ConnectWithRevocation_StapledOcsp(bool offlineContext) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public Task ConnectWithRevocation_StapledOcsp(bool offlineContext, bool noIntermediates) { // Offline will only work if // a) the revocation has been checked recently enough that it is cached, or @@ -115,12 +118,13 @@ public Task ConnectWithRevocation_StapledOcsp(bool offlineContext) // // At high load, the server's background fetch might not have completed before // this test runs. - return ConnectWithRevocation_WithCallback_Core(X509RevocationMode.Offline, offlineContext); + return ConnectWithRevocation_WithCallback_Core(X509RevocationMode.Offline, offlineContext, noIntermediates); } [Fact] [PlatformSpecific(TestPlatforms.Linux)] [ActiveIssue("https://github.com/dotnet/runtime/issues/70981", typeof(PlatformDetection), nameof(PlatformDetection.IsDebian10))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/70981", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public Task ConnectWithRevocation_ServerCertWithoutContext_NoStapledOcsp() { // Offline will only work if @@ -190,7 +194,8 @@ static bool CertificateValidationCallback( private async Task ConnectWithRevocation_WithCallback_Core( X509RevocationMode revocationMode, - bool? offlineContext = false) + bool? offlineContext = false, + bool noIntermediates = false) { string offlinePart = offlineContext.HasValue ? offlineContext.GetValueOrDefault().ToString().ToLower() : "null"; string serverName = $"{revocationMode.ToString().ToLower()}.{offlinePart}.server.example"; @@ -201,13 +206,15 @@ private async Task ConnectWithRevocation_WithCallback_Core( PkiOptions.EndEntityRevocationViaOcsp | PkiOptions.CrlEverywhere, out RevocationResponder responder, out CertificateAuthority rootAuthority, - out CertificateAuthority intermediateAuthority, + out CertificateAuthority[] intermediateAuthorities, out X509Certificate2 serverCert, + intermediateAuthorityCount: noIntermediates ? 0 : 1, subjectName: serverName, keySize: 2048, extensions: TestHelper.BuildTlsServerCertExtensions(serverName)); - X509Certificate2 issuerCert = intermediateAuthority.CloneIssuerCert(); + CertificateAuthority issuingAuthority = noIntermediates ? rootAuthority : intermediateAuthorities[0]; + X509Certificate2 issuerCert = issuingAuthority.CloneIssuerCert(); X509Certificate2 rootCert = rootAuthority.CloneIssuerCert(); SslClientAuthenticationOptions clientOpts = new SslClientAuthenticationOptions @@ -241,71 +248,80 @@ private async Task ConnectWithRevocation_WithCallback_Core( serverCert = temp; } - await using (clientStream) - await using (serverStream) - using (responder) - using (rootAuthority) - using (intermediateAuthority) - using (serverCert) - using (issuerCert) - using (rootCert) - await using (SslStream tlsClient = new SslStream(clientStream)) - await using (SslStream tlsServer = new SslStream(serverStream)) + try { - intermediateAuthority.Revoke(serverCert, serverCert.NotBefore); - - SslServerAuthenticationOptions serverOpts = new SslServerAuthenticationOptions(); - - if (offlineContext.HasValue) + await using (clientStream) + await using (serverStream) + using (responder) + using (rootAuthority) + using (serverCert) + using (issuerCert) + using (rootCert) + await using (SslStream tlsClient = new SslStream(clientStream)) + await using (SslStream tlsServer = new SslStream(serverStream)) { - serverOpts.ServerCertificateContext = SslStreamCertificateContext.Create( - serverCert, - new X509Certificate2Collection(issuerCert), - offlineContext.GetValueOrDefault()); + issuingAuthority.Revoke(serverCert, serverCert.NotBefore); - if (revocationMode == X509RevocationMode.Offline) + SslServerAuthenticationOptions serverOpts = new SslServerAuthenticationOptions(); + + if (offlineContext.HasValue) { - if (offlineContext.GetValueOrDefault(false)) - { - // Add a delay just to show we're not winning because of race conditions. - await Task.Delay(200); - } - else + serverOpts.ServerCertificateContext = SslStreamCertificateContext.Create( + serverCert, + new X509Certificate2Collection(issuerCert), + offlineContext.GetValueOrDefault()); + + if (revocationMode == X509RevocationMode.Offline) { - if (!OperatingSystem.IsLinux()) + if (offlineContext.GetValueOrDefault(false)) { - throw new InvalidOperationException( - "This test configuration uses reflection and is only defined for Linux."); + // Add a delay just to show we're not winning because of race conditions. + await Task.Delay(200); } - - FieldInfo pendingDownloadTaskField = typeof(SslStreamCertificateContext).GetField( - "_pendingDownload", - BindingFlags.Instance | BindingFlags.NonPublic); - - if (pendingDownloadTaskField is null) + else { - throw new InvalidOperationException("Cannot find the pending download field."); - } - - Task download = (Task)pendingDownloadTaskField.GetValue(serverOpts.ServerCertificateContext); - - // If it's null, it should mean it has already finished. If not, it might not have. - if (download is not null) - { - await download; + if (!OperatingSystem.IsLinux()) + { + throw new InvalidOperationException( + "This test configuration uses reflection and is only defined for Linux."); + } + + FieldInfo pendingDownloadTaskField = typeof(SslStreamCertificateContext).GetField( + "_pendingDownload", + BindingFlags.Instance | BindingFlags.NonPublic); + + if (pendingDownloadTaskField is null) + { + throw new InvalidOperationException("Cannot find the pending download field."); + } + + Task download = (Task)pendingDownloadTaskField.GetValue(serverOpts.ServerCertificateContext); + + // If it's null, it should mean it has already finished. If not, it might not have. + if (download is not null) + { + await download; + } } } } + else + { + serverOpts.ServerCertificate = serverCert; + } + + Task serverTask = tlsServer.AuthenticateAsServerAsync(serverOpts); + Task clientTask = tlsClient.AuthenticateAsClientAsync(clientOpts); + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout(clientTask, serverTask); } - else + } + finally + { + foreach (CertificateAuthority intermediateAuthority in intermediateAuthorities) { - serverOpts.ServerCertificate = serverCert; + intermediateAuthority.Dispose(); } - - Task serverTask = tlsServer.AuthenticateAsServerAsync(serverOpts); - Task clientTask = tlsClient.AuthenticateAsClientAsync(clientOpts); - - await TestConfiguration.WhenAllOrAnyFailedWithTimeout(clientTask, serverTask); } static bool CertificateValidationCallback( diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamMutualAuthenticationTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamMutualAuthenticationTest.cs index 2332a00e1779c2..fc34a29fdc7ba8 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamMutualAuthenticationTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamMutualAuthenticationTest.cs @@ -4,6 +4,7 @@ using System.IO; using System.Threading.Tasks; using System.Net.Test.Common; +using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using Xunit; @@ -16,11 +17,13 @@ public class SslStreamMutualAuthenticationTest : IDisposable { private readonly X509Certificate2 _clientCertificate; private readonly X509Certificate2 _serverCertificate; + private readonly X509Certificate2 _selfSignedCertificate; public SslStreamMutualAuthenticationTest() { _serverCertificate = Configuration.Certificates.GetServerCertificate(); _clientCertificate = Configuration.Certificates.GetClientCertificate(); + _selfSignedCertificate = Configuration.Certificates.GetSelfSignedServerCertificate(); } public void Dispose() @@ -80,6 +83,171 @@ public async Task SslStream_RequireClientCert_IsMutuallyAuthenticated_ReturnsTru } } + [ClassData(typeof(SslProtocolSupport.SupportedSslProtocolsTestData))] + [PlatformSpecific(TestPlatforms.Linux)] // https://github.com/dotnet/runtime/issues/65563 + [Theory] + public async Task SslStream_ResumedSessionsClientCollection_IsMutuallyAuthenticatedCorrect( + SslProtocols protocol) + { + var clientOptions = new SslClientAuthenticationOptions + { + EnabledSslProtocols = protocol, + RemoteCertificateValidationCallback = delegate { return true; }, + TargetHost = Guid.NewGuid().ToString("N") + }; + + // Create options with certificate context so TLS resume is possible on Linux + var serverOptions = new SslServerAuthenticationOptions + { + ClientCertificateRequired = true, + ServerCertificateContext = SslStreamCertificateContext.Create(_serverCertificate, null), + RemoteCertificateValidationCallback = delegate { return true; }, + EnabledSslProtocols = protocol + }; + + for (int i = 0; i < 5; i++) + { + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + using (client) + using (server) + { + bool expectMutualAuthentication = (i % 2) == 0; + + clientOptions.ClientCertificates = expectMutualAuthentication ? new X509CertificateCollection() { _clientCertificate } : null; + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions), + server.AuthenticateAsServerAsync(serverOptions)); + + // mutual authentication should only be set if client set certificate + Assert.Equal(expectMutualAuthentication, server.IsMutuallyAuthenticated); + Assert.Equal(expectMutualAuthentication, client.IsMutuallyAuthenticated); + + if (expectMutualAuthentication) + { + Assert.NotNull(server.RemoteCertificate); + } + else + { + Assert.Null(server.RemoteCertificate); + } + }; + } + } + + [ClassData(typeof(SslProtocolSupport.SupportedSslProtocolsTestData))] + [PlatformSpecific(TestPlatforms.Linux)] // https://github.com/dotnet/runtime/issues/65563 + [Theory] + public async Task SslStream_ResumedSessionsCallbackSet_IsMutuallyAuthenticatedCorrect( + SslProtocols protocol) + { + var clientOptions = new SslClientAuthenticationOptions + { + EnabledSslProtocols = protocol, + RemoteCertificateValidationCallback = delegate { return true; }, + TargetHost = Guid.NewGuid().ToString("N") + }; + + // Create options with certificate context so TLS resume is possible on Linux + var serverOptions = new SslServerAuthenticationOptions + { + ClientCertificateRequired = true, + ServerCertificateContext = SslStreamCertificateContext.Create(_serverCertificate, null), + RemoteCertificateValidationCallback = delegate { return true; }, + EnabledSslProtocols = protocol + }; + + for (int i = 0; i < 5; i++) + { + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + using (client) + using (server) + { + bool expectMutualAuthentication = (i % 2) == 0; + + clientOptions.LocalCertificateSelectionCallback = (s, t, l, r, a) => + { + return expectMutualAuthentication ? _clientCertificate : null; + }; + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions), + server.AuthenticateAsServerAsync(serverOptions)); + + // mutual authentication should only be set if client set certificate + Assert.Equal(expectMutualAuthentication, server.IsMutuallyAuthenticated); + Assert.Equal(expectMutualAuthentication, client.IsMutuallyAuthenticated); + + if (expectMutualAuthentication) + { + Assert.NotNull(server.RemoteCertificate); + } + else + { + Assert.Null(server.RemoteCertificate); + } + }; + } + } + + [ClassData(typeof(SslProtocolSupport.SupportedSslProtocolsTestData))] + [PlatformSpecific(TestPlatforms.Linux)] // https://github.com/dotnet/runtime/issues/65563 + [Theory] + public async Task SslStream_ResumedSessionsCallbackMaybeSet_IsMutuallyAuthenticatedCorrect( + SslProtocols protocol) + { + var clientOptions = new SslClientAuthenticationOptions + { + EnabledSslProtocols = protocol, + RemoteCertificateValidationCallback = delegate { return true; }, + TargetHost = Guid.NewGuid().ToString("N") + }; + + // Create options with certificate context so TLS resume is possible on Linux + var serverOptions = new SslServerAuthenticationOptions + { + ClientCertificateRequired = true, + ServerCertificateContext = SslStreamCertificateContext.Create(_serverCertificate, null), + RemoteCertificateValidationCallback = delegate { return true; }, + EnabledSslProtocols = protocol + }; + + for (int i = 0; i < 5; i++) + { + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + using (client) + using (server) + { + bool expectMutualAuthentication = (i % 2) == 0; + + if (expectMutualAuthentication) + { + clientOptions.LocalCertificateSelectionCallback = (s, t, l, r, a) => _clientCertificate; + } + else + { + clientOptions.LocalCertificateSelectionCallback = null; + } + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions), + server.AuthenticateAsServerAsync(serverOptions)); + + // mutual authentication should only be set if client set certificate + Assert.Equal(expectMutualAuthentication, server.IsMutuallyAuthenticated); + Assert.Equal(expectMutualAuthentication, client.IsMutuallyAuthenticated); + + if (expectMutualAuthentication) + { + Assert.NotNull(server.RemoteCertificate); + } + else + { + Assert.Null(server.RemoteCertificate); + } + }; + } + } + private static bool AllowAnyCertificate( object sender, X509Certificate certificate, diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs index 8704cfb77ec671..4cccf0f0fd70c3 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs @@ -63,7 +63,7 @@ public static IEnumerable SslStream_StreamToStream_Authentication_Succ using (X509Certificate2 clientCert = Configuration.Certificates.GetClientCertificate()) { yield return new object[] { new X509Certificate2(serverCert), new X509Certificate2(clientCert) }; - yield return new object[] { new X509Certificate(serverCert.Export(X509ContentType.Pfx)), new X509Certificate(clientCert.Export(X509ContentType.Pfx)) }; + yield return new object[] { new X509Certificate(serverCert.Export(X509ContentType.Pfx), (string)null, X509KeyStorageFlags.DefaultKeySet), new X509Certificate(clientCert.Export(X509ContentType.Pfx), (string)null, X509KeyStorageFlags.DefaultKeySet) }; } } diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj b/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj index e00fd263ac15e6..7c3d432f34cb46 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj @@ -7,6 +7,8 @@ true true + true + true @@ -73,6 +75,8 @@ Link="Common\System\Net\HttpsTestClient.cs" /> + if it has completed. Another delegate if OnCompleted was called before the operation could complete, /// in which case it's the delegate to invoke when the operation does complete. /// - private Action? _continuation; + private volatile Action? _continuation; private ExecutionContext? _executionContext; private object? _scheduler; /// Current token value given to a ValueTask and then verified against the value it passes back to us. @@ -1007,7 +1007,7 @@ protected override void OnCompleted(SocketAsyncEventArgs _) /// This instance. public ValueTask AcceptAsync(Socket socket, CancellationToken cancellationToken) { - Debug.Assert(Volatile.Read(ref _continuation) == null, "Expected null continuation to indicate reserved for use"); + Debug.Assert(_continuation == null, "Expected null continuation to indicate reserved for use"); if (socket.AcceptAsync(this, cancellationToken)) { @@ -1031,7 +1031,7 @@ public ValueTask AcceptAsync(Socket socket, CancellationToken cancellati /// This instance. public ValueTask ReceiveAsync(Socket socket, CancellationToken cancellationToken) { - Debug.Assert(Volatile.Read(ref _continuation) == null, "Expected null continuation to indicate reserved for use"); + Debug.Assert(_continuation == null, "Expected null continuation to indicate reserved for use"); if (socket.ReceiveAsync(this, cancellationToken)) { @@ -1051,7 +1051,7 @@ public ValueTask ReceiveAsync(Socket socket, CancellationToken cancellation public ValueTask ReceiveFromAsync(Socket socket, CancellationToken cancellationToken) { - Debug.Assert(Volatile.Read(ref _continuation) == null, "Expected null continuation to indicate reserved for use"); + Debug.Assert(_continuation == null, "Expected null continuation to indicate reserved for use"); if (socket.ReceiveFromAsync(this, cancellationToken)) { @@ -1072,7 +1072,7 @@ public ValueTask ReceiveFromAsync(Socket socket, Cancel public ValueTask ReceiveMessageFromAsync(Socket socket, CancellationToken cancellationToken) { - Debug.Assert(Volatile.Read(ref _continuation) == null, "Expected null continuation to indicate reserved for use"); + Debug.Assert(_continuation == null, "Expected null continuation to indicate reserved for use"); if (socket.ReceiveMessageFromAsync(this, cancellationToken)) { @@ -1097,7 +1097,7 @@ public ValueTask ReceiveMessageFromAsync(Socket /// This instance. public ValueTask SendAsync(Socket socket, CancellationToken cancellationToken) { - Debug.Assert(Volatile.Read(ref _continuation) == null, "Expected null continuation to indicate reserved for use"); + Debug.Assert(_continuation == null, "Expected null continuation to indicate reserved for use"); if (socket.SendAsync(this, cancellationToken)) { @@ -1117,7 +1117,7 @@ public ValueTask SendAsync(Socket socket, CancellationToken cancellationTok public ValueTask SendAsyncForNetworkStream(Socket socket, CancellationToken cancellationToken) { - Debug.Assert(Volatile.Read(ref _continuation) == null, "Expected null continuation to indicate reserved for use"); + Debug.Assert(_continuation == null, "Expected null continuation to indicate reserved for use"); if (socket.SendAsync(this, cancellationToken)) { @@ -1136,7 +1136,7 @@ public ValueTask SendAsyncForNetworkStream(Socket socket, CancellationToken canc public ValueTask SendPacketsAsync(Socket socket, CancellationToken cancellationToken) { - Debug.Assert(Volatile.Read(ref _continuation) == null, "Expected null continuation to indicate reserved for use"); + Debug.Assert(_continuation == null, "Expected null continuation to indicate reserved for use"); if (socket.SendPacketsAsync(this, cancellationToken)) { @@ -1155,7 +1155,7 @@ public ValueTask SendPacketsAsync(Socket socket, CancellationToken cancellationT public ValueTask SendToAsync(Socket socket, CancellationToken cancellationToken) { - Debug.Assert(Volatile.Read(ref _continuation) == null, "Expected null continuation to indicate reserved for use"); + Debug.Assert(_continuation == null, "Expected null continuation to indicate reserved for use"); if (socket.SendToAsync(this, cancellationToken)) { @@ -1175,7 +1175,7 @@ public ValueTask SendToAsync(Socket socket, CancellationToken cancellationT public ValueTask ConnectAsync(Socket socket) { - Debug.Assert(Volatile.Read(ref _continuation) == null, "Expected null continuation to indicate reserved for use"); + Debug.Assert(_continuation == null, "Expected null continuation to indicate reserved for use"); try { @@ -1201,7 +1201,7 @@ public ValueTask ConnectAsync(Socket socket) public ValueTask DisconnectAsync(Socket socket, CancellationToken cancellationToken) { - Debug.Assert(Volatile.Read(ref _continuation) == null, $"Expected null continuation to indicate reserved for use"); + Debug.Assert(_continuation == null, $"Expected null continuation to indicate reserved for use"); if (socket.DisconnectAsync(this, cancellationToken)) { diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs index 84cf302cb2b89e..c60d8d8ee12c34 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs @@ -307,7 +307,7 @@ internal unsafe SocketError DoOperationConnectEx(Socket socket, SafeSocketHandle handle, PtrSocketAddressBuffer, _socketAddress!.Size, - (IntPtr)((byte*)_singleBufferHandle.Pointer + _offset), + (IntPtr)(bufferPtr + _offset), _count, out int bytesTransferred, overlapped); diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs index d045b94061f4d1..8e1302e6c95a00 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs @@ -7,6 +7,7 @@ using Xunit; using Xunit.Abstractions; using Xunit.Sdk; +using System.Linq; namespace System.Net.Sockets.Tests { @@ -218,6 +219,58 @@ public ConnectTask(ITestOutputHelper output) : base(output) {} public sealed class ConnectEap : Connect { public ConnectEap(ITestOutputHelper output) : base(output) {} + + [Theory] + [PlatformSpecific(TestPlatforms.Windows)] + [InlineData(true)] + [InlineData(false)] + public async Task ConnectAsync_WithData_DataReceived(bool useArrayApi) + { + using Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + listener.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + IPEndPoint serverEp = (IPEndPoint)listener.LocalEndPoint!; + listener.Listen(); + + var serverTask = Task.Run(async () => + { + using Socket handler = await listener.AcceptAsync(); + using var cts = new CancellationTokenSource(TestSettings.PassingTestTimeout); + byte[] recvBuffer = new byte[6]; + int received = await handler.ReceiveAsync(recvBuffer, SocketFlags.None, cts.Token); + Assert.True(received == 4); + + recvBuffer.AsSpan(0, 4).SequenceEqual(new byte[] { 2, 3, 4, 5 }); + }); + + using var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + byte[] buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }; + + var mre = new ManualResetEventSlim(false); + var saea = new SocketAsyncEventArgs(); + saea.RemoteEndPoint = serverEp; + + // Slice the buffer to test the offset management: + if (useArrayApi) + { + saea.SetBuffer(buffer, 2, 4); + } + else + { + saea.SetBuffer(buffer.AsMemory(2, 4)); + } + + saea.Completed += (_, __) => mre.Set(); + + if (client.ConnectAsync(saea)) + { + Assert.True(mre.Wait(TestSettings.PassingTestTimeout), "Timed out while waiting for connection"); + } + + Assert.Equal(SocketError.Success, saea.SocketError); + + await serverTask; + } } public sealed class ConnectCancellableTask : Connect diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj b/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj index 0d7df7229dbe7a..884db1eea549e0 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj @@ -4,6 +4,7 @@ true $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser true + true 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 e76ae87717ad42..94b7e980745566 100644 --- a/src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs +++ b/src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.Runtime.Serialization; using System.Text.RegularExpressions; +using System.Threading; namespace System.Net { @@ -127,10 +128,9 @@ public bool UseDefaultCredentials private void UpdateRegexList() { - Regex[]? regexBypassList = null; if (_bypassList is ChangeTrackingArrayList bypassList) { - bypassList.IsChanged = false; + Regex[]? regexBypassList = null; if (bypassList.Count > 0) { regexBypassList = new Regex[bypassList.Count]; @@ -139,9 +139,14 @@ private void UpdateRegexList() regexBypassList[i] = new Regex((string)bypassList[i]!, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } } - } - _regexBypassList = regexBypassList; + _regexBypassList = regexBypassList; + bypassList.IsChanged = false; + } + else + { + _regexBypassList = null; + } } private bool IsMatchInBypassList(Uri input) @@ -219,7 +224,10 @@ public ChangeTrackingArrayList() { } public ChangeTrackingArrayList(ICollection c) : base(c) { } - public bool IsChanged { get; set; } + // While this type isn't intended to be mutated concurrently with reads, non-concurrent updates + // to the list might result in lazy initialization, and it's possible concurrent HTTP requests could race + // to trigger that initialization. + public volatile bool IsChanged; // Override the methods that can add, remove, or change the regexes in the bypass list. // Methods that only read (like CopyTo, BinarySearch, etc.) and methods that reorder diff --git a/src/libraries/System.Net.WebSockets.Client/src/Resources/Strings.resx b/src/libraries/System.Net.WebSockets.Client/src/Resources/Strings.resx index 401be8dc707616..e3d5079aafc0bc 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/Resources/Strings.resx +++ b/src/libraries/System.Net.WebSockets.Client/src/Resources/Strings.resx @@ -129,4 +129,10 @@ The WebSocket failed to negotiate max client window bits. The client requested {0} but the server responded with {1}. + + UseDefaultCredentials, Credentials, Proxy, ClientCertificates, RemoteCertificateValidationCallback and Cookies must not be set on ClientWebSocketOptions when an HttpMessageInvoker instance is also specified. These options should be set on the HttpMessageInvoker's underlying HttpMessageHandler instead. + + + An HttpMessageInvoker instance must be passed to ConnectAsync when using HTTP/2. + diff --git a/src/libraries/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj b/src/libraries/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj index 28bc6b32221b13..b7da9d3e90becb 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj +++ b/src/libraries/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj @@ -45,7 +45,6 @@ - diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocket.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocket.cs index 487aab5594d813..4e55cd9575b8b4 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocket.cs +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocket.cs @@ -135,6 +135,9 @@ public override Task SendAsync(ArraySegment buffer, WebSocketMessageType m public override ValueTask SendAsync(ReadOnlyMemory buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) => ConnectedWebSocket.SendAsync(buffer, messageType, endOfMessage, cancellationToken); + public override ValueTask SendAsync(ReadOnlyMemory buffer, WebSocketMessageType messageType, WebSocketMessageFlags messageFlags, CancellationToken cancellationToken) => + ConnectedWebSocket.SendAsync(buffer, messageType, messageFlags, cancellationToken); + public override Task ReceiveAsync(ArraySegment buffer, CancellationToken cancellationToken) => ConnectedWebSocket.ReceiveAsync(buffer, cancellationToken); diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocketOptions.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocketOptions.cs index 6a8de0a712f581..c4069223db053f 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocketOptions.cs +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocketOptions.cs @@ -30,6 +30,14 @@ public sealed class ClientWebSocketOptions private HttpVersionPolicy _versionPolicy = HttpVersionPolicy.RequestVersionOrLower; private bool _collectHttpResponseDetails; + internal bool AreCompatibleWithCustomInvoker() => + !UseDefaultCredentials && + Credentials is null && + (_clientCertificates?.Count ?? 0) == 0 && + RemoteCertificateValidationCallback is null && + Cookies is null && + (Proxy is null || Proxy == WebSocketHandle.DefaultWebProxy.Instance); + internal ClientWebSocketOptions() { } // prevent external instantiation #region HTTP Settings 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 6bb365012ef779..e27661ea29efff 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 @@ -48,9 +48,22 @@ public void Abort() public async Task ConnectAsync(Uri uri, HttpMessageInvoker? invoker, CancellationToken cancellationToken, ClientWebSocketOptions options) { bool disposeHandler = false; - invoker ??= new HttpMessageInvoker(SetupHandler(options, out disposeHandler)); - HttpResponseMessage? response = null; + if (invoker is null) + { + if (options.HttpVersion.Major >= 2 || options.HttpVersionPolicy == HttpVersionPolicy.RequestVersionOrHigher) + { + throw new ArgumentException(SR.net_WebSockets_CustomInvokerRequiredForHttp2, nameof(options)); + } + invoker = new HttpMessageInvoker(SetupHandler(options, out disposeHandler)); + } + else if (!options.AreCompatibleWithCustomInvoker()) + { + // This will not throw if the Proxy is a DefaultWebProxy. + throw new ArgumentException(SR.net_WebSockets_OptionsIncompatibleWithCustomInvoker, nameof(options)); + } + + HttpResponseMessage? response = null; bool disposeResponse = false; // force non-secure request to 1.1 whenever it is possible as HttpClient does @@ -237,12 +250,7 @@ private static SocketsHttpHandler SetupHandler(ClientWebSocketOptions options, o // Create the handler for this request and populate it with all of the options. // Try to use a shared handler rather than creating a new one just for this request, if // the options are compatible. - if (options.Credentials == null && - !options.UseDefaultCredentials && - options.Proxy == null && - options.Cookies == null && - options.RemoteCertificateValidationCallback == null && - (options._clientCertificates?.Count ?? 0) == 0) + if (options.AreCompatibleWithCustomInvoker() && options.Proxy is null) { disposeHandler = false; handler = s_defaultHandler; @@ -518,7 +526,7 @@ private static void ValidateHeader(HttpHeaders headers, string name, string expe } /// Used as a sentinel to indicate that ClientWebSocket should use the system's default proxy. - private sealed class DefaultWebProxy : IWebProxy + internal sealed class DefaultWebProxy : IWebProxy { public static DefaultWebProxy Instance { get; } = new DefaultWebProxy(); public ICredentials? Credentials { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } diff --git a/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs index 70cb6317f36635..d81baa53e4639b 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs @@ -16,14 +16,14 @@ public sealed class InvokerAbortTest : AbortTest { public InvokerAbortTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + protected override bool UseCustomInvoker => true; } public sealed class HttpClientAbortTest : AbortTest { public HttpClientAbortTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } public class AbortTest : ClientWebSocketTestBase diff --git a/src/libraries/System.Net.WebSockets.Client/tests/CancelTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/CancelTest.cs index 5919f63157d705..534f62cca17d51 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/CancelTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/CancelTest.cs @@ -14,14 +14,14 @@ public sealed class InvokerCancelTest : CancelTest { public InvokerCancelTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + protected override bool UseCustomInvoker => true; } public sealed class HttpClientCancelTest : CancelTest { public HttpClientCancelTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } public class CancelTest : ClientWebSocketTestBase diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs b/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs index a32587bc862fc9..0c8b94778a58ea 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Net.Test.Common; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -10,13 +9,10 @@ using Xunit; using Xunit.Abstractions; using System.Net.Http; -using System.Net.WebSockets.Client.Tests; +using System.Diagnostics; namespace System.Net.WebSockets.Client.Tests { - /// - /// ClientWebSocket tests that do require a remote server. - /// public class ClientWebSocketTestBase { public static readonly object[][] EchoServers = System.Net.Test.Common.Configuration.WebSockets.EchoServers; @@ -112,7 +108,38 @@ protected static async Task ReceiveEntireMessageAsync(We } } - protected virtual HttpMessageInvoker? GetInvoker() => null; + protected virtual bool UseCustomInvoker => false; + + protected virtual bool UseHttpClient => false; + + protected bool UseSharedHandler => !UseCustomInvoker && !UseHttpClient; + + protected Action? ConfigureCustomHandler; + + internal HttpMessageInvoker? GetInvoker() + { + var handler = new HttpClientHandler(); + + if (PlatformDetection.IsNotBrowser) + { + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + } + + ConfigureCustomHandler?.Invoke(handler); + + if (UseCustomInvoker) + { + Debug.Assert(!UseHttpClient); + return new HttpMessageInvoker(handler); + } + + if (UseHttpClient) + { + return new HttpClient(handler); + } + + return null; + } protected Task GetConnectedWebSocket(Uri uri, int TimeOutMilliseconds, ITestOutputHelper output) => WebSocketHelper.GetConnectedWebSocket(uri, TimeOutMilliseconds, output, invoker: GetInvoker()); diff --git a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs index c09df107c8538c..9034b38bd43f46 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs @@ -18,14 +18,14 @@ public sealed class InvokerCloseTest : CloseTest { public InvokerCloseTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + protected override bool UseCustomInvoker => true; } public sealed class HttpClientCloseTest : CloseTest { public HttpClientCloseTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } public class CloseTest : ClientWebSocketTestBase diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs index 1ec2f1ee39fa45..bdbbcb75222fa5 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.IO; using System.Net.Http; using System.Net.Test.Common; using System.Threading; @@ -11,34 +10,59 @@ using Xunit; using Xunit.Abstractions; -using static System.Net.Http.Functional.Tests.TestHelper; - namespace System.Net.WebSockets.Client.Tests { public sealed class InvokerConnectTest_Http2 : ConnectTest_Http2 { public InvokerConnectTest_Http2(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseCustomInvoker => true; } public sealed class HttpClientConnectTest_Http2 : ConnectTest_Http2 { public HttpClientConnectTest_Http2(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } - public class ConnectTest_Http2 : ClientWebSocketTestBase + public sealed class HttpClientConnectTest_Http2_NoInvoker : ClientWebSocketTestBase { - public ConnectTest_Http2(ITestOutputHelper output) : base(output) { } + public HttpClientConnectTest_Http2_NoInvoker(ITestOutputHelper output) : base(output) { } + + public static IEnumerable ConnectAsync_Http2WithNoInvoker_ThrowsArgumentException_MemberData() + { + yield return Options(options => options.HttpVersion = HttpVersion.Version20); + yield return Options(options => options.HttpVersion = HttpVersion.Version30); + yield return Options(options => options.HttpVersion = new Version(2, 1)); + yield return Options(options => options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher); + static object[] Options(Action configureOptions) => + new object[] { configureOptions }; + } [Theory] - [InlineData(false)] - [InlineData(true)] + [MemberData(nameof(ConnectAsync_Http2WithNoInvoker_ThrowsArgumentException_MemberData))] + [SkipOnPlatform(TestPlatforms.Browser, "HTTP/2 WebSockets aren't supported on Browser")] + public async Task ConnectAsync_Http2WithNoInvoker_ThrowsArgumentException(Action configureOptions) + { + using var ws = new ClientWebSocket(); + configureOptions(ws.Options); + + Task connectTask = ws.ConnectAsync(new Uri("wss://dummy"), CancellationToken.None); + + Assert.Equal(TaskStatus.Faulted, connectTask.Status); + await Assert.ThrowsAsync("options", () => connectTask); + } + } + + public abstract class ConnectTest_Http2 : ClientWebSocketTestBase + { + public ConnectTest_Http2(ITestOutputHelper output) : base(output) { } + + [Fact] [SkipOnPlatform(TestPlatforms.Browser, "System.Net.Sockets is not supported on this platform")] - public async Task ConnectAsync_VersionNotSupported_NoSsl_Throws(bool useHandler) + public async Task ConnectAsync_VersionNotSupported_NoSsl_Throws() { await Http2LoopbackServer.CreateClientAndServerAsync(async uri => { @@ -46,17 +70,10 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; - Task t; - if (useHandler) - { - var handler = new SocketsHttpHandler(); - t = cws.ConnectAsync(uri, new HttpMessageInvoker(handler), cts.Token); - } - else - { - t = cws.ConnectAsync(uri, cts.Token); - } + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + + Task t = cws.ConnectAsync(uri, GetInvoker(), cts.Token); + var ex = await Assert.ThrowsAnyAsync(() => t); Assert.IsType(ex.InnerException); Assert.True(ex.InnerException.Data.Contains("SETTINGS_ENABLE_CONNECT_PROTOCOL")); @@ -65,8 +82,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => async server => { Http2LoopbackConnection connection = await server.EstablishConnectionAsync(new SettingsEntry { SettingId = SettingId.EnableConnect, Value = 0 }); - }, new Http2Options() { WebSocketEndpoint = true, UseSsl = false } - ); + }, new Http2Options() { WebSocketEndpoint = true, UseSsl = false }); } [Fact] @@ -79,10 +95,9 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; - Task t; - var handler = CreateSocketsHttpHandler(allowAllCertificates: true); - t = cws.ConnectAsync(uri, new HttpMessageInvoker(handler), cts.Token); + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + + Task t = cws.ConnectAsync(uri, GetInvoker(), cts.Token); var ex = await Assert.ThrowsAnyAsync(() => t); Assert.IsType(ex.InnerException); @@ -92,31 +107,22 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => async server => { Http2LoopbackConnection connection = await server.EstablishConnectionAsync(new SettingsEntry { SettingId = SettingId.EnableConnect, Value = 0 }); - }, new Http2Options() { WebSocketEndpoint = true } - ); + }, new Http2Options() { WebSocketEndpoint = true }); } [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] - [Theory] - [MemberData(nameof(SecureEchoServersAndBoolean))] + [Fact] [SkipOnPlatform(TestPlatforms.Browser, "System.Net.Sockets is not supported on this platform")] - public async Task ConnectAsync_Http11Server_DowngradeFail(Uri server, bool useHandler) + public async Task ConnectAsync_Http11Server_DowngradeFail() { using (var cws = new ClientWebSocket()) using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; - Task t; - if (useHandler) - { - var handler = new SocketsHttpHandler(); - t = cws.ConnectAsync(server, new HttpMessageInvoker(handler), cts.Token); - } - else - { - t = cws.ConnectAsync(server, cts.Token); - } + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + + Task t = cws.ConnectAsync(Test.Common.Configuration.WebSockets.SecureRemoteEchoServer, GetInvoker(), cts.Token); + var ex = await Assert.ThrowsAnyAsync(() => t); Assert.IsType(ex.InnerException); Assert.True(ex.InnerException.Data.Contains("HTTP2_ENABLED")); @@ -126,34 +132,23 @@ public async Task ConnectAsync_Http11Server_DowngradeFail(Uri server, bool useHa [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] [Theory] - [MemberData(nameof(EchoServersAndBoolean))] + [MemberData(nameof(EchoServers))] [SkipOnPlatform(TestPlatforms.Browser, "System.Net.Sockets is not supported on this platform")] - public async Task ConnectAsync_Http11Server_DowngradeSuccess(Uri server, bool useHandler) + public async Task ConnectAsync_Http11Server_DowngradeSuccess(Uri server) { using (var cws = new ClientWebSocket()) using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionOrLower; - if (useHandler) - { - var handler = new SocketsHttpHandler(); - await cws.ConnectAsync(server, new HttpMessageInvoker(handler), cts.Token); - } - else - { - await cws.ConnectAsync(server, cts.Token); - } + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; + await cws.ConnectAsync(server, GetInvoker(), cts.Token); Assert.Equal(WebSocketState.Open, cws.State); } } - - [Theory] - [InlineData(false)] - [InlineData(true)] + [Fact] [SkipOnPlatform(TestPlatforms.Browser, "System.Net.Sockets is not supported on this platform")] - public async Task ConnectAsync_VersionSupported_NoSsl_Success(bool useHandler) + public async Task ConnectAsync_VersionSupported_NoSsl_Success() { await Http2LoopbackServer.CreateClientAndServerAsync(async uri => { @@ -161,16 +156,8 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; - if (useHandler) - { - var handler = new SocketsHttpHandler(); - await cws.ConnectAsync(uri, new HttpMessageInvoker(handler), cts.Token); - } - else - { - await cws.ConnectAsync(uri, cts.Token); - } + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + await cws.ConnectAsync(uri, GetInvoker(), cts.Token); } }, async server => @@ -178,8 +165,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => Http2LoopbackConnection connection = await server.EstablishConnectionAsync(new SettingsEntry { SettingId = SettingId.EnableConnect, Value = 1 }); (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync(readBody: false); await connection.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.OK); - }, new Http2Options() { WebSocketEndpoint = true, UseSsl = false } - ); + }, new Http2Options() { WebSocketEndpoint = true, UseSsl = false }); } [Fact] @@ -192,10 +178,8 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; - - var handler = CreateSocketsHttpHandler(allowAllCertificates: true); - await cws.ConnectAsync(uri, new HttpMessageInvoker(handler), cts.Token); + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + await cws.ConnectAsync(uri, GetInvoker(), cts.Token); } }, async server => @@ -203,8 +187,39 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => Http2LoopbackConnection connection = await server.EstablishConnectionAsync(new SettingsEntry { SettingId = SettingId.EnableConnect, Value = 1 }); (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync(readBody: false); await connection.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.OK); - }, new Http2Options() { WebSocketEndpoint = true } - ); + }, new Http2Options() { WebSocketEndpoint = true }); + } + + [Fact] + [SkipOnPlatform(TestPlatforms.Browser, "HTTP/2 WebSockets aren't supported on Browser")] + public async Task ConnectAsync_SameHttp2ConnectionUsedForMultipleWebSocketConnection() + { + await Http2LoopbackServer.CreateClientAndServerAsync(async uri => + { + using var cws1 = new ClientWebSocket(); + cws1.Options.HttpVersion = HttpVersion.Version20; + cws1.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + + using var cws2 = new ClientWebSocket(); + cws2.Options.HttpVersion = HttpVersion.Version20; + cws2.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + + using var cts = new CancellationTokenSource(TimeOutMilliseconds); + HttpMessageInvoker? invoker = GetInvoker(); + + await cws1.ConnectAsync(uri, invoker, cts.Token); + await cws2.ConnectAsync(uri, invoker, cts.Token); + }, + async server => + { + await using Http2LoopbackConnection connection = await server.EstablishConnectionAsync(new SettingsEntry { SettingId = SettingId.EnableConnect, Value = 1 }); + + (int streamId1, _) = await connection.ReadAndParseRequestHeaderAsync(readBody: false); + await connection.SendResponseHeadersAsync(streamId1, endStream: false, HttpStatusCode.OK); + + (int streamId2, _) = await connection.ReadAndParseRequestHeaderAsync(readBody: false); + await connection.SendResponseHeadersAsync(streamId2, endStream: false, HttpStatusCode.OK); + }, new Http2Options() { WebSocketEndpoint = true, UseSsl = false }); } } } diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs index fd0eee1265cfa1..1809ab8cabc0eb 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs @@ -3,12 +3,11 @@ using System.Collections.Generic; using System.IO; -using System.Linq; using System.Net.Http; using System.Net.Test.Common; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; - using Xunit; using Xunit.Abstractions; @@ -17,14 +16,85 @@ namespace System.Net.WebSockets.Client.Tests public sealed class InvokerConnectTest : ConnectTest { public InvokerConnectTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + + protected override bool UseCustomInvoker => true; + + public static IEnumerable ConnectAsync_CustomInvokerWithIncompatibleWebSocketOptions_ThrowsArgumentException_MemberData() + { + yield return Throw(options => options.UseDefaultCredentials = true); + yield return NoThrow(options => options.UseDefaultCredentials = false); + yield return Throw(options => options.Credentials = new NetworkCredential()); + yield return Throw(options => options.Proxy = new WebProxy()); + + // Will result in an exception on apple mobile platforms + // and crash the test. + if (PlatformDetection.IsNotAppleMobile) + { + yield return Throw(options => options.ClientCertificates.Add(Test.Common.Configuration.Certificates.GetClientCertificate())); + } + + yield return NoThrow(options => options.ClientCertificates = new X509CertificateCollection()); + yield return Throw(options => options.RemoteCertificateValidationCallback = delegate { return true; }); + yield return Throw(options => options.Cookies = new CookieContainer()); + + // We allow no proxy or the default proxy to be used + yield return NoThrow(options => { }); + yield return NoThrow(options => options.Proxy = null); + + // These options don't conflict with the custom invoker + yield return NoThrow(options => options.HttpVersion = new Version(2, 0)); + yield return NoThrow(options => options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher); + yield return NoThrow(options => options.SetRequestHeader("foo", "bar")); + yield return NoThrow(options => options.AddSubProtocol("foo")); + yield return NoThrow(options => options.KeepAliveInterval = TimeSpan.FromSeconds(42)); + yield return NoThrow(options => options.DangerousDeflateOptions = new WebSocketDeflateOptions()); + yield return NoThrow(options => options.CollectHttpResponseDetails = true); + + static object[] Throw(Action configureOptions) => + new object[] { configureOptions, true }; + + static object[] NoThrow(Action configureOptions) => + new object[] { configureOptions, false }; + } + + [Theory] + [MemberData(nameof(ConnectAsync_CustomInvokerWithIncompatibleWebSocketOptions_ThrowsArgumentException_MemberData))] + [SkipOnPlatform(TestPlatforms.Browser, "Custom invoker is ignored on Browser")] + public async Task ConnectAsync_CustomInvokerWithIncompatibleWebSocketOptions_ThrowsArgumentException(Action configureOptions, bool shouldThrow) + { + using var invoker = new HttpMessageInvoker(new SocketsHttpHandler + { + ConnectCallback = (_, _) => ValueTask.FromException(new Exception("ConnectCallback")) + }); + + using var ws = new ClientWebSocket(); + configureOptions(ws.Options); + + Task connectTask = ws.ConnectAsync(new Uri("wss://dummy"), invoker, CancellationToken.None); + if (shouldThrow) + { + Assert.Equal(TaskStatus.Faulted, connectTask.Status); + await Assert.ThrowsAsync("options", () => connectTask); + } + else + { + WebSocketException ex = await Assert.ThrowsAsync(() => connectTask); + Assert.NotNull(ex.InnerException); + Assert.Contains("ConnectCallback", ex.InnerException.Message); + } + + foreach (X509Certificate cert in ws.Options.ClientCertificates) + { + cert.Dispose(); + } + } } public sealed class HttpClientConnectTest : ConnectTest { public HttpClientConnectTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } public class ConnectTest : ClientWebSocketTestBase @@ -258,7 +328,13 @@ public async Task ConnectAndCloseAsync_UseProxyServer_ExpectedClosedState(Uri se using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create()) { - cws.Options.Proxy = new WebProxy(proxyServer.Uri); + ConfigureCustomHandler = handler => handler.Proxy = new WebProxy(proxyServer.Uri); + + if (UseSharedHandler) + { + cws.Options.Proxy = new WebProxy(proxyServer.Uri); + } + await ConnectAsync(cws, server, cts.Token); string expectedCloseStatusDescription = "Client close status"; @@ -267,6 +343,7 @@ public async Task ConnectAndCloseAsync_UseProxyServer_ExpectedClosedState(Uri se Assert.Equal(WebSocketState.Closed, cws.State); Assert.Equal(WebSocketCloseStatus.NormalClosure, cws.CloseStatus); Assert.Equal(expectedCloseStatusDescription, cws.CloseStatusDescription); + Assert.Equal(1, proxyServer.Connections); } } diff --git a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs index 262e45ae414db8..158aa0983a01b1 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs @@ -18,14 +18,14 @@ public sealed class InvokerDeflateTests : DeflateTests { public InvokerDeflateTests(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + protected override bool UseCustomInvoker => true; } public sealed class HttpClientDeflateTests : DeflateTests { public HttpClientDeflateTests(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } [PlatformSpecific(~TestPlatforms.Browser)] @@ -87,6 +87,91 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => }), new LoopbackServer.Options { WebSocketEndpoint = true }); } + [ConditionalFact(nameof(WebSocketsSupported))] + public async Task ThrowsWhenContinuationHasDifferentCompressionFlags() + { + var deflateOpt = new WebSocketDeflateOptions + { + ClientMaxWindowBits = 14, + ClientContextTakeover = true, + ServerMaxWindowBits = 14, + ServerContextTakeover = true + }; + await LoopbackServer.CreateClientAndServerAsync(async uri => + { + using var cws = new ClientWebSocket(); + using var cts = new CancellationTokenSource(TimeOutMilliseconds); + + cws.Options.DangerousDeflateOptions = deflateOpt; + await ConnectAsync(cws, uri, cts.Token); + + + await cws.SendAsync(Memory.Empty, WebSocketMessageType.Text, WebSocketMessageFlags.DisableCompression, default); + Assert.Throws("messageFlags", () => + cws.SendAsync(Memory.Empty, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, default)); + }, server => server.AcceptConnectionAsync(async connection => + { + string extensionsReply = CreateDeflateOptionsHeader(deflateOpt); + await LoopbackHelper.WebSocketHandshakeAsync(connection, extensionsReply); + }), new LoopbackServer.Options { WebSocketEndpoint = true }); + } + + [ConditionalFact(nameof(WebSocketsSupported))] + public async Task SendHelloWithDisableCompression() + { + byte[] bytes = "Hello"u8.ToArray(); + + int prefixLength = 2; + byte[] rawPrefix = new byte[] { 0x81, 0x85 }; // fin=1, rsv=0, opcode=text; mask=1, len=5 + int rawRemainingBytes = 9; // mask bytes (4) + payload bytes (5) + byte[] compressedPrefix = new byte[] { 0xc1, 0x87 }; // fin=1, rsv=compressed, opcode=text; mask=1, len=7 + int compressedRemainingBytes = 11; // mask bytes (4) + payload bytes (7) + + var deflateOpt = new WebSocketDeflateOptions + { + ClientMaxWindowBits = 14, + ClientContextTakeover = true, + ServerMaxWindowBits = 14, + ServerContextTakeover = true + }; + + await LoopbackServer.CreateClientAndServerAsync(async uri => + { + using var cws = new ClientWebSocket(); + using var cts = new CancellationTokenSource(TimeOutMilliseconds); + + cws.Options.DangerousDeflateOptions = deflateOpt; + await ConnectAsync(cws, uri, cts.Token); + + await cws.SendAsync(bytes, WebSocketMessageType.Text, true, cts.Token); + + WebSocketMessageFlags flags = WebSocketMessageFlags.DisableCompression | WebSocketMessageFlags.EndOfMessage; + await cws.SendAsync(bytes, WebSocketMessageType.Text, flags, cts.Token); + }, server => server.AcceptConnectionAsync(async connection => + { + var buffer = new byte[compressedRemainingBytes]; + string extensionsReply = CreateDeflateOptionsHeader(deflateOpt); + await LoopbackHelper.WebSocketHandshakeAsync(connection, extensionsReply); + + // first message is compressed + await ReadExactAsync(buffer, prefixLength); + Assert.Equal(compressedPrefix, buffer[..prefixLength]); + // read rest of the frame + await ReadExactAsync(buffer, compressedRemainingBytes); + + // second message is not compressed + await ReadExactAsync(buffer, prefixLength); + Assert.Equal(rawPrefix, buffer[..prefixLength]); + // read rest of the frame + await ReadExactAsync(buffer, rawRemainingBytes); + + async Task ReadExactAsync(byte[] buf, int n) + { + await connection.Stream.ReadAtLeastAsync(buf.AsMemory(0, n), n); + } + }), new LoopbackServer.Options { WebSocketEndpoint = true }); + } + private static string CreateDeflateOptionsHeader(WebSocketDeflateOptions options) { var builder = new StringBuilder(); diff --git a/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.Http2.cs b/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.Http2.cs index 5f3be83d5bfb79..ef21a36e44fa8f 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.Http2.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.Http2.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Net.Http; -using System.Net.Sockets; using System.Net.Test.Common; using System.Threading; using System.Threading.Tasks; @@ -11,19 +10,29 @@ using Xunit; using Xunit.Abstractions; -using static System.Net.Http.Functional.Tests.TestHelper; - namespace System.Net.WebSockets.Client.Tests { - public class SendReceiveTest_Http2 : ClientWebSocketTestBase + public sealed class HttpClientSendReceiveTest_Http2 : SendReceiveTest_Http2 + { + public HttpClientSendReceiveTest_Http2(ITestOutputHelper output) : base(output) { } + + protected override bool UseHttpClient => true; + } + + public sealed class InvokerSendReceiveTest_Http2 : SendReceiveTest_Http2 + { + public InvokerSendReceiveTest_Http2(ITestOutputHelper output) : base(output) { } + + protected override bool UseCustomInvoker => true; + } + + public abstract class SendReceiveTest_Http2 : ClientWebSocketTestBase { public SendReceiveTest_Http2(ITestOutputHelper output) : base(output) { } - [Theory] - [InlineData(false)] - [InlineData(true)] + [Fact] [SkipOnPlatform(TestPlatforms.Browser, "System.Net.Sockets is not supported on this platform")] - public async Task ReceiveNoThrowAfterSend_NoSsl(bool useHandler) + public async Task ReceiveNoThrowAfterSend_NoSsl() { var serverMessage = new byte[] { 4, 5, 6 }; await Http2LoopbackServer.CreateClientAndServerAsync(async uri => @@ -32,16 +41,9 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; - if (useHandler) - { - var handler = new SocketsHttpHandler(); - await cws.ConnectAsync(uri, new HttpMessageInvoker(handler), cts.Token); - } - else - { - await cws.ConnectAsync(uri, cts.Token); - } + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + + await cws.ConnectAsync(uri, GetInvoker(), cts.Token); await cws.SendAsync(new byte[] { 2, 3, 4 }, WebSocketMessageType.Binary, true, cts.Token); @@ -63,8 +65,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => byte[] constructMessage = prefix.Concat(serverMessage).ToArray(); await connection.SendResponseDataAsync(streamId, constructMessage, endStream: false); - }, new Http2Options() { WebSocketEndpoint = true, UseSsl = false } - ); + }, new Http2Options() { WebSocketEndpoint = true, UseSsl = false }); } [Fact] @@ -78,10 +79,9 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; - var handler = CreateSocketsHttpHandler(allowAllCertificates: true); - await cws.ConnectAsync(uri, new HttpMessageInvoker(handler), cts.Token); + await cws.ConnectAsync(uri, GetInvoker(), cts.Token); await cws.SendAsync(new byte[] { 2, 3, 4 }, WebSocketMessageType.Binary, true, cts.Token); @@ -103,8 +103,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => byte[] constructMessage = prefix.Concat(serverMessage).ToArray(); await connection.SendResponseDataAsync(streamId, constructMessage, endStream: false); - }, new Http2Options() { WebSocketEndpoint = true } - ); + }, new Http2Options() { WebSocketEndpoint = true }); } } } diff --git a/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs index 6597b6f9ec6315..ec3913c02c16c9 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs @@ -17,28 +17,28 @@ public sealed class InvokerMemorySendReceiveTest : MemorySendReceiveTest { public InvokerMemorySendReceiveTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + protected override bool UseCustomInvoker => true; } public sealed class HttpClientMemorySendReceiveTest : MemorySendReceiveTest { public HttpClientMemorySendReceiveTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } public sealed class InvokerArraySegmentSendReceiveTest : ArraySegmentSendReceiveTest { public InvokerArraySegmentSendReceiveTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + protected override bool UseCustomInvoker => true; } public sealed class HttpClientArraySegmentSendReceiveTest : ArraySegmentSendReceiveTest { public HttpClientArraySegmentSendReceiveTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } public class MemorySendReceiveTest : SendReceiveTest diff --git a/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.Parser.cs b/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.Parser.cs index 43a498b589af8c..fa2e1a2fc8598e 100644 --- a/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.Parser.cs +++ b/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.Parser.cs @@ -9,7 +9,6 @@ using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; namespace Generators { diff --git a/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.cs b/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.cs index e57f11d6f4ea5a..90ea79020dda9f 100644 --- a/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.cs +++ b/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.cs @@ -7,7 +7,6 @@ using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; namespace Generators { @@ -40,7 +39,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) IncrementalValuesProvider eventSourceClasses = context.SyntaxProvider.ForAttributeWithMetadataName( - context, EventSourceAutoGenerateAttribute, (node, _) => node is ClassDeclarationSyntax, GetSemanticTargetForGeneration) diff --git a/src/libraries/System.Private.CoreLib/gen/System.Private.CoreLib.Generators.csproj b/src/libraries/System.Private.CoreLib/gen/System.Private.CoreLib.Generators.csproj index 90d6ae99bb27cb..9b8934e682a883 100644 --- a/src/libraries/System.Private.CoreLib/gen/System.Private.CoreLib.Generators.csproj +++ b/src/libraries/System.Private.CoreLib/gen/System.Private.CoreLib.Generators.csproj @@ -13,16 +13,6 @@ - - - - - - - - - - diff --git a/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.csproj b/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.csproj index 413026393547ba..4cf77fe8bffa1c 100644 --- a/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.csproj +++ b/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.csproj @@ -10,8 +10,8 @@ $(NoWarn);0809;0618;CS8614;CS3015 SilverlightPlatform true - true - true + true + true $(DefineConstants);FEATURE_WASM_PERFTRACING $(DefineConstants);FEATURE_WASM_THREADS $(DefineConstants);BUILDING_CORELIB_REFERENCE @@ -51,4 +51,4 @@ - \ No newline at end of file + diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 9b8eee748e7132..66a83c4b379817 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -2603,6 +2603,9 @@ Common Language Runtime detected an invalid program. + + The time zone ID '{0}' is invalid. + The time zone ID '{0}' was found on the local computer, but the file at '{1}' was corrupt. @@ -2627,6 +2630,9 @@ This assembly does not have a file table because it was loaded from memory. + + Unsupported unseekable file. + Unable to read beyond the end of the stream. 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 6f3a16e6136363..4cac14ebc579c8 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 @@ -254,6 +254,8 @@ + + @@ -617,7 +619,6 @@ - @@ -1762,6 +1763,9 @@ Common\Interop\Windows\Kernel32\Interop.SystemTimeToFileTime.cs + + Common\Interop\Windows\Kernel32\Interop.Threading.cs + Common\Interop\Windows\Kernel32\Interop.TimeZone.cs @@ -1789,6 +1793,12 @@ Common\Interop\Windows\Kernel32\Interop.WIN32_FIND_DATA.cs + + Common\Interop\Windows\Kernel32\Interop.WriteFile_IntPtr.cs + + + Common\Interop\Windows\Kernel32\Interop.WriteFile_SafeHandle_IntPtr.cs + Common\Interop\Windows\Kernel32\Interop.WriteFile_SafeHandle_NativeOverlapped.cs @@ -1968,12 +1978,6 @@ Common\Interop\Windows\Kernel32\Interop.SetEnvironmentVariable.cs - - Common\Interop\Windows\Kernel32\Interop.WriteFile_SafeHandle_IntPtr.cs - - - Common\Interop\Windows\Kernel32\Interop.WriteFile_SafeHandle_IntPtr.cs - Common\System\IO\Win32Marshal.cs @@ -2379,8 +2383,8 @@ - - + + @@ -2416,9 +2420,6 @@ - - Interop\Windows\Kernel32\Interop.Threading.cs - @@ -2456,4 +2457,7 @@ - \ No newline at end of file + + + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 32435ec4367ec8..b665d3094e1dc9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -1328,17 +1328,17 @@ public static int IndexOf(T[] array, T value, int startIndex, int count) { if (Unsafe.SizeOf() == sizeof(byte)) { - int result = SpanHelpers.IndexOf( + int result = SpanHelpers.IndexOfValueType( ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), startIndex), Unsafe.As(ref value), count); return (result >= 0 ? startIndex : 0) + result; } - else if (Unsafe.SizeOf() == sizeof(char)) + else if (Unsafe.SizeOf() == sizeof(short)) { - int result = SpanHelpers.IndexOf( - ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), startIndex), - Unsafe.As(ref value), + int result = SpanHelpers.IndexOfValueType( + ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), startIndex), + Unsafe.As(ref value), count); return (result >= 0 ? startIndex : 0) + result; } @@ -1586,19 +1586,19 @@ public static int LastIndexOf(T[] array, T value, int startIndex, int count) if (Unsafe.SizeOf() == sizeof(byte)) { int endIndex = startIndex - count + 1; - int result = SpanHelpers.LastIndexOf( + int result = SpanHelpers.LastIndexOfValueType( ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), endIndex), Unsafe.As(ref value), count); return (result >= 0 ? endIndex : 0) + result; } - else if (Unsafe.SizeOf() == sizeof(char)) + else if (Unsafe.SizeOf() == sizeof(short)) { int endIndex = startIndex - count + 1; - int result = SpanHelpers.LastIndexOf( - ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), endIndex), - Unsafe.As(ref value), + int result = SpanHelpers.LastIndexOfValueType( + ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), endIndex), + Unsafe.As(ref value), count); return (result >= 0 ? endIndex : 0) + result; @@ -1606,7 +1606,7 @@ ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)) else if (Unsafe.SizeOf() == sizeof(int)) { int endIndex = startIndex - count + 1; - int result = SpanHelpers.LastIndexOf( + int result = SpanHelpers.LastIndexOfValueType( ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), endIndex), Unsafe.As(ref value), count); @@ -1616,7 +1616,7 @@ ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), else if (Unsafe.SizeOf() == sizeof(long)) { int endIndex = startIndex - count + 1; - int result = SpanHelpers.LastIndexOf( + int result = SpanHelpers.LastIndexOfValueType( ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), endIndex), Unsafe.As(ref value), count); diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Decoder.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Decoder.cs index b0c491a5beaba7..2ec9959536427e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Decoder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Decoder.cs @@ -592,8 +592,9 @@ private static unsafe void Vector128Decode(ref byte* srcBytes, ref byte* destByt // lookup Vector128 hiNibbles = Vector128.ShiftRightLogical(str.AsInt32(), 4).AsByte() & mask2F; + Vector128 loNibbles = str & mask2F; Vector128 hi = SimdShuffle(lutHi, hiNibbles, mask8F); - Vector128 lo = SimdShuffle(lutLo, str, mask8F); + Vector128 lo = SimdShuffle(lutLo, loNibbles, mask8F); // Check for invalid input: if any "and" values from lo and hi are not zero, // fall back on bytewise code to do error checking and reporting: diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs index fd6ce30c9e14b5..22eff2b8b16834 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs @@ -14,22 +14,37 @@ public static int CountDigits(UInt128 value) { ulong upper = value.Upper; - if (upper < 5) + // 1e19 is 8AC7_2304_89E8_0000 + // 1e20 is 5_6BC7_5E2D_6310_0000 + // 1e21 is 36_35C9_ADC5_DEA0_0000 + + if (upper == 0) { + // We have less than 64-bits, so just return the lower count return CountDigits(value.Lower); } - int digits = 19; + // We have more than 1e19, so we have at least 20 digits + int digits = 20; if (upper > 5) { - digits++; + // ((2^128) - 1) / 1e20 < 34_02_823_669_209_384_635 which + // is 18.5318 digits, meaning the result definitely fits + // into 64-bits and we only need to add the lower digit count + value /= new UInt128(0x5, 0x6BC7_5E2D_6310_0000); // value /= 1e20 + Debug.Assert(value.Upper == 0); + digits += CountDigits(value.Lower); } - else if (value.Lower >= 0x6BC75E2D63100000) + else if ((upper == 5) && (value.Lower >= 0x6BC75E2D63100000)) { + // We're greater than 1e20, but definitely less than 1e21 + // so we have exactly 21 digits + digits++; + Debug.Assert(digits == 21); } return digits; diff --git a/src/libraries/System.Private.CoreLib/src/System/Byte.cs b/src/libraries/System.Private.CoreLib/src/System/Byte.cs index 6541d1a1f8cf0a..2a454153011960 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Byte.cs @@ -920,7 +920,7 @@ private static bool TryConvertFromTruncating(TOther value, out byte resu /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(byte value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(byte value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -987,14 +987,14 @@ static bool INumberBase.TryConvertToChecked(byte value, [NotNullWh } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(byte value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(byte value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1061,14 +1061,14 @@ static bool INumberBase.TryConvertToSaturating(byte value, [NotNul } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(byte value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(byte value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1135,7 +1135,7 @@ static bool INumberBase.TryConvertToTruncating(byte value, [NotNul } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Char.cs b/src/libraries/System.Private.CoreLib/src/System/Char.cs index b93226bb9778e8..d506f503d48bee 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Char.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Char.cs @@ -1715,7 +1715,7 @@ static bool INumberBase.TryConvertFromTruncating(TOther value, out /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(char value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(char value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1782,14 +1782,14 @@ static bool INumberBase.TryConvertToChecked(char value, [NotNullWh } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(char value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(char value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1856,14 +1856,14 @@ static bool INumberBase.TryConvertToSaturating(char value, [NotNul } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(char value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(char value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1930,7 +1930,7 @@ static bool INumberBase.TryConvertToTruncating(char value, [NotNul } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs index 7a49887d17c287..e700a1fb8a5de5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs @@ -528,13 +528,11 @@ private static T GetItemWhenAvailable(ConcurrentQueueSegment segment, int i) // If the expected sequence number is not yet written, we're still waiting for // an enqueuer to finish storing it. Spin until it's there. - if ((segment._slots[i].SequenceNumber & segment._slotsMask) != expectedSequenceNumberAndMask) + SpinWait spinner = default; + // Must read SequenceNumber before reading Item, thus Volatile.Read + while ((Volatile.Read(ref segment._slots[i].SequenceNumber) & segment._slotsMask) != expectedSequenceNumberAndMask) { - SpinWait spinner = default; - while ((Volatile.Read(ref segment._slots[i].SequenceNumber) & segment._slotsMask) != expectedSequenceNumberAndMask) - { - spinner.SpinOnce(); - } + spinner.SpinOnce(); } // Return the value from the slot. @@ -683,7 +681,7 @@ public bool TryDequeue([MaybeNullWhen(false)] out T result) // check and this check, another item could have arrived). if (head._nextSegment == null) { - result = default!; + result = default; return false; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs index e9f9f6c307f98d..7a793902166a02 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs @@ -231,7 +231,7 @@ public bool TryDequeue([MaybeNullWhen(false)] out T result) if (_size == 0) { - result = default!; + result = default; return false; } @@ -263,7 +263,7 @@ public bool TryPeek([MaybeNullWhen(false)] out T result) { if (_size == 0) { - result = default!; + result = default; return false; } diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs new file mode 100644 index 00000000000000..c40e4708e47aab --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs @@ -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. + +using System.Threading; + +namespace System +{ + public readonly partial struct DateTimeOffset + { + // 0 == in process of being loaded, 1 == loaded + private static volatile int s_androidTZDataLoaded = -1; + + // Now on Android does the following + // 1) quickly returning a fast path result when first called if the right AppContext data element is set + // 2) starting a background thread to load TimeZoneInfo local cache + // + // On Android, loading AndroidTZData is expensive for startup performance. + // The fast result relies on `System.TimeZoneInfo.LocalDateTimeOffset` being set + // in the App Context's properties as the appropriate local date time offset from UTC. + // monovm_initialize(_preparsed) can be leveraged to do so. + // However, to handle timezone changes during the app lifetime, AndroidTZData needs to be loaded. + // So, on first call, we return the fast path and start a background thread to load + // the TimeZoneInfo Local cache implementation which loads AndroidTZData. + public static DateTimeOffset Now + { + get + { + DateTime utcDateTime = DateTime.UtcNow; + + if (s_androidTZDataLoaded == 1) // The background thread finished, the cache is loaded. + { + return ToLocalTime(utcDateTime, true); + } + + object? localDateTimeOffset = AppContext.GetData("System.TimeZoneInfo.LocalDateTimeOffset"); + if (localDateTimeOffset == null) // If no offset property provided through monovm app context, default + { + // no need to create the thread, load tzdata now + s_androidTZDataLoaded = 1; + return ToLocalTime(utcDateTime, true); + } + + // The cache isn't loaded yet. + if (Interlocked.CompareExchange(ref s_androidTZDataLoaded, 0, -1) == -1) + { + new Thread(() => + { + try + { + // Delay the background thread to avoid impacting startup, if it still coincides after 1s, startup is already perceived as slow + Thread.Sleep(1000); + + _ = TimeZoneInfo.Local; // Load AndroidTZData + } + finally + { + s_androidTZDataLoaded = 1; + } + }) { IsBackground = true }.Start(); + } + + // Fast path obtained offset incorporated into ToLocalTime(DateTime.UtcNow, true) logic + int localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset); + TimeSpan offset = TimeSpan.FromSeconds(localDateTimeOffsetSeconds); + long localTicks = utcDateTime.Ticks + offset.Ticks; + if (localTicks < DateTime.MinTicks || localTicks > DateTime.MaxTicks) + throw new ArgumentException(SR.Arg_ArgumentOutOfRangeException); + + return new DateTimeOffset(localTicks, offset); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.NonAndroid.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.NonAndroid.cs new file mode 100644 index 00000000000000..511e40ef6c8d12 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.NonAndroid.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 +{ + public readonly partial struct DateTimeOffset + { + // Returns a DateTimeOffset representing the current date and time. The + // resolution of the returned value depends on the system timer. + public static DateTimeOffset Now => ToLocalTime(DateTime.UtcNow, true); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs index 3f8feead885a77..b2289d1377aceb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs @@ -34,7 +34,7 @@ namespace System [StructLayout(LayoutKind.Auto)] [Serializable] [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] - public readonly struct DateTimeOffset + public readonly partial struct DateTimeOffset : IComparable, ISpanFormattable, IComparable, @@ -321,10 +321,6 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se _dateTime = _dateTime.AddMicroseconds(microsecond); } - // Returns a DateTimeOffset representing the current date and time. The - // resolution of the returned value depends on the system timer. - public static DateTimeOffset Now => ToLocalTime(DateTime.UtcNow, true); - public static DateTimeOffset UtcNow { get diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs index 5436baebfdc572..8b469e385b13d8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs @@ -1645,7 +1645,7 @@ private static bool TryConvertFrom(TOther value, out decimal result) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(decimal value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(decimal value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1712,26 +1712,26 @@ static bool INumberBase.TryConvertToChecked(decimal value, [Not } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(decimal value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(decimal value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(decimal value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(decimal value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } - private static bool TryConvertTo(decimal value, [NotNullWhen(true)] out TOther result) + private static bool TryConvertTo(decimal value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase { // In order to reduce overall code duplication and improve the inlinabilty of these @@ -1804,7 +1804,7 @@ private static bool TryConvertTo(decimal value, [NotNullWhen(true)] out } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs index 703738efbc3c94..1c8a6012ca99fa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs @@ -46,24 +46,46 @@ private void RegisterCommandCallback() private void OnEventSourceCommand(object? sender, EventCommandEventArgs e) { - if (e.Command == EventCommand.Enable || e.Command == EventCommand.Update) - { - Debug.Assert(e.Arguments != null); + // Should only be enable or disable + Debug.Assert(e.Command == EventCommand.Enable || e.Command == EventCommand.Disable); - if (e.Arguments.TryGetValue("EventCounterIntervalSec", out string? valueStr) && float.TryParse(valueStr, out float value)) + lock (s_counterGroupLock) // Lock the CounterGroup + { + if (e.Command == EventCommand.Enable || e.Command == EventCommand.Update) { - lock (s_counterGroupLock) // Lock the CounterGroup + Debug.Assert(e.Arguments != null); + + if (!e.Arguments.TryGetValue("EventCounterIntervalSec", out string? valueStr) + || !float.TryParse(valueStr, out float intervalValue)) { - EnableTimer(value); + // Command is Enable but no EventCounterIntervalSec arg so ignore + return; } + + EnableTimer(intervalValue); } - } - else if (e.Command == EventCommand.Disable) - { - lock (s_counterGroupLock) + else if (e.Command == EventCommand.Disable) { - DisableTimer(); + // Since we allow sessions to send multiple Enable commands to update the interval, we cannot + // rely on ref counting to determine when to enable and disable counters. You will get an arbitrary + // number of Enables and one Disable per session. + // + // Previously we would turn off counters when we received any Disable command, but that meant that any + // session could turn off counters for all other sessions. To get to a good place we now will only + // turn off counters once the EventSource that provides the counters is disabled. We can then end up + // keeping counters on too long in certain circumstances - if one session enables counters, then a second + // session enables the EventSource but not counters we will stay on until both sessions terminate, even + // if the first session terminates first. + if (!_eventSource.IsEnabled()) + { + DisableTimer(); + } } + + Debug.Assert((s_counterGroupEnabledList == null && !_eventSource.IsEnabled()) + || (_eventSource.IsEnabled() && s_counterGroupEnabledList!.Contains(this)) + || (_pollingIntervalInMilliseconds == 0 && !s_counterGroupEnabledList!.Contains(this)) + || (!_eventSource.IsEnabled() && !s_counterGroupEnabledList!.Contains(this))); } } 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 e9e8c3235e39a3..145a85264920c0 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 @@ -2645,9 +2645,6 @@ internal void DoCommand(EventCommandEventArgs commandArgs) m_eventSourceEnabled = true; } - this.OnEventCommand(commandArgs); - this.m_eventCommandExecuted?.Invoke(this, commandArgs); - if (!commandArgs.enable) { // If we are disabling, maybe we can turn on 'quick checks' to filter @@ -2679,6 +2676,9 @@ internal void DoCommand(EventCommandEventArgs commandArgs) m_eventSourceEnabled = false; } } + + this.OnEventCommand(commandArgs); + this.m_eventCommandExecuted?.Invoke(this, commandArgs); } else { @@ -2807,8 +2807,13 @@ private void EnsureDescriptorsInitialized() // Today, we only send the manifest to ETW, custom listeners don't get it. private unsafe void SendManifest(byte[]? rawManifest) { - if (rawManifest == null) + if (rawManifest == null + // Don't send the manifest for NativeRuntimeEventSource, it is conceptually + // an extension of the native coreclr provider + || m_name.Equals("Microsoft-Windows-DotNETRuntime")) + { return; + } Debug.Assert(!SelfDescribingEvents); diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index 177eb912575ede..75580d6a88c8ba 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -1243,7 +1243,7 @@ private static bool TryConvertFrom(TOther value, out double result) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(double value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(double value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1304,26 +1304,26 @@ static bool INumberBase.TryConvertToChecked(double value, [NotNu } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(double value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(double value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(double value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(double value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } - private static bool TryConvertTo(double value, [NotNullWhen(true)] out TOther result) + private static bool TryConvertTo(double value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase { // In order to reduce overall code duplication and improve the inlinabilty of these @@ -1402,7 +1402,7 @@ private static bool TryConvertTo(double value, [NotNullWhen(true)] out T } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Enum.cs b/src/libraries/System.Private.CoreLib/src/System/Enum.cs index 89404415b5e17d..cd4535867b0334 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Enum.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Enum.cs @@ -436,7 +436,7 @@ private static int FindDefinedIndex(ulong[] ulValues, ulong ulValue) int ulValuesLength = ulValues.Length; ref ulong start = ref MemoryMarshal.GetArrayDataReference(ulValues); return ulValuesLength <= NumberOfValuesThreshold ? - SpanHelpers.IndexOf(ref start, ulValue, ulValuesLength) : + SpanHelpers.IndexOfValueType(ref Unsafe.As(ref start), (long)ulValue, ulValuesLength) : SpanHelpers.BinarySearch(ref start, ulValuesLength, ulValue); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs index 09e3ed1dda3c26..a1f244246de9ff 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs @@ -12,6 +12,88 @@ internal sealed partial class CultureData // ICU constants private const int ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY = 100; // max size of keyword or value private const int ICU_ULOC_FULLNAME_CAPACITY = 157; // max size of locale name + private const int WINDOWS_MAX_COLLATION_NAME_LENGTH = 8; // max collation name length in the culture name + + /// + /// Process the locale name that ICU returns and convert it to the format that .NET expects. + /// + /// The locale name that ICU returns. + /// The extension part in the original culture name. + /// The index of the collation in the name. + /// + /// BCP 47 specifications allow for extensions in the locale name, following the format language-script-region-extensions-collation. However, + /// not all extensions supported by ICU are supported in .NET. In the locale name, extensions are separated from the rest of the name using '-u-' or '-t-'. + /// In .NET, only the collation extension is supported. If the name includes a collation extension, it will be prefixed with '-u-co-'. + /// For example, en-US-u-co-search would be converted to the ICU name en_US@collation=search, which would then be translated to the .NET name en-US_search. + /// All extensions in the ICU names start with @. When normalizing the name to the .NET format, we retain the extensions in the name to ensure differentiation + /// between names with extensions and those without. For example, we may have a name like en-US and en-US-u-xx. Although .NET doesn't support the extension xx, + /// we still include it in the name to distinguish it from the name without the extension. + /// + private static string NormalizeCultureName(string name, ReadOnlySpan extension, out int collationStart) + { + Debug.Assert(name is not null); + Debug.Assert(name.Length <= ICU_ULOC_FULLNAME_CAPACITY); + + collationStart = -1; + bool changed = false; + Span buffer = stackalloc char[ICU_ULOC_FULLNAME_CAPACITY]; + int bufferIndex = 0; + + for (int i = 0; i < name.Length && bufferIndex < ICU_ULOC_FULLNAME_CAPACITY; i++) + { + char c = name[i]; + if (c == '-' && i < name.Length - 1 && name[i + 1] == '-') + { + // ICU changes names like `qps_plocm` (one underscore) to `qps__plocm` (two underscores) + // The reason this occurs is because, while ICU canonicalizing, ulocimp_getCountry returns an empty string since the country code value is > 3 (rightly so). + // But append an extra '_' thinking that country code was in-fact appended (for the empty string value as well). + // Before processing, the name qps__plocm will be converted to its .NET name equivalent, which is qps--plocm. + changed = true; + buffer[bufferIndex++] = '-'; + i++; + } + else if (c == '@') + { + changed = true; + + if (!extension.IsEmpty && extension.TryCopyTo(buffer.Slice(bufferIndex))) + { + bufferIndex += extension.Length; + } + + int collationIndex = name.IndexOf("collation=", i + 1, StringComparison.Ordinal); + if (collationIndex > 0) + { + collationIndex += "collation=".Length; + + // format of the locale properties is @key=value;collation=collationName;key=value;key=value + int endOfCollation = name.IndexOf(';', collationIndex); + if (endOfCollation < 0) + { + endOfCollation = name.Length; + } + + int length = Math.Min(WINDOWS_MAX_COLLATION_NAME_LENGTH, endOfCollation - collationIndex); // Windows doesn't allow collation names longer than 8 characters + if (buffer.Length - bufferIndex >= length + 1) + { + collationStart = bufferIndex; + buffer[bufferIndex++] = '_'; + name.AsSpan(collationIndex, length).CopyTo(buffer.Slice(bufferIndex)); + bufferIndex += length; + } + } + + // done getting all parts can be supported in the .NET culture names. + break; + } + else + { + buffer[bufferIndex++] = name[i]; + } + } + + return changed ? new string(buffer.Slice(0, bufferIndex)) : name; + } /// /// This method uses the sRealName field (which is initialized by the constructor before this is called) to @@ -26,16 +108,15 @@ private bool InitIcuCultureDataCore() string realNameBuffer = _sRealName; // Basic validation - if (!IsValidCultureName(realNameBuffer, out var index)) + if (!IsValidCultureName(realNameBuffer, out var index, out int indexOfExtensions)) { return false; } // Replace _ (alternate sort) with @collation= for ICU - ReadOnlySpan alternateSortName = default; if (index > 0) { - alternateSortName = realNameBuffer.AsSpan(index + 1); + ReadOnlySpan alternateSortName = realNameBuffer.AsSpan(index + 1); realNameBuffer = string.Concat(realNameBuffer.AsSpan(0, index), ICU_COLLATION_KEYWORD, alternateSortName); } @@ -45,22 +126,9 @@ private bool InitIcuCultureDataCore() return false; // fail } - // Replace the ICU collation keyword with an _ Debug.Assert(_sWindowsName != null); - index = _sWindowsName.IndexOf(ICU_COLLATION_KEYWORD, StringComparison.Ordinal); - if (index >= 0) - { - // Use original culture name if alternateSortName is not set, which is possible even if the normalized - // culture name has "@collation=". - // "zh-TW-u-co-zhuyin" is a good example. The term "u-co-" means the following part will be the sort name - // and it will be treated in ICU as "zh-TW@collation=zhuyin". - _sName = alternateSortName.Length == 0 ? realNameBuffer : string.Concat(_sWindowsName.AsSpan(0, index), "_", alternateSortName); - } - else - { - _sName = _sWindowsName; - } - _sRealName = _sName; + + _sRealName = NormalizeCultureName(_sWindowsName, indexOfExtensions > 0 ? _sRealName.AsSpan(indexOfExtensions) : ReadOnlySpan.Empty, out int collationStart); _iLanguage = LCID; if (_iLanguage == 0) @@ -69,11 +137,11 @@ private bool InitIcuCultureDataCore() } _bNeutral = TwoLetterISOCountryName.Length == 0; _sSpecificCulture = _bNeutral ? IcuLocaleData.GetSpecificCultureName(_sRealName) : _sRealName; + // Remove the sort from sName unless custom culture - if (index > 0 && !_bNeutral && !IsCustomCultureId(_iLanguage)) - { - _sName = _sWindowsName.Substring(0, index); - } + // To ensure compatibility, it is necessary to allow the creation of cultures like zh_CN (using ICU notation) in the case of _bNeutral. + _sName = collationStart < 0 || _bNeutral ? _sRealName : _sRealName.Substring(0, collationStart); + return true; } @@ -369,7 +437,7 @@ private static CultureInfo[] IcuEnumCultures(CultureTypes types) } bool enumNeutrals = (types & CultureTypes.NeutralCultures) != 0; - bool enumSpecificss = (types & CultureTypes.SpecificCultures) != 0; + bool enumSpecifics = (types & CultureTypes.SpecificCultures) != 0; List list = new List(); if (enumNeutrals) @@ -384,7 +452,7 @@ private static CultureInfo[] IcuEnumCultures(CultureTypes types) if (index + length <= bufferLength) { CultureInfo ci = CultureInfo.GetCultureInfo(new string(chars, index, length)); - if ((enumNeutrals && ci.IsNeutralCulture) || (enumSpecificss && !ci.IsNeutralCulture)) + if ((enumNeutrals && ci.IsNeutralCulture) || (enumSpecifics && !ci.IsNeutralCulture)) { list.Add(ci); } @@ -418,10 +486,14 @@ private static string IcuGetConsoleFallbackName(string cultureName) /// * Disallow input that starts or ends with '-' or '_'. /// * Disallow input that has any combination of consecutive '-' or '_'. /// * Disallow input that has multiple '_'. + /// + /// The IsValidCultureName method also identifies the presence of any extensions in the name (such as -u- or -t-) and returns the index of the extension. + /// This is necessary because we need to append the extensions to the name when normalizing it to the .NET format. /// - private static bool IsValidCultureName(string subject, out int indexOfUnderscore) + private static bool IsValidCultureName(string subject, out int indexOfUnderscore, out int indexOfExtensions) { indexOfUnderscore = -1; + indexOfExtensions = -1; if (subject.Length == 0) return true; // Invariant Culture if (subject.Length == 1 || subject.Length > LocaleNameMaxLength) return false; @@ -446,6 +518,16 @@ private static bool IsValidCultureName(string subject, out int indexOfUnderscore seenUnderscore = true; indexOfUnderscore = i; } + else + { + if (indexOfExtensions < 0 && i < subject.Length - 2 && (subject[i + 1] is 'u' or 't') && subject[i + 2] == '-') // we have -u- or -t- which is an extension + { + if (subject[i + 1] == 't' || i >= subject.Length - 6 || subject[i + 3] != 'c' || subject[i + 4] != 'o' || subject[i + 5] != '-' ) // not -u-co- collation extension + { + indexOfExtensions = i; + } + } + } } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs index ef51de122f7179..76d2b06b20b903 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs @@ -264,8 +264,8 @@ internal static int IndexOfOrdinalIgnoreCase(ReadOnlySpan source, ReadOnly { // Do a quick search for the first element of "value". int relativeIndex = isLetter ? - SpanHelpers.IndexOfAny(ref Unsafe.Add(ref searchSpace, offset), valueCharU, valueCharL, searchSpaceLength) : - SpanHelpers.IndexOf(ref Unsafe.Add(ref searchSpace, offset), valueChar, searchSpaceLength); + SpanHelpers.IndexOfAnyChar(ref Unsafe.Add(ref searchSpace, offset), valueCharU, valueCharL, searchSpaceLength) : + SpanHelpers.IndexOfChar(ref Unsafe.Add(ref searchSpace, offset), valueChar, searchSpaceLength); if (relativeIndex < 0) { break; diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index ba931c1f93cec9..ecab10dbc00d66 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -1727,7 +1727,7 @@ private static bool TryConvertFrom(TOther value, out Half result) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(Half value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(Half value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1788,26 +1788,26 @@ static bool INumberBase.TryConvertToChecked(Half value, [NotNullWh } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(Half value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(Half value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(Half value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(Half value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } - private static bool TryConvertTo(Half value, [NotNullWhen(true)] out TOther result) + private static bool TryConvertTo(Half value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase { // In order to reduce overall code duplication and improve the inlinabilty of these @@ -1879,7 +1879,7 @@ private static bool TryConvertTo(Half value, [NotNullWhen(true)] out TOt } else { - result = default!; + result = default; return false; } } 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 67237805c3a744..f538524471a918 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 @@ -328,7 +328,7 @@ private static void CreateParentsAndDirectory(string fullPath, UnixFileMode unix } ReadOnlySpan mkdirPath = fullPath.AsSpan(0, i); - int result = Interop.Sys.MkDir(mkdirPath, (int)unixCreateMode); + int result = Interop.Sys.MkDir(mkdirPath, (int)DefaultUnixCreateDirectoryMode); if (result == 0) { break; // Created parent. @@ -360,7 +360,8 @@ private static void CreateParentsAndDirectory(string fullPath, UnixFileMode unix for (i = stackDir.Length - 1; i >= 0; i--) { ReadOnlySpan mkdirPath = fullPath.AsSpan(0, stackDir[i]); - int result = Interop.Sys.MkDir(mkdirPath, (int)unixCreateMode); + UnixFileMode mode = i == 0 ? unixCreateMode : DefaultUnixCreateDirectoryMode; + int result = Interop.Sys.MkDir(mkdirPath, (int)mode); if (result < 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); 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 2da113fe16f314..63d3621d001fcf 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 @@ -326,6 +326,8 @@ public override ValueTask ReadAsync(Memory buffer, CancellationToken { if (_readLen == _readPos && buffer.Length >= _bufferSize) { + // invalidate the buffered data, otherwise certain Seek operation followed by a ReadAsync could try to re-use data from _buffer + _readPos = _readLen = 0; // hot path #1: the read buffer is empty and buffering would not be beneficial // To find out why we are bypassing cache here, please see WriteAsync comments. return _strategy.ReadAsync(buffer, cancellationToken); diff --git a/src/libraries/System.Private.CoreLib/src/System/IParsable.cs b/src/libraries/System.Private.CoreLib/src/System/IParsable.cs index af220483d640ef..e2891fbd689475 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IParsable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IParsable.cs @@ -8,7 +8,7 @@ namespace System /// Defines a mechanism for parsing a string to a value. /// The type that implements this interface. public interface IParsable - where TSelf : IParsable + where TSelf : IParsable? { /// Parses a string into a value. /// The string to parse. diff --git a/src/libraries/System.Private.CoreLib/src/System/ISpanParsable.cs b/src/libraries/System.Private.CoreLib/src/System/ISpanParsable.cs index 8ad117cfa4c6eb..2e24d5173e2fbd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ISpanParsable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ISpanParsable.cs @@ -8,7 +8,7 @@ namespace System /// Defines a mechanism for parsing a span of characters to a value. /// The type that implements this interface. public interface ISpanParsable : IParsable - where TSelf : ISpanParsable + where TSelf : ISpanParsable? { /// Parses a span of characters into a value. /// The span of characters to parse. diff --git a/src/libraries/System.Private.CoreLib/src/System/Int128.cs b/src/libraries/System.Private.CoreLib/src/System/Int128.cs index b67d6e89a7006b..12083208808100 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int128.cs @@ -1156,7 +1156,7 @@ public static Int128 Log2(Int128 value) } // We simplify the logic here by just doing unsigned division on the - // one's complement representation and then taking the correct sign. + // two's complement representation and then taking the correct sign. ulong sign = (left._upper ^ right._upper) & (1UL << 63); @@ -1172,17 +1172,13 @@ public static Int128 Log2(Int128 value) UInt128 result = (UInt128)(left) / (UInt128)(right); - if (result == 0U) - { - sign = 0; - } - else if (sign != 0) + if (sign != 0) { result = ~result + 1U; } return new Int128( - result.Upper | sign, + result.Upper, result.Lower ); } @@ -1227,36 +1223,8 @@ public static Int128 Log2(Int128 value) /// public static Int128 operator %(Int128 left, Int128 right) { - // We simplify the logic here by just doing unsigned modulus on the - // one's complement representation and then taking the correct sign. - - ulong sign = (left._upper ^ right._upper) & (1UL << 63); - - if (IsNegative(left)) - { - left = ~left + 1U; - } - - if (IsNegative(right)) - { - right = ~right + 1U; - } - - UInt128 result = (UInt128)(left) % (UInt128)(right); - - if (result == 0U) - { - sign = 0; - } - else if (sign != 0) - { - result = ~result + 1U; - } - - return new Int128( - result.Upper | sign, - result.Lower - ); + Int128 quotient = left / right; + return left - (quotient * right); } // @@ -1273,76 +1241,43 @@ public static Int128 Log2(Int128 value) /// public static Int128 operator *(Int128 left, Int128 right) { - // We simplify the logic here by just doing unsigned multiplication on - // the one's complement representation and then taking the correct sign. - - ulong sign = (left._upper ^ right._upper) & (1UL << 63); - - if (IsNegative(left)) - { - left = ~left + 1U; - } - - if (IsNegative(right)) - { - right = ~right + 1U; - } - - UInt128 result = (UInt128)(left) * (UInt128)(right); - - if (result == 0U) - { - sign = 0; - } - else if (sign != 0) - { - result = ~result + 1U; - } - - return new Int128( - result.Upper | sign, - result.Lower - ); + // Multiplication is the same for signed and unsigned provided the "upper" bits aren't needed + return (Int128)((UInt128)(left) * (UInt128)(right)); } /// public static Int128 operator checked *(Int128 left, Int128 right) { - // We simplify the logic here by just doing unsigned multiplication on - // the one's complement representation and then taking the correct sign. - - ulong sign = (left._upper ^ right._upper) & (1UL << 63); + Int128 upper = BigMul(left, right, out Int128 lower); - if (IsNegative(left)) + if (((upper != 0) || (lower < 0)) && ((~upper != 0) || (lower >= 0))) { - left = ~left + 1U; - } + // The upper bits can safely be either Zero or AllBitsSet + // where the former represents a positive value and the + // latter a negative value. + // + // However, when the upper bits are Zero, we also need to + // confirm the lower bits are positive, otherwise we have + // a positive value greater than MaxValue and should throw + // + // Likewise, when the upper bits are AllBitsSet, we also + // need to confirm the lower bits are negative, otherwise + // we have a large negative value less than MinValue and + // should throw. - if (IsNegative(right)) - { - right = ~right + 1U; - } - - UInt128 result = checked((UInt128)(left) * (UInt128)(right)); - - if ((long)(result.Upper) < 0) - { ThrowHelper.ThrowOverflowException(); } - if (result == 0U) - { - sign = 0; - } - else if (sign != 0) - { - result = ~result + 1U; - } + return lower; + } - return new Int128( - result.Upper | sign, - result.Lower - ); + internal static Int128 BigMul(Int128 left, Int128 right, out Int128 lower) + { + // This follows the same logic as is used in `long Math.BigMul(long, long, out long)` + + UInt128 upper = UInt128.BigMul((UInt128)(left), (UInt128)(right), out UInt128 ulower); + lower = (Int128)(ulower); + return (Int128)(upper) - ((left >> 127) & right) - ((right >> 127) & left); } // @@ -1865,7 +1800,7 @@ private static bool TryConvertFromTruncating(TOther value, out Int128 re /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(Int128 value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(Int128 value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1926,14 +1861,14 @@ static bool INumberBase.TryConvertToChecked(Int128 value, [NotNu } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(Int128 value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(Int128 value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -2000,14 +1935,14 @@ static bool INumberBase.TryConvertToSaturating(Int128 value, [No } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(Int128 value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(Int128 value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -2069,7 +2004,7 @@ static bool INumberBase.TryConvertToTruncating(Int128 value, [No } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Int16.cs b/src/libraries/System.Private.CoreLib/src/System/Int16.cs index a357d272360fa9..94fe0e4844f084 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int16.cs @@ -1148,7 +1148,7 @@ private static bool TryConvertFromTruncating(TOther value, out short res /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(short value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(short value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1209,14 +1209,14 @@ static bool INumberBase.TryConvertToChecked(short value, [NotNull } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(short value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(short value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1278,14 +1278,14 @@ static bool INumberBase.TryConvertToSaturating(short value, [NotN } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(short value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(short value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1346,7 +1346,7 @@ static bool INumberBase.TryConvertToTruncating(short value, [NotN } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Int32.cs b/src/libraries/System.Private.CoreLib/src/System/Int32.cs index 317c789c7d676a..7b7c3f2765fb10 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int32.cs @@ -1158,7 +1158,7 @@ private static bool TryConvertFromTruncating(TOther value, out int resul /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(int value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(int value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1219,14 +1219,14 @@ static bool INumberBase.TryConvertToChecked(int value, [NotNullWhen } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(int value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(int value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1290,14 +1290,14 @@ static bool INumberBase.TryConvertToSaturating(int value, [NotNullW } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(int value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(int value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1358,7 +1358,7 @@ static bool INumberBase.TryConvertToTruncating(int value, [NotNullW } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Int64.cs b/src/libraries/System.Private.CoreLib/src/System/Int64.cs index 569240329da35c..8752d93c2f8372 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int64.cs @@ -1143,7 +1143,7 @@ private static bool TryConvertFromTruncating(TOther value, out long resu /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(long value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(long value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1204,14 +1204,14 @@ static bool INumberBase.TryConvertToChecked(long value, [NotNullWh } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(long value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(long value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1283,14 +1283,14 @@ static bool INumberBase.TryConvertToSaturating(long value, [NotNul } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(long value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(long value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1351,7 +1351,7 @@ static bool INumberBase.TryConvertToTruncating(long value, [NotNul } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs b/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs index 9f6b414343b15e..3fbed0cea35200 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs @@ -1117,7 +1117,7 @@ private static bool TryConvertFromTruncating(TOther value, out nint resu /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(nint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(nint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1178,14 +1178,14 @@ static bool INumberBase.TryConvertToChecked(nint value, [NotNullWh } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(nint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(nint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1250,14 +1250,14 @@ static bool INumberBase.TryConvertToSaturating(nint value, [NotNul } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(nint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(nint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1318,7 +1318,7 @@ static bool INumberBase.TryConvertToTruncating(nint value, [NotNul } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 658a464d6d3d2d..fd6229f70c1b05 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -7,7 +7,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; namespace System { @@ -269,28 +268,33 @@ public static bool Contains(this Span span, T value) where T : IEquatable< if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.Contains( + { + return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.Contains( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.ContainsValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), span.Length); - - if (Unsafe.SizeOf() == sizeof(int)) - return 0 <= SpanHelpers.IndexOfValueType( + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); - - if (Unsafe.SizeOf() == sizeof(long)) - return 0 <= SpanHelpers.IndexOfValueType( + } + else if (Unsafe.SizeOf() == sizeof(long)) + { + return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); + } } return SpanHelpers.Contains(ref MemoryMarshal.GetReference(span), value, span.Length); @@ -308,28 +312,33 @@ public static bool Contains(this ReadOnlySpan span, T value) where T : IEq if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.Contains( + { + return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.Contains( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.ContainsValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), span.Length); - - if (Unsafe.SizeOf() == sizeof(int)) - return 0 <= SpanHelpers.IndexOfValueType( + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); - - if (Unsafe.SizeOf() == sizeof(long)) - return 0 <= SpanHelpers.IndexOfValueType( + } + else if (Unsafe.SizeOf() == sizeof(long)) + { + return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); + } } return SpanHelpers.Contains(ref MemoryMarshal.GetReference(span), value, span.Length); @@ -346,15 +355,15 @@ public static int IndexOf(this Span span, T value) where T : IEquatable if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.IndexOf( + return SpanHelpers.IndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.IndexOf( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + if (Unsafe.SizeOf() == sizeof(short)) + return SpanHelpers.IndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), span.Length); if (Unsafe.SizeOf() == sizeof(int)) @@ -412,16 +421,33 @@ public static int LastIndexOf(this Span span, T value) where T : IEquatabl if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.LastIndexOf( + { + return SpanHelpers.LastIndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.LastIndexOf( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + return SpanHelpers.LastIndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), span.Length); + } + else if (Unsafe.SizeOf() == sizeof(long)) + { + return SpanHelpers.LastIndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } } return SpanHelpers.LastIndexOf(ref MemoryMarshal.GetReference(span), value, span.Length); @@ -503,7 +529,7 @@ public static int IndexOfAnyExcept(this Span span, ReadOnlySpan values) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOfAnyExcept(this ReadOnlySpan span, T value) where T : IEquatable? { - if (RuntimeHelpers.IsBitwiseEquatable()) + if (SpanHelpers.CanVectorizeAndBenefit(span.Length)) { if (Unsafe.SizeOf() == sizeof(byte)) { @@ -512,36 +538,34 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); } - - if (Unsafe.SizeOf() == sizeof(short)) + else if (Unsafe.SizeOf() == sizeof(short)) { return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); } - - if (Unsafe.SizeOf() == sizeof(int)) + else if (Unsafe.SizeOf() == sizeof(int)) { return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); } - - if (Unsafe.SizeOf() == sizeof(long)) + else { + Debug.Assert(Unsafe.SizeOf() == sizeof(long)); + return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); } } - - return SpanHelpers.IndexOfAnyExcept( - ref MemoryMarshal.GetReference(span), - value, - span.Length); + else + { + return SpanHelpers.IndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value, span.Length); + } } /// Searches for the first index of any value other than the specified or . @@ -553,18 +577,30 @@ ref MemoryMarshal.GetReference(span), /// The index in the span of the first occurrence of any value other than and . /// If all of the values are or , returns -1. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1) where T : IEquatable? { - for (int i = 0; i < span.Length; i++) + if (SpanHelpers.CanVectorizeAndBenefit(span.Length)) { - if (!EqualityComparer.Default.Equals(span[i], value0) && - !EqualityComparer.Default.Equals(span[i], value1)) + if (Unsafe.SizeOf() == sizeof(byte)) { - return i; + return SpanHelpers.IndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.IndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); } } - return -1; + return SpanHelpers.IndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value0, value1, span.Length); } /// Searches for the first index of any value other than the specified , , or . @@ -577,25 +613,38 @@ public static int IndexOfAnyExcept(this ReadOnlySpan span, T value0, T val /// The index in the span of the first occurrence of any value other than , , and . /// If all of the values are , , and , returns -1. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1, T value2) where T : IEquatable? { - for (int i = 0; i < span.Length; i++) + if (RuntimeHelpers.IsBitwiseEquatable()) { - if (!EqualityComparer.Default.Equals(span[i], value0) && - !EqualityComparer.Default.Equals(span[i], value1) && - !EqualityComparer.Default.Equals(span[i], value2)) + if (Unsafe.SizeOf() == sizeof(byte)) { - return i; + return SpanHelpers.IndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.IndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); } } - return -1; + return SpanHelpers.IndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int IndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1, T value2, T value3) where T : IEquatable? + private static int IndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1, T value2, T value3) where T : IEquatable? { - if (RuntimeHelpers.IsBitwiseEquatable()) + if (SpanHelpers.CanVectorizeAndBenefit(span.Length)) { if (Unsafe.SizeOf() == sizeof(byte)) { @@ -617,40 +666,9 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value3), span.Length); } - else if (Unsafe.SizeOf() == sizeof(int)) - { - return SpanHelpers.IndexOfAnyExceptValueType( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), - Unsafe.As(ref value3), - span.Length); - } - else if (Unsafe.SizeOf() == sizeof(long)) - { - return SpanHelpers.IndexOfAnyExceptValueType( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), - Unsafe.As(ref value3), - span.Length); - } - } - - for (int i = 0; i < span.Length; i++) - { - if (!EqualityComparer.Default.Equals(span[i], value0) && - !EqualityComparer.Default.Equals(span[i], value1) && - !EqualityComparer.Default.Equals(span[i], value2) && - !EqualityComparer.Default.Equals(span[i], value3)) - { - return i; - } } - return -1; + return SpanHelpers.IndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value0, value1, value2, value3, span.Length); } /// Searches for the first index of any value other than the specified . @@ -680,7 +698,7 @@ public static int IndexOfAnyExcept(this ReadOnlySpan span, ReadOnlySpan case 3: return IndexOfAnyExcept(span, values[0], values[1], values[2]); - case 4: // common for searching whitespaces + case 4: return IndexOfAnyExcept(span, values[0], values[1], values[2], values[3]); default: @@ -751,17 +769,46 @@ public static int LastIndexOfAnyExcept(this Span span, ReadOnlySpan val /// The index in the span of the last occurrence of any value other than . /// If all of the values are , returns -1. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOfAnyExcept(this ReadOnlySpan span, T value) where T : IEquatable? { - for (int i = span.Length - 1; i >= 0; i--) + if (SpanHelpers.CanVectorizeAndBenefit(span.Length)) { - if (!EqualityComparer.Default.Equals(span[i], value)) + if (Unsafe.SizeOf() == sizeof(byte)) { - return i; + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); } - } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + else + { + Debug.Assert(Unsafe.SizeOf() == sizeof(long)); - return -1; + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + } + else + { + return SpanHelpers.LastIndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value, span.Length); + } } /// Searches for the last index of any value other than the specified or . @@ -773,18 +820,30 @@ public static int LastIndexOfAnyExcept(this ReadOnlySpan span, T value) wh /// The index in the span of the last occurrence of any value other than and . /// If all of the values are or , returns -1. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1) where T : IEquatable? { - for (int i = span.Length - 1; i >= 0; i--) + if (SpanHelpers.CanVectorizeAndBenefit(span.Length)) { - if (!EqualityComparer.Default.Equals(span[i], value0) && - !EqualityComparer.Default.Equals(span[i], value1)) + if (Unsafe.SizeOf() == sizeof(byte)) { - return i; + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); } } - return -1; + return SpanHelpers.LastIndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value0, value1, span.Length); } /// Searches for the last index of any value other than the specified , , or . @@ -797,19 +856,62 @@ public static int LastIndexOfAnyExcept(this ReadOnlySpan span, T value0, T /// The index in the span of the last occurrence of any value other than , , and . /// If all of the values are , , and , returns -1. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1, T value2) where T : IEquatable? { - for (int i = span.Length - 1; i >= 0; i--) + if (RuntimeHelpers.IsBitwiseEquatable()) { - if (!EqualityComparer.Default.Equals(span[i], value0) && - !EqualityComparer.Default.Equals(span[i], value1) && - !EqualityComparer.Default.Equals(span[i], value2)) + if (Unsafe.SizeOf() == sizeof(byte)) { - return i; + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); } } - return -1; + return SpanHelpers.LastIndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LastIndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1, T value2, T value3) where T : IEquatable? + { + if (SpanHelpers.CanVectorizeAndBenefit(span.Length)) + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + Unsafe.As(ref value3), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + Unsafe.As(ref value3), + span.Length); + } + } + + return SpanHelpers.LastIndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value0, value1, value2, value3, span.Length); } /// Searches for the last index of any value other than the specified . @@ -840,6 +942,9 @@ public static int LastIndexOfAnyExcept(this ReadOnlySpan span, ReadOnlySpa case 3: return LastIndexOfAnyExcept(span, values[0], values[1], values[2]); + case 4: + return LastIndexOfAnyExcept(span, values[0], values[1], values[2], values[3]); + default: for (int i = span.Length - 1; i >= 0; i--) { @@ -912,15 +1017,27 @@ public static int IndexOf(this ReadOnlySpan span, T value) where T : IEqua if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.IndexOf( + return SpanHelpers.IndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.IndexOf( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + if (Unsafe.SizeOf() == sizeof(short)) + return SpanHelpers.IndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + + if (Unsafe.SizeOf() == sizeof(int)) + return SpanHelpers.IndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + + if (Unsafe.SizeOf() == sizeof(long)) + return SpanHelpers.IndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), span.Length); } @@ -966,16 +1083,33 @@ public static int LastIndexOf(this ReadOnlySpan span, T value) where T : I if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.LastIndexOf( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), - span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.LastIndexOf( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + { + return SpanHelpers.LastIndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + return SpanHelpers.LastIndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(long)) + { + return SpanHelpers.LastIndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } } return SpanHelpers.LastIndexOf(ref MemoryMarshal.GetReference(span), value, span.Length); @@ -1024,18 +1158,21 @@ public static int IndexOfAny(this Span span, T value0, T value1) where T : if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.IndexOfAny( + { + return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value0), Unsafe.As(ref value1), span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.IndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.IndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), span.Length); + } } return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length); @@ -1054,20 +1191,23 @@ public static int IndexOfAny(this Span span, T value0, T value1, T value2) if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.IndexOfAny( + { + return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value0), Unsafe.As(ref value1), Unsafe.As(ref value2), span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.IndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.IndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), span.Length); + } } return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length); @@ -1094,18 +1234,21 @@ public static int IndexOfAny(this ReadOnlySpan span, T value0, T value1) w if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.IndexOfAny( + { + return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value0), Unsafe.As(ref value1), span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.IndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.IndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), span.Length); + } } return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length); @@ -1124,20 +1267,23 @@ public static int IndexOfAny(this ReadOnlySpan span, T value0, T value1, T if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.IndexOfAny( + { + return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value0), Unsafe.As(ref value1), Unsafe.As(ref value2), span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.IndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.IndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), span.Length); + } } return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length); @@ -1158,7 +1304,7 @@ public static int IndexOfAny(this ReadOnlySpan span, ReadOnlySpan value ref byte valueRef = ref Unsafe.As(ref MemoryMarshal.GetReference(values)); if (values.Length == 2) { - return SpanHelpers.IndexOfAny( + return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), valueRef, Unsafe.Add(ref valueRef, 1), @@ -1166,7 +1312,7 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), } else if (values.Length == 3) { - return SpanHelpers.IndexOfAny( + return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), valueRef, Unsafe.Add(ref valueRef, 1), @@ -1175,30 +1321,27 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), } } - if (Unsafe.SizeOf() == sizeof(char)) + if (Unsafe.SizeOf() == sizeof(short)) { - ref char spanRef = ref Unsafe.As(ref MemoryMarshal.GetReference(span)); - ref char valueRef = ref Unsafe.As(ref MemoryMarshal.GetReference(values)); + ref short spanRef = ref Unsafe.As(ref MemoryMarshal.GetReference(span)); + ref short valueRef = ref Unsafe.As(ref MemoryMarshal.GetReference(values)); switch (values.Length) { case 0: return -1; case 1: - return SpanHelpers.IndexOf( - ref spanRef, - valueRef, - span.Length); + return SpanHelpers.IndexOfValueType(ref spanRef, valueRef, span.Length); case 2: - return SpanHelpers.IndexOfAny( + return SpanHelpers.IndexOfAnyValueType( ref spanRef, valueRef, Unsafe.Add(ref valueRef, 1), span.Length); case 3: - return SpanHelpers.IndexOfAny( + return SpanHelpers.IndexOfAnyValueType( ref spanRef, valueRef, Unsafe.Add(ref valueRef, 1), @@ -1206,7 +1349,7 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length); case 4: - return SpanHelpers.IndexOfAny( + return SpanHelpers.IndexOfAnyValueType( ref spanRef, valueRef, Unsafe.Add(ref valueRef, 1), @@ -1215,7 +1358,7 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length); case 5: - return SpanHelpers.IndexOfAny( + return SpanHelpers.IndexOfAnyValueType( ref spanRef, valueRef, Unsafe.Add(ref valueRef, 1), @@ -1225,7 +1368,7 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length); default: - return IndexOfAnyProbabilistic(ref spanRef, span.Length, ref valueRef, values.Length); + return IndexOfAnyProbabilistic(ref Unsafe.As(ref spanRef), span.Length, ref Unsafe.As(ref valueRef), values.Length); } } } @@ -1272,12 +1415,25 @@ private static unsafe int IndexOfAnyProbabilistic(ref char searchSpace, int sear [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOfAny(this Span span, T value0, T value1) where T : IEquatable? { - if (Unsafe.SizeOf() == sizeof(byte) && RuntimeHelpers.IsBitwiseEquatable()) - return SpanHelpers.LastIndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - span.Length); + if (RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); + } + } return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length); } @@ -1292,13 +1448,27 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOfAny(this Span span, T value0, T value1, T value2) where T : IEquatable? { - if (Unsafe.SizeOf() == sizeof(byte) && RuntimeHelpers.IsBitwiseEquatable()) - return SpanHelpers.LastIndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), - span.Length); + if (RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); + } + } return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length); } @@ -1330,12 +1500,25 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(values)), [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOfAny(this ReadOnlySpan span, T value0, T value1) where T : IEquatable? { - if (Unsafe.SizeOf() == sizeof(byte) && RuntimeHelpers.IsBitwiseEquatable()) - return SpanHelpers.LastIndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - span.Length); + if (RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); + } + } return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length); } @@ -1350,13 +1533,27 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOfAny(this ReadOnlySpan span, T value0, T value1, T value2) where T : IEquatable? { - if (Unsafe.SizeOf() == sizeof(byte) && RuntimeHelpers.IsBitwiseEquatable()) - return SpanHelpers.LastIndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), - span.Length); + if (RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); + } + } return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length); } @@ -1371,28 +1568,68 @@ public static int LastIndexOfAny(this ReadOnlySpan span, ReadOnlySpan v { if (RuntimeHelpers.IsBitwiseEquatable()) { - if (Unsafe.SizeOf() == sizeof(char)) + if (Unsafe.SizeOf() == sizeof(byte)) { + ref byte valueRef = ref Unsafe.As(ref MemoryMarshal.GetReference(values)); + if (values.Length == 2) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + valueRef, + Unsafe.Add(ref valueRef, 1), + span.Length); + } + else if (values.Length == 3) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + valueRef, + Unsafe.Add(ref valueRef, 1), + Unsafe.Add(ref valueRef, 2), + span.Length); + } + } + + if (Unsafe.SizeOf() == sizeof(short)) + { + ref short spanRef = ref Unsafe.As(ref MemoryMarshal.GetReference(span)); + ref short valueRef = ref Unsafe.As(ref MemoryMarshal.GetReference(values)); switch (values.Length) { case 0: return -1; case 1: - return LastIndexOf(span, values[0]); + return SpanHelpers.LastIndexOfValueType(ref spanRef, valueRef, span.Length); case 2: - return LastIndexOfAny(span, values[0], values[1]); + return SpanHelpers.LastIndexOfAnyValueType( + ref spanRef, + valueRef, + Unsafe.Add(ref valueRef, 1), + span.Length); case 3: - return LastIndexOfAny(span, values[0], values[1], values[2]); + return SpanHelpers.LastIndexOfAnyValueType( + ref spanRef, + valueRef, + Unsafe.Add(ref valueRef, 1), + Unsafe.Add(ref valueRef, 2), + span.Length); + +#if !MONO // We don't have a mono overload for 4 values + case 4: + return SpanHelpers.LastIndexOfAnyValueType( + ref spanRef, + valueRef, + Unsafe.Add(ref valueRef, 1), + Unsafe.Add(ref valueRef, 2), + Unsafe.Add(ref valueRef, 3), + span.Length); +#endif default: - return LastIndexOfAnyProbabilistic( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - span.Length, - ref Unsafe.As(ref MemoryMarshal.GetReference(values)), - values.Length); + return LastIndexOfAnyProbabilistic(ref Unsafe.As(ref spanRef), span.Length, ref Unsafe.As(ref valueRef), values.Length); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs b/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs index c19e7d036b04b3..f7e613f9628298 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs @@ -317,7 +317,7 @@ internal unsafe ref struct BigInteger private int _length; private fixed uint _blocks[MaxBlockCount]; - public static void Add(ref BigInteger lhs, ref BigInteger rhs, out BigInteger result) + public static void Add(scoped ref BigInteger lhs, scoped ref BigInteger rhs, out BigInteger result) { // determine which operand has the smaller length ref BigInteger large = ref (lhs._length < rhs._length) ? ref rhs : ref lhs; @@ -369,7 +369,7 @@ public static void Add(ref BigInteger lhs, ref BigInteger rhs, out BigInteger re } } - public static int Compare(ref BigInteger lhs, ref BigInteger rhs) + public static int Compare(scoped ref BigInteger lhs, scoped ref BigInteger rhs) { Debug.Assert(unchecked((uint)(lhs._length)) <= MaxBlockCount); Debug.Assert(unchecked((uint)(rhs._length)) <= MaxBlockCount); @@ -427,7 +427,7 @@ public static uint CountSignificantBits(ref BigInteger value) return (lastIndex * BitsPerBlock) + CountSignificantBits(value._blocks[lastIndex]); } - public static void DivRem(ref BigInteger lhs, ref BigInteger rhs, out BigInteger quo, out BigInteger rem) + public static void DivRem(scoped ref BigInteger lhs, scoped ref BigInteger rhs, out BigInteger quo, out BigInteger rem) { // This is modified from the libraries BigIntegerCalculator.DivRem.cs implementation: // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.DivRem.cs @@ -558,6 +558,11 @@ public static void DivRem(ref BigInteger lhs, ref BigInteger rhs, out BigInteger if (digit > 0) { + // rem and rhs have different lifetimes here and compiler is warning + // about potential for one to copy into the other. This is a place + // ref scoped parameters would alleviate. + // https://github.com/dotnet/roslyn/issues/64393 +#pragma warning disable CS9080 // Now it's time to subtract our current quotient uint carry = SubtractDivisor(ref rem, n, ref rhs, digit); @@ -571,6 +576,7 @@ public static void DivRem(ref BigInteger lhs, ref BigInteger rhs, out BigInteger Debug.Assert(carry == 1); } +#pragma warning restore CS9080 } // We have the digit! @@ -693,7 +699,7 @@ public static uint HeuristicDivide(ref BigInteger dividend, ref BigInteger divis return quotient; } - public static void Multiply(ref BigInteger lhs, uint value, out BigInteger result) + public static void Multiply(scoped ref BigInteger lhs, uint value, out BigInteger result) { if (lhs._length <= 1) { @@ -739,7 +745,7 @@ public static void Multiply(ref BigInteger lhs, uint value, out BigInteger resul } } - public static void Multiply(ref BigInteger lhs, ref BigInteger rhs, out BigInteger result) + public static void Multiply(scoped ref BigInteger lhs, scoped ref BigInteger rhs, out BigInteger result) { if (lhs._length <= 1) { @@ -1032,7 +1038,7 @@ public void Multiply(uint value) Multiply(ref this, value, out this); } - public void Multiply(ref BigInteger value) + public void Multiply(scoped ref BigInteger value) { if (value._length <= 1) { @@ -1115,7 +1121,7 @@ public static void SetUInt64(out BigInteger result, ulong value) } } - public static void SetValue(out BigInteger result, ref BigInteger value) + public static void SetValue(out BigInteger result, scoped ref BigInteger value) { int rhsLength = value._length; result._length = rhsLength; diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs index e88014c3a95183..bec85ff737bab6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs @@ -794,7 +794,7 @@ public FloatingPointInfo(ushort denormalMantissaBits, ushort exponentBits, int m 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648 }; - private static void AccumulateDecimalDigitsIntoBigInteger(ref NumberBuffer number, uint firstIndex, uint lastIndex, out BigInteger result) + private static void AccumulateDecimalDigitsIntoBigInteger(scoped ref NumberBuffer number, uint firstIndex, uint lastIndex, out BigInteger result) { BigInteger.SetZero(out result); diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditionOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditionOperators.cs index ca11868381a295..ecdfe953a0c8ca 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditionOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditionOperators.cs @@ -8,7 +8,7 @@ namespace System.Numerics /// The type that will be added to . /// The type that contains the sum of and . public interface IAdditionOperators - where TSelf : IAdditionOperators + where TSelf : IAdditionOperators? { /// Adds two values together to compute their sum. /// The value to which is added. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditiveIdentity.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditiveIdentity.cs index 1e95b5fb35cc86..be11ca7e2fbf7a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditiveIdentity.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditiveIdentity.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. /// The type that contains the additive identify of . public interface IAdditiveIdentity - where TSelf : IAdditiveIdentity + where TSelf : IAdditiveIdentity? { /// Gets the additive identity of the current type. static abstract TResult AdditiveIdentity { get; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryFloatingPointIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryFloatingPointIeee754.cs index 7e6565579d4ff0..151ae4c11d0829 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryFloatingPointIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryFloatingPointIeee754.cs @@ -8,7 +8,7 @@ namespace System.Numerics public interface IBinaryFloatingPointIeee754 : IBinaryNumber, IFloatingPointIeee754 - where TSelf : IBinaryFloatingPointIeee754 + where TSelf : IBinaryFloatingPointIeee754? { } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryInteger.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryInteger.cs index e182d35b04246b..067d6c338384e1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryInteger.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryInteger.cs @@ -8,7 +8,7 @@ namespace System.Numerics public interface IBinaryInteger : IBinaryNumber, IShiftOperators - where TSelf : IBinaryInteger + where TSelf : IBinaryInteger? { /// Computes the quotient and remainder of two values. /// The value which divides. @@ -25,7 +25,12 @@ static virtual (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right) /// The number of leading zeros in . static virtual TSelf LeadingZeroCount(TSelf value) { - TSelf bitCount = TSelf.CreateChecked(value.GetByteCount() * 8L); + if (!typeof(TSelf).IsValueType) + { + ArgumentNullException.ThrowIfNull(value); + } + + TSelf bitCount = TSelf.CreateChecked(value!.GetByteCount() * 8L); if (value == TSelf.Zero) { @@ -132,7 +137,12 @@ static virtual TSelf ReadLittleEndian(ReadOnlySpan source, bool isUnsigned /// The result of rotating left by . static virtual TSelf RotateLeft(TSelf value, int rotateAmount) { - int bitCount = checked(value.GetByteCount() * 8); + if (!typeof(TSelf).IsValueType) + { + ArgumentNullException.ThrowIfNull(value); + } + + int bitCount = checked(value!.GetByteCount() * 8); return (value << rotateAmount) | (value >> (bitCount - rotateAmount)); } @@ -142,7 +152,12 @@ static virtual TSelf RotateLeft(TSelf value, int rotateAmount) /// The result of rotating right by . static virtual TSelf RotateRight(TSelf value, int rotateAmount) { - int bitCount = checked(value.GetByteCount() * 8); + if (!typeof(TSelf).IsValueType) + { + ArgumentNullException.ThrowIfNull(value); + } + + int bitCount = checked(value!.GetByteCount() * 8); return (value >> rotateAmount) | (value << (bitCount - rotateAmount)); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryNumber.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryNumber.cs index 9b45fb59cc73b1..5dbe5bc9576098 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryNumber.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryNumber.cs @@ -8,7 +8,7 @@ namespace System.Numerics public interface IBinaryNumber : IBitwiseOperators, INumber - where TSelf : IBinaryNumber + where TSelf : IBinaryNumber? { /// Gets an instance of the binary type in which all bits are set. static virtual TSelf AllBitsSet => ~TSelf.Zero; diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBitwiseOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBitwiseOperators.cs index 15a7aa311f6427..36399a1b6d96c1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBitwiseOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBitwiseOperators.cs @@ -8,7 +8,7 @@ namespace System.Numerics /// The type that will is used in the operation with . /// The type that contains the result of op . public interface IBitwiseOperators - where TSelf : IBitwiseOperators + where TSelf : IBitwiseOperators? { /// Computes the bitwise-and of two values. /// The value to and with . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IComparisonOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IComparisonOperators.cs index d6b8883aa718c9..474ea46e7f656c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IComparisonOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IComparisonOperators.cs @@ -9,7 +9,7 @@ namespace System.Numerics /// The type that is returned as a result of the comparison. public interface IComparisonOperators : IEqualityOperators - where TSelf : IComparisonOperators + where TSelf : IComparisonOperators? { /// Compares two values to determine which is less. /// The value to compare with . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IDecrementOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IDecrementOperators.cs index 159ad6bc44e2dc..edc0022c584479 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IDecrementOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IDecrementOperators.cs @@ -6,7 +6,7 @@ namespace System.Numerics /// Defines a mechanism for decrementing a given value. /// The type that implements this interface. public interface IDecrementOperators - where TSelf : IDecrementOperators + where TSelf : IDecrementOperators? { /// Decrements a value. /// The value to decrement. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IDivisionOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IDivisionOperators.cs index a8ed07a6335910..20535020efcd71 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IDivisionOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IDivisionOperators.cs @@ -8,7 +8,7 @@ namespace System.Numerics /// The type that will divide . /// The type that contains the quotient of and . public interface IDivisionOperators - where TSelf : IDivisionOperators + where TSelf : IDivisionOperators? { /// Divides two values together to compute their quotient. /// The value which divides. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IEqualityOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IEqualityOperators.cs index 397e4489aefbeb..ebf11d8576e4ed 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IEqualityOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IEqualityOperators.cs @@ -8,18 +8,18 @@ namespace System.Numerics /// The type that will be compared with . /// The type that is returned as a result of the comparison. public interface IEqualityOperators - where TSelf : IEqualityOperators + where TSelf : IEqualityOperators? { /// Compares two values to determine equality. /// The value to compare with . /// The value to compare with . /// true if is equal to ; otherwise, false. - static abstract TResult operator ==(TSelf left, TOther right); + static abstract TResult operator ==(TSelf? left, TOther? right); /// Compares two values to determine inequality. /// The value to compare with . /// The value to compare with . /// true if is not equal to ; otherwise, false. - static abstract TResult operator !=(TSelf left, TOther right); + static abstract TResult operator !=(TSelf? left, TOther? right); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IExponentialFunctions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IExponentialFunctions.cs index 85660ab3ba3c8b..b060924c8ceadb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IExponentialFunctions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IExponentialFunctions.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. public interface IExponentialFunctions : IFloatingPointConstants - where TSelf : IExponentialFunctions + where TSelf : IExponentialFunctions? { /// Computes E raised to a given power. /// The power to which E is raised. @@ -37,6 +37,6 @@ public interface IExponentialFunctions /// Computes 10 raised to a given power and subtracts one. /// The power to which 10 is raised. /// 10 - 1 - static virtual TSelf Exp10M1(TSelf x) => TSelf.Exp10M1(x) - TSelf.One; + static virtual TSelf Exp10M1(TSelf x) => TSelf.Exp10(x) - TSelf.One; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPoint.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPoint.cs index 9b5f9757543616..c8e58d8f8fb826 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPoint.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPoint.cs @@ -9,7 +9,7 @@ public interface IFloatingPoint : IFloatingPointConstants, INumber, ISignedNumber - where TSelf : IFloatingPoint + where TSelf : IFloatingPoint? { /// Computes the ceiling of a value. /// The value whose ceiling is to be computed. @@ -36,7 +36,7 @@ public interface IFloatingPoint /// The value to round. /// The mode under which should be rounded. /// The result of rounding to the nearest integer using . - static virtual TSelf Round(TSelf x, MidpointRounding mode) => TSelf.Round(x, digits: 0, MidpointRounding.ToEven); + static virtual TSelf Round(TSelf x, MidpointRounding mode) => TSelf.Round(x, digits: 0, mode); /// Rounds a value to a specified number of fractional-digits using the default rounding mode (). /// The value to round. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointConstants.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointConstants.cs index 7bb8bbd229bd17..1ab2d6a4ed55f9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointConstants.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointConstants.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements the interface. public interface IFloatingPointConstants : INumberBase - where TSelf : IFloatingPointConstants + where TSelf : IFloatingPointConstants? { /// Gets the mathematical constant e. static abstract TSelf E { get; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointIeee754.cs index 7d9e33c994957f..35d7b42cd79310 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointIeee754.cs @@ -13,7 +13,7 @@ public interface IFloatingPointIeee754 IPowerFunctions, IRootFunctions, ITrigonometricFunctions - where TSelf : IFloatingPointIeee754 + where TSelf : IFloatingPointIeee754? { /// Gets the smallest value such that can be added to 0 that does not result in 0. static abstract TSelf Epsilon { get; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IHyperbolicFunctions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IHyperbolicFunctions.cs index 3fe24e7ef6eba2..8929a539145d60 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IHyperbolicFunctions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IHyperbolicFunctions.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. public interface IHyperbolicFunctions : IFloatingPointConstants - where TSelf : IHyperbolicFunctions + where TSelf : IHyperbolicFunctions? { /// Computes the hyperbolic arc-cosine of a value. /// The value, in radians, whose hyperbolic arc-cosine is to be computed. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IIncrementOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IIncrementOperators.cs index 9024a4a6615ace..9dc434e4c42527 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IIncrementOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IIncrementOperators.cs @@ -6,7 +6,7 @@ namespace System.Numerics /// Defines a mechanism for incrementing a given value. /// The type that implements this interface. public interface IIncrementOperators - where TSelf : IIncrementOperators + where TSelf : IIncrementOperators? { /// Increments a value. /// The value to increment. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/ILogarithmicFunctions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/ILogarithmicFunctions.cs index 668a8b42d1d1f6..343327946e4233 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/ILogarithmicFunctions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/ILogarithmicFunctions.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. public interface ILogarithmicFunctions : IFloatingPointConstants - where TSelf : ILogarithmicFunctions + where TSelf : ILogarithmicFunctions? { /// Computes the natural (base-E) logarithm of a value. /// The value whose natural logarithm is to be computed. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IMinMaxValue.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IMinMaxValue.cs index fe424d1aa6733f..aabfdd10a9fba2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IMinMaxValue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IMinMaxValue.cs @@ -6,7 +6,7 @@ namespace System.Numerics /// Defines a mechanism for getting the minimum and maximum value of a type. /// The type that implements this interface. public interface IMinMaxValue - where TSelf : IMinMaxValue + where TSelf : IMinMaxValue? { /// Gets the minimum value of the current type. static abstract TSelf MinValue { get; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IModulusOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IModulusOperators.cs index fae966ef0be180..772241f9f16865 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IModulusOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IModulusOperators.cs @@ -9,7 +9,7 @@ namespace System.Numerics /// The type that contains the modulus or remainder of and . /// This type represents the % in C# which is often used to compute the remainder and may differ from an actual modulo operation depending on the type that implements the interface. public interface IModulusOperators - where TSelf : IModulusOperators + where TSelf : IModulusOperators? { /// Divides two values together to compute their modulus or remainder. /// The value which divides. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplicativeIdentity.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplicativeIdentity.cs index fa087d0d8a967b..32c7c40db8c915 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplicativeIdentity.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplicativeIdentity.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. /// The type that contains the multiplicative identify of . public interface IMultiplicativeIdentity - where TSelf : IMultiplicativeIdentity + where TSelf : IMultiplicativeIdentity? { /// Gets the multiplicative identity of the current type. static abstract TResult MultiplicativeIdentity { get; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplyOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplyOperators.cs index a3e4d52ec42b81..e93e3d466823c2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplyOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplyOperators.cs @@ -8,7 +8,7 @@ namespace System.Numerics /// The type that will multiply . /// The type that contains the product of and . public interface IMultiplyOperators - where TSelf : IMultiplyOperators + where TSelf : IMultiplyOperators? { /// Multiplies two values together to compute their product. /// The value which multiplies. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumber.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumber.cs index a3413ee78641e1..e41d0b999850b8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumber.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumber.cs @@ -14,7 +14,7 @@ public interface INumber IComparisonOperators, IModulusOperators, INumberBase - where TSelf : INumber + where TSelf : INumber? { /// Clamps a value to an inclusive minimum and maximum value. /// The value to clamp. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs index ed159927d5d339..1bfcc4bb6a5bd4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs @@ -24,7 +24,7 @@ public interface INumberBase ISubtractionOperators, IUnaryPlusOperators, IUnaryNegationOperators - where TSelf : INumberBase + where TSelf : INumberBase? { /// Gets the value 1 for the type. static abstract TSelf One { get; } @@ -49,7 +49,9 @@ public interface INumberBase /// is not representable by . [MethodImpl(MethodImplOptions.AggressiveInlining)] static virtual TSelf CreateChecked(TOther value) +#nullable disable where TOther : INumberBase +#nullable restore { TSelf? result; @@ -57,7 +59,7 @@ static virtual TSelf CreateChecked(TOther value) { result = (TSelf)(object)value; } - else if (!TSelf.TryConvertFromChecked(value, out result) && !TOther.TryConvertToChecked(value, out result)) + else if (!TSelf.TryConvertFromChecked(value, out result) && !TOther.TryConvertToChecked(value, out result)) { ThrowHelper.ThrowNotSupportedException(); } @@ -72,7 +74,9 @@ static virtual TSelf CreateChecked(TOther value) /// is not supported. [MethodImpl(MethodImplOptions.AggressiveInlining)] static virtual TSelf CreateSaturating(TOther value) +#nullable disable where TOther : INumberBase +#nullable restore { TSelf? result; @@ -80,7 +84,7 @@ static virtual TSelf CreateSaturating(TOther value) { result = (TSelf)(object)value; } - else if (!TSelf.TryConvertFromSaturating(value, out result) && !TOther.TryConvertToSaturating(value, out result)) + else if (!TSelf.TryConvertFromSaturating(value, out result) && !TOther.TryConvertToSaturating(value, out result)) { ThrowHelper.ThrowNotSupportedException(); } @@ -95,7 +99,9 @@ static virtual TSelf CreateSaturating(TOther value) /// is not supported. [MethodImpl(MethodImplOptions.AggressiveInlining)] static virtual TSelf CreateTruncating(TOther value) +#nullable disable where TOther : INumberBase +#nullable restore { TSelf? result; @@ -103,7 +109,7 @@ static virtual TSelf CreateTruncating(TOther value) { result = (TSelf)(object)value; } - else if (!TSelf.TryConvertFromTruncating(value, out result) && !TOther.TryConvertToTruncating(value, out result)) + else if (!TSelf.TryConvertFromTruncating(value, out result) && !TOther.TryConvertToTruncating(value, out result)) { ThrowHelper.ThrowNotSupportedException(); } @@ -268,24 +274,30 @@ static virtual TSelf CreateTruncating(TOther value) /// On return, contains an instance of converted from . /// false if is not supported; otherwise, true. /// is not representable by . - protected static abstract bool TryConvertFromChecked(TOther value, [NotNullWhen(true)] out TSelf? result) + protected static abstract bool TryConvertFromChecked(TOther value, [MaybeNullWhen(false)] out TSelf result) +#nullable disable where TOther : INumberBase; +#nullable restore /// Tries to convert a value to an instance of the current type, saturating any values that fall outside the representable range of the current type. /// The type of . /// The value which is used to create the instance of . /// On return, contains an instance of converted from . /// false if is not supported; otherwise, true. - protected static abstract bool TryConvertFromSaturating(TOther value, [NotNullWhen(true)] out TSelf? result) + protected static abstract bool TryConvertFromSaturating(TOther value, [MaybeNullWhen(false)] out TSelf result) +#nullable disable where TOther : INumberBase; +#nullable restore /// Tries to convert a value to an instance of the current type, truncating any values that fall outside the representable range of the current type. /// The type of . /// The value which is used to create the instance of . /// On return, contains an instance of converted from . /// false if is not supported; otherwise, true. - protected static abstract bool TryConvertFromTruncating(TOther value, [NotNullWhen(true)] out TSelf? result) + protected static abstract bool TryConvertFromTruncating(TOther value, [MaybeNullWhen(false)] out TSelf result) +#nullable disable where TOther : INumberBase; +#nullable restore /// Tries to convert an instance of the current type to another type, throwing an overflow exception for any values that fall outside the representable range of the current type. /// The type to which should be converted. @@ -293,24 +305,30 @@ protected static abstract bool TryConvertFromTruncating(TOther value, [N /// On return, contains an instance of converted from . /// false if is not supported; otherwise, true. /// is not representable by . - protected static abstract bool TryConvertToChecked(TSelf value, [NotNullWhen(true)] out TOther? result) + protected static abstract bool TryConvertToChecked(TSelf value, [MaybeNullWhen(false)] out TOther result) +#nullable disable where TOther : INumberBase; +#nullable restore /// Tries to convert an instance of the current type to another type, saturating any values that fall outside the representable range of the current type. /// The type to which should be converted. /// The value which is used to create the instance of . /// On return, contains an instance of converted from . /// false if is not supported; otherwise, true. - protected static abstract bool TryConvertToSaturating(TSelf value, [NotNullWhen(true)] out TOther? result) + protected static abstract bool TryConvertToSaturating(TSelf value, [MaybeNullWhen(false)] out TOther result) +#nullable disable where TOther : INumberBase; +#nullable restore /// Tries to convert an instance of the current type to another type, truncating any values that fall outside the representable range of the current type. /// The type to which should be converted. /// The value which is used to create the instance of . /// On return, contains an instance of converted from . /// false if is not supported; otherwise, true. - protected static abstract bool TryConvertToTruncating(TSelf value, [NotNullWhen(true)] out TOther? result) + protected static abstract bool TryConvertToTruncating(TSelf value, [MaybeNullWhen(false)] out TOther result) +#nullable disable where TOther : INumberBase; +#nullable restore /// Tries to parses a string into a value. /// The string to parse. @@ -319,7 +337,7 @@ protected static abstract bool TryConvertToTruncating(TSelf value, [NotN /// On return, contains the result of successfully parsing or an undefined value on failure. /// true if was successfully parsed; otherwise, false. /// is not a supported value. - static abstract bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out TSelf result); + static abstract bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out TSelf result); /// Tries to parses a span of characters into a value. /// The span of characters to parse. @@ -328,6 +346,6 @@ protected static abstract bool TryConvertToTruncating(TSelf value, [NotN /// On return, contains the result of successfully parsing or an undefined value on failure. /// true if was successfully parsed; otherwise, false. /// is not a supported value. - static abstract bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out TSelf result); + static abstract bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out TSelf result); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IPowerFunctions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IPowerFunctions.cs index 9c37729b6a6490..5f054364e4c75a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IPowerFunctions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IPowerFunctions.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. public interface IPowerFunctions : INumberBase - where TSelf : IPowerFunctions + where TSelf : IPowerFunctions? { /// Computes a value raised to a given power. /// The value which is raised to the power of . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IRootFunctions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IRootFunctions.cs index f8eef19b5f6fff..715ab83e5a5033 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IRootFunctions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IRootFunctions.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. public interface IRootFunctions : IFloatingPointConstants - where TSelf : IRootFunctions + where TSelf : IRootFunctions? { /// Computes the cube-root of a value. /// The value whose cube-root is to be computed. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IShiftOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IShiftOperators.cs index 0ef088e5f5b447..45ccece20b9bdc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IShiftOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IShiftOperators.cs @@ -8,7 +8,7 @@ namespace System.Numerics /// The type used to specify the amount by which should be shifted. /// The type that contains the result of shifting by . public interface IShiftOperators - where TSelf : IShiftOperators + where TSelf : IShiftOperators? { /// Shifts a value left by a given amount. /// The value which is shifted left by . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/ISignedNumber.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/ISignedNumber.cs index df1ed28b195630..cc4e9651d5d305 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/ISignedNumber.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/ISignedNumber.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements the interface. public interface ISignedNumber : INumberBase - where TSelf : ISignedNumber + where TSelf : ISignedNumber? { /// Gets the value -1 for the type. static abstract TSelf NegativeOne { get; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/ISubtractionOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/ISubtractionOperators.cs index b79879bef3651e..e422900d604962 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/ISubtractionOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/ISubtractionOperators.cs @@ -8,7 +8,7 @@ namespace System.Numerics /// The type that will be subtracted from . /// The type that contains the difference of subtracted from . public interface ISubtractionOperators - where TSelf : ISubtractionOperators + where TSelf : ISubtractionOperators? { /// Subtracts two values to compute their difference. /// The value from which is subtracted. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/ITrigonometricFunctions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/ITrigonometricFunctions.cs index 8539cd13c5f5e5..cac27fad008344 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/ITrigonometricFunctions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/ITrigonometricFunctions.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. public interface ITrigonometricFunctions : IFloatingPointConstants - where TSelf : ITrigonometricFunctions + where TSelf : ITrigonometricFunctions? { /// Computes the arc-cosine of a value. /// The value whose arc-cosine is to be computed. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryNegationOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryNegationOperators.cs index 6a8ee11fe07a49..0af4580635cb3f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryNegationOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryNegationOperators.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. /// The type that contains the result of negating . public interface IUnaryNegationOperators - where TSelf : IUnaryNegationOperators + where TSelf : IUnaryNegationOperators? { /// Computes the unary negation of a value. /// The value for which to compute its unary negation. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryPlusOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryPlusOperators.cs index 916c5f13fc5b19..5ba2d43f6a94c9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryPlusOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryPlusOperators.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. /// The type that contains the result of negating . public interface IUnaryPlusOperators - where TSelf : IUnaryPlusOperators + where TSelf : IUnaryPlusOperators? { /// Computes the unary plus of a value. /// The value for which to compute its unary plus. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnsignedNumber.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnsignedNumber.cs index 1cae84b36b7afc..d5de09443e2e52 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnsignedNumber.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnsignedNumber.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements the interface. public interface IUnsignedNumber : INumberBase - where TSelf : IUnsignedNumber + where TSelf : IUnsignedNumber? { } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector.cs index b9eb29598bb6e9..5dee06aa6adf00 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector.cs @@ -895,6 +895,14 @@ public static bool LessThanOrEqualAll(Vector left, Vector right) public static bool LessThanOrEqualAny(Vector left, Vector right) where T : struct => LessThanOrEqual(left, right).As() != Vector.Zero; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector LoadUnsafe(ref T source, nuint elementOffset) + where T : struct + { + source = ref Unsafe.Add(ref source, elementOffset); + return Unsafe.ReadUnaligned>(ref Unsafe.As(ref source)); + } + /// Computes the maximum of two vectors on a per-element basis. /// The vector to compare with . /// The vector to compare with . @@ -1658,6 +1666,14 @@ public static Vector SquareRoot(Vector value) return result; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void StoreUnsafe(this Vector source, ref T destination, nuint elementOffset) + where T : struct + { + destination = ref Unsafe.Add(ref destination, elementOffset); + Unsafe.WriteUnaligned(ref Unsafe.As(ref destination), source); + } + /// Subtracts two vectors to compute their difference. /// The vector from which will be subtracted. /// The vector to subtract from . diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs deleted file mode 100644 index 530c5e767f7745..00000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs +++ /dev/null @@ -1,34 +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; - -namespace System.Reflection -{ - internal sealed partial class FieldAccessor - { - private readonly RtFieldInfo _fieldInfo; - public InvocationFlags _invocationFlags; - - public FieldAccessor(RtFieldInfo fieldInfo) - { - _fieldInfo = fieldInfo; - } - - [DebuggerStepThrough] - [DebuggerHidden] - public object? GetValue(object? obj) - { - // Todo: add strategy for calling IL Emit-based version - return _fieldInfo.GetValueNonEmit(obj); - } - - [DebuggerStepThrough] - [DebuggerHidden] - public void SetValue(object? obj, object? value) - { - // Todo: add strategy for calling IL Emit-based version - _fieldInfo.SetValueNonEmit(obj, value); - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs index 13ff9e34df9201..98069a70ef55b4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -236,6 +236,7 @@ BindingFlags invokeAttr shouldCopyBack[i] = copyBackArg; copyOfParameters[i] = arg; +#pragma warning disable 8500 if (isValueType) { #if !MONO // Temporary until Mono is updated. @@ -254,6 +255,7 @@ BindingFlags invokeAttr ByReference objRef = ByReference.Create(ref copyOfParameters[i]); *(ByReference*)(byrefParameters + i) = objRef; } +#pragma warning restore 8500 } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs index b1f3222736a1a8..1fd9c177ddb8a0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -146,7 +146,9 @@ internal void ThrowNoInvokeException() Span shouldCopyBackParameters = new(ref argStorage._copyBack0, argCount); StackAllocatedByRefs byrefStorage = default; +#pragma warning disable 8500 IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; +#pragma warning restore 8500 CheckArguments( copyOfParameters, @@ -299,7 +301,9 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] Span shouldCopyBackParameters = new(ref argStorage._copyBack0, argCount); StackAllocatedByRefs byrefStorage = default; +#pragma warning disable 8500 IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; +#pragma warning restore 8500 CheckArguments( copyOfParameters, diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs index 770a59d40eb91f..bf534c58e70ed4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -143,7 +143,9 @@ private void ThrowNoInvokeException() Span shouldCopyBackParameters = new(ref argStorage._copyBack0, argCount); StackAllocatedByRefs byrefStorage = default; +#pragma warning disable 8500 IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; +#pragma warning restore 8500 CheckArguments( copyOfParameters, 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 7d8a33c091b35e..b2f9e9622a2a19 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Resources/ResourceReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Resources/ResourceReader.cs @@ -9,6 +9,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Text; +using System.Threading; namespace System.Resources #if RESOURCES_EXTENSIONS @@ -60,10 +61,11 @@ public sealed partial class // it make sense to use anything less than one page? private const int DefaultFileStreamBufferSize = 4096; - private BinaryReader _store; // backing store we're reading from. - // Used by RuntimeResourceSet and this class's enumerator. Maps - // resource name to a value, a ResourceLocator, or a - // LooselyLinkedManifestResource. + // Backing store we're reading from. Usages outside of constructor + // initialization must be protected by lock (this). + private BinaryReader _store; + // Used by RuntimeResourceSet and this class's enumerator. + // Accesses must be protected by lock(_resCache). internal Dictionary? _resCache; private long _nameSectionOffset; // Offset to name section of file. private long _dataSectionOffset; // Offset to Data section of file. @@ -88,7 +90,6 @@ public sealed partial class // Version number of .resources file, for compatibility private int _version; - public #if RESOURCES_EXTENSIONS DeserializingResourceReader(string fileName) @@ -169,13 +170,16 @@ private unsafe void Dispose(bool disposing) } } - internal static unsafe int ReadUnalignedI4(int* p) + private static unsafe int ReadUnalignedI4(int* p) { return BinaryPrimitives.ReadInt32LittleEndian(new ReadOnlySpan(p, sizeof(int))); } private void SkipString() { + // Note: this method assumes that it is called either during object + // construction or within another method that locks on this. + int stringLength = _store.Read7BitEncodedInt(); if (stringLength < 0) { @@ -234,6 +238,7 @@ public IDictionaryEnumerator GetEnumerator() return new ResourceEnumerator(this); } + // Called from RuntimeResourceSet internal ResourceEnumerator GetEnumeratorInternal() { return new ResourceEnumerator(this); @@ -243,6 +248,7 @@ internal ResourceEnumerator GetEnumeratorInternal() // To read the data, seek to _dataSectionOffset + dataPos, then // read the resource type & data. // This does a binary search through the names. + // Called from RuntimeResourceSet internal int FindPosForResource(string name) { Debug.Assert(_store != null, "ResourceReader is closed!"); @@ -327,6 +333,8 @@ internal int FindPosForResource(string name) private unsafe bool CompareStringEqualsName(string name) { Debug.Assert(_store != null, "ResourceReader is closed!"); + Debug.Assert(Monitor.IsEntered(this)); // uses _store + int byteLen = _store.Read7BitEncodedInt(); if (byteLen < 0) { @@ -459,68 +467,74 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset) } // This takes a virtual offset into the data section and reads a String - // from that location. - // Anyone who calls LoadObject should make sure they take a lock so - // no one can cause us to do a seek in here. + // from that location. Called from RuntimeResourceSet internal string? LoadString(int pos) { Debug.Assert(_store != null, "ResourceReader is closed!"); - _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin); - string? s = null; - int typeIndex = _store.Read7BitEncodedInt(); - if (_version == 1) - { - if (typeIndex == -1) - return null; - if (FindType(typeIndex) != typeof(string)) - throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Type, FindType(typeIndex).FullName)); - s = _store.ReadString(); - } - else + + lock (this) { - ResourceTypeCode typeCode = (ResourceTypeCode)typeIndex; - if (typeCode != ResourceTypeCode.String && typeCode != ResourceTypeCode.Null) + _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin); + string? s = null; + int typeIndex = _store.Read7BitEncodedInt(); + if (_version == 1) { - string? typeString; - if (typeCode < ResourceTypeCode.StartOfUserTypes) - typeString = typeCode.ToString(); - else - typeString = FindType(typeCode - ResourceTypeCode.StartOfUserTypes).FullName; - throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Type, typeString)); - } - if (typeCode == ResourceTypeCode.String) // ignore Null + if (typeIndex == -1) + return null; + if (FindType(typeIndex) != typeof(string)) + throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Type, FindType(typeIndex).FullName)); s = _store.ReadString(); + } + else + { + ResourceTypeCode typeCode = (ResourceTypeCode)typeIndex; + if (typeCode != ResourceTypeCode.String && typeCode != ResourceTypeCode.Null) + { + string? typeString; + if (typeCode < ResourceTypeCode.StartOfUserTypes) + typeString = typeCode.ToString(); + else + typeString = FindType(typeCode - ResourceTypeCode.StartOfUserTypes).FullName; + throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Type, typeString)); + } + if (typeCode == ResourceTypeCode.String) // ignore Null + s = _store.ReadString(); + } + return s; } - return s; } // Called from RuntimeResourceSet internal object? LoadObject(int pos) { - if (_version == 1) - return LoadObjectV1(pos); - return LoadObjectV2(pos, out _); + lock (this) + { + return _version == 1 ? LoadObjectV1(pos) : LoadObjectV2(pos, out _); + } } + // Called from RuntimeResourceSet internal object? LoadObject(int pos, out ResourceTypeCode typeCode) { - if (_version == 1) + lock (this) { - object? o = LoadObjectV1(pos); - typeCode = (o is string) ? ResourceTypeCode.String : ResourceTypeCode.StartOfUserTypes; - return o; + if (_version == 1) + { + object? o = LoadObjectV1(pos); + typeCode = (o is string) ? ResourceTypeCode.String : ResourceTypeCode.StartOfUserTypes; + return o; + } + return LoadObjectV2(pos, out typeCode); } - return LoadObjectV2(pos, out typeCode); } // This takes a virtual offset into the data section and reads an Object // from that location. - // Anyone who calls LoadObject should make sure they take a lock so - // no one can cause us to do a seek in here. - internal object? LoadObjectV1(int pos) + private object? LoadObjectV1(int pos) { Debug.Assert(_store != null, "ResourceReader is closed!"); Debug.Assert(_version == 1, ".resources file was not a V1 .resources file!"); + Debug.Assert(Monitor.IsEntered(this)); // uses _store try { @@ -540,6 +554,8 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset) private object? _LoadObjectV1(int pos) { + Debug.Assert(Monitor.IsEntered(this)); // uses _store + _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin); int typeIndex = _store.Read7BitEncodedInt(); if (typeIndex == -1) @@ -596,10 +612,11 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset) } } - internal object? LoadObjectV2(int pos, out ResourceTypeCode typeCode) + private object? LoadObjectV2(int pos, out ResourceTypeCode typeCode) { Debug.Assert(_store != null, "ResourceReader is closed!"); Debug.Assert(_version >= 2, ".resources file was not a V2 (or higher) .resources file!"); + Debug.Assert(Monitor.IsEntered(this)); // uses _store try { @@ -619,6 +636,8 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset) private object? _LoadObjectV2(int pos, out ResourceTypeCode typeCode) { + Debug.Assert(Monitor.IsEntered(this)); // uses _store + _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin); typeCode = (ResourceTypeCode)_store.Read7BitEncodedInt(); @@ -755,6 +774,7 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset) [MemberNotNull(nameof(_typeNamePositions))] private void ReadResources() { + Debug.Assert(!Monitor.IsEntered(this)); // only called during init Debug.Assert(_store != null, "ResourceReader is closed!"); try @@ -777,6 +797,8 @@ private void ReadResources() [MemberNotNull(nameof(_typeNamePositions))] private void _ReadResources() { + Debug.Assert(!Monitor.IsEntered(this)); // only called during init + // Read ResourceManager header // Check for magic number int magicNum = _store.ReadInt32(); @@ -962,6 +984,8 @@ private Type FindType(int typeIndex) "Custom readers as well as custom objects on the resources file are not observable by the trimmer and so required assemblies, types and members may be removed.")] private Type UseReflectionToGetType(int typeIndex) { + Debug.Assert(Monitor.IsEntered(this)); // uses _store + long oldPos = _store.BaseStream.Position; try { @@ -1000,6 +1024,8 @@ private Type UseReflectionToGetType(int typeIndex) private string TypeNameFromTypeCode(ResourceTypeCode typeCode) { Debug.Assert(typeCode >= 0, "can't be negative"); + Debug.Assert(Monitor.IsEntered(this)); // uses _store + if (typeCode < ResourceTypeCode.StartOfUserTypes) { Debug.Assert(!string.Equals(typeCode.ToString(), "LastPrimitive"), "Change ResourceTypeCode metadata order so LastPrimitive isn't what Enum.ToString prefers."); @@ -1077,31 +1103,31 @@ public DictionaryEntry Entry if (!_currentIsValid) throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted); if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed); - string key; + string key = _reader.AllocateStringForNameIndex(_currentName, out _dataPosition); // AllocateStringForNameIndex could lock on _reader + object? value = null; - lock (_reader) - { // locks should be taken in the same order as in RuntimeResourceSet.GetObject to avoid deadlock - lock (_reader._resCache) + // Lock the cache first, then the reader (in this case, we don't actually need to lock the reader and cache at the same time). + // Lock order MUST match RuntimeResourceSet.GetObject to avoid deadlock. + Debug.Assert(!Monitor.IsEntered(_reader)); + lock (_reader._resCache) + { + if (_reader._resCache.TryGetValue(key, out ResourceLocator locator)) { - key = _reader.AllocateStringForNameIndex(_currentName, out _dataPosition); // AllocateStringForNameIndex could lock on _reader - if (_reader._resCache.TryGetValue(key, out ResourceLocator locator)) - { - value = locator.Value; - } - if (value == null) - { - if (_dataPosition == -1) - value = _reader.GetValueForNameIndex(_currentName); - else - value = _reader.LoadObject(_dataPosition); - // If enumeration and subsequent lookups happen very - // frequently in the same process, add a ResourceLocator - // to _resCache here. But WinForms enumerates and - // just about everyone else does lookups. So caching - // here may bloat working set. - } + value = locator.Value; } } + if (value is null) + { + if (_dataPosition == -1) + value = _reader.GetValueForNameIndex(_currentName); + else + value = _reader.LoadObject(_dataPosition); + // If enumeration and subsequent lookups happen very + // frequently in the same process, add a ResourceLocator + // to _resCache here (we'll also need to extend the lock block!). + // But WinForms enumerates and just about everyone else does lookups. + // So caching here may bloat working set. + } return new DictionaryEntry(key, value); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Resources/RuntimeResourceSet.cs b/src/libraries/System.Private.CoreLib/src/System/Resources/RuntimeResourceSet.cs index 11f3b5eb9515db..957b6269e94db8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Resources/RuntimeResourceSet.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Resources/RuntimeResourceSet.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Threading; namespace System.Resources #if RESOURCES_EXTENSIONS @@ -283,6 +284,9 @@ private IDictionaryEnumerator GetEnumeratorHelper() object? value; ResourceLocator resEntry; + // Lock the cache first, then the reader (reader locks implicitly through its methods). + // Lock order MUST match ResourceReader.ResourceEnumerator.Entry to avoid deadlock. + Debug.Assert(!Monitor.IsEntered(reader)); lock (cache) { // Find the offset within the data section @@ -295,7 +299,7 @@ private IDictionaryEnumerator GetEnumeratorHelper() // When data type cannot be cached dataPos = resEntry.DataPosition; - return isString ? reader.LoadString(dataPos) : reader.LoadObject(dataPos, out _); + return isString ? reader.LoadString(dataPos) : reader.LoadObject(dataPos); } dataPos = reader.FindPosForResource(key); @@ -353,14 +357,11 @@ private IDictionaryEnumerator GetEnumeratorHelper() return value; } - private static object? ReadValue (ResourceReader reader, int dataPos, bool isString, out ResourceLocator locator) + private static object? ReadValue(ResourceReader reader, int dataPos, bool isString, out ResourceLocator locator) { object? value; ResourceTypeCode typeCode; - // Normally calling LoadString or LoadObject requires - // taking a lock. Note that in this case, we took a - // lock before calling this method. if (isString) { value = reader.LoadString(dataPos); diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs index f312049b82fcab..9aeef16dadb4dd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs @@ -1492,14 +1492,14 @@ private static bool TryConvertFrom(TOther value, out NFloat result) } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(NFloat value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(NFloat value, [MaybeNullWhen(false)] out TOther result) { if (typeof(TOther) == typeof(byte)) { @@ -1605,26 +1605,26 @@ static bool INumberBase.TryConvertToChecked(NFloat value, [NotNu } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(NFloat value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(NFloat value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(NFloat value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(NFloat value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } - private static bool TryConvertTo(NFloat value, [NotNullWhen(true)] out TOther result) + private static bool TryConvertTo(NFloat value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase { if (typeof(TOther) == typeof(byte)) @@ -1754,7 +1754,7 @@ private static bool TryConvertTo(NFloat value, [NotNullWhen(true)] out T } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs b/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs index 81d6f994da54f9..d4b45f21bc9d74 100644 --- a/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs +++ b/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs @@ -459,8 +459,6 @@ public override bool IsAssignableFrom([NotNullWhen(true)] Type? c) string name, BindingFlags bindingFlags, Binder? binder, object? target, object?[]? providedArgs, ParameterModifier[]? modifiers, CultureInfo? culture, string[]? namedParams) { - ArgumentNullException.ThrowIfNull(name); - const BindingFlags MemberBindingMask = (BindingFlags)0x000000FF; const BindingFlags InvocationMask = (BindingFlags)0x0000FF00; const BindingFlags BinderGetSetField = BindingFlags.GetField | BindingFlags.SetField; @@ -567,10 +565,12 @@ public override bool IsAssignableFrom([NotNullWhen(true)] Type? c) // PutDispProperty and\or PutRefDispProperty ==> SetProperty. if ((bindingFlags & (BindingFlags.PutDispProperty | BindingFlags.PutRefDispProperty)) != 0) bindingFlags |= BindingFlags.SetProperty; + + ArgumentNullException.ThrowIfNull(name); if (name.Length == 0 || name.Equals("[DISPID=0]")) { // in InvokeMember we always pretend there is a default member if none is provided and we make it ToString - name = GetDefaultMemberName()! ?? "ToString"; + name = GetDefaultMemberName() ?? "ToString"; } // GetField or SetField diff --git a/src/libraries/System.Private.CoreLib/src/System/SByte.cs b/src/libraries/System.Private.CoreLib/src/System/SByte.cs index 8b9dc686c15a21..afd9b04f08e265 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SByte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SByte.cs @@ -1114,7 +1114,7 @@ private static bool TryConvertFromTruncating(TOther value, out sbyte res /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(sbyte value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(sbyte value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1175,14 +1175,14 @@ static bool INumberBase.TryConvertToChecked(sbyte value, [NotNull } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(sbyte value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(sbyte value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1243,14 +1243,14 @@ static bool INumberBase.TryConvertToSaturating(sbyte value, [NotN } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(sbyte value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(sbyte value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1311,7 +1311,7 @@ static bool INumberBase.TryConvertToTruncating(sbyte value, [NotN } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index cd31393df23aea..54e1ef148e9f11 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -1223,7 +1223,7 @@ private static bool TryConvertFrom(TOther value, out float result) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(float value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(float value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1284,26 +1284,26 @@ static bool INumberBase.TryConvertToChecked(float value, [NotNull } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(float value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(float value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(float value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(float value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } - private static bool TryConvertTo(float value, [NotNullWhen(true)] out TOther result) + private static bool TryConvertTo(float value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase { // In order to reduce overall code duplication and improve the inlinabilty of these @@ -1382,7 +1382,7 @@ private static bool TryConvertTo(float value, [NotNullWhen(true)] out TO } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Byte.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Byte.cs index 5029e01a3161d7..61565cfd27db38 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Byte.cs @@ -2,10 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; namespace System @@ -22,7 +22,7 @@ public static int IndexOf(ref byte searchSpace, int searchSpaceLength, ref byte int valueTailLength = valueLength - 1; if (valueTailLength == 0) - return IndexOf(ref searchSpace, value, searchSpaceLength); // for single-byte values use plain IndexOf + return IndexOfValueType(ref searchSpace, value, searchSpaceLength); // for single-byte values use plain IndexOf nint offset = 0; byte valueHead = value; @@ -38,7 +38,7 @@ public static int IndexOf(ref byte searchSpace, int searchSpaceLength, ref byte while (remainingSearchSpaceLength > 0) { // Do a quick search for the first element of "value". - int relativeIndex = IndexOf(ref Unsafe.Add(ref searchSpace, offset), valueHead, remainingSearchSpaceLength); + int relativeIndex = IndexOfValueType(ref Unsafe.Add(ref searchSpace, offset), valueHead, remainingSearchSpaceLength); if (relativeIndex < 0) break; @@ -197,7 +197,7 @@ public static int LastIndexOf(ref byte searchSpace, int searchSpaceLength, ref b int valueTailLength = valueLength - 1; if (valueTailLength == 0) - return LastIndexOf(ref searchSpace, value, searchSpaceLength); // for single-byte values use plain LastIndexOf + return LastIndexOfValueType(ref searchSpace, value, searchSpaceLength); // for single-byte values use plain LastIndexOf int offset = 0; byte valueHead = value; @@ -217,7 +217,7 @@ public static int LastIndexOf(ref byte searchSpace, int searchSpaceLength, ref b break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there. // Do a quick search for the first element of "value". - int relativeIndex = LastIndexOf(ref searchSpace, valueHead, remainingSearchSpaceLength); + int relativeIndex = LastIndexOfValueType(ref searchSpace, valueHead, remainingSearchSpaceLength); if (relativeIndex < 0) break; @@ -333,828 +333,52 @@ ref Unsafe.Add(ref searchSpace, offset + bitPos), } } - // Adapted from IndexOf(...) - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static bool Contains(ref byte searchSpace, byte value, int length) + [DoesNotReturn] + private static void ThrowMustBeNullTerminatedString() { - Debug.Assert(length >= 0); - - uint uValue = value; // Use uint for comparisons to avoid unnecessary 8->32 extensions - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (uint)length; - - if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) - { - lengthToExamine = UnalignedCountVector(ref searchSpace); - } - - while (lengthToExamine >= 8) - { - lengthToExamine -= 8; - ref byte start = ref Unsafe.AddByteOffset(ref searchSpace, offset); - - if (uValue == Unsafe.AddByteOffset(ref start, 0) || - uValue == Unsafe.AddByteOffset(ref start, 1) || - uValue == Unsafe.AddByteOffset(ref start, 2) || - uValue == Unsafe.AddByteOffset(ref start, 3) || - uValue == Unsafe.AddByteOffset(ref start, 4) || - uValue == Unsafe.AddByteOffset(ref start, 5) || - uValue == Unsafe.AddByteOffset(ref start, 6) || - uValue == Unsafe.AddByteOffset(ref start, 7)) - { - goto Found; - } - - offset += 8; - } - - if (lengthToExamine >= 4) - { - lengthToExamine -= 4; - ref byte start = ref Unsafe.AddByteOffset(ref searchSpace, offset); - - if (uValue == Unsafe.AddByteOffset(ref start, 0) || - uValue == Unsafe.AddByteOffset(ref start, 1) || - uValue == Unsafe.AddByteOffset(ref start, 2) || - uValue == Unsafe.AddByteOffset(ref start, 3)) - { - goto Found; - } - - offset += 4; - } - - while (lengthToExamine > 0) - { - lengthToExamine--; - - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) - goto Found; - - offset++; - } - - if (Vector.IsHardwareAccelerated && (offset < (uint)length)) - { - lengthToExamine = ((uint)length - offset) & (nuint)~(Vector.Count - 1); - - Vector values = new(value); - Vector matches; - - while (offset < lengthToExamine) - { - matches = Vector.Equals(values, LoadVector(ref searchSpace, offset)); - if (matches == Vector.Zero) - { - offset += (nuint)Vector.Count; - continue; - } - - goto Found; - } - - // The total length is at least Vector.Count, so instead of falling back to a - // sequential scan for the remainder, we check the vector read from the end -- note: unaligned read necessary. - // We do this only if at least one element is left. - if (offset < (uint)length) - { - offset = (uint)(length - Vector.Count); - matches = Vector.Equals(values, LoadVector(ref searchSpace, offset)); - if (matches != Vector.Zero) - { - goto Found; - } - } - } - - return false; - - Found: - return true; + throw new ArgumentException(SR.Arg_MustBeNullTerminatedString); } + // IndexOfNullByte processes memory in aligned chunks, and thus it won't crash even if it accesses memory beyond the null terminator. + // This behavior is an implementation detail of the runtime and callers outside System.Private.CoreLib must not depend on it. [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe int IndexOf(ref byte searchSpace, byte value, int length) + internal static unsafe int IndexOfNullByte(ref byte searchSpace) { - Debug.Assert(length >= 0); + const int Length = int.MaxValue; - uint uValue = value; // Use uint for comparisons to avoid unnecessary 8->32 extensions + const uint uValue = 0; // Use uint for comparisons to avoid unnecessary 8->32 extensions nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; + nuint lengthToExamine = (nuint)(uint)Length; if (Vector128.IsHardwareAccelerated) { // Avx2 branch also operates on Sse2 sizes, so check is combined. - if (length >= Vector128.Count * 2) - { - lengthToExamine = UnalignedCountVector128(ref searchSpace); - } - } - else if (Vector.IsHardwareAccelerated) - { - if (length >= Vector.Count * 2) - { - lengthToExamine = UnalignedCountVector(ref searchSpace); - } - } - SequentialScan: - while (lengthToExamine >= 8) - { - lengthToExamine -= 8; - - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) - goto Found; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) - goto Found1; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) - goto Found2; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) - goto Found3; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 4)) - goto Found4; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 5)) - goto Found5; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 6)) - goto Found6; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 7)) - goto Found7; - - offset += 8; - } - - if (lengthToExamine >= 4) - { - lengthToExamine -= 4; - - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) - goto Found; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) - goto Found1; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) - goto Found2; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) - goto Found3; - - offset += 4; - } - - while (lengthToExamine > 0) - { - lengthToExamine -= 1; - - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) - goto Found; - - offset += 1; - } - - // We get past SequentialScan only if IsHardwareAccelerated is true; and remain length is greater than Vector length. - // However, we still have the redundant check to allow the JIT to see that the code is unreachable and eliminate it when the platform does not - // have hardware accelerated. After processing Vector lengths we return to SequentialScan to finish any remaining. - if (Vector256.IsHardwareAccelerated) - { - if (offset < (nuint)(uint)length) - { - if ((((nuint)(uint)Unsafe.AsPointer(ref searchSpace) + offset) & (nuint)(Vector256.Count - 1)) != 0) - { - // Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches - // with no upper bound e.g. String.strlen. - // Start with a check on Vector128 to align to Vector256, before moving to processing Vector256. - // This ensures we do not fault across memory pages while searching for an end of string. - Vector128 values = Vector128.Create(value); - Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); - - // Same method as below - uint matches = Vector128.Equals(values, search).ExtractMostSignificantBits(); - if (matches == 0) - { - // Zero flags set so no matches - offset += (nuint)Vector128.Count; - } - else - { - // Find bitflag offset of first match and add to current offset - return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); - } - } - - lengthToExamine = GetByteVector256SpanLength(offset, length); - if (lengthToExamine > offset) - { - Vector256 values = Vector256.Create(value); - do - { - Vector256 search = Vector256.LoadUnsafe(ref searchSpace, offset); - uint matches = Vector256.Equals(values, search).ExtractMostSignificantBits(); - // Note that MoveMask has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - if (matches == 0) - { - // Zero flags set so no matches - offset += (nuint)Vector256.Count; - continue; - } - - // Find bitflag offset of first match and add to current offset - return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); - } while (lengthToExamine > offset); - } - - lengthToExamine = GetByteVector128SpanLength(offset, length); - if (lengthToExamine > offset) - { - Vector128 values = Vector128.Create(value); - Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); - - // Same method as above - uint matches = Vector128.Equals(values, search).ExtractMostSignificantBits(); - if (matches == 0) - { - // Zero flags set so no matches - offset += (nuint)Vector128.Count; - } - else - { - // Find bitflag offset of first match and add to current offset - return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); - } - } - - if (offset < (nuint)(uint)length) - { - lengthToExamine = ((nuint)(uint)length - offset); - goto SequentialScan; - } - } - } - else if (Vector128.IsHardwareAccelerated) - { - if (offset < (nuint)(uint)length) - { - lengthToExamine = GetByteVector128SpanLength(offset, length); - - Vector128 values = Vector128.Create(value); - while (lengthToExamine > offset) - { - Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); - - // Same method as above - Vector128 compareResult = Vector128.Equals(values, search); - if (compareResult == Vector128.Zero) - { - // Zero flags set so no matches - offset += (nuint)Vector128.Count; - continue; - } - - // Find bitflag offset of first match and add to current offset - uint matches = compareResult.ExtractMostSignificantBits(); - return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); - } - - if (offset < (nuint)(uint)length) - { - lengthToExamine = ((nuint)(uint)length - offset); - goto SequentialScan; - } - } + lengthToExamine = UnalignedCountVector128(ref searchSpace); } else if (Vector.IsHardwareAccelerated) { - if (offset < (nuint)(uint)length) - { - lengthToExamine = GetByteVectorSpanLength(offset, length); - - Vector values = new Vector(value); - - while (lengthToExamine > offset) - { - var matches = Vector.Equals(values, LoadVector(ref searchSpace, offset)); - if (Vector.Zero.Equals(matches)) - { - offset += (nuint)Vector.Count; - continue; - } - - // Find offset of first match and add to current offset - return (int)offset + LocateFirstFoundByte(matches); - } - - if (offset < (nuint)(uint)length) - { - lengthToExamine = ((nuint)(uint)length - offset); - goto SequentialScan; - } - } - } - return -1; - Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 - return (int)offset; - Found1: - return (int)(offset + 1); - Found2: - return (int)(offset + 2); - Found3: - return (int)(offset + 3); - Found4: - return (int)(offset + 4); - Found5: - return (int)(offset + 5); - Found6: - return (int)(offset + 6); - Found7: - return (int)(offset + 7); - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static int LastIndexOf(ref byte searchSpace, byte value, int length) - { - Debug.Assert(length >= 0); - - uint uValue = value; // Use uint for comparisons to avoid unnecessary 8->32 extensions - nuint offset = (nuint)(uint)length; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) - { - lengthToExamine = UnalignedCountVectorFromEnd(ref searchSpace, length); + lengthToExamine = UnalignedCountVector(ref searchSpace); } SequentialScan: while (lengthToExamine >= 8) { lengthToExamine -= 8; - offset -= 8; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 7)) - goto Found7; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 6)) - goto Found6; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 5)) - goto Found5; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 4)) - goto Found4; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) - goto Found3; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) - goto Found2; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) - goto Found1; if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) goto Found; - } - - if (lengthToExamine >= 4) - { - lengthToExamine -= 4; - offset -= 4; - - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) - goto Found3; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) - goto Found2; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) - goto Found1; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) - goto Found; - } - - while (lengthToExamine > 0) - { - lengthToExamine -= 1; - offset -= 1; - - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) - goto Found; - } - - if (Vector.IsHardwareAccelerated && (offset > 0)) - { - lengthToExamine = (offset & (nuint)~(Vector.Count - 1)); - - Vector values = new Vector(value); - - while (lengthToExamine > (nuint)(Vector.Count - 1)) - { - var matches = Vector.Equals(values, LoadVector(ref searchSpace, offset - (nuint)Vector.Count)); - if (Vector.Zero.Equals(matches)) - { - offset -= (nuint)Vector.Count; - lengthToExamine -= (nuint)Vector.Count; - continue; - } - - // Find offset of first match and add to current offset - return (int)(offset) - Vector.Count + LocateLastFoundByte(matches); - } - if (offset > 0) - { - lengthToExamine = offset; - goto SequentialScan; - } - } - return -1; - Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 - return (int)offset; - Found1: - return (int)(offset + 1); - Found2: - return (int)(offset + 2); - Found3: - return (int)(offset + 3); - Found4: - return (int)(offset + 4); - Found5: - return (int)(offset + 5); - Found6: - return (int)(offset + 6); - Found7: - return (int)(offset + 7); - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static int IndexOfAny(ref byte searchSpace, byte value0, byte value1, int length) - { - Debug.Assert(length >= 0); - - uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions - uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Sse2.IsSupported || AdvSimd.Arm64.IsSupported) - { - // Avx2 branch also operates on Sse2 sizes, so check is combined. - nint vectorDiff = (nint)length - Vector128.Count; - if (vectorDiff >= 0) - { - // >= Sse2 intrinsics are supported, and length is enough to use them so use that path. - // We jump forward to the intrinsics at the end of the method so a naive branch predict - // will choose the non-intrinsic path so short lengths which don't gain anything aren't - // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths - // more than make this back from the intrinsics. - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - else if (Vector.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, as it is used later - nint vectorDiff = (nint)length - Vector.Count; - if (vectorDiff >= 0) - { - // Similar as above for Vector version - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - - uint lookUp; - while (lengthToExamine >= 8) - { - lengthToExamine -= 8; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found3; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 4); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found4; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 5); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found5; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 6); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found6; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 7); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found7; - - offset += 8; - } - - if (lengthToExamine >= 4) - { - lengthToExamine -= 4; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found3; - - offset += 4; - } - - while (lengthToExamine > 0) - { - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found; - - offset += 1; - lengthToExamine -= 1; - } - - NotFound: - return -1; - Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 - return (int)offset; - Found1: - return (int)(offset + 1); - Found2: - return (int)(offset + 2); - Found3: - return (int)(offset + 3); - Found4: - return (int)(offset + 4); - Found5: - return (int)(offset + 5); - Found6: - return (int)(offset + 6); - Found7: - return (int)(offset + 7); - - IntrinsicsCompare: - // When we move into a Vectorized block, we process everything of Vector size; - // and then for any remainder we do a final compare of Vector size but starting at - // the end and forwards, which may overlap on an earlier compare. - - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (Sse2.IsSupported) - { - int matches; - if (Avx2.IsSupported) - { - Vector256 search; - // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 - // We have already subtracted Vector128.Count from lengthToExamine so compare against that - // to see if we have double the size for Vector256.Count - if (lengthToExamine >= (nuint)Vector128.Count) - { - Vector256 values0 = Vector256.Create(value0); - Vector256 values1 = Vector256.Create(value1); - - // Subtract Vector128.Count so we have now subtracted Vector256.Count - lengthToExamine -= (nuint)Vector128.Count; - // First time this checks again against 0, however we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector256(ref searchSpace, offset); - // Bitwise Or to combine the flagged matches for the second value to our match flags - matches = Avx2.MoveMask( - Avx2.Or( - Avx2.CompareEqual(values0, search), - Avx2.CompareEqual(values1, search))); - // Note that MoveMask has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - if (matches == 0) - { - // None matched - offset += (nuint)Vector256.Count; - continue; - } - - goto IntrinsicsMatch; - } - - // Move to Vector length from end for final compare - search = LoadVector256(ref searchSpace, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = Avx2.MoveMask( - Avx2.Or( - Avx2.CompareEqual(values0, search), - Avx2.CompareEqual(values1, search))); - if (matches == 0) - { - // None matched - goto NotFound; - } - - goto IntrinsicsMatch; - } - } - - // Initial size check was done on method entry. - Debug.Assert(length >= Vector128.Count); - { - Vector128 search; - Vector128 values0 = Vector128.Create(value0); - Vector128 values1 = Vector128.Create(value1); - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector128(ref searchSpace, offset); - - matches = Sse2.MoveMask( - Sse2.Or( - Sse2.CompareEqual(values0, search), - Sse2.CompareEqual(values1, search)) - .AsByte()); - // Note that MoveMask has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - if (matches == 0) - { - // None matched - offset += (nuint)Vector128.Count; - continue; - } - - goto IntrinsicsMatch; - } - // Move to Vector length from end for final compare - search = LoadVector128(ref searchSpace, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = Sse2.MoveMask( - Sse2.Or( - Sse2.CompareEqual(values0, search), - Sse2.CompareEqual(values1, search))); - if (matches == 0) - { - // None matched - goto NotFound; - } - } - - IntrinsicsMatch: - // Find bitflag offset of first difference and add to current offset - offset += (nuint)BitOperations.TrailingZeroCount(matches); - goto Found; - } - else if (AdvSimd.Arm64.IsSupported) - { - Vector128 search; - Vector128 matches; - Vector128 values0 = Vector128.Create(value0); - Vector128 values1 = Vector128.Create(value1); - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector128(ref searchSpace, offset); - - matches = AdvSimd.Or( - AdvSimd.CompareEqual(values0, search), - AdvSimd.CompareEqual(values1, search)); - - if (matches == Vector128.Zero) - { - offset += (nuint)Vector128.Count; - continue; - } - - // Find bitflag offset of first match and add to current offset - offset += FindFirstMatchedLane(matches); - - goto Found; - } - - // Move to Vector length from end for final compare - search = LoadVector128(ref searchSpace, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = AdvSimd.Or( - AdvSimd.CompareEqual(values0, search), - AdvSimd.CompareEqual(values1, search)); - - if (matches == Vector128.Zero) - { - // None matched - goto NotFound; - } - - // Find bitflag offset of first match and add to current offset - offset += FindFirstMatchedLane(matches); - - goto Found; - } - else if (Vector.IsHardwareAccelerated) - { - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - - Vector search; - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector(ref searchSpace, offset); - search = Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)); - if (Vector.Zero.Equals(search)) - { - // None matched - offset += (nuint)Vector.Count; - continue; - } - - goto VectorMatch; - } - - // Move to Vector length from end for final compare - search = LoadVector(ref searchSpace, lengthToExamine); - offset = lengthToExamine; - search = Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)); - if (Vector.Zero.Equals(search)) - { - // None matched - goto NotFound; - } - - VectorMatch: - offset += (nuint)LocateFirstFoundByte(search); - goto Found; - } - - Debug.Fail("Unreachable"); - goto NotFound; - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static int IndexOfAny(ref byte searchSpace, byte value0, byte value1, byte value2, int length) - { - Debug.Assert(length >= 0); - - uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions - uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions - uint uValue2 = value2; // Use uint for comparisons to avoid unnecessary 8->32 extensions - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector128.IsHardwareAccelerated) - { - // Avx2 branch also operates on Sse2 sizes, so check is combined. - nint vectorDiff = (nint)length - Vector128.Count; - if (vectorDiff >= 0) - { - // >= Vector128 is accelerated, and length is enough to use them so use that path. - // We jump forward to the intrinsics at the end of the method so a naive branch predict - // will choose the non-intrinsic path so short lengths which don't gain anything aren't - // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths - // more than make this back from the intrinsics. - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - else if (Vector.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, as it is used later - nint vectorDiff = (nint)length - Vector.Count; - if (vectorDiff >= 0) - { - // Similar as above for Vector version - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - - uint lookUp; - while (lengthToExamine >= 8) - { - lengthToExamine -= 8; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) goto Found3; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 4); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 4)) goto Found4; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 5); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 5)) goto Found5; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 6); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 6)) goto Found6; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 7); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 7)) goto Found7; offset += 8; @@ -1164,17 +388,13 @@ public static int IndexOfAny(ref byte searchSpace, byte value0, byte value1, byt { lengthToExamine -= 4; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) goto Found; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) goto Found3; offset += 4; @@ -1182,417 +402,149 @@ public static int IndexOfAny(ref byte searchSpace, byte value0, byte value1, byt while (lengthToExamine > 0) { - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + lengthToExamine -= 1; + + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) goto Found; offset += 1; - lengthToExamine -= 1; } - NotFound: - return -1; - Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 - return (int)offset; - Found1: - return (int)(offset + 1); - Found2: - return (int)(offset + 2); - Found3: - return (int)(offset + 3); - Found4: - return (int)(offset + 4); - Found5: - return (int)(offset + 5); - Found6: - return (int)(offset + 6); - Found7: - return (int)(offset + 7); - - IntrinsicsCompare: - // When we move into a Vectorized block, we process everything of Vector size; - // and then for any remainder we do a final compare of Vector size but starting at - // the end and forwards, which may overlap on an earlier compare. - - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (Vector128.IsHardwareAccelerated) + // We get past SequentialScan only if IsHardwareAccelerated is true; and remain length is greater than Vector length. + // However, we still have the redundant check to allow the JIT to see that the code is unreachable and eliminate it when the platform does not + // have hardware accelerated. After processing Vector lengths we return to SequentialScan to finish any remaining. + if (Vector256.IsHardwareAccelerated) { - uint matches; - if (Vector256.IsHardwareAccelerated) + if (offset < (nuint)(uint)Length) { - Vector256 search; - // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 - // We have already subtracted Vector128.Count from lengthToExamine so compare against that - // to see if we have double the size for Vector256.Count - if (lengthToExamine >= (nuint)Vector128.Count) + if ((((nuint)(uint)Unsafe.AsPointer(ref searchSpace) + offset) & (nuint)(Vector256.Count - 1)) != 0) + { + // Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches + // with no upper bound e.g. String.strlen. + // Start with a check on Vector128 to align to Vector256, before moving to processing Vector256. + // This ensures we do not fault across memory pages while searching for an end of string. + Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); + + // Same method as below + uint matches = Vector128.Equals(Vector128.Zero, search).ExtractMostSignificantBits(); + if (matches == 0) + { + // Zero flags set so no matches + offset += (nuint)Vector128.Count; + } + else + { + // Find bitflag offset of first match and add to current offset + return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); + } + } + + lengthToExamine = GetByteVector256SpanLength(offset, Length); + if (lengthToExamine > offset) { - Vector256 values0 = Vector256.Create(value0); - Vector256 values1 = Vector256.Create(value1); - Vector256 values2 = Vector256.Create(value2); - - // Subtract Vector128.Count so we have now subtracted Vector256.Count - lengthToExamine -= (nuint)Vector128.Count; - // First time this checks again against 0, however we will move into final compare if it fails. - while (lengthToExamine > offset) + do { - search = Vector256.LoadUnsafe(ref searchSpace, offset); - // Bitwise Or to combine the flagged matches for the second value to our match flags - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) | Vector256.Equals(values2, search)).ExtractMostSignificantBits(); - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, + Vector256 search = Vector256.LoadUnsafe(ref searchSpace, offset); + uint matches = Vector256.Equals(Vector256.Zero, search).ExtractMostSignificantBits(); + // Note that MoveMask has converted the equal vector elements into a set of bit flags, // So the bit position in 'matches' corresponds to the element offset. if (matches == 0) { - // None matched + // Zero flags set so no matches offset += (nuint)Vector256.Count; continue; } - goto IntrinsicsMatch; - } + // Find bitflag offset of first match and add to current offset + return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); + } while (lengthToExamine > offset); + } + + lengthToExamine = GetByteVector128SpanLength(offset, Length); + if (lengthToExamine > offset) + { + Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); - // Move to Vector length from end for final compare - search = Vector256.LoadUnsafe(ref searchSpace, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) | Vector256.Equals(values2, search)).ExtractMostSignificantBits(); + // Same method as above + uint matches = Vector128.Equals(Vector128.Zero, search).ExtractMostSignificantBits(); if (matches == 0) { - // None matched - goto NotFound; + // Zero flags set so no matches + offset += (nuint)Vector128.Count; + } + else + { + // Find bitflag offset of first match and add to current offset + return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); } + } - goto IntrinsicsMatch; + if (offset < (nuint)(uint)Length) + { + lengthToExamine = ((nuint)(uint)Length - offset); + goto SequentialScan; } } - - // Initial size check was done on method entry. - Debug.Assert(length >= Vector128.Count); + } + else if (Vector128.IsHardwareAccelerated) + { + if (offset < (nuint)(uint)Length) { - Vector128 search, compareResult; - Vector128 values0 = Vector128.Create(value0); - Vector128 values1 = Vector128.Create(value1); - Vector128 values2 = Vector128.Create(value2); - // First time this checks against 0 and we will move into final compare if it fails. + lengthToExamine = GetByteVector128SpanLength(offset, Length); + while (lengthToExamine > offset) { - search = Vector128.LoadUnsafe(ref searchSpace, offset); + Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) | Vector128.Equals(values2, search); + // Same method as above + Vector128 compareResult = Vector128.Equals(Vector128.Zero, search); if (compareResult == Vector128.Zero) { - // None matched + // Zero flags set so no matches offset += (nuint)Vector128.Count; continue; } - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - matches = compareResult.ExtractMostSignificantBits(); - goto IntrinsicsMatch; + // Find bitflag offset of first match and add to current offset + uint matches = compareResult.ExtractMostSignificantBits(); + return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); } - // Move to Vector length from end for final compare - search = Vector128.LoadUnsafe(ref searchSpace, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) | Vector128.Equals(values2, search); - if (compareResult == Vector128.Zero) + + if (offset < (nuint)(uint)Length) { - // None matched - goto NotFound; + lengthToExamine = ((nuint)(uint)Length - offset); + goto SequentialScan; } - matches = compareResult.ExtractMostSignificantBits(); } - - IntrinsicsMatch: - // Find bitflag offset of first difference and add to current offset - offset += (nuint)BitOperations.TrailingZeroCount(matches); - goto Found; } else if (Vector.IsHardwareAccelerated) { - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - Vector values2 = new Vector(value2); - - Vector search; - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector(ref searchSpace, offset); - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)); - if (Vector.Zero.Equals(search)) - { - // None matched - offset += (nuint)Vector.Count; - continue; - } - - goto VectorMatch; - } - - // Move to Vector length from end for final compare - search = LoadVector(ref searchSpace, lengthToExamine); - offset = lengthToExamine; - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)); - if (Vector.Zero.Equals(search)) + if (offset < (nuint)(uint)Length) { - // None matched - goto NotFound; - } - - VectorMatch: - offset += (nuint)LocateFirstFoundByte(search); - goto Found; - } - - Debug.Fail("Unreachable"); - goto NotFound; - } - - public static int LastIndexOfAny(ref byte searchSpace, byte value0, byte value1, int length) - { - Debug.Assert(length >= 0); - - uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions - uint uValue1 = value1; - nuint offset = (nuint)(uint)length; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) - { - lengthToExamine = UnalignedCountVectorFromEnd(ref searchSpace, length); - } - SequentialScan: - uint lookUp; - while (lengthToExamine >= 8) - { - lengthToExamine -= 8; - offset -= 8; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 7); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found7; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 6); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found6; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 5); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found5; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 4); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found4; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found3; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found; - } - - if (lengthToExamine >= 4) - { - lengthToExamine -= 4; - offset -= 4; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found3; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found; - } - - while (lengthToExamine > 0) - { - lengthToExamine -= 1; - offset -= 1; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found; - } + lengthToExamine = GetByteVectorSpanLength(offset, Length); - if (Vector.IsHardwareAccelerated && (offset > 0)) - { - lengthToExamine = (offset & (nuint)~(Vector.Count - 1)); - - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - - while (lengthToExamine > (nuint)(Vector.Count - 1)) - { - Vector search = LoadVector(ref searchSpace, offset - (nuint)Vector.Count); - var matches = Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)); - if (Vector.Zero.Equals(matches)) + while (lengthToExamine > offset) { - offset -= (nuint)Vector.Count; - lengthToExamine -= (nuint)Vector.Count; - continue; - } - - // Find offset of first match and add to current offset - return (int)(offset) - Vector.Count + LocateLastFoundByte(matches); - } - - if (offset > 0) - { - lengthToExamine = offset; - goto SequentialScan; - } - } - return -1; - Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 - return (int)offset; - Found1: - return (int)(offset + 1); - Found2: - return (int)(offset + 2); - Found3: - return (int)(offset + 3); - Found4: - return (int)(offset + 4); - Found5: - return (int)(offset + 5); - Found6: - return (int)(offset + 6); - Found7: - return (int)(offset + 7); - } - - public static int LastIndexOfAny(ref byte searchSpace, byte value0, byte value1, byte value2, int length) - { - Debug.Assert(length >= 0); - - uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions - uint uValue1 = value1; - uint uValue2 = value2; - nuint offset = (nuint)(uint)length; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) - { - lengthToExamine = UnalignedCountVectorFromEnd(ref searchSpace, length); - } - SequentialScan: - uint lookUp; - while (lengthToExamine >= 8) - { - lengthToExamine -= 8; - offset -= 8; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 7); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found7; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 6); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found6; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 5); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found5; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 4); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found4; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found3; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found; - } - - if (lengthToExamine >= 4) - { - lengthToExamine -= 4; - offset -= 4; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found3; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found; - } - - while (lengthToExamine > 0) - { - lengthToExamine -= 1; - offset -= 1; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found; - } - - if (Vector.IsHardwareAccelerated && (offset > 0)) - { - lengthToExamine = (offset & (nuint)~(Vector.Count - 1)); - - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - Vector values2 = new Vector(value2); - - while (lengthToExamine > (nuint)(Vector.Count - 1)) - { - Vector search = LoadVector(ref searchSpace, offset - (nuint)Vector.Count); + var matches = Vector.Equals(Vector.Zero, LoadVector(ref searchSpace, offset)); + if (Vector.Zero.Equals(matches)) + { + offset += (nuint)Vector.Count; + continue; + } - var matches = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)); + // Find offset of first match and add to current offset + return (int)offset + LocateFirstFoundByte(matches); + } - if (Vector.Zero.Equals(matches)) + if (offset < (nuint)(uint)Length) { - offset -= (nuint)Vector.Count; - lengthToExamine -= (nuint)Vector.Count; - continue; + lengthToExamine = ((nuint)(uint)Length - offset); + goto SequentialScan; } - - // Find offset of first match and add to current offset - return (int)(offset) - Vector.Count + LocateLastFoundByte(matches); - } - - if (offset > 0) - { - lengthToExamine = offset; - goto SequentialScan; } } - return -1; + + ThrowMustBeNullTerminatedString(); Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 return (int)offset; Found1: @@ -2201,34 +1153,6 @@ private static unsafe nuint UnalignedCountVector128(ref byte searchSpace) return (nuint)(uint)((Vector128.Count - unaligned) & (Vector128.Count - 1)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe nuint UnalignedCountVectorFromEnd(ref byte searchSpace, int length) - { - nint unaligned = (nint)Unsafe.AsPointer(ref searchSpace) & (Vector.Count - 1); - return (nuint)(uint)(((length & (Vector.Count - 1)) + unaligned) & (Vector.Count - 1)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint FindFirstMatchedLane(Vector128 compareResult) - { - Debug.Assert(AdvSimd.Arm64.IsSupported); - - // Mask to help find the first lane in compareResult that is set. - // MSB 0x10 corresponds to 1st lane, 0x01 corresponds to 0th lane and so forth. - Vector128 mask = Vector128.Create((ushort)0x1001).AsByte(); - - // Find the first lane that is set inside compareResult. - Vector128 maskedSelectedLanes = AdvSimd.And(compareResult, mask); - Vector128 pairwiseSelectedLane = AdvSimd.Arm64.AddPairwise(maskedSelectedLanes, maskedSelectedLanes); - ulong selectedLanes = pairwiseSelectedLane.AsUInt64().ToScalar(); - - // It should be handled by compareResult != Vector.Zero - Debug.Assert(selectedLanes != 0); - - // Find the first lane that is set inside compareResult. - return (uint)BitOperations.TrailingZeroCount(selectedLanes) >> 2; - } - public static void Reverse(ref byte buf, nuint length) { if (Avx2.IsSupported && (nuint)Vector256.Count * 2 <= length) diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Char.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Char.cs index 29a5906643a2a7..8a44db8cc3aed8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Char.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Char.cs @@ -5,7 +5,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; namespace System @@ -24,7 +23,7 @@ public static int IndexOf(ref char searchSpace, int searchSpaceLength, ref char if (valueTailLength == 0) { // for single-char values use plain IndexOf - return IndexOf(ref searchSpace, value, searchSpaceLength); + return IndexOfChar(ref searchSpace, value, searchSpaceLength); } nint offset = 0; @@ -41,7 +40,7 @@ public static int IndexOf(ref char searchSpace, int searchSpaceLength, ref char while (remainingSearchSpaceLength > 0) { // Do a quick search for the first element of "value". - int relativeIndex = IndexOf(ref Unsafe.Add(ref searchSpace, offset), valueHead, remainingSearchSpaceLength); + int relativeIndex = IndexOfChar(ref Unsafe.Add(ref searchSpace, offset), valueHead, remainingSearchSpaceLength); if (relativeIndex < 0) break; @@ -68,6 +67,7 @@ ref Unsafe.As(ref Unsafe.Add(ref searchSpace, offset + 1)), // Based on http://0x80.pl/articles/simd-strfind.html#algorithm-1-generic-simd "Algorithm 1: Generic SIMD" by Wojciech Muła // Some details about the implementation can also be found in https://github.com/dotnet/runtime/pull/63285 SEARCH_TWO_CHARS: + ref ushort ushortSearchSpace = ref Unsafe.As(ref searchSpace); if (Vector256.IsHardwareAccelerated && searchSpaceMinusValueTailLength - Vector256.Count >= 0) { // Find the last unique (which is not equal to ch1) character @@ -88,8 +88,8 @@ ref Unsafe.As(ref Unsafe.Add(ref searchSpace, offset + 1)), // Make sure we don't go out of bounds Debug.Assert(offset + ch1ch2Distance + Vector256.Count <= searchSpaceLength); - Vector256 cmpCh2 = Vector256.Equals(ch2, LoadVector256(ref searchSpace, offset + ch1ch2Distance)); - Vector256 cmpCh1 = Vector256.Equals(ch1, LoadVector256(ref searchSpace, offset)); + Vector256 cmpCh2 = Vector256.Equals(ch2, Vector256.LoadUnsafe(ref ushortSearchSpace, (nuint)(offset + ch1ch2Distance))); + Vector256 cmpCh1 = Vector256.Equals(ch1, Vector256.LoadUnsafe(ref ushortSearchSpace, (nuint)offset)); Vector256 cmpAnd = (cmpCh1 & cmpCh2).AsByte(); // Early out: cmpAnd is all zeros @@ -155,8 +155,8 @@ ref Unsafe.As(ref value), (nuint)(uint)valueLength * 2)) // Make sure we don't go out of bounds Debug.Assert(offset + ch1ch2Distance + Vector128.Count <= searchSpaceLength); - Vector128 cmpCh2 = Vector128.Equals(ch2, LoadVector128(ref searchSpace, offset + ch1ch2Distance)); - Vector128 cmpCh1 = Vector128.Equals(ch1, LoadVector128(ref searchSpace, offset)); + Vector128 cmpCh2 = Vector128.Equals(ch2, Vector128.LoadUnsafe(ref ushortSearchSpace, (nuint)(offset + ch1ch2Distance))); + Vector128 cmpCh1 = Vector128.Equals(ch1, Vector128.LoadUnsafe(ref ushortSearchSpace, (nuint)offset)); Vector128 cmpAnd = (cmpCh1 & cmpCh2).AsByte(); // Early out: cmpAnd is all zeros @@ -214,7 +214,7 @@ public static int LastIndexOf(ref char searchSpace, int searchSpaceLength, ref c int valueTailLength = valueLength - 1; if (valueTailLength == 0) - return LastIndexOf(ref searchSpace, value, searchSpaceLength); // for single-char values use plain LastIndexOf + return LastIndexOfValueType(ref Unsafe.As(ref searchSpace), (short)value, searchSpaceLength); // for single-char values use plain LastIndexOf int offset = 0; char valueHead = value; @@ -234,7 +234,7 @@ public static int LastIndexOf(ref char searchSpace, int searchSpaceLength, ref c break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there. // Do a quick search for the first element of "value". - int relativeIndex = LastIndexOf(ref searchSpace, valueHead, remainingSearchSpaceLength); + int relativeIndex = LastIndexOfValueType(ref Unsafe.As(ref searchSpace), (short)valueHead, remainingSearchSpaceLength); if (relativeIndex == -1) break; @@ -253,6 +253,7 @@ ref Unsafe.As(ref Unsafe.Add(ref searchSpace, relativeIndex + 1)), // Based on http://0x80.pl/articles/simd-strfind.html#algorithm-1-generic-simd "Algorithm 1: Generic SIMD" by Wojciech Muła // Some details about the implementation can also be found in https://github.com/dotnet/runtime/pull/63285 SEARCH_TWO_CHARS: + ref ushort ushortSearchSpace = ref Unsafe.As(ref searchSpace); if (Vector256.IsHardwareAccelerated && searchSpaceMinusValueTailLength >= Vector256.Count) { offset = searchSpaceMinusValueTailLength - Vector256.Count; @@ -270,8 +271,8 @@ ref Unsafe.As(ref Unsafe.Add(ref searchSpace, relativeIndex + 1)), do { - Vector256 cmpCh1 = Vector256.Equals(ch1, LoadVector256(ref searchSpace, (nuint)offset)); - Vector256 cmpCh2 = Vector256.Equals(ch2, LoadVector256(ref searchSpace, (nuint)(offset + ch1ch2Distance))); + Vector256 cmpCh1 = Vector256.Equals(ch1, Vector256.LoadUnsafe(ref ushortSearchSpace, (nuint)offset)); + Vector256 cmpCh2 = Vector256.Equals(ch2, Vector256.LoadUnsafe(ref ushortSearchSpace, (nuint)(offset + ch1ch2Distance))); Vector256 cmpAnd = (cmpCh1 & cmpCh2).AsByte(); // Early out: cmpAnd is all zeros @@ -319,8 +320,8 @@ ref Unsafe.As(ref value), (nuint)(uint)valueLength * 2)) do { - Vector128 cmpCh1 = Vector128.Equals(ch1, LoadVector128(ref searchSpace, (nuint)offset)); - Vector128 cmpCh2 = Vector128.Equals(ch2, LoadVector128(ref searchSpace, (nuint)(offset + ch1ch2Distance))); + Vector128 cmpCh1 = Vector128.Equals(ch1, Vector128.LoadUnsafe(ref ushortSearchSpace, (nuint)offset)); + Vector128 cmpCh2 = Vector128.Equals(ch2, Vector128.LoadUnsafe(ref ushortSearchSpace, (nuint)(offset + ch1ch2Distance))); Vector128 cmpAnd = (cmpCh1 & cmpCh2).AsByte(); // Early out: cmpAnd is all zeros @@ -420,104 +421,13 @@ public static unsafe int SequenceCompareTo(ref char first, int firstLength, ref return lengthDelta; } - // Adapted from IndexOf(...) + // IndexOfNullCharacter processes memory in aligned chunks, and thus it won't crash even if it accesses memory beyond the null terminator. + // This behavior is an implementation detail of the runtime and callers outside System.Private.CoreLib must not depend on it. [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe bool Contains(ref char searchSpace, char value, int length) + public static unsafe int IndexOfNullCharacter(ref char searchSpace) { - Debug.Assert(length >= 0); - - fixed (char* pChars = &searchSpace) - { - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (uint)length; - - if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) - { - // Figure out how many characters to read sequentially until we are vector aligned - // This is equivalent to: - // unaligned = ((int)pCh % Unsafe.SizeOf>()) / ElementsPerByte - // length = (Vector.Count - unaligned) % Vector.Count - const int ElementsPerByte = sizeof(ushort) / sizeof(byte); - int unaligned = (int)((uint)((int)pChars & (Unsafe.SizeOf>() - 1)) / ElementsPerByte); - lengthToExamine = (uint)((Vector.Count - unaligned) & (Vector.Count - 1)); - } - - while (lengthToExamine >= 4) - { - lengthToExamine -= 4; - char* pStart = pChars + offset; - - if (value == pStart[0] || - value == pStart[1] || - value == pStart[2] || - value == pStart[3]) - { - goto Found; - } - - offset += 4; - } - - while (lengthToExamine > 0) - { - lengthToExamine--; - - if (value == pChars[offset]) - goto Found; - - offset++; - } - - // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow - // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware acceleration. - if (Vector.IsHardwareAccelerated && (offset < (uint)length)) - { - // Get the highest multiple of Vector.Count that is within the search space. - // That will be how many times we iterate in the loop below. - // This is equivalent to: lengthToExamine = Vector.Count + ((uint)length - offset) / Vector.Count) - lengthToExamine = ((uint)length - offset) & (nuint)~(Vector.Count - 1); - - Vector values = new(value); - Vector matches; - - while (offset < lengthToExamine) - { - // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned - Debug.Assert(((int)(pChars + offset) % Unsafe.SizeOf>()) == 0); - matches = Vector.Equals(values, Unsafe.Read>(pChars + offset)); - if (matches == Vector.Zero) - { - offset += (nuint)Vector.Count; - continue; - } - - goto Found; - } - - // The total length is at least Vector.Count, so instead of falling back to a - // sequential scan for the remainder, we check the vector read from the end -- note: unaligned read necessary. - // We do this only if at least one element is left. - if (offset < (uint)length) - { - matches = Vector.Equals(values, Unsafe.ReadUnaligned>(pChars + (uint)length - (uint)Vector.Count)); - if (matches != Vector.Zero) - { - goto Found; - } - } - } - - return false; - - Found: - return true; - } - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe int IndexOf(ref char searchSpace, char value, int length) - { - Debug.Assert(length >= 0); + const char value = '\0'; + const int length = int.MaxValue; nint offset = 0; nint lengthToExamine = length; @@ -530,18 +440,12 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) { // Avx2 branch also operates on Sse2 sizes, so check is combined. // Needs to be double length to allow us to align the data first. - if (length >= Vector128.Count * 2) - { - lengthToExamine = UnalignedCountVector128(ref searchSpace); - } + lengthToExamine = UnalignedCountVector128(ref searchSpace); } else if (Vector.IsHardwareAccelerated) { // Needs to be double length to allow us to align the data first. - if (length >= Vector.Count * 2) - { - lengthToExamine = UnalignedCountVector(ref searchSpace); - } + lengthToExamine = UnalignedCountVector(ref searchSpace); } SequentialScan: @@ -600,11 +504,10 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) // method, so the alignment only acts as best endeavour. The GC cost is likely to dominate over // the misalignment that may occur after; to we default to giving the GC a free hand to relocate and // its up to the caller whether they are operating over fixed data. - Vector128 values = Vector128.Create((ushort)value); Vector128 search = Vector128.LoadUnsafe(ref ushortSearchSpace, (nuint)offset); // Same method as below - uint matches = Vector128.Equals(values, search).AsByte().ExtractMostSignificantBits(); + uint matches = Vector128.Equals(Vector128.Zero, search).AsByte().ExtractMostSignificantBits(); if (matches == 0) { // Zero flags set so no matches @@ -620,13 +523,12 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) lengthToExamine = GetCharVector256SpanLength(offset, length); if (lengthToExamine > 0) { - Vector256 values = Vector256.Create((ushort)value); do { Debug.Assert(lengthToExamine >= Vector256.Count); Vector256 search = Vector256.LoadUnsafe(ref ushortSearchSpace, (nuint)offset); - uint matches = Vector256.Equals(values, search).AsByte().ExtractMostSignificantBits(); + uint matches = Vector256.Equals(Vector256.Zero, search).AsByte().ExtractMostSignificantBits(); // Note that MoveMask has converted the equal vector elements into a set of bit flags, // So the bit position in 'matches' corresponds to the element offset. if (matches == 0) @@ -648,11 +550,10 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) { Debug.Assert(lengthToExamine >= Vector128.Count); - Vector128 values = Vector128.Create((ushort)value); Vector128 search = Vector128.LoadUnsafe(ref ushortSearchSpace, (nuint)offset); // Same method as above - uint matches = Vector128.Equals(values, search).AsByte().ExtractMostSignificantBits(); + uint matches = Vector128.Equals(Vector128.Zero, search).AsByte().ExtractMostSignificantBits(); if (matches == 0) { // Zero flags set so no matches @@ -684,7 +585,6 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) lengthToExamine = GetCharVector128SpanLength(offset, length); if (lengthToExamine > 0) { - Vector128 values = Vector128.Create((ushort)value); do { Debug.Assert(lengthToExamine >= Vector128.Count); @@ -692,7 +592,7 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) Vector128 search = Vector128.LoadUnsafe(ref ushortSearchSpace, (uint)offset); // Same method as above - Vector128 compareResult = Vector128.Equals(values, search); + Vector128 compareResult = Vector128.Equals(Vector128.Zero, search); if (compareResult == Vector128.Zero) { // Zero flags set so no matches @@ -725,12 +625,11 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) if (lengthToExamine > 0) { - Vector values = new Vector((ushort)value); do { Debug.Assert(lengthToExamine >= Vector.Count); - var matches = Vector.Equals(values, LoadVector(ref searchSpace, offset)); + var matches = Vector.Equals(Vector.Zero, LoadVector(ref searchSpace, offset)); if (Vector.Zero.Equals(matches)) { offset += Vector.Count; @@ -750,789 +649,8 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) } } } - return -1; - Found3: - return (int)(offset + 3); - Found2: - return (int)(offset + 2); - Found1: - return (int)(offset + 1); - Found: - return (int)(offset); - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe int IndexOfAny(ref char searchStart, char value0, char value1, int length) - { - Debug.Assert(length >= 0); - - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector128.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector128.Count; - if (vectorDiff >= 0) - { - // >= Vector128 is accelerated and length is enough to use them, so use that path. - // We jump forward to the intrinsics at the end of them method so a naive branch predict - // will choose the non-intrinsic path so short lengths which don't gain anything aren't - // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths - // more than make this back from the intrinsics. - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - else if (Vector.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector.Count; - if (vectorDiff >= 0) - { - // Similar as above for Vector version - lengthToExamine = (nuint)vectorDiff; - goto VectorCompare; - } - } - - int lookUp; - while (lengthToExamine >= 4) - { - ref char current = ref Add(ref searchStart, offset); - - lookUp = current; - if (value0 == lookUp || value1 == lookUp) - goto Found; - lookUp = Unsafe.Add(ref current, 1); - if (value0 == lookUp || value1 == lookUp) - goto Found1; - lookUp = Unsafe.Add(ref current, 2); - if (value0 == lookUp || value1 == lookUp) - goto Found2; - lookUp = Unsafe.Add(ref current, 3); - if (value0 == lookUp || value1 == lookUp) - goto Found3; - - offset += 4; - lengthToExamine -= 4; - } - - while (lengthToExamine > 0) - { - lookUp = Add(ref searchStart, offset); - if (value0 == lookUp || value1 == lookUp) - goto Found; - - offset += 1; - lengthToExamine -= 1; - } - - NotFound: - return -1; - Found3: - return (int)(offset + 3); - Found2: - return (int)(offset + 2); - Found1: - return (int)(offset + 1); - Found: - return (int)offset; - - IntrinsicsCompare: - // When we move into a Vectorized block, we process everything of Vector size; - // and then for any remainder we do a final compare of Vector size but starting at - // the end and forwards, which may overlap on an earlier compare. - - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (Vector128.IsHardwareAccelerated) - { - uint matches; - ref ushort ushortSearchStart = ref Unsafe.As(ref searchStart); - if (Vector256.IsHardwareAccelerated) - { - Vector256 search; - // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 - // We have already subtracted Vector128.Count from lengthToExamine so compare against that - // to see if we have double the size for Vector256.Count - if (lengthToExamine >= (nuint)Vector128.Count) - { - Vector256 values0 = Vector256.Create((ushort)value0); - Vector256 values1 = Vector256.Create((ushort)value1); - - // Subtract Vector128.Count so we have now subtracted Vector256.Count - lengthToExamine -= (nuint)Vector128.Count; - // First time this checks again against 0, however we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector256.LoadUnsafe(ref ushortSearchStart, offset); - // Bitwise Or to combine the flagged matches for the second value to our match flags - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search)) - .AsByte().ExtractMostSignificantBits(); - - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - if (matches == 0) - { - // None matched - offset += (nuint)Vector256.Count; - continue; - } - - goto IntrinsicsMatch; - } - - // Move to Vector length from end for final compare - search = Vector256.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search)) - .AsByte().ExtractMostSignificantBits(); - if (matches == 0) - { - // None matched - goto NotFound; - } - - goto IntrinsicsMatch; - } - } - - // Initial size check was done on method entry. - Debug.Assert(length >= Vector128.Count); - { - Vector128 search, compareResult; - Vector128 values0 = Vector128.Create((ushort)value0); - Vector128 values1 = Vector128.Create((ushort)value1); - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector128.LoadUnsafe(ref ushortSearchStart, offset); - - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search); - if (compareResult == Vector128.Zero) - { - // None matched - offset += (nuint)Vector128.Count; - continue; - } - - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - matches = compareResult.AsByte().ExtractMostSignificantBits(); - goto IntrinsicsMatch; - } - // Move to Vector length from end for final compare - search = Vector128.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search); - if (compareResult == Vector128.Zero) - { - // None matched - goto NotFound; - } - matches = compareResult.AsByte().ExtractMostSignificantBits(); - } - - IntrinsicsMatch: - // Find bitflag offset of first difference and add to current offset, - // flags are in bytes so divide by 2 for chars (shift right by 1) - offset += (nuint)(uint)BitOperations.TrailingZeroCount(matches) >> 1; - goto Found; - } - - VectorCompare: - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (!Vector128.IsHardwareAccelerated && Vector.IsHardwareAccelerated) - { - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - - Vector search; - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector(ref searchStart, offset); - search = Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)); - if (Vector.Zero.Equals(search)) - { - // None matched - offset += (nuint)Vector.Count; - continue; - } - - goto VectorMatch; - } - - // Move to Vector length from end for final compare - search = LoadVector(ref searchStart, lengthToExamine); - offset = lengthToExamine; - search = Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)); - if (Vector.Zero.Equals(search)) - { - // None matched - goto NotFound; - } - - VectorMatch: - offset += (nuint)(uint)LocateFirstFoundChar(search); - goto Found; - } - - Debug.Fail("Unreachable"); - goto NotFound; - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe int IndexOfAny(ref char searchStart, char value0, char value1, char value2, int length) - { - Debug.Assert(length >= 0); - - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector128.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector128.Count; - if (vectorDiff >= 0) - { - // >= Vector128 is accelerated and length is enough to use them, so use that path. - // We jump forward to the intrinsics at the end of them method so a naive branch predict - // will choose the non-intrinsic path so short lengths which don't gain anything aren't - // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths - // more than make this back from the intrinsics. - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - else if (Vector.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector.Count; - if (vectorDiff >= 0) - { - // Similar as above for Vector version - lengthToExamine = (nuint)vectorDiff; - goto VectorCompare; - } - } - - int lookUp; - while (lengthToExamine >= 4) - { - ref char current = ref Add(ref searchStart, offset); - - lookUp = current; - if (value0 == lookUp || value1 == lookUp || value2 == lookUp) - goto Found; - lookUp = Unsafe.Add(ref current, 1); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp) - goto Found1; - lookUp = Unsafe.Add(ref current, 2); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp) - goto Found2; - lookUp = Unsafe.Add(ref current, 3); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp) - goto Found3; - - offset += 4; - lengthToExamine -= 4; - } - - while (lengthToExamine > 0) - { - lookUp = Add(ref searchStart, offset); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp) - goto Found; - - offset += 1; - lengthToExamine -= 1; - } - - NotFound: - return -1; - Found3: - return (int)(offset + 3); - Found2: - return (int)(offset + 2); - Found1: - return (int)(offset + 1); - Found: - return (int)offset; - - IntrinsicsCompare: - // When we move into a Vectorized block, we process everything of Vector size; - // and then for any remainder we do a final compare of Vector size but starting at - // the end and forwards, which may overlap on an earlier compare. - - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (Vector128.IsHardwareAccelerated) - { - uint matches; - ref ushort ushortSearchStart = ref Unsafe.As(ref searchStart); - if (Vector256.IsHardwareAccelerated) - { - Vector256 search; - // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 - // We have already subtracted Vector128.Count from lengthToExamine so compare against that - // to see if we have double the size for Vector256.Count - if (lengthToExamine >= (nuint)Vector128.Count) - { - Vector256 values0 = Vector256.Create((ushort)value0); - Vector256 values1 = Vector256.Create((ushort)value1); - Vector256 values2 = Vector256.Create((ushort)value2); - - // Subtract Vector128.Count so we have now subtracted Vector256.Count - lengthToExamine -= (nuint)Vector128.Count; - // First time this checks again against 0, however we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector256.LoadUnsafe(ref ushortSearchStart, offset); - // Bitwise Or to combine the flagged matches for the second value to our match flags - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) | Vector256.Equals(values2, search)) - .AsByte().ExtractMostSignificantBits(); - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - if (matches == 0) - { - // None matched - offset += (nuint)Vector256.Count; - continue; - } - - goto IntrinsicsMatch; - } - - // Move to Vector length from end for final compare - search = Vector256.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) | Vector256.Equals(values2, search)) - .AsByte().ExtractMostSignificantBits(); - if (matches == 0) - { - // None matched - goto NotFound; - } - - goto IntrinsicsMatch; - } - } - - // Initial size check was done on method entry. - Debug.Assert(length >= Vector128.Count); - { - Vector128 search, compareResult; - Vector128 values0 = Vector128.Create((ushort)value0); - Vector128 values1 = Vector128.Create((ushort)value1); - Vector128 values2 = Vector128.Create((ushort)value2); - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector128.LoadUnsafe(ref ushortSearchStart, offset); - - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) | Vector128.Equals(values2, search); - if (compareResult == Vector128.Zero) - { - // None matched - offset += (nuint)Vector128.Count; - continue; - } - - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - matches = compareResult.AsByte().ExtractMostSignificantBits(); - goto IntrinsicsMatch; - } - // Move to Vector length from end for final compare - search = Vector128.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) | Vector128.Equals(values2, search); - if (compareResult == Vector128.Zero) - { - // None matched - goto NotFound; - } - matches = compareResult.AsByte().ExtractMostSignificantBits(); - } - - IntrinsicsMatch: - // Find bitflag offset of first difference and add to current offset, - // flags are in bytes so divide by 2 for chars (shift right by 1) - offset += (nuint)(uint)BitOperations.TrailingZeroCount(matches) >> 1; - goto Found; - } - - VectorCompare: - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (!Vector128.IsHardwareAccelerated && Vector.IsHardwareAccelerated) - { - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - Vector values2 = new Vector(value2); - - Vector search; - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector(ref searchStart, offset); - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)); - if (Vector.Zero.Equals(search)) - { - // None matched - offset += (nuint)Vector.Count; - continue; - } - - goto VectorMatch; - } - - // Move to Vector length from end for final compare - search = LoadVector(ref searchStart, lengthToExamine); - offset = lengthToExamine; - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)); - if (Vector.Zero.Equals(search)) - { - // None matched - goto NotFound; - } - - VectorMatch: - offset += (nuint)(uint)LocateFirstFoundChar(search); - goto Found; - } - - Debug.Fail("Unreachable"); - goto NotFound; - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe int IndexOfAny(ref char searchStart, char value0, char value1, char value2, char value3, int length) - { - Debug.Assert(length >= 0); - - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector128.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector128.Count; - if (vectorDiff >= 0) - { - // >= Vector128 is accelerated and length is enough to use them, so use that path. - // We jump forward to the intrinsics at the end of them method so a naive branch predict - // will choose the non-intrinsic path so short lengths which don't gain anything aren't - // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths - // more than make this back from the intrinsics. - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - else if (Vector.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector.Count; - if (vectorDiff >= 0) - { - // Similar as above for Vector version - lengthToExamine = (nuint)vectorDiff; - goto VectorCompare; - } - } - - int lookUp; - while (lengthToExamine >= 4) - { - ref char current = ref Add(ref searchStart, offset); - - lookUp = current; - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) - goto Found; - lookUp = Unsafe.Add(ref current, 1); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) - goto Found1; - lookUp = Unsafe.Add(ref current, 2); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) - goto Found2; - lookUp = Unsafe.Add(ref current, 3); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) - goto Found3; - - offset += 4; - lengthToExamine -= 4; - } - - while (lengthToExamine > 0) - { - lookUp = Add(ref searchStart, offset); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) - goto Found; - - offset += 1; - lengthToExamine -= 1; - } - - NotFound: - return -1; - Found3: - return (int)(offset + 3); - Found2: - return (int)(offset + 2); - Found1: - return (int)(offset + 1); - Found: - return (int)offset; - - IntrinsicsCompare: - // When we move into a Vectorized block, we process everything of Vector size; - // and then for any remainder we do a final compare of Vector size but starting at - // the end and forwards, which may overlap on an earlier compare. - - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (Vector128.IsHardwareAccelerated) - { - uint matches; - ref ushort ushortSearchStart = ref Unsafe.As(ref searchStart); - if (Vector256.IsHardwareAccelerated) - { - Vector256 search; - // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 - // We have already subtracted Vector128.Count from lengthToExamine so compare against that - // to see if we have double the size for Vector256.Count - if (lengthToExamine >= (nuint)Vector128.Count) - { - Vector256 values0 = Vector256.Create((ushort)value0); - Vector256 values1 = Vector256.Create((ushort)value1); - Vector256 values2 = Vector256.Create((ushort)value2); - Vector256 values3 = Vector256.Create((ushort)value3); - - // Subtract Vector128.Count so we have now subtracted Vector256.Count - lengthToExamine -= (nuint)Vector128.Count; - // First time this checks again against 0, however we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector256.LoadUnsafe(ref ushortSearchStart, offset); - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) - | Vector256.Equals(values2, search) | Vector256.Equals(values3, search)) - .AsByte().ExtractMostSignificantBits(); - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - if (matches == 0) - { - // None matched - offset += (nuint)Vector256.Count; - continue; - } - - goto IntrinsicsMatch; - } - - // Move to Vector length from end for final compare - search = Vector256.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) - | Vector256.Equals(values2, search) | Vector256.Equals(values3, search)) - .AsByte().ExtractMostSignificantBits(); - if (matches == 0) - { - // None matched - goto NotFound; - } - - goto IntrinsicsMatch; - } - } - - // Initial size check was done on method entry. - Debug.Assert(length >= Vector128.Count); - { - Vector128 search, compareResult; - Vector128 values0 = Vector128.Create((ushort)value0); - Vector128 values1 = Vector128.Create((ushort)value1); - Vector128 values2 = Vector128.Create((ushort)value2); - Vector128 values3 = Vector128.Create((ushort)value3); - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector128.LoadUnsafe(ref ushortSearchStart, offset); - - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) - | Vector128.Equals(values2, search) | Vector128.Equals(values3, search); - if (compareResult == Vector128.Zero) - { - // None matched - offset += (nuint)Vector128.Count; - continue; - } - - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - matches = compareResult.AsByte().ExtractMostSignificantBits(); - goto IntrinsicsMatch; - } - // Move to Vector length from end for final compare - search = Vector128.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) - | Vector128.Equals(values2, search) | Vector128.Equals(values3, search); - if (compareResult == Vector128.Zero) - { - // None matched - goto NotFound; - } - matches = compareResult.AsByte().ExtractMostSignificantBits(); - } - - IntrinsicsMatch: - // Find bitflag offset of first difference and add to current offset, - // flags are in bytes so divide by 2 for chars (shift right by 1) - offset += (nuint)(uint)BitOperations.TrailingZeroCount(matches) >> 1; - goto Found; - } - - VectorCompare: - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (!Vector128.IsHardwareAccelerated && Vector.IsHardwareAccelerated) - { - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - Vector values2 = new Vector(value2); - Vector values3 = new Vector(value3); - - Vector search; - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector(ref searchStart, offset); - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)), - Vector.Equals(search, values3)); - if (Vector.Zero.Equals(search)) - { - // None matched - offset += (nuint)Vector.Count; - continue; - } - - goto VectorMatch; - } - - // Move to Vector length from end for final compare - search = LoadVector(ref searchStart, lengthToExamine); - offset = lengthToExamine; - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)), - Vector.Equals(search, values3)); - if (Vector.Zero.Equals(search)) - { - // None matched - goto NotFound; - } - - VectorMatch: - offset += (nuint)(uint)LocateFirstFoundChar(search); - goto Found; - } - - Debug.Fail("Unreachable"); - goto NotFound; - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe int IndexOfAny(ref char searchStart, char value0, char value1, char value2, char value3, char value4, int length) - { - Debug.Assert(length >= 0); - - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector128.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector128.Count; - if (vectorDiff >= 0) - { - // >= Vector128 is accelerated and length is enough to use them, so use that path. - // We jump forward to the intrinsics at the end of them method so a naive branch predict - // will choose the non-intrinsic path so short lengths which don't gain anything aren't - // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths - // more than make this back from the intrinsics. - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - else if (Vector.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector.Count; - if (vectorDiff >= 0) - { - // Similar as above for Vector version - lengthToExamine = (nuint)vectorDiff; - goto VectorCompare; - } - } - - int lookUp; - while (lengthToExamine >= 4) - { - ref char current = ref Add(ref searchStart, offset); - - lookUp = current; - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp || value4 == lookUp) - goto Found; - lookUp = Unsafe.Add(ref current, 1); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp || value4 == lookUp) - goto Found1; - lookUp = Unsafe.Add(ref current, 2); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp || value4 == lookUp) - goto Found2; - lookUp = Unsafe.Add(ref current, 3); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp || value4 == lookUp) - goto Found3; - - offset += 4; - lengthToExamine -= 4; - } - - while (lengthToExamine > 0) - { - lookUp = Add(ref searchStart, offset); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp || value4 == lookUp) - goto Found; - - offset += 1; - lengthToExamine -= 1; - } - NotFound: - return -1; + ThrowMustBeNullTerminatedString(); Found3: return (int)(offset + 3); Found2: @@ -1540,268 +658,7 @@ public static unsafe int IndexOfAny(ref char searchStart, char value0, char valu Found1: return (int)(offset + 1); Found: - return (int)offset; - - IntrinsicsCompare: - // When we move into a Vectorized block, we process everything of Vector size; - // and then for any remainder we do a final compare of Vector size but starting at - // the end and forwards, which may overlap on an earlier compare. - - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (Vector128.IsHardwareAccelerated) - { - uint matches; - ref ushort ushortSearchStart = ref Unsafe.As(ref searchStart); - if (Vector256.IsHardwareAccelerated) - { - Vector256 search; - // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 - // We have already subtracted Vector128.Count from lengthToExamine so compare against that - // to see if we have double the size for Vector256.Count - if (lengthToExamine >= (nuint)Vector128.Count) - { - Vector256 values0 = Vector256.Create((ushort)value0); - Vector256 values1 = Vector256.Create((ushort)value1); - Vector256 values2 = Vector256.Create((ushort)value2); - Vector256 values3 = Vector256.Create((ushort)value3); - Vector256 values4 = Vector256.Create((ushort)value4); - - // Subtract Vector128.Count so we have now subtracted Vector256.Count - lengthToExamine -= (nuint)Vector128.Count; - // First time this checks again against 0, however we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector256.LoadUnsafe(ref ushortSearchStart, offset); - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) | Vector256.Equals(values2, search) - | Vector256.Equals(values3, search) | Vector256.Equals(values4, search)) - .AsByte().ExtractMostSignificantBits(); - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - if (matches == 0) - { - // None matched - offset += (nuint)Vector256.Count; - continue; - } - - goto IntrinsicsMatch; - } - - // Move to Vector length from end for final compare - search = Vector256.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) | Vector256.Equals(values2, search) - | Vector256.Equals(values3, search) | Vector256.Equals(values4, search)) - .AsByte().ExtractMostSignificantBits(); - if (matches == 0) - { - // None matched - goto NotFound; - } - - goto IntrinsicsMatch; - } - } - - // Initial size check was done on method entry. - Debug.Assert(length >= Vector128.Count); - { - Vector128 search, compareResult; - Vector128 values0 = Vector128.Create((ushort)value0); - Vector128 values1 = Vector128.Create((ushort)value1); - Vector128 values2 = Vector128.Create((ushort)value2); - Vector128 values3 = Vector128.Create((ushort)value3); - Vector128 values4 = Vector128.Create((ushort)value4); - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector128.LoadUnsafe(ref ushortSearchStart, offset); - - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) | Vector128.Equals(values2, search) - | Vector128.Equals(values3, search) | Vector128.Equals(values4, search); - if (compareResult == Vector128.Zero) - { - // None matched - offset += (nuint)Vector128.Count; - continue; - } - - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - matches = compareResult.AsByte().ExtractMostSignificantBits(); - goto IntrinsicsMatch; - } - // Move to Vector length from end for final compare - search = Vector128.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) | Vector128.Equals(values2, search) - | Vector128.Equals(values3, search) | Vector128.Equals(values4, search); - if (compareResult == Vector128.Zero) - { - // None matched - goto NotFound; - } - matches = compareResult.AsByte().ExtractMostSignificantBits(); - } - - IntrinsicsMatch: - // Find bitflag offset of first difference and add to current offset, - // flags are in bytes so divide by 2 for chars (shift right by 1) - offset += (nuint)(uint)BitOperations.TrailingZeroCount(matches) >> 1; - goto Found; - } - - VectorCompare: - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (!Vector128.IsHardwareAccelerated && Vector.IsHardwareAccelerated) - { - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - Vector values2 = new Vector(value2); - Vector values3 = new Vector(value3); - Vector values4 = new Vector(value4); - - Vector search; - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector(ref searchStart, offset); - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)), - Vector.Equals(search, values3)), - Vector.Equals(search, values4)); - if (Vector.Zero.Equals(search)) - { - // None matched - offset += (nuint)Vector.Count; - continue; - } - - goto VectorMatch; - } - - // Move to Vector length from end for final compare - search = LoadVector(ref searchStart, lengthToExamine); - offset = lengthToExamine; - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)), - Vector.Equals(search, values3)), - Vector.Equals(search, values4)); - if (Vector.Zero.Equals(search)) - { - // None matched - goto NotFound; - } - - VectorMatch: - offset += (nuint)(uint)LocateFirstFoundChar(search); - goto Found; - } - - Debug.Fail("Unreachable"); - goto NotFound; - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe int LastIndexOf(ref char searchSpace, char value, int length) - { - Debug.Assert(length >= 0); - - fixed (char* pChars = &searchSpace) - { - char* pCh = pChars + length; - char* pEndCh = pChars; - - if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) - { - // Figure out how many characters to read sequentially from the end until we are vector aligned - // This is equivalent to: length = ((int)pCh % Unsafe.SizeOf>()) / elementsPerByte - const int elementsPerByte = sizeof(ushort) / sizeof(byte); - length = ((int)pCh & (Unsafe.SizeOf>() - 1)) / elementsPerByte; - } - - SequentialScan: - while (length >= 4) - { - length -= 4; - pCh -= 4; - - if (*(pCh + 3) == value) - goto Found3; - if (*(pCh + 2) == value) - goto Found2; - if (*(pCh + 1) == value) - goto Found1; - if (*pCh == value) - goto Found; - } - - while (length > 0) - { - length--; - pCh--; - - if (*pCh == value) - goto Found; - } - - // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow - // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated. - if (Vector.IsHardwareAccelerated && pCh > pEndCh) - { - // Get the highest multiple of Vector.Count that is within the search space. - // That will be how many times we iterate in the loop below. - // This is equivalent to: length = Vector.Count * ((int)(pCh - pEndCh) / Vector.Count) - length = (int)((pCh - pEndCh) & ~(Vector.Count - 1)); - - // Get comparison Vector - Vector vComparison = new Vector(value); - - while (length > 0) - { - char* pStart = pCh - Vector.Count; - // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh (and hence pSart) is always vector aligned - Debug.Assert(((int)pStart & (Unsafe.SizeOf>() - 1)) == 0); - Vector vMatches = Vector.Equals(vComparison, Unsafe.Read>(pStart)); - if (Vector.Zero.Equals(vMatches)) - { - pCh -= Vector.Count; - length -= Vector.Count; - continue; - } - // Find offset of last match - return (int)(pStart - pEndCh) + LocateLastFoundChar(vMatches); - } - - if (pCh > pEndCh) - { - length = (int)(pCh - pEndCh); - goto SequentialScan; - } - } - - return -1; - Found: - return (int)(pCh - pEndCh); - Found1: - return (int)(pCh - pEndCh) + 1; - Found2: - return (int)(pCh - pEndCh) + 2; - Found3: - return (int)(pCh - pEndCh) + 3; - } + return (int)(offset); } // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138 @@ -1829,35 +686,6 @@ private static int LocateFirstFoundChar(Vector match) private static int LocateFirstFoundChar(ulong match) => BitOperations.TrailingZeroCount(match) >> 4; - // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int LocateLastFoundChar(Vector match) - { - var vector64 = Vector.AsVectorUInt64(match); - ulong candidate = 0; - int i = Vector.Count - 1; - - // This pattern is only unrolled by the Jit if the limit is Vector.Count - // As such, we need a dummy iteration variable for that condition to be satisfied - for (int j = 0; j < Vector.Count; j++) - { - candidate = vector64[i]; - if (candidate != 0) - { - break; - } - - i--; - } - - // Single LEA instruction with jitted const (using function result) - return i * 4 + LocateLastFoundChar(candidate); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int LocateLastFoundChar(ulong match) - => BitOperations.Log2(match) >> 4; - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector LoadVector(ref char start, nint offset) => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, offset))); @@ -1866,25 +694,6 @@ private static Vector LoadVector(ref char start, nint offset) private static Vector LoadVector(ref char start, nuint offset) => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, (nint)offset))); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 LoadVector128(ref char start, nint offset) - => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, offset))); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 LoadVector128(ref char start, nuint offset) - => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, (nint)offset))); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 LoadVector256(ref char start, nint offset) - => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, offset))); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 LoadVector256(ref char start, nuint offset) - => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, (nint)offset))); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ref char Add(ref char start, nuint offset) => ref Unsafe.Add(ref start, (nint)offset); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static nint GetCharVectorSpanLength(nint offset, nint length) => (length - offset) & ~(Vector.Count - 1); @@ -1922,21 +731,6 @@ private static unsafe nint UnalignedCountVector128(ref char searchSpace) return (nint)(uint)(-(int)Unsafe.AsPointer(ref searchSpace) / ElementsPerByte) & (Vector128.Count - 1); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int FindFirstMatchedLane(Vector128 compareResult) - { - Debug.Assert(AdvSimd.Arm64.IsSupported); - - Vector128 pairwiseSelectedLane = AdvSimd.Arm64.AddPairwise(compareResult.AsByte(), compareResult.AsByte()); - ulong selectedLanes = pairwiseSelectedLane.AsUInt64().ToScalar(); - - // It should be handled by compareResult != Vector.Zero - Debug.Assert(selectedLanes != 0); - - return BitOperations.TrailingZeroCount(selectedLanes) >> 3; - } - public static void Reverse(ref char buf, nuint length) { if (Avx2.IsSupported && (nuint)Vector256.Count * 2 <= length) diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Mono.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Mono.cs new file mode 100644 index 00000000000000..d6a7f09e7465b1 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Mono.cs @@ -0,0 +1,2697 @@ +// 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.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; + +namespace System +{ + internal static partial class SpanHelpers // helpers used by Mono + { + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static unsafe int IndexOfValueType(ref byte searchSpace, byte value, int length) + { + Debug.Assert(length >= 0); + + uint uValue = value; // Use uint for comparisons to avoid unnecessary 8->32 extensions + nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations + nuint lengthToExamine = (nuint)(uint)length; + + if (Vector128.IsHardwareAccelerated) + { + // Avx2 branch also operates on Sse2 sizes, so check is combined. + if (length >= Vector128.Count * 2) + { + lengthToExamine = UnalignedCountVector128(ref searchSpace); + } + } + else if (Vector.IsHardwareAccelerated) + { + if (length >= Vector.Count * 2) + { + lengthToExamine = UnalignedCountVector(ref searchSpace); + } + } + SequentialScan: + while (lengthToExamine >= 8) + { + lengthToExamine -= 8; + + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) + goto Found; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) + goto Found1; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) + goto Found2; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) + goto Found3; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 4)) + goto Found4; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 5)) + goto Found5; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 6)) + goto Found6; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 7)) + goto Found7; + + offset += 8; + } + + if (lengthToExamine >= 4) + { + lengthToExamine -= 4; + + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) + goto Found; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) + goto Found1; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) + goto Found2; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) + goto Found3; + + offset += 4; + } + + while (lengthToExamine > 0) + { + lengthToExamine -= 1; + + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) + goto Found; + + offset += 1; + } + + // We get past SequentialScan only if IsHardwareAccelerated is true; and remain length is greater than Vector length. + // However, we still have the redundant check to allow the JIT to see that the code is unreachable and eliminate it when the platform does not + // have hardware accelerated. After processing Vector lengths we return to SequentialScan to finish any remaining. + if (Vector256.IsHardwareAccelerated) + { + if (offset < (nuint)(uint)length) + { + if ((((nuint)(uint)Unsafe.AsPointer(ref searchSpace) + offset) & (nuint)(Vector256.Count - 1)) != 0) + { + // Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches + // with no upper bound e.g. String.strlen. + // Start with a check on Vector128 to align to Vector256, before moving to processing Vector256. + // This ensures we do not fault across memory pages while searching for an end of string. + Vector128 values = Vector128.Create(value); + Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); + + // Same method as below + uint matches = Vector128.Equals(values, search).ExtractMostSignificantBits(); + if (matches == 0) + { + // Zero flags set so no matches + offset += (nuint)Vector128.Count; + } + else + { + // Find bitflag offset of first match and add to current offset + return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); + } + } + + lengthToExamine = GetByteVector256SpanLength(offset, length); + if (lengthToExamine > offset) + { + Vector256 values = Vector256.Create(value); + do + { + Vector256 search = Vector256.LoadUnsafe(ref searchSpace, offset); + uint matches = Vector256.Equals(values, search).ExtractMostSignificantBits(); + // Note that MoveMask has converted the equal vector elements into a set of bit flags, + // So the bit position in 'matches' corresponds to the element offset. + if (matches == 0) + { + // Zero flags set so no matches + offset += (nuint)Vector256.Count; + continue; + } + + // Find bitflag offset of first match and add to current offset + return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); + } while (lengthToExamine > offset); + } + + lengthToExamine = GetByteVector128SpanLength(offset, length); + if (lengthToExamine > offset) + { + Vector128 values = Vector128.Create(value); + Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); + + // Same method as above + uint matches = Vector128.Equals(values, search).ExtractMostSignificantBits(); + if (matches == 0) + { + // Zero flags set so no matches + offset += (nuint)Vector128.Count; + } + else + { + // Find bitflag offset of first match and add to current offset + return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); + } + } + + if (offset < (nuint)(uint)length) + { + lengthToExamine = ((nuint)(uint)length - offset); + goto SequentialScan; + } + } + } + else if (Vector128.IsHardwareAccelerated) + { + if (offset < (nuint)(uint)length) + { + lengthToExamine = GetByteVector128SpanLength(offset, length); + + Vector128 values = Vector128.Create(value); + while (lengthToExamine > offset) + { + Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); + + // Same method as above + Vector128 compareResult = Vector128.Equals(values, search); + if (compareResult == Vector128.Zero) + { + // Zero flags set so no matches + offset += (nuint)Vector128.Count; + continue; + } + + // Find bitflag offset of first match and add to current offset + uint matches = compareResult.ExtractMostSignificantBits(); + return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); + } + + if (offset < (nuint)(uint)length) + { + lengthToExamine = ((nuint)(uint)length - offset); + goto SequentialScan; + } + } + } + else if (Vector.IsHardwareAccelerated) + { + if (offset < (nuint)(uint)length) + { + lengthToExamine = GetByteVectorSpanLength(offset, length); + + Vector values = new Vector(value); + + while (lengthToExamine > offset) + { + var matches = Vector.Equals(values, LoadVector(ref searchSpace, offset)); + if (Vector.Zero.Equals(matches)) + { + offset += (nuint)Vector.Count; + continue; + } + + // Find offset of first match and add to current offset + return (int)offset + LocateFirstFoundByte(matches); + } + + if (offset < (nuint)(uint)length) + { + lengthToExamine = ((nuint)(uint)length - offset); + goto SequentialScan; + } + } + } + return -1; + Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 + return (int)offset; + Found1: + return (int)(offset + 1); + Found2: + return (int)(offset + 2); + Found3: + return (int)(offset + 3); + Found4: + return (int)(offset + 4); + Found5: + return (int)(offset + 5); + Found6: + return (int)(offset + 6); + Found7: + return (int)(offset + 7); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe int IndexOfValueType(ref short searchSpace, short value, int length) + => IndexOfChar(ref Unsafe.As(ref searchSpace), Unsafe.As(ref value), length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static unsafe int IndexOfChar(ref char searchSpace, char value, int length) + { + Debug.Assert(length >= 0); + + nint offset = 0; + nint lengthToExamine = length; + + if (((int)Unsafe.AsPointer(ref searchSpace) & 1) != 0) + { + // Input isn't char aligned, we won't be able to align it to a Vector + } + else if (Sse2.IsSupported || AdvSimd.Arm64.IsSupported) + { + // Avx2 branch also operates on Sse2 sizes, so check is combined. + // Needs to be double length to allow us to align the data first. + if (length >= Vector128.Count * 2) + { + lengthToExamine = UnalignedCountVector128(ref searchSpace); + } + } + else if (Vector.IsHardwareAccelerated) + { + // Needs to be double length to allow us to align the data first. + if (length >= Vector.Count * 2) + { + lengthToExamine = UnalignedCountVector(ref searchSpace); + } + } + + SequentialScan: + // In the non-vector case lengthToExamine is the total length. + // In the vector case lengthToExamine first aligns to Vector, + // then in a second pass after the Vector lengths is the + // remaining data that is shorter than a Vector length. + while (lengthToExamine >= 4) + { + ref char current = ref Unsafe.Add(ref searchSpace, offset); + + if (value == current) + goto Found; + if (value == Unsafe.Add(ref current, 1)) + goto Found1; + if (value == Unsafe.Add(ref current, 2)) + goto Found2; + if (value == Unsafe.Add(ref current, 3)) + goto Found3; + + offset += 4; + lengthToExamine -= 4; + } + + while (lengthToExamine > 0) + { + if (value == Unsafe.Add(ref searchSpace, offset)) + goto Found; + + offset++; + lengthToExamine--; + } + + // We get past SequentialScan only if IsHardwareAccelerated or intrinsic .IsSupported is true. However, we still have the redundant check to allow + // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated. + if (Avx2.IsSupported) + { + if (offset < length) + { + Debug.Assert(length - offset >= Vector128.Count); + if (((nint)Unsafe.AsPointer(ref Unsafe.Add(ref searchSpace, (nint)offset)) & (nint)(Vector256.Count - 1)) != 0) + { + // Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches + // with no upper bound e.g. String.wcslen. Start with a check on Vector128 to align to Vector256, + // before moving to processing Vector256. + + // If the input searchSpan has been fixed or pinned, this ensures we do not fault across memory pages + // while searching for an end of string. Specifically that this assumes that the length is either correct + // or that the data is pinned otherwise it may cause an AccessViolation from crossing a page boundary into an + // unowned page. If the search is unbounded (e.g. null terminator in wcslen) and the search value is not found, + // again this will likely cause an AccessViolation. However, correctly bounded searches will return -1 rather + // than ever causing an AV. + + // If the searchSpan has not been fixed or pinned the GC can relocate it during the execution of this + // method, so the alignment only acts as best endeavour. The GC cost is likely to dominate over + // the misalignment that may occur after; to we default to giving the GC a free hand to relocate and + // its up to the caller whether they are operating over fixed data. + Vector128 values = Vector128.Create((ushort)value); + Vector128 search = LoadVector128(ref searchSpace, offset); + + // Same method as below + int matches = Sse2.MoveMask(Sse2.CompareEqual(values, search).AsByte()); + if (matches == 0) + { + // Zero flags set so no matches + offset += Vector128.Count; + } + else + { + // Find bitflag offset of first match and add to current offset + return (int)(offset + ((uint)BitOperations.TrailingZeroCount(matches) / sizeof(char))); + } + } + + lengthToExamine = GetCharVector256SpanLength(offset, length); + if (lengthToExamine > 0) + { + Vector256 values = Vector256.Create((ushort)value); + do + { + Debug.Assert(lengthToExamine >= Vector256.Count); + + Vector256 search = LoadVector256(ref searchSpace, offset); + int matches = Avx2.MoveMask(Avx2.CompareEqual(values, search).AsByte()); + // Note that MoveMask has converted the equal vector elements into a set of bit flags, + // So the bit position in 'matches' corresponds to the element offset. + if (matches == 0) + { + // Zero flags set so no matches + offset += Vector256.Count; + lengthToExamine -= Vector256.Count; + continue; + } + + // Find bitflag offset of first match and add to current offset, + // flags are in bytes so divide for chars + return (int)(offset + ((uint)BitOperations.TrailingZeroCount(matches) / sizeof(char))); + } while (lengthToExamine > 0); + } + + lengthToExamine = GetCharVector128SpanLength(offset, length); + if (lengthToExamine > 0) + { + Debug.Assert(lengthToExamine >= Vector128.Count); + + Vector128 values = Vector128.Create((ushort)value); + Vector128 search = LoadVector128(ref searchSpace, offset); + + // Same method as above + int matches = Sse2.MoveMask(Sse2.CompareEqual(values, search).AsByte()); + if (matches == 0) + { + // Zero flags set so no matches + offset += Vector128.Count; + // Don't need to change lengthToExamine here as we don't use its current value again. + } + else + { + // Find bitflag offset of first match and add to current offset, + // flags are in bytes so divide for chars + return (int)(offset + ((uint)BitOperations.TrailingZeroCount(matches) / sizeof(char))); + } + } + + if (offset < length) + { + lengthToExamine = length - offset; + goto SequentialScan; + } + } + } + else if (Sse2.IsSupported) + { + if (offset < length) + { + Debug.Assert(length - offset >= Vector128.Count); + + lengthToExamine = GetCharVector128SpanLength(offset, length); + if (lengthToExamine > 0) + { + Vector128 values = Vector128.Create((ushort)value); + do + { + Debug.Assert(lengthToExamine >= Vector128.Count); + + Vector128 search = LoadVector128(ref searchSpace, offset); + + // Same method as above + int matches = Sse2.MoveMask(Sse2.CompareEqual(values, search).AsByte()); + if (matches == 0) + { + // Zero flags set so no matches + offset += Vector128.Count; + lengthToExamine -= Vector128.Count; + continue; + } + + // Find bitflag offset of first match and add to current offset, + // flags are in bytes so divide for chars + return (int)(offset + ((uint)BitOperations.TrailingZeroCount(matches) / sizeof(char))); + } while (lengthToExamine > 0); + } + + if (offset < length) + { + lengthToExamine = length - offset; + goto SequentialScan; + } + } + } + else if (AdvSimd.Arm64.IsSupported) + { + if (offset < length) + { + Debug.Assert(length - offset >= Vector128.Count); + + lengthToExamine = GetCharVector128SpanLength(offset, length); + if (lengthToExamine > 0) + { + Vector128 values = Vector128.Create((ushort)value); + do + { + Debug.Assert(lengthToExamine >= Vector128.Count); + + Vector128 search = LoadVector128(ref searchSpace, offset); + Vector128 compareResult = AdvSimd.CompareEqual(values, search); + + if (compareResult == Vector128.Zero) + { + offset += Vector128.Count; + lengthToExamine -= Vector128.Count; + continue; + } + + return (int)(offset + FindFirstMatchedLane(compareResult)); + } while (lengthToExamine > 0); + } + + if (offset < length) + { + lengthToExamine = length - offset; + goto SequentialScan; + } + } + } + else if (Vector.IsHardwareAccelerated) + { + if (offset < length) + { + Debug.Assert(length - offset >= Vector.Count); + + lengthToExamine = GetCharVectorSpanLength(offset, length); + + if (lengthToExamine > 0) + { + Vector values = new Vector((ushort)value); + do + { + Debug.Assert(lengthToExamine >= Vector.Count); + + var matches = Vector.Equals(values, LoadVector(ref searchSpace, offset)); + if (Vector.Zero.Equals(matches)) + { + offset += Vector.Count; + lengthToExamine -= Vector.Count; + continue; + } + + // Find offset of first match + return (int)(offset + LocateFirstFoundChar(matches)); + } while (lengthToExamine > 0); + } + + if (offset < length) + { + lengthToExamine = length - offset; + goto SequentialScan; + } + } + } + return -1; + Found3: + return (int)(offset + 3); + Found2: + return (int)(offset + 2); + Found1: + return (int)(offset + 1); + Found: + return (int)(offset); + } + + internal static unsafe int IndexOfValueType(ref T searchSpace, T value, int length) where T : struct, IEquatable + { + Debug.Assert(length >= 0); + + nint index = 0; // Use nint for arithmetic to avoid unnecessary 64->32->64 truncations + if (Vector.IsHardwareAccelerated && Vector.IsSupported && (Vector.Count * 2) <= length) + { + Vector valueVector = new Vector(value); + Vector compareVector; + Vector matchVector; + if ((uint)length % (uint)Vector.Count != 0) + { + // Number of elements is not a multiple of Vector.Count, so do one + // check and shift only enough for the remaining set to be a multiple + // of Vector.Count. + compareVector = Unsafe.As>(ref Unsafe.Add(ref searchSpace, index)); + matchVector = Vector.Equals(valueVector, compareVector); + if (matchVector != Vector.Zero) + { + goto VectorMatch; + } + index += length % Vector.Count; + length -= length % Vector.Count; + } + while (length > 0) + { + compareVector = Unsafe.As>(ref Unsafe.Add(ref searchSpace, index)); + matchVector = Vector.Equals(valueVector, compareVector); + if (matchVector != Vector.Zero) + { + goto VectorMatch; + } + index += Vector.Count; + length -= Vector.Count; + } + goto NotFound; + VectorMatch: + for (int i = 0; i < Vector.Count; i++) + if (compareVector[i].Equals(value)) + return (int)(index + i); + } + + while (length >= 8) + { + if (value.Equals(Unsafe.Add(ref searchSpace, index))) + goto Found; + if (value.Equals(Unsafe.Add(ref searchSpace, index + 1))) + goto Found1; + if (value.Equals(Unsafe.Add(ref searchSpace, index + 2))) + goto Found2; + if (value.Equals(Unsafe.Add(ref searchSpace, index + 3))) + goto Found3; + if (value.Equals(Unsafe.Add(ref searchSpace, index + 4))) + goto Found4; + if (value.Equals(Unsafe.Add(ref searchSpace, index + 5))) + goto Found5; + if (value.Equals(Unsafe.Add(ref searchSpace, index + 6))) + goto Found6; + if (value.Equals(Unsafe.Add(ref searchSpace, index + 7))) + goto Found7; + + length -= 8; + index += 8; + } + + while (length >= 4) + { + if (value.Equals(Unsafe.Add(ref searchSpace, index))) + goto Found; + if (value.Equals(Unsafe.Add(ref searchSpace, index + 1))) + goto Found1; + if (value.Equals(Unsafe.Add(ref searchSpace, index + 2))) + goto Found2; + if (value.Equals(Unsafe.Add(ref searchSpace, index + 3))) + goto Found3; + + length -= 4; + index += 4; + } + + while (length > 0) + { + if (value.Equals(Unsafe.Add(ref searchSpace, index))) + goto Found; + + index += 1; + length--; + } + NotFound: + return -1; + + Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 + return (int)index; + Found1: + return (int)(index + 1); + Found2: + return (int)(index + 2); + Found3: + return (int)(index + 3); + Found4: + return (int)(index + 4); + Found5: + return (int)(index + 5); + Found6: + return (int)(index + 6); + Found7: + return (int)(index + 7); + } + + internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, int length) where T : struct, IEquatable + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + for (int i = 0; i < length; i++) + { + if (!Unsafe.Add(ref searchSpace, i).Equals(value0)) + { + return i; + } + } + } + else + { + Vector128 notEquals, value0Vector = Vector128.Create(value0); + ref T current = ref searchSpace; + ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + notEquals = ~Vector128.Equals(value0Vector, Vector128.LoadUnsafe(ref current)); + if (notEquals != Vector128.Zero) + { + return ComputeIndex(ref searchSpace, ref current, notEquals); + } + + current = ref Unsafe.Add(ref current, Vector128.Count); + } + while (!Unsafe.IsAddressGreaterThan(ref current, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the last vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + notEquals = ~Vector128.Equals(value0Vector, Vector128.LoadUnsafe(ref oneVectorAwayFromEnd)); + if (notEquals != Vector128.Zero) + { + return ComputeIndex(ref searchSpace, ref oneVectorAwayFromEnd, notEquals); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int ComputeIndex(ref T searchSpace, ref T current, Vector128 notEquals) + { + uint notEqualsElements = notEquals.ExtractMostSignificantBits(); + int index = BitOperations.TrailingZeroCount(notEqualsElements); + return index + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / Unsafe.SizeOf()); + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static int LastIndexOfValueType(ref byte searchSpace, byte value, int length) + { + Debug.Assert(length >= 0); + + uint uValue = value; // Use uint for comparisons to avoid unnecessary 8->32 extensions + nuint offset = (nuint)(uint)length; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations + nuint lengthToExamine = (nuint)(uint)length; + + if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) + { + lengthToExamine = UnalignedCountVectorFromEnd(ref searchSpace, length); + } + SequentialScan: + while (lengthToExamine >= 8) + { + lengthToExamine -= 8; + offset -= 8; + + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 7)) + goto Found7; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 6)) + goto Found6; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 5)) + goto Found5; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 4)) + goto Found4; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) + goto Found3; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) + goto Found2; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) + goto Found1; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) + goto Found; + } + + if (lengthToExamine >= 4) + { + lengthToExamine -= 4; + offset -= 4; + + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) + goto Found3; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) + goto Found2; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) + goto Found1; + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) + goto Found; + } + + while (lengthToExamine > 0) + { + lengthToExamine -= 1; + offset -= 1; + + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) + goto Found; + } + + if (Vector.IsHardwareAccelerated && (offset > 0)) + { + lengthToExamine = (offset & (nuint)~(Vector.Count - 1)); + + Vector values = new Vector(value); + + while (lengthToExamine > (nuint)(Vector.Count - 1)) + { + var matches = Vector.Equals(values, LoadVector(ref searchSpace, offset - (nuint)Vector.Count)); + if (Vector.Zero.Equals(matches)) + { + offset -= (nuint)Vector.Count; + lengthToExamine -= (nuint)Vector.Count; + continue; + } + + // Find offset of first match and add to current offset + return (int)(offset) - Vector.Count + LocateLastFoundByte(matches); + } + if (offset > 0) + { + lengthToExamine = offset; + goto SequentialScan; + } + } + return -1; + Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 + return (int)offset; + Found1: + return (int)(offset + 1); + Found2: + return (int)(offset + 2); + Found3: + return (int)(offset + 3); + Found4: + return (int)(offset + 4); + Found5: + return (int)(offset + 5); + Found6: + return (int)(offset + 6); + Found7: + return (int)(offset + 7); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe int LastIndexOfValueType(ref short searchSpace, short value, int length) + => LastIndexOfValueType(ref Unsafe.As(ref searchSpace), Unsafe.As(ref value), length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static unsafe int LastIndexOfValueType(ref char searchSpace, char value, int length) + { + Debug.Assert(length >= 0); + + fixed (char* pChars = &searchSpace) + { + char* pCh = pChars + length; + char* pEndCh = pChars; + + if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) + { + // Figure out how many characters to read sequentially from the end until we are vector aligned + // This is equivalent to: length = ((int)pCh % Unsafe.SizeOf>()) / elementsPerByte + const int elementsPerByte = sizeof(ushort) / sizeof(byte); + length = ((int)pCh & (Unsafe.SizeOf>() - 1)) / elementsPerByte; + } + + SequentialScan: + while (length >= 4) + { + length -= 4; + pCh -= 4; + + if (*(pCh + 3) == value) + goto Found3; + if (*(pCh + 2) == value) + goto Found2; + if (*(pCh + 1) == value) + goto Found1; + if (*pCh == value) + goto Found; + } + + while (length > 0) + { + length--; + pCh--; + + if (*pCh == value) + goto Found; + } + + // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow + // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated. + if (Vector.IsHardwareAccelerated && pCh > pEndCh) + { + // Get the highest multiple of Vector.Count that is within the search space. + // That will be how many times we iterate in the loop below. + // This is equivalent to: length = Vector.Count * ((int)(pCh - pEndCh) / Vector.Count) + length = (int)((pCh - pEndCh) & ~(Vector.Count - 1)); + + // Get comparison Vector + Vector vComparison = new Vector(value); + + while (length > 0) + { + char* pStart = pCh - Vector.Count; + // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh (and hence pSart) is always vector aligned + Debug.Assert(((int)pStart & (Unsafe.SizeOf>() - 1)) == 0); + Vector vMatches = Vector.Equals(vComparison, Unsafe.Read>(pStart)); + if (Vector.Zero.Equals(vMatches)) + { + pCh -= Vector.Count; + length -= Vector.Count; + continue; + } + // Find offset of last match + return (int)(pStart - pEndCh) + LocateLastFoundChar(vMatches); + } + + if (pCh > pEndCh) + { + length = (int)(pCh - pEndCh); + goto SequentialScan; + } + } + + return -1; + Found: + return (int)(pCh - pEndCh); + Found1: + return (int)(pCh - pEndCh) + 1; + Found2: + return (int)(pCh - pEndCh) + 2; + Found3: + return (int)(pCh - pEndCh) + 3; + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static unsafe int LastIndexOfValueType(ref T searchSpace, T value, int length) where T : IEquatable? + => LastIndexOf(ref searchSpace, value, length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static int IndexOfAnyValueType(ref byte searchSpace, byte value0, byte value1, int length) + { + Debug.Assert(length >= 0); + + uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions + uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions + nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations + nuint lengthToExamine = (nuint)(uint)length; + + if (Sse2.IsSupported || AdvSimd.Arm64.IsSupported) + { + // Avx2 branch also operates on Sse2 sizes, so check is combined. + nint vectorDiff = (nint)length - Vector128.Count; + if (vectorDiff >= 0) + { + // >= Sse2 intrinsics are supported, and length is enough to use them so use that path. + // We jump forward to the intrinsics at the end of the method so a naive branch predict + // will choose the non-intrinsic path so short lengths which don't gain anything aren't + // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths + // more than make this back from the intrinsics. + lengthToExamine = (nuint)vectorDiff; + goto IntrinsicsCompare; + } + } + else if (Vector.IsHardwareAccelerated) + { + // Calculate lengthToExamine here for test, as it is used later + nint vectorDiff = (nint)length - Vector.Count; + if (vectorDiff >= 0) + { + // Similar as above for Vector version + lengthToExamine = (nuint)vectorDiff; + goto IntrinsicsCompare; + } + } + + uint lookUp; + while (lengthToExamine >= 8) + { + lengthToExamine -= 8; + + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found1; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found2; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found3; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 4); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found4; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 5); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found5; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 6); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found6; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 7); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found7; + + offset += 8; + } + + if (lengthToExamine >= 4) + { + lengthToExamine -= 4; + + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found1; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found2; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found3; + + offset += 4; + } + + while (lengthToExamine > 0) + { + + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found; + + offset += 1; + lengthToExamine -= 1; + } + + NotFound: + return -1; + Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 + return (int)offset; + Found1: + return (int)(offset + 1); + Found2: + return (int)(offset + 2); + Found3: + return (int)(offset + 3); + Found4: + return (int)(offset + 4); + Found5: + return (int)(offset + 5); + Found6: + return (int)(offset + 6); + Found7: + return (int)(offset + 7); + + IntrinsicsCompare: + // When we move into a Vectorized block, we process everything of Vector size; + // and then for any remainder we do a final compare of Vector size but starting at + // the end and forwards, which may overlap on an earlier compare. + + // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. + if (Sse2.IsSupported) + { + int matches; + if (Avx2.IsSupported) + { + Vector256 search; + // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 + // We have already subtracted Vector128.Count from lengthToExamine so compare against that + // to see if we have double the size for Vector256.Count + if (lengthToExamine >= (nuint)Vector128.Count) + { + Vector256 values0 = Vector256.Create(value0); + Vector256 values1 = Vector256.Create(value1); + + // Subtract Vector128.Count so we have now subtracted Vector256.Count + lengthToExamine -= (nuint)Vector128.Count; + // First time this checks again against 0, however we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector256(ref searchSpace, offset); + // Bitwise Or to combine the flagged matches for the second value to our match flags + matches = Avx2.MoveMask( + Avx2.Or( + Avx2.CompareEqual(values0, search), + Avx2.CompareEqual(values1, search))); + // Note that MoveMask has converted the equal vector elements into a set of bit flags, + // So the bit position in 'matches' corresponds to the element offset. + if (matches == 0) + { + // None matched + offset += (nuint)Vector256.Count; + continue; + } + + goto IntrinsicsMatch; + } + + // Move to Vector length from end for final compare + search = LoadVector256(ref searchSpace, lengthToExamine); + offset = lengthToExamine; + // Same as method as above + matches = Avx2.MoveMask( + Avx2.Or( + Avx2.CompareEqual(values0, search), + Avx2.CompareEqual(values1, search))); + if (matches == 0) + { + // None matched + goto NotFound; + } + + goto IntrinsicsMatch; + } + } + + // Initial size check was done on method entry. + Debug.Assert(length >= Vector128.Count); + { + Vector128 search; + Vector128 values0 = Vector128.Create(value0); + Vector128 values1 = Vector128.Create(value1); + // First time this checks against 0 and we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector128(ref searchSpace, offset); + + matches = Sse2.MoveMask( + Sse2.Or( + Sse2.CompareEqual(values0, search), + Sse2.CompareEqual(values1, search)) + .AsByte()); + // Note that MoveMask has converted the equal vector elements into a set of bit flags, + // So the bit position in 'matches' corresponds to the element offset. + if (matches == 0) + { + // None matched + offset += (nuint)Vector128.Count; + continue; + } + + goto IntrinsicsMatch; + } + // Move to Vector length from end for final compare + search = LoadVector128(ref searchSpace, lengthToExamine); + offset = lengthToExamine; + // Same as method as above + matches = Sse2.MoveMask( + Sse2.Or( + Sse2.CompareEqual(values0, search), + Sse2.CompareEqual(values1, search))); + if (matches == 0) + { + // None matched + goto NotFound; + } + } + + IntrinsicsMatch: + // Find bitflag offset of first difference and add to current offset + offset += (nuint)BitOperations.TrailingZeroCount(matches); + goto Found; + } + else if (AdvSimd.Arm64.IsSupported) + { + Vector128 search; + Vector128 matches; + Vector128 values0 = Vector128.Create(value0); + Vector128 values1 = Vector128.Create(value1); + // First time this checks against 0 and we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector128(ref searchSpace, offset); + + matches = AdvSimd.Or( + AdvSimd.CompareEqual(values0, search), + AdvSimd.CompareEqual(values1, search)); + + if (matches == Vector128.Zero) + { + offset += (nuint)Vector128.Count; + continue; + } + + // Find bitflag offset of first match and add to current offset + offset += FindFirstMatchedLane(matches); + + goto Found; + } + + // Move to Vector length from end for final compare + search = LoadVector128(ref searchSpace, lengthToExamine); + offset = lengthToExamine; + // Same as method as above + matches = AdvSimd.Or( + AdvSimd.CompareEqual(values0, search), + AdvSimd.CompareEqual(values1, search)); + + if (matches == Vector128.Zero) + { + // None matched + goto NotFound; + } + + // Find bitflag offset of first match and add to current offset + offset += FindFirstMatchedLane(matches); + + goto Found; + } + else if (Vector.IsHardwareAccelerated) + { + Vector values0 = new Vector(value0); + Vector values1 = new Vector(value1); + + Vector search; + // First time this checks against 0 and we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector(ref searchSpace, offset); + search = Vector.BitwiseOr( + Vector.Equals(search, values0), + Vector.Equals(search, values1)); + if (Vector.Zero.Equals(search)) + { + // None matched + offset += (nuint)Vector.Count; + continue; + } + + goto VectorMatch; + } + + // Move to Vector length from end for final compare + search = LoadVector(ref searchSpace, lengthToExamine); + offset = lengthToExamine; + search = Vector.BitwiseOr( + Vector.Equals(search, values0), + Vector.Equals(search, values1)); + if (Vector.Zero.Equals(search)) + { + // None matched + goto NotFound; + } + + VectorMatch: + offset += (nuint)LocateFirstFoundByte(search); + goto Found; + } + + Debug.Fail("Unreachable"); + goto NotFound; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe int IndexOfAnyValueType(ref short searchSpace, short value0, short value1, int length) + => IndexOfAnyChar(ref Unsafe.As(ref searchSpace), Unsafe.As(ref value0), Unsafe.As(ref value1), length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static unsafe int IndexOfAnyChar(ref char searchStart, char value0, char value1, int length) + { + Debug.Assert(length >= 0); + + nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations + nuint lengthToExamine = (nuint)(uint)length; + + if (Sse2.IsSupported) + { + // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. + nint vectorDiff = (nint)length - Vector128.Count; + if (vectorDiff >= 0) + { + // >= Sse2 intrinsics are supported and length is enough to use them, so use that path. + // We jump forward to the intrinsics at the end of them method so a naive branch predict + // will choose the non-intrinsic path so short lengths which don't gain anything aren't + // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths + // more than make this back from the intrinsics. + lengthToExamine = (nuint)vectorDiff; + goto IntrinsicsCompare; + } + } + else if (Vector.IsHardwareAccelerated) + { + // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. + nint vectorDiff = (nint)length - Vector.Count; + if (vectorDiff >= 0) + { + // Similar as above for Vector version + lengthToExamine = (nuint)vectorDiff; + goto VectorCompare; + } + } + + int lookUp; + while (lengthToExamine >= 4) + { + ref char current = ref Add(ref searchStart, offset); + + lookUp = current; + if (value0 == lookUp || value1 == lookUp) + goto Found; + lookUp = Unsafe.Add(ref current, 1); + if (value0 == lookUp || value1 == lookUp) + goto Found1; + lookUp = Unsafe.Add(ref current, 2); + if (value0 == lookUp || value1 == lookUp) + goto Found2; + lookUp = Unsafe.Add(ref current, 3); + if (value0 == lookUp || value1 == lookUp) + goto Found3; + + offset += 4; + lengthToExamine -= 4; + } + + while (lengthToExamine > 0) + { + lookUp = Add(ref searchStart, offset); + if (value0 == lookUp || value1 == lookUp) + goto Found; + + offset += 1; + lengthToExamine -= 1; + } + + NotFound: + return -1; + Found3: + return (int)(offset + 3); + Found2: + return (int)(offset + 2); + Found1: + return (int)(offset + 1); + Found: + return (int)offset; + + IntrinsicsCompare: + // When we move into a Vectorized block, we process everything of Vector size; + // and then for any remainder we do a final compare of Vector size but starting at + // the end and forwards, which may overlap on an earlier compare. + + // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. + if (Sse2.IsSupported) + { + int matches; + if (Avx2.IsSupported) + { + Vector256 search; + // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 + // We have already subtracted Vector128.Count from lengthToExamine so compare against that + // to see if we have double the size for Vector256.Count + if (lengthToExamine >= (nuint)Vector128.Count) + { + Vector256 values0 = Vector256.Create((ushort)value0); + Vector256 values1 = Vector256.Create((ushort)value1); + + // Subtract Vector128.Count so we have now subtracted Vector256.Count + lengthToExamine -= (nuint)Vector128.Count; + // First time this checks again against 0, however we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector256(ref searchStart, offset); + // Bitwise Or to combine the flagged matches for the second value to our match flags + matches = Avx2.MoveMask( + Avx2.Or( + Avx2.CompareEqual(values0, search), + Avx2.CompareEqual(values1, search)) + .AsByte()); + // Note that MoveMask has converted the equal vector elements into a set of bit flags, + // So the bit position in 'matches' corresponds to the element offset. + if (matches == 0) + { + // None matched + offset += (nuint)Vector256.Count; + continue; + } + + goto IntrinsicsMatch; + } + + // Move to Vector length from end for final compare + search = LoadVector256(ref searchStart, lengthToExamine); + offset = lengthToExamine; + // Same as method as above + matches = Avx2.MoveMask( + Avx2.Or( + Avx2.CompareEqual(values0, search), + Avx2.CompareEqual(values1, search)) + .AsByte()); + if (matches == 0) + { + // None matched + goto NotFound; + } + + goto IntrinsicsMatch; + } + } + + // Initial size check was done on method entry. + Debug.Assert(length >= Vector128.Count); + { + Vector128 search; + Vector128 values0 = Vector128.Create((ushort)value0); + Vector128 values1 = Vector128.Create((ushort)value1); + // First time this checks against 0 and we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector128(ref searchStart, offset); + + matches = Sse2.MoveMask( + Sse2.Or( + Sse2.CompareEqual(values0, search), + Sse2.CompareEqual(values1, search)) + .AsByte()); + // Note that MoveMask has converted the equal vector elements into a set of bit flags, + // So the bit position in 'matches' corresponds to the element offset. + if (matches == 0) + { + // None matched + offset += (nuint)Vector128.Count; + continue; + } + + goto IntrinsicsMatch; + } + // Move to Vector length from end for final compare + search = LoadVector128(ref searchStart, lengthToExamine); + offset = lengthToExamine; + // Same as method as above + matches = Sse2.MoveMask( + Sse2.Or( + Sse2.CompareEqual(values0, search), + Sse2.CompareEqual(values1, search)) + .AsByte()); + if (matches == 0) + { + // None matched + goto NotFound; + } + } + + IntrinsicsMatch: + // Find bitflag offset of first difference and add to current offset, + // flags are in bytes so divide by 2 for chars (shift right by 1) + offset += (nuint)(uint)BitOperations.TrailingZeroCount(matches) >> 1; + goto Found; + } + + VectorCompare: + // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. + if (!Sse2.IsSupported && Vector.IsHardwareAccelerated) + { + Vector values0 = new Vector(value0); + Vector values1 = new Vector(value1); + + Vector search; + // First time this checks against 0 and we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector(ref searchStart, offset); + search = Vector.BitwiseOr( + Vector.Equals(search, values0), + Vector.Equals(search, values1)); + if (Vector.Zero.Equals(search)) + { + // None matched + offset += (nuint)Vector.Count; + continue; + } + + goto VectorMatch; + } + + // Move to Vector length from end for final compare + search = LoadVector(ref searchStart, lengthToExamine); + offset = lengthToExamine; + search = Vector.BitwiseOr( + Vector.Equals(search, values0), + Vector.Equals(search, values1)); + if (Vector.Zero.Equals(search)) + { + // None matched + goto NotFound; + } + + VectorMatch: + offset += (nuint)(uint)LocateFirstFoundChar(search); + goto Found; + } + + Debug.Fail("Unreachable"); + goto NotFound; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, int length) + => IndexOfAnyExcept(ref searchSpace, value0, value1, length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static int IndexOfAnyValueType(ref byte searchSpace, byte value0, byte value1, byte value2, int length) + { + Debug.Assert(length >= 0); + + uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions + uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions + uint uValue2 = value2; // Use uint for comparisons to avoid unnecessary 8->32 extensions + nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations + nuint lengthToExamine = (nuint)(uint)length; + + if (Sse2.IsSupported || AdvSimd.Arm64.IsSupported) + { + // Avx2 branch also operates on Sse2 sizes, so check is combined. + nint vectorDiff = (nint)length - Vector128.Count; + if (vectorDiff >= 0) + { + // >= Sse2 intrinsics are supported, and length is enough to use them so use that path. + // We jump forward to the intrinsics at the end of the method so a naive branch predict + // will choose the non-intrinsic path so short lengths which don't gain anything aren't + // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths + // more than make this back from the intrinsics. + lengthToExamine = (nuint)vectorDiff; + goto IntrinsicsCompare; + } + } + else if (Vector.IsHardwareAccelerated) + { + // Calculate lengthToExamine here for test, as it is used later + nint vectorDiff = (nint)length - Vector.Count; + if (vectorDiff >= 0) + { + // Similar as above for Vector version + lengthToExamine = (nuint)vectorDiff; + goto IntrinsicsCompare; + } + } + + uint lookUp; + while (lengthToExamine >= 8) + { + lengthToExamine -= 8; + + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found1; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found2; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found3; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 4); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found4; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 5); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found5; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 6); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found6; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 7); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found7; + + offset += 8; + } + + if (lengthToExamine >= 4) + { + lengthToExamine -= 4; + + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found1; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found2; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found3; + + offset += 4; + } + + while (lengthToExamine > 0) + { + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found; + + offset += 1; + lengthToExamine -= 1; + } + + NotFound: + return -1; + Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 + return (int)offset; + Found1: + return (int)(offset + 1); + Found2: + return (int)(offset + 2); + Found3: + return (int)(offset + 3); + Found4: + return (int)(offset + 4); + Found5: + return (int)(offset + 5); + Found6: + return (int)(offset + 6); + Found7: + return (int)(offset + 7); + + IntrinsicsCompare: + // When we move into a Vectorized block, we process everything of Vector size; + // and then for any remainder we do a final compare of Vector size but starting at + // the end and forwards, which may overlap on an earlier compare. + + // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. + if (Sse2.IsSupported) + { + int matches; + if (Avx2.IsSupported) + { + Vector256 search; + // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 + // We have already subtracted Vector128.Count from lengthToExamine so compare against that + // to see if we have double the size for Vector256.Count + if (lengthToExamine >= (nuint)Vector128.Count) + { + Vector256 values0 = Vector256.Create(value0); + Vector256 values1 = Vector256.Create(value1); + Vector256 values2 = Vector256.Create(value2); + + // Subtract Vector128.Count so we have now subtracted Vector256.Count + lengthToExamine -= (nuint)Vector128.Count; + // First time this checks again against 0, however we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector256(ref searchSpace, offset); + // Bitwise Or to combine the flagged matches for the second value to our match flags + matches = Avx2.MoveMask( + Avx2.Or( + Avx2.Or( + Avx2.CompareEqual(values0, search), + Avx2.CompareEqual(values1, search)), + Avx2.CompareEqual(values2, search))); + // Note that MoveMask has converted the equal vector elements into a set of bit flags, + // So the bit position in 'matches' corresponds to the element offset. + if (matches == 0) + { + // None matched + offset += (nuint)Vector256.Count; + continue; + } + + goto IntrinsicsMatch; + } + + // Move to Vector length from end for final compare + search = LoadVector256(ref searchSpace, lengthToExamine); + offset = lengthToExamine; + // Same as method as above + matches = Avx2.MoveMask( + Avx2.Or( + Avx2.Or( + Avx2.CompareEqual(values0, search), + Avx2.CompareEqual(values1, search)), + Avx2.CompareEqual(values2, search))); + if (matches == 0) + { + // None matched + goto NotFound; + } + + goto IntrinsicsMatch; + } + } + + // Initial size check was done on method entry. + Debug.Assert(length >= Vector128.Count); + { + Vector128 search; + Vector128 values0 = Vector128.Create(value0); + Vector128 values1 = Vector128.Create(value1); + Vector128 values2 = Vector128.Create(value2); + // First time this checks against 0 and we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector128(ref searchSpace, offset); + + matches = Sse2.MoveMask( + Sse2.Or( + Sse2.Or( + Sse2.CompareEqual(values0, search), + Sse2.CompareEqual(values1, search)), + Sse2.CompareEqual(values2, search))); + // Note that MoveMask has converted the equal vector elements into a set of bit flags, + // So the bit position in 'matches' corresponds to the element offset. + if (matches == 0) + { + // None matched + offset += (nuint)Vector128.Count; + continue; + } + + goto IntrinsicsMatch; + } + // Move to Vector length from end for final compare + search = LoadVector128(ref searchSpace, lengthToExamine); + offset = lengthToExamine; + // Same as method as above + matches = Sse2.MoveMask( + Sse2.Or( + Sse2.Or( + Sse2.CompareEqual(values0, search), + Sse2.CompareEqual(values1, search)), + Sse2.CompareEqual(values2, search))); + if (matches == 0) + { + // None matched + goto NotFound; + } + } + + IntrinsicsMatch: + // Find bitflag offset of first difference and add to current offset + offset += (nuint)BitOperations.TrailingZeroCount(matches); + goto Found; + } + else if (AdvSimd.Arm64.IsSupported) + { + Vector128 search; + Vector128 matches; + Vector128 values0 = Vector128.Create(value0); + Vector128 values1 = Vector128.Create(value1); + Vector128 values2 = Vector128.Create(value2); + // First time this checks against 0 and we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector128(ref searchSpace, offset); + + matches = AdvSimd.Or( + AdvSimd.Or( + AdvSimd.CompareEqual(values0, search), + AdvSimd.CompareEqual(values1, search)), + AdvSimd.CompareEqual(values2, search)); + + if (matches == Vector128.Zero) + { + offset += (nuint)Vector128.Count; + continue; + } + + // Find bitflag offset of first match and add to current offset + offset += FindFirstMatchedLane(matches); + + goto Found; + } + + // Move to Vector length from end for final compare + search = LoadVector128(ref searchSpace, lengthToExamine); + offset = lengthToExamine; + // Same as method as above + matches = AdvSimd.Or( + AdvSimd.Or( + AdvSimd.CompareEqual(values0, search), + AdvSimd.CompareEqual(values1, search)), + AdvSimd.CompareEqual(values2, search)); + + if (matches == Vector128.Zero) + { + // None matched + goto NotFound; + } + + // Find bitflag offset of first match and add to current offset + offset += FindFirstMatchedLane(matches); + + goto Found; + } + else if (Vector.IsHardwareAccelerated) + { + Vector values0 = new Vector(value0); + Vector values1 = new Vector(value1); + Vector values2 = new Vector(value2); + + Vector search; + // First time this checks against 0 and we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector(ref searchSpace, offset); + search = Vector.BitwiseOr( + Vector.BitwiseOr( + Vector.Equals(search, values0), + Vector.Equals(search, values1)), + Vector.Equals(search, values2)); + if (Vector.Zero.Equals(search)) + { + // None matched + offset += (nuint)Vector.Count; + continue; + } + + goto VectorMatch; + } + + // Move to Vector length from end for final compare + search = LoadVector(ref searchSpace, lengthToExamine); + offset = lengthToExamine; + search = Vector.BitwiseOr( + Vector.BitwiseOr( + Vector.Equals(search, values0), + Vector.Equals(search, values1)), + Vector.Equals(search, values2)); + if (Vector.Zero.Equals(search)) + { + // None matched + goto NotFound; + } + + VectorMatch: + offset += (nuint)LocateFirstFoundByte(search); + goto Found; + } + + Debug.Fail("Unreachable"); + goto NotFound; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe int IndexOfAnyValueType(ref short searchSpace, short value0, short value1, short value2, int length) + => IndexOfAnyValueType( + ref Unsafe.As(ref searchSpace), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static unsafe int IndexOfAnyValueType(ref char searchStart, char value0, char value1, char value2, int length) + { + Debug.Assert(length >= 0); + + nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations + nuint lengthToExamine = (nuint)(uint)length; + + if (Sse2.IsSupported) + { + // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. + nint vectorDiff = (nint)length - Vector128.Count; + if (vectorDiff >= 0) + { + // >= Sse2 intrinsics are supported and length is enough to use them, so use that path. + // We jump forward to the intrinsics at the end of them method so a naive branch predict + // will choose the non-intrinsic path so short lengths which don't gain anything aren't + // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths + // more than make this back from the intrinsics. + lengthToExamine = (nuint)vectorDiff; + goto IntrinsicsCompare; + } + } + else if (Vector.IsHardwareAccelerated) + { + // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. + nint vectorDiff = (nint)length - Vector.Count; + if (vectorDiff >= 0) + { + // Similar as above for Vector version + lengthToExamine = (nuint)vectorDiff; + goto VectorCompare; + } + } + + int lookUp; + while (lengthToExamine >= 4) + { + ref char current = ref Add(ref searchStart, offset); + + lookUp = current; + if (value0 == lookUp || value1 == lookUp || value2 == lookUp) + goto Found; + lookUp = Unsafe.Add(ref current, 1); + if (value0 == lookUp || value1 == lookUp || value2 == lookUp) + goto Found1; + lookUp = Unsafe.Add(ref current, 2); + if (value0 == lookUp || value1 == lookUp || value2 == lookUp) + goto Found2; + lookUp = Unsafe.Add(ref current, 3); + if (value0 == lookUp || value1 == lookUp || value2 == lookUp) + goto Found3; + + offset += 4; + lengthToExamine -= 4; + } + + while (lengthToExamine > 0) + { + lookUp = Add(ref searchStart, offset); + if (value0 == lookUp || value1 == lookUp || value2 == lookUp) + goto Found; + + offset += 1; + lengthToExamine -= 1; + } + + NotFound: + return -1; + Found3: + return (int)(offset + 3); + Found2: + return (int)(offset + 2); + Found1: + return (int)(offset + 1); + Found: + return (int)offset; + + IntrinsicsCompare: + // When we move into a Vectorized block, we process everything of Vector size; + // and then for any remainder we do a final compare of Vector size but starting at + // the end and forwards, which may overlap on an earlier compare. + + // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. + if (Sse2.IsSupported) + { + int matches; + if (Avx2.IsSupported) + { + Vector256 search; + // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 + // We have already subtracted Vector128.Count from lengthToExamine so compare against that + // to see if we have double the size for Vector256.Count + if (lengthToExamine >= (nuint)Vector128.Count) + { + Vector256 values0 = Vector256.Create((ushort)value0); + Vector256 values1 = Vector256.Create((ushort)value1); + Vector256 values2 = Vector256.Create((ushort)value2); + + // Subtract Vector128.Count so we have now subtracted Vector256.Count + lengthToExamine -= (nuint)Vector128.Count; + // First time this checks again against 0, however we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector256(ref searchStart, offset); + // Bitwise Or to combine the flagged matches for the second value to our match flags + matches = Avx2.MoveMask( + Avx2.Or( + Avx2.Or( + Avx2.CompareEqual(values0, search), + Avx2.CompareEqual(values1, search)), + Avx2.CompareEqual(values2, search)) + .AsByte()); + // Note that MoveMask has converted the equal vector elements into a set of bit flags, + // So the bit position in 'matches' corresponds to the element offset. + if (matches == 0) + { + // None matched + offset += (nuint)Vector256.Count; + continue; + } + + goto IntrinsicsMatch; + } + + // Move to Vector length from end for final compare + search = LoadVector256(ref searchStart, lengthToExamine); + offset = lengthToExamine; + // Same as method as above + matches = Avx2.MoveMask( + Avx2.Or( + Avx2.Or( + Avx2.CompareEqual(values0, search), + Avx2.CompareEqual(values1, search)), + Avx2.CompareEqual(values2, search)) + .AsByte()); + if (matches == 0) + { + // None matched + goto NotFound; + } + + goto IntrinsicsMatch; + } + } + + // Initial size check was done on method entry. + Debug.Assert(length >= Vector128.Count); + { + Vector128 search; + Vector128 values0 = Vector128.Create((ushort)value0); + Vector128 values1 = Vector128.Create((ushort)value1); + Vector128 values2 = Vector128.Create((ushort)value2); + // First time this checks against 0 and we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector128(ref searchStart, offset); + + matches = Sse2.MoveMask( + Sse2.Or( + Sse2.Or( + Sse2.CompareEqual(values0, search), + Sse2.CompareEqual(values1, search)), + Sse2.CompareEqual(values2, search)) + .AsByte()); + // Note that MoveMask has converted the equal vector elements into a set of bit flags, + // So the bit position in 'matches' corresponds to the element offset. + if (matches == 0) + { + // None matched + offset += (nuint)Vector128.Count; + continue; + } + + goto IntrinsicsMatch; + } + // Move to Vector length from end for final compare + search = LoadVector128(ref searchStart, lengthToExamine); + offset = lengthToExamine; + // Same as method as above + matches = Sse2.MoveMask( + Sse2.Or( + Sse2.Or( + Sse2.CompareEqual(values0, search), + Sse2.CompareEqual(values1, search)), + Sse2.CompareEqual(values2, search)) + .AsByte()); + if (matches == 0) + { + // None matched + goto NotFound; + } + } + + IntrinsicsMatch: + // Find bitflag offset of first difference and add to current offset, + // flags are in bytes so divide by 2 for chars (shift right by 1) + offset += (nuint)(uint)BitOperations.TrailingZeroCount(matches) >> 1; + goto Found; + } + + VectorCompare: + // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. + if (!Sse2.IsSupported && Vector.IsHardwareAccelerated) + { + Vector values0 = new Vector(value0); + Vector values1 = new Vector(value1); + Vector values2 = new Vector(value2); + + Vector search; + // First time this checks against 0 and we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector(ref searchStart, offset); + search = Vector.BitwiseOr( + Vector.BitwiseOr( + Vector.Equals(search, values0), + Vector.Equals(search, values1)), + Vector.Equals(search, values2)); + if (Vector.Zero.Equals(search)) + { + // None matched + offset += (nuint)Vector.Count; + continue; + } + + goto VectorMatch; + } + + // Move to Vector length from end for final compare + search = LoadVector(ref searchStart, lengthToExamine); + offset = lengthToExamine; + search = Vector.BitwiseOr( + Vector.BitwiseOr( + Vector.Equals(search, values0), + Vector.Equals(search, values1)), + Vector.Equals(search, values2)); + if (Vector.Zero.Equals(search)) + { + // None matched + goto NotFound; + } + + VectorMatch: + offset += (nuint)(uint)LocateFirstFoundChar(search); + goto Found; + } + + Debug.Fail("Unreachable"); + goto NotFound; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, int length) + => IndexOfAnyExcept(ref searchSpace, value0, value1, value2, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe int IndexOfAnyValueType(ref short searchSpace, short value0, short value1, short value2, short value3, int length) + => IndexOfAnyValueType( + ref Unsafe.As(ref searchSpace), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + Unsafe.As(ref value3), + length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static unsafe int IndexOfAnyValueType(ref char searchStart, char value0, char value1, char value2, char value3, int length) + { + Debug.Assert(length >= 0); + + nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations + nuint lengthToExamine = (nuint)(uint)length; + + if (Sse2.IsSupported) + { + // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. + nint vectorDiff = (nint)length - Vector128.Count; + if (vectorDiff >= 0) + { + // >= Sse2 intrinsics are supported and length is enough to use them, so use that path. + // We jump forward to the intrinsics at the end of them method so a naive branch predict + // will choose the non-intrinsic path so short lengths which don't gain anything aren't + // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths + // more than make this back from the intrinsics. + lengthToExamine = (nuint)vectorDiff; + goto IntrinsicsCompare; + } + } + else if (Vector.IsHardwareAccelerated) + { + // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. + nint vectorDiff = (nint)length - Vector.Count; + if (vectorDiff >= 0) + { + // Similar as above for Vector version + lengthToExamine = (nuint)vectorDiff; + goto VectorCompare; + } + } + + int lookUp; + while (lengthToExamine >= 4) + { + ref char current = ref Add(ref searchStart, offset); + + lookUp = current; + if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) + goto Found; + lookUp = Unsafe.Add(ref current, 1); + if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) + goto Found1; + lookUp = Unsafe.Add(ref current, 2); + if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) + goto Found2; + lookUp = Unsafe.Add(ref current, 3); + if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) + goto Found3; + + offset += 4; + lengthToExamine -= 4; + } + + while (lengthToExamine > 0) + { + lookUp = Add(ref searchStart, offset); + if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) + goto Found; + + offset += 1; + lengthToExamine -= 1; + } + + NotFound: + return -1; + Found3: + return (int)(offset + 3); + Found2: + return (int)(offset + 2); + Found1: + return (int)(offset + 1); + Found: + return (int)offset; + + IntrinsicsCompare: + // When we move into a Vectorized block, we process everything of Vector size; + // and then for any remainder we do a final compare of Vector size but starting at + // the end and forwards, which may overlap on an earlier compare. + + // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. + if (Sse2.IsSupported) + { + int matches; + if (Avx2.IsSupported) + { + Vector256 search; + // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 + // We have already subtracted Vector128.Count from lengthToExamine so compare against that + // to see if we have double the size for Vector256.Count + if (lengthToExamine >= (nuint)Vector128.Count) + { + Vector256 values0 = Vector256.Create((ushort)value0); + Vector256 values1 = Vector256.Create((ushort)value1); + Vector256 values2 = Vector256.Create((ushort)value2); + Vector256 values3 = Vector256.Create((ushort)value3); + + // Subtract Vector128.Count so we have now subtracted Vector256.Count + lengthToExamine -= (nuint)Vector128.Count; + // First time this checks again against 0, however we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector256(ref searchStart, offset); + // We preform the Or at non-Vector level as we are using the maximum number of non-preserved registers, + // and more causes them first to be pushed to stack and then popped on exit to preseve their values. + matches = Avx2.MoveMask(Avx2.CompareEqual(values0, search).AsByte()); + // Bitwise Or to combine the flagged matches for the second, third and fourth values to our match flags + matches |= Avx2.MoveMask(Avx2.CompareEqual(values1, search).AsByte()); + matches |= Avx2.MoveMask(Avx2.CompareEqual(values2, search).AsByte()); + matches |= Avx2.MoveMask(Avx2.CompareEqual(values3, search).AsByte()); + // Note that MoveMask has converted the equal vector elements into a set of bit flags, + // So the bit position in 'matches' corresponds to the element offset. + if (matches == 0) + { + // None matched + offset += (nuint)Vector256.Count; + continue; + } + + goto IntrinsicsMatch; + } + + // Move to Vector length from end for final compare + search = LoadVector256(ref searchStart, lengthToExamine); + offset = lengthToExamine; + // Same as method as above + matches = Avx2.MoveMask(Avx2.CompareEqual(values0, search).AsByte()); + // Bitwise Or to combine the flagged matches for the second, third and fourth values to our match flags + matches |= Avx2.MoveMask(Avx2.CompareEqual(values1, search).AsByte()); + matches |= Avx2.MoveMask(Avx2.CompareEqual(values2, search).AsByte()); + matches |= Avx2.MoveMask(Avx2.CompareEqual(values3, search).AsByte()); + if (matches == 0) + { + // None matched + goto NotFound; + } + + goto IntrinsicsMatch; + } + } + + // Initial size check was done on method entry. + Debug.Assert(length >= Vector128.Count); + { + Vector128 search; + Vector128 values0 = Vector128.Create((ushort)value0); + Vector128 values1 = Vector128.Create((ushort)value1); + Vector128 values2 = Vector128.Create((ushort)value2); + Vector128 values3 = Vector128.Create((ushort)value3); + // First time this checks against 0 and we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector128(ref searchStart, offset); + + matches = Sse2.MoveMask(Sse2.CompareEqual(values0, search).AsByte()); + matches |= Sse2.MoveMask(Sse2.CompareEqual(values1, search).AsByte()); + matches |= Sse2.MoveMask(Sse2.CompareEqual(values2, search).AsByte()); + matches |= Sse2.MoveMask(Sse2.CompareEqual(values3, search).AsByte()); + // Note that MoveMask has converted the equal vector elements into a set of bit flags, + // So the bit position in 'matches' corresponds to the element offset. + if (matches == 0) + { + // None matched + offset += (nuint)Vector128.Count; + continue; + } + + goto IntrinsicsMatch; + } + // Move to Vector length from end for final compare + search = LoadVector128(ref searchStart, lengthToExamine); + offset = lengthToExamine; + // Same as method as above + matches = Sse2.MoveMask(Sse2.CompareEqual(values0, search).AsByte()); + matches |= Sse2.MoveMask(Sse2.CompareEqual(values1, search).AsByte()); + matches |= Sse2.MoveMask(Sse2.CompareEqual(values2, search).AsByte()); + matches |= Sse2.MoveMask(Sse2.CompareEqual(values3, search).AsByte()); + if (matches == 0) + { + // None matched + goto NotFound; + } + } + + IntrinsicsMatch: + // Find bitflag offset of first difference and add to current offset, + // flags are in bytes so divide by 2 for chars (shift right by 1) + offset += (nuint)(uint)BitOperations.TrailingZeroCount(matches) >> 1; + goto Found; + } + + VectorCompare: + // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. + if (!Sse2.IsSupported && Vector.IsHardwareAccelerated) + { + Vector values0 = new Vector(value0); + Vector values1 = new Vector(value1); + Vector values2 = new Vector(value2); + Vector values3 = new Vector(value3); + + Vector search; + // First time this checks against 0 and we will move into final compare if it fails. + while (lengthToExamine > offset) + { + search = LoadVector(ref searchStart, offset); + search = Vector.BitwiseOr( + Vector.BitwiseOr( + Vector.BitwiseOr( + Vector.Equals(search, values0), + Vector.Equals(search, values1)), + Vector.Equals(search, values2)), + Vector.Equals(search, values3)); + if (Vector.Zero.Equals(search)) + { + // None matched + offset += (nuint)Vector.Count; + continue; + } + + goto VectorMatch; + } + + // Move to Vector length from end for final compare + search = LoadVector(ref searchStart, lengthToExamine); + offset = lengthToExamine; + search = Vector.BitwiseOr( + Vector.BitwiseOr( + Vector.BitwiseOr( + Vector.Equals(search, values0), + Vector.Equals(search, values1)), + Vector.Equals(search, values2)), + Vector.Equals(search, values3)); + if (Vector.Zero.Equals(search)) + { + // None matched + goto NotFound; + } + + VectorMatch: + offset += (nuint)(uint)LocateFirstFoundChar(search); + goto Found; + } + + Debug.Fail("Unreachable"); + goto NotFound; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, T value3, int length) + => IndexOfAnyExcept(ref searchSpace, value0, value1, value2, value3, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyExceptValueType(ref T searchSpace, T value, int length) + => LastIndexOfAnyExcept(ref searchSpace, value, length); + + internal static int LastIndexOfAnyValueType(ref byte searchSpace, byte value0, byte value1, int length) + { + Debug.Assert(length >= 0); + + uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions + uint uValue1 = value1; + nuint offset = (nuint)(uint)length; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations + nuint lengthToExamine = (nuint)(uint)length; + + if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) + { + lengthToExamine = UnalignedCountVectorFromEnd(ref searchSpace, length); + } + SequentialScan: + uint lookUp; + while (lengthToExamine >= 8) + { + lengthToExamine -= 8; + offset -= 8; + + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 7); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found7; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 6); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found6; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 5); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found5; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 4); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found4; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found3; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found2; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found1; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found; + } + + if (lengthToExamine >= 4) + { + lengthToExamine -= 4; + offset -= 4; + + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found3; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found2; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found1; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found; + } + + while (lengthToExamine > 0) + { + lengthToExamine -= 1; + offset -= 1; + + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found; + } + + if (Vector.IsHardwareAccelerated && (offset > 0)) + { + lengthToExamine = (offset & (nuint)~(Vector.Count - 1)); + + Vector values0 = new Vector(value0); + Vector values1 = new Vector(value1); + + while (lengthToExamine > (nuint)(Vector.Count - 1)) + { + Vector search = LoadVector(ref searchSpace, offset - (nuint)Vector.Count); + var matches = Vector.BitwiseOr( + Vector.Equals(search, values0), + Vector.Equals(search, values1)); + if (Vector.Zero.Equals(matches)) + { + offset -= (nuint)Vector.Count; + lengthToExamine -= (nuint)Vector.Count; + continue; + } + + // Find offset of first match and add to current offset + return (int)(offset) - Vector.Count + LocateLastFoundByte(matches); + } + + if (offset > 0) + { + lengthToExamine = offset; + goto SequentialScan; + } + } + return -1; + Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 + return (int)offset; + Found1: + return (int)(offset + 1); + Found2: + return (int)(offset + 2); + Found3: + return (int)(offset + 3); + Found4: + return (int)(offset + 4); + Found5: + return (int)(offset + 5); + Found6: + return (int)(offset + 6); + Found7: + return (int)(offset + 7); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyValueType(ref short searchSpace, short value0, short value1, int length) + => LastIndexOfAny(ref searchSpace, value0, value1, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, int length) + => LastIndexOfAnyExcept(ref searchSpace, value0, value1, length); + + internal static int LastIndexOfAnyValueType(ref byte searchSpace, byte value0, byte value1, byte value2, int length) + { + Debug.Assert(length >= 0); + + uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions + uint uValue1 = value1; + uint uValue2 = value2; + nuint offset = (nuint)(uint)length; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations + nuint lengthToExamine = (nuint)(uint)length; + + if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) + { + lengthToExamine = UnalignedCountVectorFromEnd(ref searchSpace, length); + } + SequentialScan: + uint lookUp; + while (lengthToExamine >= 8) + { + lengthToExamine -= 8; + offset -= 8; + + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 7); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found7; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 6); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found6; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 5); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found5; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 4); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found4; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found3; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found2; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found1; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found; + } + + if (lengthToExamine >= 4) + { + lengthToExamine -= 4; + offset -= 4; + + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found3; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found2; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found1; + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found; + } + + while (lengthToExamine > 0) + { + lengthToExamine -= 1; + offset -= 1; + + lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found; + } + + if (Vector.IsHardwareAccelerated && (offset > 0)) + { + lengthToExamine = (offset & (nuint)~(Vector.Count - 1)); + + Vector values0 = new Vector(value0); + Vector values1 = new Vector(value1); + Vector values2 = new Vector(value2); + + while (lengthToExamine > (nuint)(Vector.Count - 1)) + { + Vector search = LoadVector(ref searchSpace, offset - (nuint)Vector.Count); + + var matches = Vector.BitwiseOr( + Vector.BitwiseOr( + Vector.Equals(search, values0), + Vector.Equals(search, values1)), + Vector.Equals(search, values2)); + + if (Vector.Zero.Equals(matches)) + { + offset -= (nuint)Vector.Count; + lengthToExamine -= (nuint)Vector.Count; + continue; + } + + // Find offset of first match and add to current offset + return (int)(offset) - Vector.Count + LocateLastFoundByte(matches); + } + + if (offset > 0) + { + lengthToExamine = offset; + goto SequentialScan; + } + } + return -1; + Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 + return (int)offset; + Found1: + return (int)(offset + 1); + Found2: + return (int)(offset + 2); + Found3: + return (int)(offset + 3); + Found4: + return (int)(offset + 4); + Found5: + return (int)(offset + 5); + Found6: + return (int)(offset + 6); + Found7: + return (int)(offset + 7); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyValueType(ref short searchSpace, short value0, short value1, short value2, int length) + => LastIndexOfAny(ref searchSpace, value0, value1, value2, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, int length) + => LastIndexOfAnyExcept(ref searchSpace, value0, value1, value2, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, T value3, int length) + => LastIndexOfAnyExcept(ref searchSpace, value0, value1, value2, value3, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 LoadVector128(ref char start, nint offset) + => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, offset))); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 LoadVector128(ref char start, nuint offset) + => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, (nint)offset))); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 LoadVector256(ref char start, nint offset) + => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, offset))); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 LoadVector256(ref char start, nuint offset) + => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, (nint)offset))); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ref char Add(ref char start, nuint offset) => ref Unsafe.Add(ref start, (nint)offset); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint FindFirstMatchedLane(Vector128 compareResult) + { + Debug.Assert(AdvSimd.Arm64.IsSupported); + + // Mask to help find the first lane in compareResult that is set. + // MSB 0x10 corresponds to 1st lane, 0x01 corresponds to 0th lane and so forth. + Vector128 mask = Vector128.Create((ushort)0x1001).AsByte(); + + // Find the first lane that is set inside compareResult. + Vector128 maskedSelectedLanes = AdvSimd.And(compareResult, mask); + Vector128 pairwiseSelectedLane = AdvSimd.Arm64.AddPairwise(maskedSelectedLanes, maskedSelectedLanes); + ulong selectedLanes = pairwiseSelectedLane.AsUInt64().ToScalar(); + + // It should be handled by compareResult != Vector.Zero + Debug.Assert(selectedLanes != 0); + + // Find the first lane that is set inside compareResult. + return (uint)BitOperations.TrailingZeroCount(selectedLanes) >> 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int FindFirstMatchedLane(Vector128 compareResult) + { + Debug.Assert(AdvSimd.Arm64.IsSupported); + + Vector128 pairwiseSelectedLane = AdvSimd.Arm64.AddPairwise(compareResult.AsByte(), compareResult.AsByte()); + ulong selectedLanes = pairwiseSelectedLane.AsUInt64().ToScalar(); + + // It should be handled by compareResult != Vector.Zero + Debug.Assert(selectedLanes != 0); + + return BitOperations.TrailingZeroCount(selectedLanes) >> 3; + } + + // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LocateLastFoundChar(Vector match) + { + var vector64 = Vector.AsVectorUInt64(match); + ulong candidate = 0; + int i = Vector.Count - 1; + + // This pattern is only unrolled by the Jit if the limit is Vector.Count + // As such, we need a dummy iteration variable for that condition to be satisfied + for (int j = 0; j < Vector.Count; j++) + { + candidate = vector64[i]; + if (candidate != 0) + { + break; + } + + i--; + } + + // Single LEA instruction with jitted const (using function result) + return i * 4 + LocateLastFoundChar(candidate); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LocateLastFoundChar(ulong match) + => BitOperations.Log2(match) >> 4; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe nuint UnalignedCountVectorFromEnd(ref byte searchSpace, int length) + { + nint unaligned = (nint)Unsafe.AsPointer(ref searchSpace) & (Vector.Count - 1); + return (nuint)(uint)(((length & (Vector.Count - 1)) + unaligned) & (Vector.Count - 1)); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs index 709a423680af0f..5a4f52b4e679ca 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs @@ -293,115 +293,6 @@ public static unsafe bool Contains(ref T searchSpace, T value, int length) wh return true; } - internal static unsafe int IndexOfValueType(ref T searchSpace, T value, int length) where T : struct, IEquatable - { - Debug.Assert(length >= 0); - - nint index = 0; // Use nint for arithmetic to avoid unnecessary 64->32->64 truncations - if (Vector.IsHardwareAccelerated && Vector.IsSupported && (Vector.Count * 2) <= length) - { - Vector valueVector = new Vector(value); - Vector compareVector; - Vector matchVector; - if ((uint)length % (uint)Vector.Count != 0) - { - // Number of elements is not a multiple of Vector.Count, so do one - // check and shift only enough for the remaining set to be a multiple - // of Vector.Count. - compareVector = Unsafe.As>(ref Unsafe.Add(ref searchSpace, index)); - matchVector = Vector.Equals(valueVector, compareVector); - if (matchVector != Vector.Zero) - { - goto VectorMatch; - } - index += length % Vector.Count; - length -= length % Vector.Count; - } - while (length > 0) - { - compareVector = Unsafe.As>(ref Unsafe.Add(ref searchSpace, index)); - matchVector = Vector.Equals(valueVector, compareVector); - if (matchVector != Vector.Zero) - { - goto VectorMatch; - } - index += Vector.Count; - length -= Vector.Count; - } - goto NotFound; - VectorMatch: - for (int i = 0; i < Vector.Count; i++) - if (compareVector[i].Equals(value)) - return (int)(index + i); - } - - while (length >= 8) - { - if (value.Equals(Unsafe.Add(ref searchSpace, index))) - goto Found; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 1))) - goto Found1; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 2))) - goto Found2; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 3))) - goto Found3; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 4))) - goto Found4; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 5))) - goto Found5; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 6))) - goto Found6; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 7))) - goto Found7; - - length -= 8; - index += 8; - } - - while (length >= 4) - { - if (value.Equals(Unsafe.Add(ref searchSpace, index))) - goto Found; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 1))) - goto Found1; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 2))) - goto Found2; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 3))) - goto Found3; - - length -= 4; - index += 4; - } - - while (length > 0) - { - if (value.Equals(Unsafe.Add(ref searchSpace, index))) - goto Found; - - index += 1; - length--; - } - NotFound: - return -1; - - Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 - return (int)index; - Found1: - return (int)(index + 1); - Found2: - return (int)(index + 2); - Found3: - return (int)(index + 3); - Found4: - return (int)(index + 4); - Found5: - return (int)(index + 5); - Found6: - return (int)(index + 6); - Found7: - return (int)(index + 7); - } - public static unsafe int IndexOf(ref T searchSpace, T value, int length) where T : IEquatable? { Debug.Assert(length >= 0); @@ -1162,7 +1053,7 @@ public static int LastIndexOfAny(ref T searchSpace, int searchSpaceLength, re return -1; // not found } - public static int IndexOfAnyExcept(ref T searchSpace, T value0, int length) + internal static int IndexOfAnyExcept(ref T searchSpace, T value0, int length) { Debug.Assert(length >= 0, "Expected non-negative length"); @@ -1177,113 +1068,125 @@ public static int IndexOfAnyExcept(ref T searchSpace, T value0, int length) return -1; } - public static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, int length) where T : struct, IEquatable + internal static int LastIndexOfAnyExcept(ref T searchSpace, T value0, int length) { Debug.Assert(length >= 0, "Expected non-negative length"); - Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); - if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + for (int i = length -1; i >= 0; i--) { - for (int i = 0; i < length; i++) + if (!EqualityComparer.Default.Equals(Unsafe.Add(ref searchSpace, i), value0)) { - if (!Unsafe.Add(ref searchSpace, i).Equals(value0)) - { - return i; - } + return i; } } - else - { - Vector128 notEquals, value0Vector = Vector128.Create(value0); - ref T current = ref searchSpace; - ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); - // Loop until either we've finished all elements or there's less than a vector's-worth remaining. - do - { - notEquals = ~Vector128.Equals(value0Vector, Vector128.LoadUnsafe(ref current)); - if (notEquals != Vector128.Zero) - { - return ComputeIndex(ref searchSpace, ref current, notEquals); - } + return -1; + } - current = ref Unsafe.Add(ref current, Vector128.Count); - } - while (!Unsafe.IsAddressGreaterThan(ref current, ref oneVectorAwayFromEnd)); + internal static int IndexOfAnyExcept(ref T searchSpace, T value0, T value1, int length) + { + Debug.Assert(length >= 0, "Expected non-negative length"); - // If any elements remain, process the last vector in the search space. - if ((uint)length % Vector128.Count != 0) + for (int i = 0; i < length; i++) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if (!EqualityComparer.Default.Equals(current, value0) && !EqualityComparer.Default.Equals(current, value1)) { - notEquals = ~Vector128.Equals(value0Vector, Vector128.LoadUnsafe(ref oneVectorAwayFromEnd)); - if (notEquals != Vector128.Zero) - { - return ComputeIndex(ref searchSpace, ref oneVectorAwayFromEnd, notEquals); - } + return i; } } return -1; } - internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, T value3, int length) where T : struct, IEquatable + internal static int LastIndexOfAnyExcept(ref T searchSpace, T value0, T value1, int length) { Debug.Assert(length >= 0, "Expected non-negative length"); - Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); - if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + for (int i = length - 1; i >= 0; i--) { - for (int i = 0; i < length; i++) + ref T current = ref Unsafe.Add(ref searchSpace, i); + if (!EqualityComparer.Default.Equals(current, value0) && !EqualityComparer.Default.Equals(current, value1)) { - T current = Unsafe.Add(ref searchSpace, i); - if (!current.Equals(value0) && !current.Equals(value1) && !current.Equals(value2) && !current.Equals(value3)) - { - return i; - } + return i; } } - else - { - Vector128 notEquals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), values2 = Vector128.Create(value2), values3 = Vector128.Create(value3); - ref T currentSearchSpace = ref searchSpace; - ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); - // Loop until either we've finished all elements or there's less than a vector's-worth remaining. - do + return -1; + } + + internal static int IndexOfAnyExcept(ref T searchSpace, T value0, T value1, T value2, int length) + { + Debug.Assert(length >= 0, "Expected non-negative length"); + + for (int i = 0; i < length; i++) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if (!EqualityComparer.Default.Equals(current, value0) + && !EqualityComparer.Default.Equals(current, value1) + && !EqualityComparer.Default.Equals(current, value2)) { - current = Vector128.LoadUnsafe(ref currentSearchSpace); - notEquals = ~(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) - | Vector128.Equals(values2, current) | Vector128.Equals(values3, current)); - if (notEquals != Vector128.Zero) - { - return ComputeIndex(ref searchSpace, ref currentSearchSpace, notEquals); - } + return i; + } + } - currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + return -1; + } + + internal static int LastIndexOfAnyExcept(ref T searchSpace, T value0, T value1, T value2, int length) + { + Debug.Assert(length >= 0, "Expected non-negative length"); + + for (int i = length - 1; i >= 0; i--) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if (!EqualityComparer.Default.Equals(current, value0) + && !EqualityComparer.Default.Equals(current, value1) + && !EqualityComparer.Default.Equals(current, value2)) + { + return i; } - while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + } - // If any elements remain, process the last vector in the search space. - if ((uint)length % Vector128.Count != 0) + return -1; + } + + internal static int IndexOfAnyExcept(ref T searchSpace, T value0, T value1, T value2, T value3, int length) + { + Debug.Assert(length >= 0, "Expected non-negative length"); + + for (int i = 0; i < length; i++) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if (!EqualityComparer.Default.Equals(current, value0) + && !EqualityComparer.Default.Equals(current, value1) + && !EqualityComparer.Default.Equals(current, value2) + && !EqualityComparer.Default.Equals(current, value3)) { - current = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); - notEquals = ~(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) - | Vector128.Equals(values2, current) | Vector128.Equals(values3, current)); - if (notEquals != Vector128.Zero) - { - return ComputeIndex(ref searchSpace, ref oneVectorAwayFromEnd, notEquals); - } + return i; } } return -1; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int ComputeIndex(ref T searchSpace, ref T current, Vector128 notEquals) where T : struct, IEquatable + internal static int LastIndexOfAnyExcept(ref T searchSpace, T value0, T value1, T value2, T value3, int length) { - uint notEqualsElements = notEquals.ExtractMostSignificantBits(); - int index = BitOperations.TrailingZeroCount(notEqualsElements); - return index + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / Unsafe.SizeOf()); + Debug.Assert(length >= 0, "Expected non-negative length"); + + for (int i = length - 1; i >= 0; i--) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if (!EqualityComparer.Default.Equals(current, value0) + && !EqualityComparer.Default.Equals(current, value1) + && !EqualityComparer.Default.Equals(current, value2) + && !EqualityComparer.Default.Equals(current, value3)) + { + return i; + } + } + + return -1; } public static bool SequenceEqual(ref T first, ref T second, int length) where T : IEquatable? @@ -1395,5 +1298,1377 @@ public static int SequenceCompareTo(ref T first, int firstLength, ref T secon } return firstLength.CompareTo(secondLength); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool CanVectorizeAndBenefit(int length) where T : IEquatable? + { + if (Vector128.IsHardwareAccelerated && RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return length >= Vector128.Count; + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return length >= Vector128.Count; + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + return length >= Vector128.Count; + } + else if (Unsafe.SizeOf() == sizeof(long)) + { + return length >= Vector128.Count; + } + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static bool ContainsValueType(ref T searchSpace, T value, int length) where T : struct, INumber + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = 0; + + while (length >= 8) + { + length -= 8; + + if (Unsafe.Add(ref searchSpace, offset) == value + || Unsafe.Add(ref searchSpace, offset + 1) == value + || Unsafe.Add(ref searchSpace, offset + 2) == value + || Unsafe.Add(ref searchSpace, offset + 3) == value + || Unsafe.Add(ref searchSpace, offset + 4) == value + || Unsafe.Add(ref searchSpace, offset + 5) == value + || Unsafe.Add(ref searchSpace, offset + 6) == value + || Unsafe.Add(ref searchSpace, offset + 7) == value) + { + return true; + } + + offset += 8; + } + + if (length >= 4) + { + length -= 4; + + if (Unsafe.Add(ref searchSpace, offset) == value + || Unsafe.Add(ref searchSpace, offset + 1) == value + || Unsafe.Add(ref searchSpace, offset + 2) == value + || Unsafe.Add(ref searchSpace, offset + 3) == value) + { + return true; + } + + offset += 4; + } + + while (length > 0) + { + length -= 1; + + if (Unsafe.Add(ref searchSpace, offset) == value) return true; + + offset += 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, values = Vector256.Create(value); + ref T currentSearchSpace = ref searchSpace; + ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + equals = Vector256.Equals(values, Vector256.LoadUnsafe(ref currentSearchSpace)); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); + continue; + } + + return true; + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the last vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + equals = Vector256.Equals(values, Vector256.LoadUnsafe(ref oneVectorAwayFromEnd)); + if (equals != Vector256.Zero) + { + return true; + } + } + } + else + { + Vector128 equals, values = Vector128.Create(value); + ref T currentSearchSpace = ref searchSpace; + ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + equals = Vector128.Equals(values, Vector128.LoadUnsafe(ref currentSearchSpace)); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + continue; + } + + return true; + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + equals = Vector128.Equals(values, Vector128.LoadUnsafe(ref oneVectorAwayFromEnd)); + if (equals != Vector128.Zero) + { + return true; + } + } + } + + return false; + } + +#if !MONO + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfChar(ref char searchSpace, char value, int length) + => IndexOfValueType(ref Unsafe.As(ref searchSpace), (short)value, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfValueType(ref T searchSpace, T value, int length) where T : struct, INumber + => IndexOfValueType>(ref searchSpace, value, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value, int length) where T : struct, INumber + => IndexOfValueType>(ref searchSpace, value, length); +#endif + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int IndexOfValueType(ref TValue searchSpace, TValue value, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = 0; + + while (length >= 8) + { + length -= 8; + + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset) == value)) return (int)offset; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 1) == value)) return (int)offset + 1; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 2) == value)) return (int)offset + 2; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 3) == value)) return (int)offset + 3; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 4) == value)) return (int)offset + 4; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 5) == value)) return (int)offset + 5; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 6) == value)) return (int)offset + 6; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 7) == value)) return (int)offset + 7; + + offset += 8; + } + + if (length >= 4) + { + length -= 4; + + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset) == value)) return (int)offset; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 1) == value)) return (int)offset + 1; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 2) == value)) return (int)offset + 2; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 3) == value)) return (int)offset + 3; + + offset += 4; + } + + while (length > 0) + { + length -= 1; + + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset) == value)) return (int)offset; + + offset += 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, values = Vector256.Create(value); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + equals = TNegator.NegateIfNeeded(Vector256.Equals(values, Vector256.LoadUnsafe(ref currentSearchSpace))); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the last vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + equals = TNegator.NegateIfNeeded(Vector256.Equals(values, Vector256.LoadUnsafe(ref oneVectorAwayFromEnd))); + if (equals != Vector256.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + else + { + Vector128 equals, values = Vector128.Create(value); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + equals = TNegator.NegateIfNeeded(Vector128.Equals(values, Vector128.LoadUnsafe(ref currentSearchSpace))); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + equals = TNegator.NegateIfNeeded(Vector128.Equals(values, Vector128.LoadUnsafe(ref oneVectorAwayFromEnd))); + if (equals != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + + return -1; + } + +#if !MONO + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyChar(ref char searchSpace, char value0, char value1, int length) + => IndexOfAnyValueType(ref Unsafe.As(ref searchSpace), (short)value0, (short)value1, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T value1, int length) where T : struct, INumber + => IndexOfAnyValueType>(ref searchSpace, value0, value1, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, int length) where T : struct, INumber + => IndexOfAnyValueType>(ref searchSpace, value0, value1, length); +#endif + + // having INumber constraint here allows to use == operator and get better perf compared to .Equals + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int IndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = 0; + TValue lookUp; + + if (typeof(TValue) == typeof(byte)) // this optimization is beneficial only to byte + { + while (length >= 8) + { + length -= 8; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset; + lookUp = Unsafe.Add(ref current, 1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 1; + lookUp = Unsafe.Add(ref current, 2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 2; + lookUp = Unsafe.Add(ref current, 3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 3; + lookUp = Unsafe.Add(ref current, 4); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 4; + lookUp = Unsafe.Add(ref current, 5); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 5; + lookUp = Unsafe.Add(ref current, 6); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 6; + lookUp = Unsafe.Add(ref current, 7); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 7; + + offset += 8; + } + } + + while (length >= 4) + { + length -= 4; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset; + lookUp = Unsafe.Add(ref current, 1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 1; + lookUp = Unsafe.Add(ref current, 2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 2; + lookUp = Unsafe.Add(ref current, 3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 3; + + offset += 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset; + + offset += 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector256.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(values0, current) | Vector256.Equals(values1, current)); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the last vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + current = Vector256.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = TNegator.NegateIfNeeded(Vector256.Equals(values0, current) | Vector256.Equals(values1, current)); + if (equals != Vector256.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector128.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(values0, current) | Vector128.Equals(values1, current)); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + current = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = TNegator.NegateIfNeeded(Vector128.Equals(values0, current) | Vector128.Equals(values1, current)); + if (equals != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + + return -1; + } + +#if !MONO + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T value1, T value2, int length) where T : struct, INumber + => IndexOfAnyValueType>(ref searchSpace, value0, value1, value2, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, int length) where T : struct, INumber + => IndexOfAnyValueType>(ref searchSpace, value0, value1, value2, length); +#endif + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int IndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = 0; + TValue lookUp; + + if (typeof(TValue) == typeof(byte)) // this optimization is beneficial only to byte + { + while (length >= 8) + { + length -= 8; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset; + lookUp = Unsafe.Add(ref current, 1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 1; + lookUp = Unsafe.Add(ref current, 2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 2; + lookUp = Unsafe.Add(ref current, 3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 3; + lookUp = Unsafe.Add(ref current, 4); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 4; + lookUp = Unsafe.Add(ref current, 5); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 5; + lookUp = Unsafe.Add(ref current, 6); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 6; + lookUp = Unsafe.Add(ref current, 7); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 7; + + offset += 8; + } + } + + while (length >= 4) + { + length -= 4; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset; + lookUp = Unsafe.Add(ref current, 1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 1; + lookUp = Unsafe.Add(ref current, 2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 2; + lookUp = Unsafe.Add(ref current, 3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 3; + + offset += 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset; + + offset += 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1), values2 = Vector256.Create(value2); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector256.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(values0, current) | Vector256.Equals(values1, current) | Vector256.Equals(values2, current)); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the last vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + current = Vector256.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = TNegator.NegateIfNeeded(Vector256.Equals(values0, current) | Vector256.Equals(values1, current) | Vector256.Equals(values2, current)); + if (equals != Vector256.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), values2 = Vector128.Create(value2); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector128.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) | Vector128.Equals(values2, current)); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + current = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = TNegator.NegateIfNeeded(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) | Vector128.Equals(values2, current)); + if (equals != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + + return -1; + } + +#if !MONO + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T value1, T value2, T value3, int length) where T : struct, INumber + => IndexOfAnyValueType>(ref searchSpace, value0, value1, value2, value3, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, T value3, int length) where T : struct, INumber + => IndexOfAnyValueType>(ref searchSpace, value0, value1, value2, value3, length); +#endif + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int IndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, TValue value3, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = 0; + TValue lookUp; + + while (length >= 4) + { + length -= 4; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset; + lookUp = Unsafe.Add(ref current, 1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset + 1; + lookUp = Unsafe.Add(ref current, 2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset + 2; + lookUp = Unsafe.Add(ref current, 3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset + 3; + + offset += 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset; + + offset += 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1), values2 = Vector256.Create(value2), values3 = Vector256.Create(value3); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector256.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(values0, current) | Vector256.Equals(values1, current) + | Vector256.Equals(values2, current) | Vector256.Equals(values3, current)); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the last vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + current = Vector256.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = TNegator.NegateIfNeeded(Vector256.Equals(values0, current) | Vector256.Equals(values1, current) + | Vector256.Equals(values2, current) | Vector256.Equals(values3, current)); + if (equals != Vector256.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), values2 = Vector128.Create(value2), values3 = Vector128.Create(value3); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector128.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) + | Vector128.Equals(values2, current) | Vector128.Equals(values3, current)); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + current = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = TNegator.NegateIfNeeded(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) + | Vector128.Equals(values2, current) | Vector128.Equals(values3, current)); + if (equals != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T value1, T value2, T value3, T value4, int length) + where T : struct, INumber + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = 0; + T lookUp; + + while (length >= 4) + { + length -= 4; + + ref T current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset; + lookUp = Unsafe.Add(ref current, 1); + if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset + 1; + lookUp = Unsafe.Add(ref current, 2); + if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset + 2; + lookUp = Unsafe.Add(ref current, 3); + if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset + 3; + + offset += 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset; + + offset += 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1), + values2 = Vector256.Create(value2), values3 = Vector256.Create(value3), values4 = Vector256.Create(value4); + ref T currentSearchSpace = ref searchSpace; + ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector256.LoadUnsafe(ref currentSearchSpace); + equals = Vector256.Equals(values0, current) | Vector256.Equals(values1, current) | Vector256.Equals(values2, current) + | Vector256.Equals(values3, current) | Vector256.Equals(values4, current); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the last vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + current = Vector256.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = Vector256.Equals(values0, current) | Vector256.Equals(values1, current) | Vector256.Equals(values2, current) + | Vector256.Equals(values3, current) | Vector256.Equals(values4, current); + if (equals != Vector256.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), + values2 = Vector128.Create(value2), values3 = Vector128.Create(value3), values4 = Vector128.Create(value4); + ref T currentSearchSpace = ref searchSpace; + ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector128.LoadUnsafe(ref currentSearchSpace); + equals = Vector128.Equals(values0, current) | Vector128.Equals(values1, current) | Vector128.Equals(values2, current) + | Vector128.Equals(values3, current) | Vector128.Equals(values4, current); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + current = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = Vector128.Equals(values0, current) | Vector128.Equals(values1, current) | Vector128.Equals(values2, current) + | Vector128.Equals(values3, current) | Vector128.Equals(values4, current); + if (equals != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + + return -1; + } + +#if !MONO + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfValueType(ref T searchSpace, T value, int length) where T : struct, INumber + => LastIndexOfValueType>(ref searchSpace, value, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyExceptValueType(ref T searchSpace, T value, int length) where T : struct, INumber + => LastIndexOfValueType>(ref searchSpace, value, length); +#endif + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int LastIndexOfValueType(ref TValue searchSpace, TValue value, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = (nuint)length - 1; + + while (length >= 8) + { + length -= 8; + + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset) == value)) return (int)offset; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 1) == value)) return (int)offset - 1; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 2) == value)) return (int)offset - 2; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 3) == value)) return (int)offset - 3; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 4) == value)) return (int)offset - 4; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 5) == value)) return (int)offset - 5; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 6) == value)) return (int)offset - 6; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 7) == value)) return (int)offset - 7; + + offset -= 8; + } + + if (length >= 4) + { + length -= 4; + + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset) == value)) return (int)offset; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 1) == value)) return (int)offset - 1; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 2) == value)) return (int)offset - 2; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 3) == value)) return (int)offset - 3; + + offset -= 4; + } + + while (length > 0) + { + length -= 1; + + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset) == value)) return (int)offset; + + offset -= 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, values = Vector256.Create(value); + nint offset = length - Vector256.Count; + + // Loop until either we've finished all elements -or- there's one or less than a vector's-worth remaining. + while (offset > 0) + { + equals = TNegator.NegateIfNeeded(Vector256.Equals(values, Vector256.LoadUnsafe(ref searchSpace, (nuint)(offset)))); + + if (equals == Vector256.Zero) + { + offset -= Vector256.Count; + continue; + } + + return ComputeLastIndex(offset, equals); + } + + // Process the first vector in the search space. + + equals = TNegator.NegateIfNeeded(Vector256.Equals(values, Vector256.LoadUnsafe(ref searchSpace))); + + if (equals != Vector256.Zero) + { + return ComputeLastIndex(offset: 0, equals); + } + } + else + { + Vector128 equals, values = Vector128.Create(value); + nint offset = length - Vector128.Count; + + // Loop until either we've finished all elements -or- there's one or less than a vector's-worth remaining. + while (offset > 0) + { + equals = TNegator.NegateIfNeeded(Vector128.Equals(values, Vector128.LoadUnsafe(ref searchSpace, (nuint)(offset)))); + + if (equals == Vector128.Zero) + { + offset -= Vector128.Count; + continue; + } + + return ComputeLastIndex(offset, equals); + } + + + // Process the first vector in the search space. + + equals = TNegator.NegateIfNeeded(Vector128.Equals(values, Vector128.LoadUnsafe(ref searchSpace))); + + if (equals != Vector128.Zero) + { + return ComputeLastIndex(offset: 0, equals); + } + } + + return -1; + } + +#if !MONO + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyValueType(ref T searchSpace, T value0, T value1, int length) where T : struct, INumber + => LastIndexOfAnyValueType>(ref searchSpace, value0, value1, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, int length) where T : struct, INumber + => LastIndexOfAnyValueType>(ref searchSpace, value0, value1, length); +#endif + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int LastIndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = (nuint)length - 1; + TValue lookUp; + + if (typeof(TValue) == typeof(byte)) // this optimization is beneficial only to byte + { + while (length >= 8) + { + length -= 8; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset; + lookUp = Unsafe.Add(ref current, -1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 1; + lookUp = Unsafe.Add(ref current, -2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 2; + lookUp = Unsafe.Add(ref current, -3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 3; + lookUp = Unsafe.Add(ref current, -4); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 4; + lookUp = Unsafe.Add(ref current, -5); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 5; + lookUp = Unsafe.Add(ref current, -6); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 6; + lookUp = Unsafe.Add(ref current, -7); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 7; + + offset -= 8; + } + } + + while (length >= 4) + { + length -= 4; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset; + lookUp = Unsafe.Add(ref current, -1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 1; + lookUp = Unsafe.Add(ref current, -2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 2; + lookUp = Unsafe.Add(ref current, -3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 3; + + offset -= 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset; + + offset -= 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1); + nint offset = length - Vector256.Count; + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + while (offset > 0) + { + current = Vector256.LoadUnsafe(ref searchSpace, (nuint)(offset)); + equals = TNegator.NegateIfNeeded(Vector256.Equals(current, values0) | Vector256.Equals(current, values1)); + + if (equals == Vector256.Zero) + { + offset -= Vector256.Count; + continue; + } + + return ComputeLastIndex(offset, equals); + } + + // Process the first vector in the search space. + + current = Vector256.LoadUnsafe(ref searchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(current, values0) | Vector256.Equals(current, values1)); + + if (equals != Vector256.Zero) + { + return ComputeLastIndex(offset: 0, equals); + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1); + nint offset = length - Vector128.Count; + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + while (offset > 0) + { + current = Vector128.LoadUnsafe(ref searchSpace, (nuint)(offset)); + equals = TNegator.NegateIfNeeded(Vector128.Equals(current, values0) | Vector128.Equals(current, values1)); + if (equals == Vector128.Zero) + { + offset -= Vector128.Count; + continue; + } + + return ComputeLastIndex(offset, equals); + } + + // Process the first vector in the search space. + + current = Vector128.LoadUnsafe(ref searchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(current, values0) | Vector128.Equals(current, values1)); + + if (equals != Vector128.Zero) + { + return ComputeLastIndex(offset: 0, equals); + } + } + + return -1; + } + +#if !MONO + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyValueType(ref T searchSpace, T value0, T value1, T value2, int length) where T : struct, INumber + => LastIndexOfAnyValueType>(ref searchSpace, value0, value1, value2, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, int length) where T : struct, INumber + => LastIndexOfAnyValueType>(ref searchSpace, value0, value1, value2, length); +#endif + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int LastIndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = (nuint)length - 1; + TValue lookUp; + + if (typeof(TValue) == typeof(byte)) // this optimization is beneficial only to byte + { + while (length >= 8) + { + length -= 8; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset; + lookUp = Unsafe.Add(ref current, -1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 1; + lookUp = Unsafe.Add(ref current, -2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 2; + lookUp = Unsafe.Add(ref current, -3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 3; + lookUp = Unsafe.Add(ref current, -4); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 4; + lookUp = Unsafe.Add(ref current, -5); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 5; + lookUp = Unsafe.Add(ref current, -6); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 6; + lookUp = Unsafe.Add(ref current, -7); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 7; + + offset -= 8; + } + } + + while (length >= 4) + { + length -= 4; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset; + lookUp = Unsafe.Add(ref current, -1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 1; + lookUp = Unsafe.Add(ref current, -2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 2; + lookUp = Unsafe.Add(ref current, -3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 3; + + offset -= 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset; + + offset -= 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1), values2 = Vector256.Create(value2); + nint offset = length - Vector256.Count; + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + while (offset > 0) + { + current = Vector256.LoadUnsafe(ref searchSpace, (nuint)(offset)); + equals = TNegator.NegateIfNeeded(Vector256.Equals(current, values0) | Vector256.Equals(current, values1) | Vector256.Equals(current, values2)); + + if (equals == Vector256.Zero) + { + offset -= Vector256.Count; + continue; + } + + return ComputeLastIndex(offset, equals); + } + + // Process the first vector in the search space. + + current = Vector256.LoadUnsafe(ref searchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(current, values0) | Vector256.Equals(current, values1) | Vector256.Equals(current, values2)); + + if (equals != Vector256.Zero) + { + return ComputeLastIndex(offset: 0, equals); + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), values2 = Vector128.Create(value2); + nint offset = length - Vector128.Count; + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + while (offset > 0) + { + current = Vector128.LoadUnsafe(ref searchSpace, (nuint)(offset)); + equals = TNegator.NegateIfNeeded(Vector128.Equals(current, values0) | Vector128.Equals(current, values1) | Vector128.Equals(current, values2)); + + if (equals == Vector128.Zero) + { + offset -= Vector128.Count; + continue; + } + + return ComputeLastIndex(offset, equals); + } + + // Process the first vector in the search space. + + current = Vector128.LoadUnsafe(ref searchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(current, values0) | Vector128.Equals(current, values1) | Vector128.Equals(current, values2)); + + if (equals != Vector128.Zero) + { + return ComputeLastIndex(offset: 0, equals); + } + } + + return -1; + } + +#if !MONO + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyValueType(ref T searchSpace, T value0, T value1, T value2, T value3, int length) where T : struct, INumber + => LastIndexOfAnyValueType>(ref searchSpace, value0, value1, value2, value3, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, T value3, int length) where T : struct, INumber + => LastIndexOfAnyValueType>(ref searchSpace, value0, value1, value2, value3, length); +#endif + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int LastIndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, TValue value3, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = (nuint)length - 1; + TValue lookUp; + + while (length >= 4) + { + length -= 4; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset; + lookUp = Unsafe.Add(ref current, -1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset - 1; + lookUp = Unsafe.Add(ref current, -2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset - 2; + lookUp = Unsafe.Add(ref current, -3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset - 3; + + offset -= 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset; + + offset -= 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1), values2 = Vector256.Create(value2), values3 = Vector256.Create(value3); + nint offset = length - Vector256.Count; + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + while (offset > 0) + { + current = Vector256.LoadUnsafe(ref searchSpace, (nuint)(offset)); + equals = TNegator.NegateIfNeeded(Vector256.Equals(current, values0) | Vector256.Equals(current, values1) + | Vector256.Equals(current, values2) | Vector256.Equals(current, values3)); + if (equals == Vector256.Zero) + { + offset -= Vector256.Count; + continue; + } + + return ComputeLastIndex(offset, equals); + } + + // Process the first vector in the search space. + + current = Vector256.LoadUnsafe(ref searchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(current, values0) | Vector256.Equals(current, values1) | Vector256.Equals(current, values2) | Vector256.Equals(current, values3)); + + if (equals != Vector256.Zero) + { + return ComputeLastIndex(offset: 0, equals); + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), values2 = Vector128.Create(value2), values3 = Vector128.Create(value3); + nint offset = length - Vector128.Count; + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + while (offset > 0) + { + current = Vector128.LoadUnsafe(ref searchSpace, (nuint)(offset)); + equals = TNegator.NegateIfNeeded(Vector128.Equals(current, values0) | Vector128.Equals(current, values1) | Vector128.Equals(current, values2) | Vector128.Equals(current, values3)); + + if (equals == Vector128.Zero) + { + offset -= Vector128.Count; + continue; + } + + return ComputeLastIndex(offset, equals); + } + + // Process the first vector in the search space. + + current = Vector128.LoadUnsafe(ref searchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(current, values0) | Vector128.Equals(current, values1) | Vector128.Equals(current, values2) | Vector128.Equals(current, values3)); + + if (equals != Vector128.Zero) + { + return ComputeLastIndex(offset: 0, equals); + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ComputeFirstIndex(ref T searchSpace, ref T current, Vector128 equals) where T : struct + { + uint notEqualsElements = equals.ExtractMostSignificantBits(); + int index = BitOperations.TrailingZeroCount(notEqualsElements); + return index + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ComputeFirstIndex(ref T searchSpace, ref T current, Vector256 equals) where T : struct + { + uint notEqualsElements = equals.ExtractMostSignificantBits(); + int index = BitOperations.TrailingZeroCount(notEqualsElements); + return index + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ComputeLastIndex(nint offset, Vector128 equals) where T : struct + { + uint notEqualsElements = equals.ExtractMostSignificantBits(); + int index = 31 - BitOperations.LeadingZeroCount(notEqualsElements); // 31 = 32 (bits in Int32) - 1 (indexing from zero) + return (int)offset + index; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ComputeLastIndex(nint offset, Vector256 equals) where T : struct + { + uint notEqualsElements = equals.ExtractMostSignificantBits(); + int index = 31 - BitOperations.LeadingZeroCount(notEqualsElements); // 31 = 32 (bits in Int32) - 1 (indexing from zero) + return (int)offset + index; + } + + private interface INegator where T : struct + { + static abstract bool NegateIfNeeded(bool equals); + static abstract Vector128 NegateIfNeeded(Vector128 equals); + static abstract Vector256 NegateIfNeeded(Vector256 equals); + } + + private readonly struct DontNegate : INegator where T : struct + { + public static bool NegateIfNeeded(bool equals) => equals; + public static Vector128 NegateIfNeeded(Vector128 equals) => equals; + public static Vector256 NegateIfNeeded(Vector256 equals) => equals; + } + + private readonly struct Negate : INegator where T : struct + { + public static bool NegateIfNeeded(bool equals) => !equals; + public static Vector128 NegateIfNeeded(Vector128 equals) => ~equals; + public static Vector256 NegateIfNeeded(Vector256 equals) => ~equals; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.cs index d35b5e2d2034ee..c46f5ec8ae4251 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.cs @@ -3,9 +3,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; namespace System diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs index e9df30a7d78ec1..50b30cb5b01ec0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs @@ -994,7 +994,7 @@ public string Replace(char oldChar, char newChar) if (firstIndex < 0) return this; - int remainingLength = Length - firstIndex; + nuint remainingLength = (uint)(Length - firstIndex); string result = FastAllocateString(Length); int copyLength = firstIndex; @@ -1006,35 +1006,56 @@ public string Replace(char oldChar, char newChar) } // Copy the remaining characters, doing the replacement as we go. - ref ushort pSrc = ref Unsafe.Add(ref Unsafe.As(ref _firstChar), copyLength); - ref ushort pDst = ref Unsafe.Add(ref Unsafe.As(ref result._firstChar), copyLength); + ref ushort pSrc = ref Unsafe.Add(ref GetRawStringDataAsUInt16(), (uint)copyLength); + ref ushort pDst = ref Unsafe.Add(ref result.GetRawStringDataAsUInt16(), (uint)copyLength); + nuint i = 0; - if (Vector.IsHardwareAccelerated && remainingLength >= Vector.Count) + if (Vector.IsHardwareAccelerated && Length >= Vector.Count) { - Vector oldChars = new Vector(oldChar); - Vector newChars = new Vector(newChar); + Vector oldChars = new(oldChar); + Vector newChars = new(newChar); - do + Vector original; + Vector equals; + Vector results; + + if (remainingLength > (nuint)Vector.Count) { - Vector original = Unsafe.ReadUnaligned>(ref Unsafe.As(ref pSrc)); - Vector equals = Vector.Equals(original, oldChars); - Vector results = Vector.ConditionalSelect(equals, newChars, original); - Unsafe.WriteUnaligned(ref Unsafe.As(ref pDst), results); - - pSrc = ref Unsafe.Add(ref pSrc, Vector.Count); - pDst = ref Unsafe.Add(ref pDst, Vector.Count); - remainingLength -= Vector.Count; + nuint lengthToExamine = remainingLength - (nuint)Vector.Count; + + do + { + original = Vector.LoadUnsafe(ref pSrc, i); + equals = Vector.Equals(original, oldChars); + results = Vector.ConditionalSelect(equals, newChars, original); + results.StoreUnsafe(ref pDst, i); + + i += (nuint)Vector.Count; + } + while (i < lengthToExamine); } - while (remainingLength >= Vector.Count); - } - for (; remainingLength > 0; remainingLength--) - { - ushort currentChar = pSrc; - pDst = currentChar == oldChar ? newChar : currentChar; + // There are [0, Vector.Count) elements remaining now. + // As the operation is idempotent, and we know that in total there are at least Vector.Count + // elements available, we read a vector from the very end of the string, perform the replace + // and write to the destination at the very end. + // Thus we can eliminate the scalar processing of the remaining elements. + // We perform this operation even if there are 0 elements remaining, as it is cheaper than the + // additional check which would introduce a branch here. - pSrc = ref Unsafe.Add(ref pSrc, 1); - pDst = ref Unsafe.Add(ref pDst, 1); + i = (uint)(Length - Vector.Count); + original = Vector.LoadUnsafe(ref GetRawStringDataAsUInt16(), i); + equals = Vector.Equals(original, oldChars); + results = Vector.ConditionalSelect(equals, newChars, original); + results.StoreUnsafe(ref result.GetRawStringDataAsUInt16(), i); + } + else + { + for (; i < remainingLength; ++i) + { + ushort currentChar = Unsafe.Add(ref pSrc, i); + Unsafe.Add(ref pDst, i) = currentChar == oldChar ? newChar : currentChar; + } } return result; @@ -1067,7 +1088,7 @@ public string Replace(string oldValue, string? newValue) int i = 0; while (true) { - int pos = SpanHelpers.IndexOf(ref Unsafe.Add(ref _firstChar, i), c, Length - i); + int pos = SpanHelpers.IndexOfChar(ref Unsafe.Add(ref _firstChar, i), c, Length - i); if (pos < 0) { break; diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Searching.cs b/src/libraries/System.Private.CoreLib/src/System/String.Searching.cs index 945959f104a7e3..df95295a116fa5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Searching.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Searching.cs @@ -27,7 +27,8 @@ public bool Contains(string value, StringComparison comparisonType) #pragma warning restore CA2249 } - public bool Contains(char value) => SpanHelpers.Contains(ref _firstChar, value, Length); + public bool Contains(char value) + => SpanHelpers.ContainsValueType(ref Unsafe.As(ref _firstChar), (short)value, Length); public bool Contains(char value, StringComparison comparisonType) { @@ -36,8 +37,7 @@ public bool Contains(char value, StringComparison comparisonType) // Returns the index of the first occurrence of a specified character in the current instance. // The search starts at startIndex and runs thorough the next count characters. - // - public int IndexOf(char value) => SpanHelpers.IndexOf(ref _firstChar, value, Length); + public int IndexOf(char value) => SpanHelpers.IndexOfChar(ref _firstChar, value, Length); public int IndexOf(char value, int startIndex) { @@ -78,10 +78,10 @@ private int IndexOfCharOrdinalIgnoreCase(char value) { char valueUc = (char)(value | 0x20); char valueLc = (char)(value & ~0x20); - return SpanHelpers.IndexOfAny(ref _firstChar, valueLc, valueUc, Length); + return SpanHelpers.IndexOfAnyChar(ref _firstChar, valueLc, valueUc, Length); } - return SpanHelpers.IndexOf(ref _firstChar, value, Length); + return SpanHelpers.IndexOfChar(ref _firstChar, value, Length); } public unsafe int IndexOf(char value, int startIndex, int count) @@ -96,7 +96,7 @@ public unsafe int IndexOf(char value, int startIndex, int count) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_Count); } - int result = SpanHelpers.IndexOf(ref Unsafe.Add(ref _firstChar, startIndex), value, count); + int result = SpanHelpers.IndexOfChar(ref Unsafe.Add(ref _firstChar, startIndex), value, count); return result < 0 ? result : result + startIndex; } @@ -280,8 +280,8 @@ public int IndexOf(string value, int startIndex, int count, StringComparison com // The search starts at startIndex and runs backwards to startIndex - count + 1. // The character at position startIndex is included in the search. startIndex is the larger // index within the string. - // - public int LastIndexOf(char value) => SpanHelpers.LastIndexOf(ref _firstChar, value, Length); + public int LastIndexOf(char value) + => SpanHelpers.LastIndexOfValueType(ref Unsafe.As(ref _firstChar), (short)value, Length); public int LastIndexOf(char value, int startIndex) { @@ -306,7 +306,7 @@ public unsafe int LastIndexOf(char value, int startIndex, int count) } int startSearchAt = startIndex + 1 - count; - int result = SpanHelpers.LastIndexOf(ref Unsafe.Add(ref _firstChar, startSearchAt), value, count); + int result = SpanHelpers.LastIndexOfValueType(ref Unsafe.As(ref Unsafe.Add(ref _firstChar, startSearchAt)), (short)value, count); return result < 0 ? result : result + startSearchAt; } diff --git a/src/libraries/System.Private.CoreLib/src/System/String.cs b/src/libraries/System.Private.CoreLib/src/System/String.cs index 77d2168b0d38b8..39d9153f1d1725 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.cs @@ -508,6 +508,7 @@ public static bool IsNullOrWhiteSpace([NotNullWhen(false)] string? value) public ref readonly char GetPinnableReference() => ref _firstChar; internal ref char GetRawStringData() => ref _firstChar; + internal ref ushort GetRawStringDataAsUInt16() => ref Unsafe.As(ref _firstChar); // Helper for encodings so they can talk to our buffer directly // stringLength must be the exact size we'll expect @@ -593,39 +594,9 @@ public StringRuneEnumerator EnumerateRunes() return new StringRuneEnumerator(this); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe int wcslen(char* ptr) - { - // IndexOf processes memory in aligned chunks, and thus it won't crash even if it accesses memory beyond the null terminator. - // This IndexOf behavior is an implementation detail of the runtime and callers outside System.Private.CoreLib must not depend on it. - int length = SpanHelpers.IndexOf(ref *ptr, '\0', int.MaxValue); - if (length < 0) - { - ThrowMustBeNullTerminatedString(); - } + internal static unsafe int wcslen(char* ptr) => SpanHelpers.IndexOfNullCharacter(ref *ptr); - return length; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe int strlen(byte* ptr) - { - // IndexOf processes memory in aligned chunks, and thus it won't crash even if it accesses memory beyond the null terminator. - // This IndexOf behavior is an implementation detail of the runtime and callers outside System.Private.CoreLib must not depend on it. - int length = SpanHelpers.IndexOf(ref *ptr, (byte)'\0', int.MaxValue); - if (length < 0) - { - ThrowMustBeNullTerminatedString(); - } - - return length; - } - - [DoesNotReturn] - private static void ThrowMustBeNullTerminatedString() - { - throw new ArgumentException(SR.Arg_MustBeNullTerminatedString); - } + internal static unsafe int strlen(byte* ptr) => SpanHelpers.IndexOfNullByte(ref *ptr); // // IConvertible implementation 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 ba233dc62aadee..1c0c6f493b033a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs @@ -1052,10 +1052,10 @@ public StringBuilder Append(char value) int nextCharIndex = m_ChunkLength; char[] chars = m_ChunkChars; - if ((uint)nextCharIndex < (uint)chars.Length) + if ((uint)chars.Length > (uint)nextCharIndex) { chars[nextCharIndex] = value; - m_ChunkLength = nextCharIndex + 1; + m_ChunkLength++; } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs index 2d1a37ca13108f..fdf37dcd20a088 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs @@ -257,7 +257,7 @@ public static void Invoke(Event e) // The NtStatus code for the operation is in the InternalLow field uint ntStatus = (uint)(nint)e.nativeOverlapped->InternalLow; uint errorCode = Interop.Errors.ERROR_SUCCESS; - if (ntStatus != Interop.StatusOptions.STATUS_SUCCESS) + if (!Interop.StatusOptions.NT_SUCCESS(ntStatus)) { errorCode = Interop.NtDll.RtlNtStatusToDosError((int)ntStatus); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.CpuUtilizationReader.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.Unix.cs similarity index 80% rename from src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.CpuUtilizationReader.Unix.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.Unix.cs index ea754f9185e9bf..100ddf0c675d13 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.CpuUtilizationReader.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.Unix.cs @@ -5,6 +5,11 @@ namespace System.Threading { internal sealed partial class PortableThreadPool { + private static partial class WorkerThread + { + private static bool IsIOPending => false; + } + private struct CpuUtilizationReader { private Interop.Sys.ProcessCpuInformation _cpuInfo; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.CpuUtilizationReader.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.Windows.cs similarity index 75% rename from src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.CpuUtilizationReader.Windows.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.Windows.cs index 4818512ba8183a..6f5d7eff9d96c9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.CpuUtilizationReader.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.Windows.cs @@ -8,6 +8,20 @@ namespace System.Threading { internal sealed partial class PortableThreadPool { + private static partial class WorkerThread + { + private static bool IsIOPending + { + get + { + bool success = + Interop.Kernel32.GetThreadIOPendingFlag(Interop.Kernel32.GetCurrentThread(), out Interop.BOOL isIOPending); + Debug.Assert(success); + return !success || isIOPending != Interop.BOOL.FALSE; + } + } + } + private struct CpuUtilizationReader { public long _idleTime; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs index 18777f4a555ab1..9527f84a876604 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @@ -10,7 +10,7 @@ internal sealed partial class PortableThreadPool /// /// The worker thread infastructure for the CLR thread pool. /// - private static class WorkerThread + private static partial class WorkerThread { private const int SemaphoreSpinCountDefaultBaseline = 70; #if !TARGET_ARM64 && !TARGET_ARM && !TARGET_LOONGARCH64 @@ -115,6 +115,12 @@ private static void WorkerThreadStart() } } + // The thread cannot exit if it has IO pending, otherwise the IO may be canceled + if (IsIOPending) + { + continue; + } + threadAdjustmentLock.Acquire(); try { diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs index e03c67beaa58da..d9b46074b8e351 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs @@ -107,7 +107,7 @@ private static int ParseGMTNumericZone(string name) { return new TimeZoneInfo(id, TimeSpan.FromSeconds(0), id, name, name, null, disableDaylightSavingTime:true); } - if (name.StartsWith("GMT", StringComparison.Ordinal)) + if (name.Length >= 3 && name[0] == 'G' && name[1] == 'M' && name[2] == 'T') { return new TimeZoneInfo(id, TimeSpan.FromSeconds(ParseGMTNumericZone(name)), id, name, name, null, disableDaylightSavingTime:true); } diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.NonAndroid.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.NonAndroid.cs index a9d6c462610fe6..16be46b6c01117 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.NonAndroid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.NonAndroid.cs @@ -24,17 +24,61 @@ private static TimeZoneInfo GetLocalTimeZoneCore() return GetLocalTimeZoneFromTzFile(); } + private static byte[] ReadAllBytesFromSeekableNonZeroSizeFile(string path, int maxFileSize) + { + using FileStream fs = File.OpenRead(path); + if (!fs.CanSeek) + { + throw new IOException(SR.IO_UnseekableFile); + } + + if (fs.Length == 0 || fs.Length > maxFileSize) + { + throw new IOException(fs.Length == 0 ? SR.IO_InvalidReadLength : SR.IO_FileTooLong); + } + + byte[] bytes = new byte[fs.Length]; + fs.ReadExactly(bytes, 0, bytes.Length); + return bytes; + } + + // Bitmap covering the ASCII range. The bits is set for the characters [a-z], [A-Z], [0-9], '/', '-', and '_'. + private static byte[] asciiBitmap = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0xA8, 0xFF, 0x03, 0xFE, 0xFF, 0xFF, 0x87, 0xFE, 0xFF, 0xFF, 0x07 }; + private static bool IdContainsAnyDisallowedChars(string zoneId) + { + for (int i = 0; i < zoneId.Length; i++) + { + int c = zoneId[i]; + if (c > 0x7F) + { + return true; + } + int value = c >> 3; + if ((asciiBitmap[value] & (ulong)(1UL << (c - (value << 3)))) == 0) + { + return true; + } + } + return false; + } + private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id, out TimeZoneInfo? value, out Exception? e) { value = null; e = null; + if (Path.IsPathRooted(id) || IdContainsAnyDisallowedChars(id)) + { + e = new TimeZoneNotFoundException(SR.Format(SR.InvalidTimeZone_InvalidId, id)); + return TimeZoneInfoResult.TimeZoneNotFoundException; + } + string timeZoneDirectory = GetTimeZoneDirectory(); string timeZoneFilePath = Path.Combine(timeZoneDirectory, id); byte[] rawData; try { - rawData = File.ReadAllBytes(timeZoneFilePath); + rawData = ReadAllBytesFromSeekableNonZeroSizeFile(timeZoneFilePath, maxFileSize: 20 * 1024 * 1024 /* 20 MB */); // timezone files usually less than 1 MB. } catch (UnauthorizedAccessException ex) { @@ -51,7 +95,7 @@ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id, e = ex; return TimeZoneInfoResult.TimeZoneNotFoundException; } - catch (IOException ex) + catch (Exception ex) when (ex is IOException || ex is OutOfMemoryException) { e = new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_InvalidFileData, id, timeZoneFilePath), ex); return TimeZoneInfoResult.InvalidTimeZoneException; diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs index e75cb7f0650afc..fb30f2c9996a21 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs @@ -1793,7 +1793,7 @@ private static bool TryConvertFromTruncating(TOther value, out UInt128 r /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(UInt128 value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(UInt128 value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1860,14 +1860,14 @@ static bool INumberBase.TryConvertToChecked(UInt128 value, [Not } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(UInt128 value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(UInt128 value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1940,14 +1940,14 @@ static bool INumberBase.TryConvertToSaturating(UInt128 value, [ } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(UInt128 value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(UInt128 value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -2014,7 +2014,7 @@ static bool INumberBase.TryConvertToTruncating(UInt128 value, [ } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs index 19069064d13697..5e50f54f64193d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs @@ -946,7 +946,7 @@ private static bool TryConvertFromTruncating(TOther value, out ushort re /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(ushort value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(ushort value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1013,14 +1013,14 @@ static bool INumberBase.TryConvertToChecked(ushort value, [NotNu } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(ushort value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(ushort value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1087,14 +1087,14 @@ static bool INumberBase.TryConvertToSaturating(ushort value, [No } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(ushort value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(ushort value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1161,7 +1161,7 @@ static bool INumberBase.TryConvertToTruncating(ushort value, [No } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs index cce0a8b78a629d..4f137f25dd3037 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs @@ -949,7 +949,7 @@ private static bool TryConvertFromTruncating(TOther value, out uint resu /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(uint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(uint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1016,14 +1016,14 @@ static bool INumberBase.TryConvertToChecked(uint value, [NotNullWh } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(uint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(uint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1096,14 +1096,14 @@ static bool INumberBase.TryConvertToSaturating(uint value, [NotNul } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(uint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(uint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1170,7 +1170,7 @@ static bool INumberBase.TryConvertToTruncating(uint value, [NotNul } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs index f3010971b12a26..c3f210e350dad1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs @@ -948,7 +948,7 @@ private static bool TryConvertFromTruncating(TOther value, out ulong res /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(ulong value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(ulong value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1015,14 +1015,14 @@ static bool INumberBase.TryConvertToChecked(ulong value, [NotNull } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(ulong value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(ulong value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1089,14 +1089,14 @@ static bool INumberBase.TryConvertToSaturating(ulong value, [NotN } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(ulong value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(ulong value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1163,7 +1163,7 @@ static bool INumberBase.TryConvertToTruncating(ulong value, [NotN } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs b/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs index 29403c4e09907c..04c8384c34957d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs @@ -933,7 +933,7 @@ private static bool TryConvertFromTruncating(TOther value, out nuint res /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(nuint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(nuint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1000,14 +1000,14 @@ static bool INumberBase.TryConvertToChecked(nuint value, [NotNull } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(nuint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(nuint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1074,14 +1074,14 @@ static bool INumberBase.TryConvertToSaturating(nuint value, [NotN } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(nuint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(nuint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1148,7 +1148,7 @@ static bool INumberBase.TryConvertToTruncating(nuint value, [NotN } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryReader.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryReader.cs index aa3046d42f8c7b..3d909709a8fa5e 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryReader.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryReader.cs @@ -1350,14 +1350,14 @@ private unsafe int ReadArray(float[] array, int offset, int count) public override int ReadArray(string localName, string namespaceUri, float[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } public override int ReadArray(XmlDictionaryString localName, XmlDictionaryString namespaceUri, float[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } @@ -1373,14 +1373,14 @@ private unsafe int ReadArray(double[] array, int offset, int count) public override int ReadArray(string localName, string namespaceUri, double[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } public override int ReadArray(XmlDictionaryString localName, XmlDictionaryString namespaceUri, double[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } @@ -1396,14 +1396,14 @@ private unsafe int ReadArray(decimal[] array, int offset, int count) public override int ReadArray(string localName, string namespaceUri, decimal[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } public override int ReadArray(XmlDictionaryString localName, XmlDictionaryString namespaceUri, decimal[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs index be47e110a151bd..ff3c4cbd434417 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs @@ -12,6 +12,7 @@ using System.Runtime.Serialization; using System.Globalization; using System.Collections.Generic; +using System.Buffers.Binary; namespace System.Xml { @@ -757,12 +758,8 @@ public override unsafe void WriteFloatText(float f) { int offset; byte[] buffer = GetTextNodeBuffer(1 + sizeof(float), out offset); - byte* bytes = (byte*)&f; - buffer[offset + 0] = (byte)XmlBinaryNodeType.FloatText; - buffer[offset + 1] = bytes[0]; - buffer[offset + 2] = bytes[1]; - buffer[offset + 3] = bytes[2]; - buffer[offset + 4] = bytes[3]; + buffer[offset] = (byte)XmlBinaryNodeType.FloatText; + BinaryPrimitives.WriteSingleLittleEndian(buffer.AsSpan(offset + 1, sizeof(float)), f); Advance(1 + sizeof(float)); } } @@ -778,16 +775,8 @@ public override unsafe void WriteDoubleText(double d) { int offset; byte[] buffer = GetTextNodeBuffer(1 + sizeof(double), out offset); - byte* bytes = (byte*)&d; - buffer[offset + 0] = (byte)XmlBinaryNodeType.DoubleText; - buffer[offset + 1] = bytes[0]; - buffer[offset + 2] = bytes[1]; - buffer[offset + 3] = bytes[2]; - buffer[offset + 4] = bytes[3]; - buffer[offset + 5] = bytes[4]; - buffer[offset + 6] = bytes[5]; - buffer[offset + 7] = bytes[6]; - buffer[offset + 8] = bytes[7]; + buffer[offset] = (byte)XmlBinaryNodeType.DoubleText; + BinaryPrimitives.WriteDoubleLittleEndian(buffer.AsSpan(offset + 1, sizeof(double)), d); Advance(1 + sizeof(double)); } } @@ -798,9 +787,24 @@ public override unsafe void WriteDecimalText(decimal d) byte[] buffer = GetTextNodeBuffer(1 + sizeof(decimal), out offset); byte* bytes = (byte*)&d; buffer[offset++] = (byte)XmlBinaryNodeType.DecimalText; - for (int i = 0; i < sizeof(decimal); i++) + if (BitConverter.IsLittleEndian) { - buffer[offset++] = bytes[i]; + for (int i = 0; i < sizeof(decimal); i++) + { + buffer[offset++] = bytes[i]; + } + } + else + { + Span bits = stackalloc int[4]; + decimal.TryGetBits(d, bits, out int intsWritten); + Debug.Assert(intsWritten == 4); + + Span span = buffer.AsSpan(offset, sizeof(decimal)); + BinaryPrimitives.WriteInt32LittleEndian(span, bits[3]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(4), bits[2]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(8), bits[0]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(12), bits[1]); } Advance(1 + sizeof(decimal)); } @@ -870,15 +874,146 @@ private void WriteArrayInfo(XmlBinaryNodeType nodeType, int count) WriteMultiByteInt32(count); } - public unsafe void UnsafeWriteArray(XmlBinaryNodeType nodeType, int count, byte* array, byte* arrayMax) + public unsafe void UnsafeWriteBoolArray(bool[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.BoolTextWithEndElement, count); + fixed (bool* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, count); + } + } + + public unsafe void UnsafeWriteInt16Array(short[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.Int16TextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (short* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(short) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(short), out int bufferOffset).AsSpan(bufferOffset, sizeof(short)); + BinaryPrimitives.WriteInt16LittleEndian(span, array[offset + i]); + Advance(sizeof(short)); + } + } + } + + public unsafe void UnsafeWriteInt32Array(int[] array, int offset, int count) { - WriteArrayInfo(nodeType, count); - UnsafeWriteArray(array, (int)(arrayMax - array)); + WriteArrayInfo(XmlBinaryNodeType.Int32TextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (int* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(int) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(int), out int bufferOffset).AsSpan(bufferOffset, sizeof(int)); + BinaryPrimitives.WriteInt32LittleEndian(span, array[offset + i]); + Advance(sizeof(int)); + } + } + } + + public unsafe void UnsafeWriteInt64Array(long[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.Int64TextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (long* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(long) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(long), out int bufferOffset).AsSpan(bufferOffset, sizeof(long)); + BinaryPrimitives.WriteInt64LittleEndian(span, array[offset + i]); + Advance(sizeof(long)); + } + } + } + + public unsafe void UnsafeWriteFloatArray(float[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.FloatTextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (float* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(float) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(float), out int bufferOffset).AsSpan(bufferOffset, sizeof(float)); + BinaryPrimitives.WriteSingleLittleEndian(span, array[offset + i]); + Advance(sizeof(float)); + } + } + } + + public unsafe void UnsafeWriteDoubleArray(double[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.DoubleTextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (double* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(double) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(double), out int bufferOffset).AsSpan(bufferOffset, sizeof(double)); + BinaryPrimitives.WriteDoubleLittleEndian(span, array[offset + i]); + Advance(sizeof(double)); + } + } } - private unsafe void UnsafeWriteArray(byte* array, int byteCount) + public unsafe void UnsafeWriteDecimalArray(decimal[] array, int offset, int count) { - base.UnsafeWriteBytes(array, byteCount); + WriteArrayInfo(XmlBinaryNodeType.DecimalTextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (decimal* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(decimal) * count); + } + } + else + { + Span bits = stackalloc int[4]; + for (int i = 0; i < count; i++) + { + decimal.TryGetBits(array[offset + i], bits, out int intsWritten); + Debug.Assert(intsWritten == 4); + + Span span = GetBuffer(16, out int bufferOffset).AsSpan(bufferOffset, 16); + BinaryPrimitives.WriteInt32LittleEndian(span, bits[3]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(4), bits[2]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(8), bits[0]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(12), bits[1]); + Advance(16); + } + } } public void WriteDateTimeArray(DateTime[] array, int offset, int count) @@ -1160,20 +1295,6 @@ private void WriteStartArray(string? prefix, XmlDictionaryString localName, XmlD WriteEndElement(); } - private unsafe void UnsafeWriteArray(string? prefix, string localName, string? namespaceUri, - XmlBinaryNodeType nodeType, int count, byte* array, byte* arrayMax) - { - WriteStartArray(prefix, localName, namespaceUri, count); - _writer.UnsafeWriteArray(nodeType, count, array, arrayMax); - } - - private unsafe void UnsafeWriteArray(string? prefix, XmlDictionaryString localName, XmlDictionaryString? namespaceUri, - XmlBinaryNodeType nodeType, int count, byte* array, byte* arrayMax) - { - WriteStartArray(prefix, localName, namespaceUri, count); - _writer.UnsafeWriteArray(nodeType, count, array, arrayMax); - } - private static void CheckArray(Array array, int offset, int count) { ArgumentNullException.ThrowIfNull(array); @@ -1199,10 +1320,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (bool* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.BoolTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteBoolArray(array, offset, count); } } } @@ -1218,10 +1337,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (bool* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.BoolTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteBoolArray(array, offset, count); } } } @@ -1237,10 +1354,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (short* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int16TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt16Array(array, offset, count); } } } @@ -1256,10 +1371,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (short* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int16TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt16Array(array, offset, count); } } } @@ -1275,10 +1388,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (int* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int32TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt32Array(array, offset, count); } } } @@ -1294,10 +1405,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (int* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int32TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt32Array(array, offset, count); } } } @@ -1313,10 +1422,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (long* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int64TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt64Array(array, offset, count); } } } @@ -1332,10 +1439,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (long* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int64TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt64Array(array, offset, count); } } } @@ -1351,10 +1456,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (float* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteFloatArray(array, offset, count); } } } @@ -1370,10 +1473,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (float* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteFloatArray(array, offset, count); } } } @@ -1389,10 +1490,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (double* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteDoubleArray(array, offset, count); } } } @@ -1408,10 +1507,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (double* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteDoubleArray(array, offset, count); } } } @@ -1427,10 +1524,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (decimal* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteDecimalArray(array, offset, count); } } } @@ -1446,10 +1541,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (decimal* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteDecimalArray(array, offset, count); } } } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs index f193d1815cd591..20ab189caba51d 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs @@ -373,10 +373,18 @@ public long ReadInt64() => BitConverter.IsLittleEndian ? ReadRawBytes() : BinaryPrimitives.ReverseEndianness(ReadRawBytes()); public float ReadSingle() - => ReadRawBytes(); + { + float f = BinaryPrimitives.ReadSingleLittleEndian(GetBuffer(sizeof(float), out int offset).AsSpan(offset, sizeof(float))); + Advance(sizeof(float)); + return f; + } public double ReadDouble() - => ReadRawBytes(); + { + double d = BinaryPrimitives.ReadDoubleLittleEndian(GetBuffer(sizeof(double), out int offset).AsSpan(offset, sizeof(double))); + Advance(sizeof(double)); + return d; + } public decimal ReadDecimal() { @@ -964,10 +972,10 @@ public ulong GetUInt64(int offset) => (ulong)GetInt64(offset); public float GetSingle(int offset) - => ReadRawBytes(offset); + => BinaryPrimitives.ReadSingleLittleEndian(_buffer.AsSpan(offset, sizeof(float))); public double GetDouble(int offset) - => ReadRawBytes(offset); + => BinaryPrimitives.ReadDoubleLittleEndian(_buffer.AsSpan(offset, sizeof(double))); public decimal GetDecimal(int offset) { diff --git a/src/libraries/System.Private.Xml/src/System/Xml/BinaryXml/XmlBinaryReader.cs b/src/libraries/System.Private.Xml/src/System/Xml/BinaryXml/XmlBinaryReader.cs index 5b9838dbb47bb8..403023da70d224 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/BinaryXml/XmlBinaryReader.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/BinaryXml/XmlBinaryReader.cs @@ -3493,7 +3493,9 @@ private XmlNodeType CheckText(bool attr) Debug.Assert(_checkCharacters, "this.checkCharacters"); // grab local copy (perf) - ReadOnlySpan data = _data.AsSpan(_tokDataPos, _end - _tokDataPos); + // Get the bytes for the current token. _tokDataPos is the beginning position, + // and _pos has advanced to the next token (1 past the end of this token). + ReadOnlySpan data = _data.AsSpan(_tokDataPos, _pos - _tokDataPos); Debug.Assert(data.Length % 2 == 0, "Data size should not be odd"); if (!attr) 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 c2aa7970b546f6..d593a97e7d56ec 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 @@ -760,7 +760,7 @@ public override void WriteComment(string? text) { try { - if (null != text && (text.Contains("--") || text.StartsWith('-'))) + if (null != text && (text.Contains("--") || text.EndsWith('-'))) { throw new ArgumentException(SR.Xml_InvalidCommentChars); } diff --git a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs index f7b5e223303f53..e10326033c5dec 100644 --- a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs +++ b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs @@ -203,6 +203,7 @@ public static void Xml_ListRoot() // horizon that it's not worth the trouble. #if !XMLSERIALIZERGENERATORTESTS [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74247", TestPlatforms.tvOS)] public static void Xml_ReadOnlyCollection() { ReadOnlyCollection roc = new ReadOnlyCollection(new string[] { "one", "two" }); @@ -224,6 +225,7 @@ public static void Xml_ReadOnlyCollection() [Theory] [MemberData(nameof(Xml_ImmutableCollections_MemberData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74247", TestPlatforms.tvOS)] public static void Xml_ImmutableCollections(Type type, object collection, Type createException, Type addException, string expectedXml, string exMsg = null) { XmlSerializer serializer; diff --git a/src/libraries/System.Private.Xml/tests/XmlWriter/System.Xml.RW.XmlWriter.Tests.csproj b/src/libraries/System.Private.Xml/tests/XmlWriter/System.Xml.RW.XmlWriter.Tests.csproj index c3e76cb1a30474..3e074e9a835e3f 100644 --- a/src/libraries/System.Private.Xml/tests/XmlWriter/System.Xml.RW.XmlWriter.Tests.csproj +++ b/src/libraries/System.Private.Xml/tests/XmlWriter/System.Xml.RW.XmlWriter.Tests.csproj @@ -7,5 +7,6 @@ + \ No newline at end of file diff --git a/src/libraries/System.Private.Xml/tests/XmlWriter/XmlTextWriterTests.cs b/src/libraries/System.Private.Xml/tests/XmlWriter/XmlTextWriterTests.cs new file mode 100644 index 00000000000000..5b1063a1fc6060 --- /dev/null +++ b/src/libraries/System.Private.Xml/tests/XmlWriter/XmlTextWriterTests.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using Xunit; + +namespace System.Xml.Tests +{ + public class XmlTextWriterTests + { + public static IEnumerable PositiveTestCases + { + get + { + yield return new string[] { null }; // will be normalized to empty string + yield return new string[] { "" }; + yield return new string[] { "This is some data." }; + yield return new string[] { " << brackets and whitespace >> " }; // brackets & surrounding whitespace are ok + yield return new string[] { "&" }; // entities are ok (treated opaquely) + yield return new string[] { "Hello\r\nthere." }; // newlines are ok + yield return new string[] { "\U0001F643 Upside-down smiley \U0001F643" }; // correctly paired surrogates are ok + yield return new string[] { "\uFFFD\uFFFE\uFFFF" }; // replacement char & private use are ok + } + } + + public static IEnumerable BadSurrogateTestCases + { + get + { + yield return new string[] { "\uD800 Unpaired high surrogate." }; + yield return new string[] { "\uDFFF Unpaired low surrogate." }; + yield return new string[] { "Unpaired high surrogate at end. \uD800" }; + yield return new string[] { "Unpaired low surrogate at end. \uDFFF" }; + yield return new string[] { "Unpaired surrogates \uDFFF\uD800 in middle." }; + } + } + + [Theory] + [MemberData(nameof(PositiveTestCases))] + [InlineData("]]")] // ]] without trailing > is ok + [InlineData("-->")] // end of comment marker ok (meaningless to cdata tag) + public void WriteCData_SuccessCases(string cdataText) + { + StringWriter sw = new StringWriter(); + XmlTextWriter xw = new XmlTextWriter(sw); + + xw.WriteCData(cdataText); + + Assert.Equal($"", sw.ToString()); + } + + [Theory] + [MemberData(nameof(BadSurrogateTestCases), DisableDiscoveryEnumeration = true)] // disable enumeration to avoid test harness misinterpreting unpaired surrogates + [InlineData("]]>")] // end of cdata marker forbidden (ambiguous close tag) + public void WriteCData_FailureCases(string cdataText) + { + StringWriter sw = new StringWriter(); + XmlTextWriter xw = new XmlTextWriter(sw); + + Assert.Throws(() => xw.WriteCData(cdataText)); + } + + [Theory] + [MemberData(nameof(PositiveTestCases))] + [InlineData("-12345")] // hyphen at beginning is ok + [InlineData("123- -45")] // single hyphens are ok in middle + [InlineData("]]>")] // end of cdata marker ok (meaningless to comment tag) + public void WriteComment_SuccessCases(string commentText) + { + StringWriter sw = new StringWriter(); + XmlTextWriter xw = new XmlTextWriter(sw); + + xw.WriteComment(commentText); + + Assert.Equal($"", sw.ToString()); + } + + [Theory] + [MemberData(nameof(BadSurrogateTestCases), DisableDiscoveryEnumeration = true)] // disable enumeration to avoid test harness misinterpreting unpaired surrogates + [InlineData("123--45")] // double-hyphen in middle is forbidden (ambiguous comment close tag) + [InlineData("12345-")] // hyphen at end is forbidden (ambiguous comment close tag) + [InlineData("-->")] // end of comment marker forbidden (ambiguous close tag) + public void WriteComment_FailureCases(string commentText) + { + StringWriter sw = new StringWriter(); + XmlTextWriter xw = new XmlTextWriter(sw); + + Assert.Throws(() => xw.WriteComment(commentText)); + } + } +} diff --git a/src/libraries/System.Reflection.Context/Directory.Build.props b/src/libraries/System.Reflection.Context/Directory.Build.props index ed15ad04f46183..798ccfd363e813 100644 --- a/src/libraries/System.Reflection.Context/Directory.Build.props +++ b/src/libraries/System.Reflection.Context/Directory.Build.props @@ -1,7 +1,6 @@  - 4.0.3.0 ECMA diff --git a/src/libraries/System.Reflection.Context/Directory.Build.targets b/src/libraries/System.Reflection.Context/Directory.Build.targets new file mode 100644 index 00000000000000..0d1f58eb0e89d0 --- /dev/null +++ b/src/libraries/System.Reflection.Context/Directory.Build.targets @@ -0,0 +1,9 @@ + + + + + 4.0.3.0 + + diff --git a/src/libraries/System.Reflection.Context/src/System.Reflection.Context.csproj b/src/libraries/System.Reflection.Context/src/System.Reflection.Context.csproj index 0f87d7ce83ddf7..e59132bc7df043 100644 --- a/src/libraries/System.Reflection.Context/src/System.Reflection.Context.csproj +++ b/src/libraries/System.Reflection.Context/src/System.Reflection.Context.csproj @@ -2,6 +2,9 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.1;netstandard2.0 true + + false + 0 true true Provides classes that enable customized reflection contexts. diff --git a/src/libraries/System.Reflection.Context/tests/CustomReflectionContextTests.cs b/src/libraries/System.Reflection.Context/tests/CustomReflectionContextTests.cs index 18636bf7a034fb..b23d5a7cbfe46e 100644 --- a/src/libraries/System.Reflection.Context/tests/CustomReflectionContextTests.cs +++ b/src/libraries/System.Reflection.Context/tests/CustomReflectionContextTests.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; using System.Linq; using Xunit; @@ -65,6 +66,7 @@ public void MapType_ParameterAttributes_Success() } [Fact] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ICloneable))] [ActiveIssue("https://github.com/mono/mono/issues/15191", TestRuntimes.Mono)] public void MapType_Interface_Throws() { diff --git a/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs b/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs index c3210feac38304..9ec6c94cd8b691 100644 --- a/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs +++ b/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs @@ -117,19 +117,9 @@ private sealed class ProxyAssembly [RequiresDynamicCode("Defining a dynamic assembly requires generating code at runtime")] public ProxyAssembly(AssemblyLoadContext alc) { - string name; - if (alc == AssemblyLoadContext.Default) - { - name = "ProxyBuilder"; - } - else - { - string? alcName = alc.Name; - name = string.IsNullOrEmpty(alcName) ? $"DispatchProxyTypes.{alc.GetHashCode()}" : $"DispatchProxyTypes.{alcName}"; - } AssemblyBuilderAccess builderAccess = alc.IsCollectible ? AssemblyBuilderAccess.RunAndCollect : AssemblyBuilderAccess.Run; - _ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(name), builderAccess); + _ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("ProxyBuilder"), builderAccess); _mb = _ab.DefineDynamicModule("testmod"); } diff --git a/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs b/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs index 3be55f8c0912cf..59c28e9600a8a6 100644 --- a/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs +++ b/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs @@ -681,5 +681,24 @@ static object CreateTestDispatchProxy(Type type) => .MakeGenericMethod(typeof(IDisposable), type) .Invoke(null, null); } + + [Fact] + public static void Test_Multiple_AssemblyLoadContextsWithBadName() + { + if (typeof(DispatchProxyTests).Assembly.Location == "") + return; + + Assembly assembly = Assembly.LoadFile(typeof(DispatchProxyTests).Assembly.Location); + Type type = assembly.GetType(typeof(DispatchProxyTests).FullName); + MethodInfo method = type.GetMethod(nameof(Demo), BindingFlags.NonPublic | BindingFlags.Static); + Assert.True((bool)method.Invoke(null, null)); + } + + internal static bool Demo() + { + TestType_IHelloService proxy = DispatchProxy.Create(); + proxy.Hello("Hello"); + return true; + } } } diff --git a/src/libraries/System.Reflection.Extensions/tests/RuntimeReflectionExtensionTests.cs b/src/libraries/System.Reflection.Extensions/tests/RuntimeReflectionExtensionTests.cs index 0dd03017c9e4b3..5b8aa82795c00c 100644 --- a/src/libraries/System.Reflection.Extensions/tests/RuntimeReflectionExtensionTests.cs +++ b/src/libraries/System.Reflection.Extensions/tests/RuntimeReflectionExtensionTests.cs @@ -101,6 +101,9 @@ public void GetRuntimeProperties() Assert.Contains("CanRead", propertyNames); Assert.Contains("CanWrite", propertyNames); Assert.Contains("CanSeek", propertyNames); + + List props = typeof(TestClass).GetRuntimeProperties().ToList(); + Assert.Equal(2, props.Count); } [Fact] @@ -359,6 +362,16 @@ private class TestDerived : TestBase public override void Foo() { throw null; } } + private class TestClassBase + { + internal int TestClassBaseProperty { get; set; } + } + + private class TestClass : TestClassBase + { + internal int TestClassProperty { get; set; } + } + abstract class TestTypeBase : IDisposable { public abstract bool CanRead { get; } diff --git a/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj b/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj index 73a99e80c14fed..3f403e35d4207d 100644 --- a/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj +++ b/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj @@ -1,4 +1,4 @@ - + $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true @@ -9,6 +9,8 @@ The System.Reflection.Metadata library is built-in as part of the shared framework in .NET Runtime. The package can be installed when you need to use it in other target frameworks. README.md + 2 + false diff --git a/src/libraries/System.Reflection/tests/AssemblyTests.cs b/src/libraries/System.Reflection/tests/AssemblyTests.cs index bd86b761db560f..a3808541dcf8d5 100644 --- a/src/libraries/System.Reflection/tests/AssemblyTests.cs +++ b/src/libraries/System.Reflection/tests/AssemblyTests.cs @@ -148,7 +148,7 @@ public void ExportedTypes(Type type, bool expected) [Fact] [SkipOnPlatform(TestPlatforms.Browser, "entry assembly won't be xunit.console on browser")] - [ActiveIssue("https://github.com/dotnet/runtime/issues/36892", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/36892", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst | TestPlatforms.Android)] public void GetEntryAssembly() { Assert.NotNull(Assembly.GetEntryAssembly()); @@ -872,6 +872,7 @@ public static void AssemblyGetForwardedTypes() [Fact] [ActiveIssue("https://github.com/dotnet/runtimelab/issues/155", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + [ActiveIssue("https://github.com/dotnet/runtimelab/issues/77821", TestPlatforms.Android)] public static void AssemblyGetForwardedTypesLoadFailure() { Assembly a = typeof(TypeInForwardedAssembly).Assembly; diff --git a/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs b/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs index e6d59b787cc564..5e41d80f197c47 100644 --- a/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs @@ -69,7 +69,6 @@ public void Invoke_StaticConstructor_NullObject_NullParameters() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsInvokingStaticConstructorsSupported))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/40351", TestRuntimes.Mono)] public void Invoke_StaticConstructorMultipleTimes() { ConstructorInfo[] constructors = GetConstructors(typeof(ClassWithStaticConstructorThatIsCalledMultipleTimesViaReflection)); diff --git a/src/libraries/System.Reflection/tests/DefaultBinderTests.cs b/src/libraries/System.Reflection/tests/DefaultBinderTests.cs index 99018fed4b6a1c..874e1f3d5864c6 100644 --- a/src/libraries/System.Reflection/tests/DefaultBinderTests.cs +++ b/src/libraries/System.Reflection/tests/DefaultBinderTests.cs @@ -167,6 +167,14 @@ public static void InvokeWithNamedParametersOutOfOrder() Assert.Equal(8, result); } + [Theory] + [InlineData("")] + [InlineData(null)] + public static void InvokeWithCreateInstance(string name) + { + Assert.IsType(typeof(Sample).InvokeMember(name, BindingFlags.CreateInstance, null, null, null)); + } + public class Test { public void TestMethod(int param1) { } diff --git a/src/libraries/System.Resources.Extensions/tests/BinaryResourceWriterUnitTest.cs b/src/libraries/System.Resources.Extensions/tests/BinaryResourceWriterUnitTest.cs index c06921cecc834a..21accbca7fe127 100644 --- a/src/libraries/System.Resources.Extensions/tests/BinaryResourceWriterUnitTest.cs +++ b/src/libraries/System.Resources.Extensions/tests/BinaryResourceWriterUnitTest.cs @@ -10,6 +10,8 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization.Formatters.Binary; +using System.Threading; +using System.Threading.Tasks; using Xunit; namespace System.Resources.Extensions.Tests @@ -151,7 +153,6 @@ public static void EmptyResources() Assert.Equal(writerBuffer, binaryWriterBuffer); } - [Fact] public static void PrimitiveResources() { @@ -500,6 +501,50 @@ public static void EmbeddedResourcesAreUpToDate() } } + /// + /// This test has multiple threads simultaneously loop over the keys of a moderately-sized resx using + /// and call for each key. + /// This has historically been prone to thread-safety bugs because of the shared cache state and internal + /// method calls between RuntimeResourceSet and . + /// + /// Running with TRUE replicates https://github.com/dotnet/runtime/issues/74868, + /// while running with FALSE replicates the error from https://github.com/dotnet/runtime/issues/74052. + /// + /// + /// Whether to use vs. when enumerating; + /// these follow fairly different code paths. + /// ] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] + [InlineData(false)] + [InlineData(true)] + public static void TestResourceManagerIsSafeForConcurrentAccessAndEnumeration(bool useEnumeratorEntry) + { + ResourceManager manager = new( + typeof(TestData).FullName, + typeof(TestData).Assembly, + typeof(DeserializingResourceReader).Assembly.GetType("System.Resources.Extensions.RuntimeResourceSet", throwOnError: true)); + + const int Threads = 10; + using Barrier barrier = new(Threads); + Task task = Task.WhenAll(Enumerable.Range(0, Threads).Select(_ => Task.Run(WaitForBarrierThenEnumerateResources))); + + Assert.True(task.Wait(TimeSpan.FromSeconds(30))); + + void WaitForBarrierThenEnumerateResources() + { + barrier.SignalAndWait(); + + ResourceSet set = manager.GetResourceSet(CultureInfo.InvariantCulture, createIfNotExists: true, tryParents: true); + IDictionaryEnumerator enumerator = set.GetEnumerator(); + while (enumerator.MoveNext()) + { + object key = useEnumeratorEntry ? enumerator.Entry.Key : enumerator.Key; + manager.GetObject((string)key); + Thread.Sleep(1); + } + } + } + private static void ResourceValueEquals(object expected, object actual) { if (actual is Bitmap bitmap) @@ -537,5 +582,4 @@ private static void BitmapEquals(Bitmap left, Bitmap right) } } } - } diff --git a/src/libraries/System.Resources.ResourceManager/tests/ResourceManagerTests.cs b/src/libraries/System.Resources.ResourceManager/tests/ResourceManagerTests.cs index ebdac1cd0c48ab..a9dd8253b60c26 100644 --- a/src/libraries/System.Resources.ResourceManager/tests/ResourceManagerTests.cs +++ b/src/libraries/System.Resources.ResourceManager/tests/ResourceManagerTests.cs @@ -12,6 +12,9 @@ using System.Diagnostics; using Microsoft.DotNet.RemoteExecutor; using Xunit; +using System.Threading; +using System.Threading.Tasks; +using System.Collections; [assembly:NeutralResourcesLanguage("en")] @@ -222,6 +225,52 @@ public static void IgnoreCase(string key, string expectedValue) Assert.Equal(expectedValue, manager.GetString(key.ToLower(), culture)); } + /// + /// This test has multiple threads simultaneously loop over the keys of a moderately-sized resx using + /// and call for each key. + /// This has historically been prone to thread-safety bugs because of the shared cache state and internal + /// method calls between RuntimeResourceSet and . + /// + /// Running with TRUE replicates https://github.com/dotnet/runtime/issues/74868, + /// while running with FALSE replicates the error from https://github.com/dotnet/runtime/issues/74052. + /// + /// + /// Whether to use vs. when enumerating; + /// these follow fairly different code paths. + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void TestResourceManagerIsSafeForConcurrentAccessAndEnumeration(bool useEnumeratorEntry) + { + ResourceManager manager = new("System.Resources.Tests.Resources.AToZResx", typeof(ResourceManagerTests).GetTypeInfo().Assembly); + + const int Threads = 10; + using Barrier barrier = new(Threads); + Task[] tasks = Enumerable.Range(0, Threads) + .Select(_ => Task.Factory.StartNew( + WaitForBarrierThenEnumerateResources, + CancellationToken.None, + TaskCreationOptions.LongRunning, + TaskScheduler.Default)) + .ToArray(); + + Assert.True(Task.WaitAll(tasks, TimeSpan.FromSeconds(30))); + + void WaitForBarrierThenEnumerateResources() + { + barrier.SignalAndWait(); + + ResourceSet set = manager.GetResourceSet(CultureInfo.InvariantCulture, createIfNotExists: true, tryParents: true); + IDictionaryEnumerator enumerator = set.GetEnumerator(); + while (enumerator.MoveNext()) + { + object key = useEnumeratorEntry ? enumerator.Entry.Key : enumerator.Key; + manager.GetObject((string)key); + Thread.Sleep(1); + } + } + } public static IEnumerable EnglishNonStringResourceData() { diff --git a/src/libraries/System.Resources.ResourceManager/tests/Resources/AToZResx.Designer.cs b/src/libraries/System.Resources.ResourceManager/tests/Resources/AToZResx.Designer.cs new file mode 100644 index 00000000000000..047939cdc644a9 --- /dev/null +++ b/src/libraries/System.Resources.ResourceManager/tests/Resources/AToZResx.Designer.cs @@ -0,0 +1,297 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace System.Resources.Tests.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class AToZResx { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal AToZResx() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Resources.Tests.Resources.AToZResx", typeof(AToZResx).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to a. + /// + internal static string A { + get { + return ResourceManager.GetString("A", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to b. + /// + internal static string B { + get { + return ResourceManager.GetString("B", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to c. + /// + internal static string C { + get { + return ResourceManager.GetString("C", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to d. + /// + internal static string D { + get { + return ResourceManager.GetString("D", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to e. + /// + internal static string E { + get { + return ResourceManager.GetString("E", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to f. + /// + internal static string F { + get { + return ResourceManager.GetString("F", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to g. + /// + internal static string G { + get { + return ResourceManager.GetString("G", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to h. + /// + internal static string H { + get { + return ResourceManager.GetString("H", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to i. + /// + internal static string I { + get { + return ResourceManager.GetString("I", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to j. + /// + internal static string J { + get { + return ResourceManager.GetString("J", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to k. + /// + internal static string K { + get { + return ResourceManager.GetString("K", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to l. + /// + internal static string L { + get { + return ResourceManager.GetString("L", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to m. + /// + internal static string M { + get { + return ResourceManager.GetString("M", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to n. + /// + internal static string N { + get { + return ResourceManager.GetString("N", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to o. + /// + internal static string O { + get { + return ResourceManager.GetString("O", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to p. + /// + internal static string P { + get { + return ResourceManager.GetString("P", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to q. + /// + internal static string Q { + get { + return ResourceManager.GetString("Q", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to r. + /// + internal static string R { + get { + return ResourceManager.GetString("R", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to s. + /// + internal static string S { + get { + return ResourceManager.GetString("S", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to t. + /// + internal static string T { + get { + return ResourceManager.GetString("T", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to u. + /// + internal static string U { + get { + return ResourceManager.GetString("U", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to v. + /// + internal static string V { + get { + return ResourceManager.GetString("V", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to w. + /// + internal static string W { + get { + return ResourceManager.GetString("W", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to x. + /// + internal static string X { + get { + return ResourceManager.GetString("X", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to y. + /// + internal static string Y { + get { + return ResourceManager.GetString("Y", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to z. + /// + internal static string Z { + get { + return ResourceManager.GetString("Z", resourceCulture); + } + } + } +} diff --git a/src/libraries/System.Resources.ResourceManager/tests/Resources/AToZResx.resx b/src/libraries/System.Resources.ResourceManager/tests/Resources/AToZResx.resx new file mode 100644 index 00000000000000..e1c5ddaba7ed80 --- /dev/null +++ b/src/libraries/System.Resources.ResourceManager/tests/Resources/AToZResx.resx @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + a + + + b + + + c + + + d + + + e + + + f + + + g + + + h + + + i + + + j + + + k + + + l + + + m + + + n + + + o + + + p + + + q + + + r + + + s + + + t + + + u + + + v + + + w + + + x + + + y + + + z + + \ No newline at end of file diff --git a/src/libraries/System.Resources.ResourceManager/tests/System.Resources.ResourceManager.Tests.csproj b/src/libraries/System.Resources.ResourceManager/tests/System.Resources.ResourceManager.Tests.csproj index 66cb937a3d1e20..f5ee7f0c22b59b 100644 --- a/src/libraries/System.Resources.ResourceManager/tests/System.Resources.ResourceManager.Tests.csproj +++ b/src/libraries/System.Resources.ResourceManager/tests/System.Resources.ResourceManager.Tests.csproj @@ -8,6 +8,11 @@ + + True + True + AToZResx.resx + @@ -19,10 +24,13 @@ - + + + ResXFileCodeGenerator + AToZResx.Designer.cs + false Non-Resx @@ -40,23 +48,14 @@ ResXFileCodeGenerator TestResx.Designer.cs - - - - + + + + - - - + + + diff --git a/src/libraries/System.Resources.ResourceManager/tests/runtimeconfig.template.json b/src/libraries/System.Resources.ResourceManager/tests/runtimeconfig.template.json deleted file mode 100644 index e3ad204dd9e512..00000000000000 --- a/src/libraries/System.Resources.ResourceManager/tests/runtimeconfig.template.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "configProperties": { - "System.Drawing.EnableUnixSupport": true - } -} \ No newline at end of file diff --git a/src/libraries/System.Runtime.Caching/Directory.Build.props b/src/libraries/System.Runtime.Caching/Directory.Build.props index 30af6959a0707b..d68d22c1b917f8 100644 --- a/src/libraries/System.Runtime.Caching/Directory.Build.props +++ b/src/libraries/System.Runtime.Caching/Directory.Build.props @@ -1,10 +1,6 @@  - - 4.0.0.0 Microsoft true diff --git a/src/libraries/System.Runtime.Caching/Directory.Build.targets b/src/libraries/System.Runtime.Caching/Directory.Build.targets new file mode 100644 index 00000000000000..e8aeeb47a8da9c --- /dev/null +++ b/src/libraries/System.Runtime.Caching/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + 4.0.0.0 + + diff --git a/src/libraries/System.Runtime.Caching/src/System.Runtime.Caching.csproj b/src/libraries/System.Runtime.Caching/src/System.Runtime.Caching.csproj index 869129ba71f1da..8c62f669dae915 100644 --- a/src/libraries/System.Runtime.Caching/src/System.Runtime.Caching.csproj +++ b/src/libraries/System.Runtime.Caching/src/System.Runtime.Caching.csproj @@ -4,6 +4,9 @@ true Annotations true + + false + 0 true true true diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Environment.StackTrace.cs b/src/libraries/System.Runtime.Extensions/tests/System/Environment.StackTrace.cs index bca195864c8354..255d03483505f2 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Environment.StackTrace.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Environment.StackTrace.cs @@ -15,7 +15,6 @@ public class EnvironmentStackTrace static string s_stackTrace; [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/73051", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] [ActiveIssue("https://github.com/mono/mono/issues/15315", TestRuntimes.Mono)] public void StackTraceTest() { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs index 82fb93b871026b..c764b3b84a1026 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs @@ -55,21 +55,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { // Collect all methods adorned with JSExportAttribute var attributedMethods = context.SyntaxProvider - .CreateSyntaxProvider( - static (node, ct) => ShouldVisitNode(node), - static (context, ct) => - { - MethodDeclarationSyntax syntax = (MethodDeclarationSyntax)context.Node; - if (context.SemanticModel.GetDeclaredSymbol(syntax, ct) is IMethodSymbol methodSymbol - && methodSymbol.GetAttributes().Any(static attribute => attribute.AttributeClass?.ToDisplayString() == Constants.JSExportAttribute)) - { - return new { Syntax = syntax, Symbol = methodSymbol }; - } - - return null; - }) - .Where( - static modelData => modelData is not null); + .ForAttributeWithMetadataName(Constants.JSExportAttribute, + static (node, ct) => node is MethodDeclarationSyntax, + static (context, ct) => new { Syntax = (MethodDeclarationSyntax)context.TargetNode, Symbol = (IMethodSymbol)context.TargetSymbol }); // Validate if attributed methods can have source generated var methodsWithDiagnostics = attributedMethods.Select(static (data, ct) => @@ -301,25 +289,12 @@ private static (MemberDeclarationSyntax, ImmutableArray) GenerateSou return (PrintGeneratedSource(incrementalContext.StubMethodSyntaxTemplate, incrementalContext.SignatureContext, wrapper, registration), incrementalContext.Diagnostics.AddRange(diagnostics.Diagnostics)); } - private static bool ShouldVisitNode(SyntaxNode syntaxNode) - { - // We only support C# method declarations. - if (syntaxNode.Language != LanguageNames.CSharp - || !syntaxNode.IsKind(SyntaxKind.MethodDeclaration)) - { - return false; - } - - // Filter out methods with no attributes early. - return ((MethodDeclarationSyntax)syntaxNode).AttributeLists.Count > 0; - } - private static Diagnostic? GetDiagnosticIfInvalidMethodForGeneration(MethodDeclarationSyntax methodSyntax, IMethodSymbol method) { // Verify the method has no generic types or defined implementation // and is marked static and partial. if (methodSyntax.TypeParameterList is not null - || methodSyntax.Body is null + || (methodSyntax.Body is null && methodSyntax.ExpressionBody is null) || !methodSyntax.Modifiers.Any(SyntaxKind.StaticKeyword) || methodSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.cs index 1d4ef3812b6401..54b03ec734d589 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.cs @@ -60,21 +60,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { // Collect all methods adorned with JSImportAttribute var attributedMethods = context.SyntaxProvider - .CreateSyntaxProvider( - static (node, ct) => ShouldVisitNode(node), - static (context, ct) => - { - MethodDeclarationSyntax syntax = (MethodDeclarationSyntax)context.Node; - if (context.SemanticModel.GetDeclaredSymbol(syntax, ct) is IMethodSymbol methodSymbol - && methodSymbol.GetAttributes().Any(static attribute => attribute.AttributeClass?.ToDisplayString() == Constants.JSImportAttribute)) - { - return new { Syntax = syntax, Symbol = methodSymbol }; - } - - return null; - }) - .Where( - static modelData => modelData is not null); + .ForAttributeWithMetadataName(Constants.JSImportAttribute, + static (node, ct) => node is MethodDeclarationSyntax, + static (context, ct) => new { Syntax = (MethodDeclarationSyntax)context.TargetNode, Symbol = (IMethodSymbol)context.TargetSymbol }); // Validate if attributed methods can have source generated var methodsWithDiagnostics = attributedMethods.Select(static (data, ct) => @@ -304,19 +292,6 @@ private static (MemberDeclarationSyntax, ImmutableArray) GenerateSou return (PrintGeneratedSource(incrementalContext.StubMethodSyntaxTemplate, incrementalContext.SignatureContext, code), incrementalContext.Diagnostics.AddRange(diagnostics.Diagnostics)); } - private static bool ShouldVisitNode(SyntaxNode syntaxNode) - { - // We only support C# method declarations. - if (syntaxNode.Language != LanguageNames.CSharp - || !syntaxNode.IsKind(SyntaxKind.MethodDeclaration)) - { - return false; - } - - // Filter out methods with no attributes early. - return ((MethodDeclarationSyntax)syntaxNode).AttributeLists.Count > 0; - } - private static Diagnostic? GetDiagnosticIfInvalidMethodForGeneration(MethodDeclarationSyntax methodSyntax, IMethodSymbol method) { // Verify the method has no generic types or defined implementation diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.csproj index 70eda110a1b22e..90bdaf34e53dd3 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportStubContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportStubContext.cs index 055e81315e68e4..005e30581affea 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportStubContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportStubContext.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; +using System.Text; using System.Threading; using Microsoft.CodeAnalysis; @@ -16,6 +18,11 @@ namespace Microsoft.Interop.JavaScript { internal sealed class JSSignatureContext : IEquatable { + private static SymbolDisplayFormat s_typeNameFormat { get; } = new SymbolDisplayFormat( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes + ); + internal static readonly string GeneratorName = typeof(JSImportGenerator).Assembly.GetName().Name; internal static readonly string GeneratorVersion = typeof(JSImportGenerator).Assembly.GetName().Version.ToString(); @@ -118,14 +125,8 @@ public static JSSignatureContext Create( }; int typesHash = Math.Abs((int)hash); - - var fullName = $"{method.ContainingType.ToDisplayString()}.{method.Name}"; - string qualifiedName; - var ns = string.Join(".", method.ContainingType.ToDisplayParts().Where(p => p.Kind == SymbolDisplayPartKind.NamespaceName).Select(x => x.ToString()).ToArray()); - var cn = string.Join("/", method.ContainingType.ToDisplayParts().Where(p => p.Kind == SymbolDisplayPartKind.ClassName).Select(x => x.ToString()).ToArray()); - var qclasses = method.ContainingType.ContainingNamespace == null ? ns : ns + "." + cn; - qualifiedName = $"[{env.Compilation.AssemblyName}]{qclasses}:{method.Name}"; + string qualifiedName = GetFullyQualifiedMethodName(env, method); return new JSSignatureContext() { @@ -143,6 +144,17 @@ public static JSSignatureContext Create( }; } + private static string GetFullyQualifiedMethodName(StubEnvironment env, IMethodSymbol method) + { + // Mono style nested class name format. + string typeName = method.ContainingType.ToDisplayString(s_typeNameFormat).Replace(".", "/"); + + if (!method.ContainingType.ContainingNamespace.IsGlobalNamespace) + typeName = $"{method.ContainingType.ContainingNamespace.ToDisplayString()}.{typeName}"; + + return $"[{env.Compilation.AssemblyName}]{typeName}:{method.Name}"; + } + private static (ImmutableArray, IMarshallingGeneratorFactory) GenerateTypeInformation(IMethodSymbol method, GeneratorDiagnostics diagnostics, StubEnvironment env) { var jsMarshallingAttributeParser = new JSMarshallingAttributeInfoParser(env.Compilation, diagnostics, method); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/ref/System.Runtime.InteropServices.JavaScript.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/ref/System.Runtime.InteropServices.JavaScript.cs index 54b2c5352f8c21..c910e7f8228c5d 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/ref/System.Runtime.InteropServices.JavaScript.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/ref/System.Runtime.InteropServices.JavaScript.cs @@ -201,7 +201,9 @@ public sealed class JSMarshalerType [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public struct JSMarshalerArgument { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public delegate void ArgumentToManagedCallback(ref JSMarshalerArgument arg, out T value); + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public delegate void ArgumentToJSCallback(ref JSMarshalerArgument arg, T value); public void Initialize() { throw null; } public void ToManaged(out bool value) { throw null; } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj index 353071ad0c5a0c..82b95560ab2a94 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj @@ -5,6 +5,14 @@ enable + + true + + + + $(DefineConstants);FEATURE_WASM_THREADS + + $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs index 3c7ba82f162641..2fbf3f448f5cd5 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs @@ -85,6 +85,9 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer) } catch (Exception ex) { + if (ex is TargetInvocationException refEx && refEx.InnerException != null) + ex = refEx.InnerException; + arg_exc.ToJS(ex); } } @@ -121,7 +124,7 @@ public static void ReleaseJSOwnedObjectByGCHandle(JSMarshalerArgument* arguments public static void CreateTaskCallback(JSMarshalerArgument* arguments_buffer) { ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() - ref JSMarshalerArgument arg_return = ref arguments_buffer[1]; // used as return vaule + ref JSMarshalerArgument arg_return = ref arguments_buffer[1]; // used as return value try { JSHostImplementation.TaskCallback holder = new JSHostImplementation.TaskCallback(); @@ -192,6 +195,34 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer) } } + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 + // the marshaled signature is: + // string GetManagedStackTrace(GCHandle exception) + public static void GetManagedStackTrace(JSMarshalerArgument* arguments_buffer) + { + ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() + ref JSMarshalerArgument arg_return = ref arguments_buffer[1]; // used as return value + ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller + try + { + GCHandle exception_gc_handle = (GCHandle)arg_1.slot.GCHandle; + if (exception_gc_handle.Target is Exception exception) + { + arg_return.ToJS(exception.StackTrace); + } + else + { + throw new InvalidOperationException("Exception is null"); + } + } + catch (Exception ex) + { + arg_exc.ToJS(ex); + } + } + +#if FEATURE_WASM_THREADS + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 // the marshaled signature is: // void InstallSynchronizationContext() @@ -207,6 +238,8 @@ public static void InstallSynchronizationContext (JSMarshalerArgument* arguments } } +#endif + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void StopProfile() { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs index 9c87e4ec351408..47d14676d064f9 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if FEATURE_WASM_THREADS + using System; using System.Threading; using System.Threading.Channels; @@ -141,3 +143,5 @@ private void Pump () { } } } + +#endif diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs index f3101e881fe9ac..13876867c8a544 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs @@ -13,12 +13,14 @@ public partial struct JSMarshalerArgument /// Helps with marshaling of the Task result or Function arguments. /// It's used by JSImport code generator and should not be used by developers in source code. /// + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public delegate void ArgumentToManagedCallback(ref JSMarshalerArgument arg, out T value); /// /// Helps with marshaling of the Task result or Function arguments. /// It's used by JSImport code generator and should not be used by developers in source code. /// + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public delegate void ArgumentToJSCallback(ref JSMarshalerArgument arg, T value); /// diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs index 71a795545f3317..81341d23ebaba5 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs @@ -5,6 +5,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using System.Threading; using Xunit; #pragma warning disable xUnit1026 // Theory methods should use all of their parameters @@ -18,6 +19,19 @@ public unsafe void StructSize() Assert.Equal(16, sizeof(JSMarshalerArgument)); } + [Fact] + public async Task CancelableImportAsync() + { + var cts = new CancellationTokenSource(); + var exTask = Assert.ThrowsAsync(async () => await JSHost.ImportAsync("JavaScriptTestHelper", "./JavaScriptTestHelper.mjs", cts.Token)); + cts.Cancel(); + var actualEx2 = await exTask; + Assert.Equal("OperationCanceledException", actualEx2.Message); + + var actualEx = await Assert.ThrowsAsync(async () => await JSHost.ImportAsync("JavaScriptTestHelper", "./JavaScriptTestHelper.mjs", new CancellationToken(true))); + Assert.Equal("OperationCanceledException", actualEx.Message); + } + [Fact] public unsafe void GlobalThis() { @@ -1199,7 +1213,62 @@ public void JsExportString(string value) public void JsExportStringNoNs() { var actual = JavaScriptTestHelper.invoke2_String("test", nameof(JavaScriptTestHelperNoNamespace.EchoString)); - Assert.Equal("test!", actual); + Assert.Equal("test51", actual); + } + + [Fact] + public void JsExportStructClassRecords() + { + var actual = JavaScriptTestHelper.invokeStructClassRecords("test"); + Assert.Equal(48, actual.Length); + Assert.Equal("test11", actual[0]); + Assert.Equal("test12", actual[1]); + Assert.Equal("test13", actual[2]); + Assert.Equal("test14", actual[3]); + Assert.Equal("test15", actual[4]); + Assert.Equal("test16", actual[5]); + Assert.Equal("test17", actual[6]); + Assert.Equal("test18", actual[7]); + Assert.Equal("test19", actual[8]); + Assert.Equal("test21", actual[9]); + Assert.Equal("test22", actual[10]); + Assert.Equal("test23", actual[11]); + Assert.Equal("test24", actual[12]); + Assert.Equal("test25", actual[13]); + Assert.Equal("test31", actual[14]); + Assert.Equal("test32", actual[15]); + Assert.Equal("test33", actual[16]); + Assert.Equal("test34", actual[17]); + Assert.Equal("test35", actual[18]); + Assert.Equal("test41", actual[19]); + Assert.Equal("test42", actual[20]); + Assert.Equal("test43", actual[21]); + Assert.Equal("test44", actual[22]); + Assert.Equal("test45", actual[23]); + Assert.Equal("test51", actual[24]); + Assert.Equal("test52", actual[25]); + Assert.Equal("test53", actual[26]); + Assert.Equal("test54", actual[27]); + Assert.Equal("test55", actual[28]); + Assert.Equal("test56", actual[29]); + Assert.Equal("test57", actual[30]); + Assert.Equal("test58", actual[31]); + Assert.Equal("test59", actual[32]); + Assert.Equal("test61", actual[33]); + Assert.Equal("test62", actual[34]); + Assert.Equal("test63", actual[35]); + Assert.Equal("test64", actual[36]); + Assert.Equal("test65", actual[37]); + Assert.Equal("test71", actual[38]); + Assert.Equal("test72", actual[39]); + Assert.Equal("test73", actual[40]); + Assert.Equal("test74", actual[41]); + Assert.Equal("test75", actual[42]); + Assert.Equal("test81", actual[43]); + Assert.Equal("test82", actual[44]); + Assert.Equal("test83", actual[45]); + Assert.Equal("test84", actual[46]); + Assert.Equal("test85", actual[47]); } [Fact] @@ -1303,11 +1372,36 @@ public void JsExportException(Exception value, string clazz) [Fact] public void JsExportThrows() { - var ex = Assert.Throws(() => JavaScriptTestHelper.invoke1_String("-t-e-s-t-", nameof(JavaScriptTestHelper.Throw))); + var ex = Assert.Throws(() => JavaScriptTestHelper.invoke1_String("-t-e-s-t-", nameof(JavaScriptTestHelper.ThrowFromJSExport))); Assert.DoesNotContain("Unexpected error", ex.Message); Assert.Contains("-t-e-s-t-", ex.Message); } + [Fact] + public void JSImportReturnError() + { + var err = JavaScriptTestHelper.returnError() as Exception; + Assert.NotNull(err); + Assert.Contains("this-is-error", err.Message); + } + + [Fact] + public void JsExportCatchToString() + { + var toString = JavaScriptTestHelper.catch1toString("-t-e-s-t-", nameof(JavaScriptTestHelper.ThrowFromJSExport)); + Assert.DoesNotContain("Unexpected error", toString); + Assert.Contains("-t-e-s-t-", toString); + Assert.DoesNotContain(nameof(JavaScriptTestHelper.ThrowFromJSExport), toString); + } + + [Fact] + public void JsExportCatchStack() + { + var stack = JavaScriptTestHelper.catch1stack("-t-e-s-t-", nameof(JavaScriptTestHelper.ThrowFromJSExport)); + Assert.Contains(nameof(JavaScriptTestHelper.ThrowFromJSExport), stack); + Assert.Contains("catch1stack", stack); + } + #endregion Exception #region JSObject @@ -1851,12 +1945,12 @@ private void JsImportTest(T value var exThrow0 = Assert.Throws(() => JavaScriptTestHelper.throw0()); Assert.Contains("throw-0-msg", exThrow0.Message); Assert.DoesNotContain(" at ", exThrow0.Message); - Assert.Contains(" at Module.throw0", exThrow0.StackTrace); + Assert.Contains("throw0fn", exThrow0.StackTrace); var exThrow1 = Assert.Throws(() => throw1(value)); Assert.Contains("throw1-msg", exThrow1.Message); Assert.DoesNotContain(" at ", exThrow1.Message); - Assert.Contains(" at Module.throw1", exThrow1.StackTrace); + Assert.Contains("throw1fn", exThrow1.StackTrace); // anything is a system.object, sometimes it would be JSObject wrapper if (typeof(T).IsPrimitive) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index aaa5d16c2b8cdb..4f06fc8f4a42e6 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -32,8 +32,14 @@ public static void ConsoleWriteLine([JSMarshalAs] string message) Console.WriteLine(message); } + [JSImport("catch1toString", "JavaScriptTestHelper")] + public static partial string catch1toString(string message, string functionName); + + [JSImport("catch1stack", "JavaScriptTestHelper")] + public static partial string catch1stack(string message, string functionName); + [JSExport] - public static void Throw(string message) + public static void ThrowFromJSExport(string message) { throw new ArgumentException(message); } @@ -57,10 +63,14 @@ public static DateTime Now() [return: JSMarshalAs] internal static partial string getClass1(); - [JSImport("throw0", "JavaScriptTestHelper")] + [JSImport("throw0fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial void throw0(); + [JSImport("returnError", "JavaScriptTestHelper")] + [return: JSMarshalAs] + internal static partial object returnError(); + [JSImport("echo1", "JavaScriptTestHelper")] [return: JSMarshalAs>] internal static partial Task echo1_Task([JSMarshalAs>] Task arg1); @@ -215,7 +225,7 @@ internal static partial void Relaxed(string a1, Exception ex, [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_Int32([JSMarshalAs] int value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial int throw1_Int32([JSMarshalAs] int value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -241,7 +251,7 @@ public static int EchoInt32([JSMarshalAs] int arg1) [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_String([JSMarshalAs] string value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial string throw1_String([JSMarshalAs] string value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -250,6 +260,9 @@ public static int EchoInt32([JSMarshalAs] int arg1) [JSImport("invoke2", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial string invoke2_String([JSMarshalAs] string value, [JSMarshalAs] string name); + [JSImport("invokeStructClassRecords", "JavaScriptTestHelper")] + [return: JSMarshalAs>] + internal static partial string[] invokeStructClassRecords([JSMarshalAs] string value); [JSExport] [return: JSMarshalAs] public static string EchoString([JSMarshalAs] string arg1) @@ -270,7 +283,7 @@ public static string EchoString([JSMarshalAs] string arg1) [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_Object([JSMarshalAs] object value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial object throw1_Object([JSMarshalAs] object value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -296,7 +309,7 @@ public static object EchoObject([JSMarshalAs] object arg1) [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_Exception([JSMarshalAs] Exception value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial Exception throw1_Exception([JSMarshalAs] Exception value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -428,7 +441,7 @@ public static Func BackFuncOfIntInt([JSMarshalAs] internal static partial bool identity1_Boolean([JSMarshalAs] bool value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool throw1_Boolean([JSMarshalAs] bool value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -455,7 +468,7 @@ public static bool EchoBoolean([JSMarshalAs] bool arg1) [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_Char([JSMarshalAs] char value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial char throw1_Char([JSMarshalAs] char value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -481,7 +494,7 @@ public static char EchoChar([JSMarshalAs] char arg1) [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_Byte([JSMarshalAs] byte value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial byte throw1_Byte([JSMarshalAs] byte value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -507,7 +520,7 @@ public static byte EchoByte([JSMarshalAs] byte arg1) [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_Int16([JSMarshalAs] short value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial short throw1_Int16([JSMarshalAs] short value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -533,7 +546,7 @@ public static short EchoInt16([JSMarshalAs] short arg1) [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_Int52([JSMarshalAs] long value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial long throw1_Int52([JSMarshalAs] long value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -559,7 +572,7 @@ public static long EchoInt52([JSMarshalAs] long arg1) [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_BigInt64([JSMarshalAs] long value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial long throw1_BigInt64([JSMarshalAs] long value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -585,7 +598,7 @@ public static long EchoBigInt64([JSMarshalAs] long arg1) [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_Double([JSMarshalAs] double value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial double throw1_Double([JSMarshalAs] double value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -611,7 +624,7 @@ public static double EchoDouble([JSMarshalAs] double arg1) [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_Single([JSMarshalAs] float value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial float throw1_Single([JSMarshalAs] float value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -637,7 +650,7 @@ public static float EchoSingle([JSMarshalAs] float arg1) [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_IntPtr([JSMarshalAs] IntPtr value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial IntPtr throw1_IntPtr([JSMarshalAs] IntPtr value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -664,7 +677,7 @@ public static IntPtr EchoIntPtr([JSMarshalAs] IntPtr arg1) [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal unsafe static partial bool identity1_VoidPtr([JSMarshalAs] void* value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal unsafe static partial void* throw1_VoidPtr([JSMarshalAs] void* value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -690,7 +703,7 @@ public static IntPtr EchoIntPtr([JSMarshalAs] IntPtr arg1) [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_DateTime([JSMarshalAs] DateTime value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial DateTime throw1_DateTime([JSMarshalAs] DateTime value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -716,7 +729,7 @@ public static DateTime EchoDateTime([JSMarshalAs] DateTime arg1) [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_DateTimeOffset([JSMarshalAs] DateTimeOffset value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial DateTimeOffset throw1_DateTimeOffset([JSMarshalAs] DateTimeOffset value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -743,7 +756,7 @@ public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs] DateT [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_NullableBoolean([JSMarshalAs] bool? value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool? throw1_NullableBoolean([JSMarshalAs] bool? value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -770,7 +783,7 @@ public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs] DateT [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_NullableInt32([JSMarshalAs] int? value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial int? throw1_NullableInt32([JSMarshalAs] int? value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -797,7 +810,7 @@ public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs] DateT [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_NullableBigInt64([JSMarshalAs] long? value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial long? throw1_NullableBigInt64([JSMarshalAs] long? value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -824,7 +837,7 @@ public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs] DateT [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_NullableIntPtr([JSMarshalAs] IntPtr? value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial IntPtr? throw1_NullableIntPtr([JSMarshalAs] IntPtr? value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -851,7 +864,7 @@ public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs] DateT [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_NullableDouble([JSMarshalAs] double? value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial double? throw1_NullableDouble([JSMarshalAs] double? value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -878,7 +891,7 @@ public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs] DateT [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_NullableDateTime([JSMarshalAs] DateTime? value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial DateTime? throw1_NullableDateTime([JSMarshalAs] DateTime? value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -904,7 +917,7 @@ public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs] DateT [JSImport("identity1", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial bool identity1_JSObject([JSMarshalAs] JSObject value); - [JSImport("throw1", "JavaScriptTestHelper")] + [JSImport("throw1fn", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial JSObject throw1_JSObject([JSMarshalAs] JSObject value); [JSImport("invoke1", "JavaScriptTestHelper")] @@ -935,11 +948,302 @@ public static async Task InitializeAsync() } } +namespace JavaScriptTestHelperNamespace +{ + public partial class JavaScriptTestHelper + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) + { + return message + "11"; + } + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "12"; + + public partial class DoubleNestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "13"; + } + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "14"; + + public partial record class DoubleNestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "15"; + } + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "16"; + + public partial struct DoubleNestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "17"; + } + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "18"; + + public partial record struct DoubleNestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "19"; + } + } + } + + public partial class JavaScriptTestHelperStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "21"; + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "22"; + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "23"; + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) + { + return message + "24"; + } + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "25"; + } + } + + public partial record class JavaScriptTestHelperRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "31"; + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "32"; + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "33"; + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "34"; + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "35"; + } + } + + public partial record struct JavaScriptTestHelperRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "41"; + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "42"; + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "43"; + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "44"; + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) + { + return message + "45"; + } + } + } +} + public partial class JavaScriptTestHelperNoNamespace { [System.Runtime.InteropServices.JavaScript.JSExport] - public static string EchoString(string message) + public static string EchoString(string message) => message + "51"; + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "52"; + + public partial class DoubleNestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "53"; + } + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "54"; + + public partial record class DoubleNestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "55"; + } + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "56"; + + public partial struct DoubleNestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "57"; + } + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "58"; + + public partial record struct DoubleNestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "59"; + } + } +} + +public partial class JavaScriptTestHelperStructNoNamespace +{ + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "61"; + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "62"; + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "63"; + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "64"; + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "65"; + } +} + +public partial record class JavaScriptTestHelperRecordClassNoNamespace +{ + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "71"; + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "72"; + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "73"; + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "74"; + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "75"; + } +} + +public partial record struct JavaScriptTestHelperRecordStructNoNamespace +{ + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "81"; + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "82"; + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "83"; + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "84"; + } + + public partial record struct NestedRecordStruct { - return message + "!"; + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "85"; } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs index c3cc24ed73979d..927a5194c561fd 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs @@ -105,12 +105,38 @@ export function retrieve1() { return val; } -export function throw0() { +export function throw0fn() { //console.log(`throw0()`) throw new Error('throw-0-msg'); } -export function throw1(arg1) { +export function returnError() { + return new Error('this-is-error'); +} + +export function catch1toString(message, functionName) { + const JavaScriptTestHelper = dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper; + const fn = JavaScriptTestHelper[functionName]; + try { + fn(message); + return "bad"; + } catch (err) { + return err.toString(); + } +} + +export function catch1stack(message, functionName) { + const JavaScriptTestHelper = dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper; + const fn = JavaScriptTestHelper[functionName]; + try { + fn(message); + return "bad"; + } catch (err) { + return err.stack; + } +} + +export function throw1fn(arg1) { //console.log(`throw1(arg1:${arg1 !== null ? arg1 : ''})`) throw new Error('throw1-msg ' + arg1); } @@ -171,6 +197,59 @@ export function invoke2(arg1, name) { return res; } +export function invokeStructClassRecords(arg1) { + return [ + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedClass.DoubleNestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedRecordClass.DoubleNestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedStruct.DoubleNestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedRecordStruct.DoubleNestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperStruct.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperStruct.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperStruct.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperStruct.NestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordClass.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordClass.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordClass.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordClass.NestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordStruct.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordStruct.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordStruct.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordStruct.NestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedClass.DoubleNestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedRecordClass.DoubleNestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedStruct.DoubleNestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedRecordStruct.DoubleNestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperStructNoNamespace.EchoString(arg1), + dllExports.JavaScriptTestHelperStructNoNamespace.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperStructNoNamespace.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperStructNoNamespace.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperStructNoNamespace.NestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordClassNoNamespace.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordClassNoNamespace.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordClassNoNamespace.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordClassNoNamespace.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordClassNoNamespace.NestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordStructNoNamespace.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordStructNoNamespace.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordStructNoNamespace.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordStructNoNamespace.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordStructNoNamespace.NestedRecordStruct.EchoString(arg1), + ]; +} + export async function awaitvoid(arg1) { // console.log("awaitvoid:" + typeof arg1); await arg1; diff --git a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/CheckArchitectureTests.cs b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/CheckArchitectureTests.cs index 1405411989d9ef..29c91dce7580ca 100644 --- a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/CheckArchitectureTests.cs +++ b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/CheckArchitectureTests.cs @@ -29,7 +29,15 @@ public void VerifyArchitecture() break; case Architecture.Arm64: - Assert.Equal(IntPtr.Size == 4 ? Architecture.Arm : Architecture.Arm64, processArch); + if (IntPtr.Size == 8) + { + Assert.Equal(Architecture.Arm64, processArch); + } + else + { + // armv7/armv6 process running on arm64 host + Assert.True(processArch == Architecture.Arm || processArch == Architecture.Armv6, $"Unexpected process architecture: {processArch}"); + } break; case Architecture.Wasm: diff --git a/src/libraries/System.Runtime.InteropServices/System.Runtime.InteropServices.sln b/src/libraries/System.Runtime.InteropServices/System.Runtime.InteropServices.sln index 0a0c735457a584..06d730d4986ae8 100644 --- a/src/libraries/System.Runtime.InteropServices/System.Runtime.InteropServices.sln +++ b/src/libraries/System.Runtime.InteropServices/System.Runtime.InteropServices.sln @@ -1,4 +1,8 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.33602.30 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Private.CoreLib", "..\..\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj", "{94B59BA0-491F-4B59-ADFF-A057EC3EC835}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}" @@ -41,17 +45,23 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{E1AEBD5D-AE4 EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Checked|Any CPU = Checked|Any CPU + Checked|x64 = Checked|x64 + Checked|x86 = Checked|x86 Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 Release|x86 = Release|x86 - Checked|Any CPU = Checked|Any CPU - Checked|x64 = Checked|x64 - Checked|x86 = Checked|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Checked|Any CPU.ActiveCfg = Checked|x64 + {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Checked|Any CPU.Build.0 = Checked|x64 + {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Checked|x64.ActiveCfg = Checked|x64 + {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Checked|x64.Build.0 = Checked|x64 + {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Checked|x86.ActiveCfg = Checked|x86 + {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Checked|x86.Build.0 = Checked|x86 {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Debug|Any CPU.ActiveCfg = Debug|x64 {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Debug|Any CPU.Build.0 = Debug|x64 {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Debug|x64.ActiveCfg = Debug|x64 @@ -64,12 +74,12 @@ Global {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Release|x64.Build.0 = Release|x64 {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Release|x86.ActiveCfg = Release|x86 {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Release|x86.Build.0 = Release|x86 - {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Checked|Any CPU.ActiveCfg = Checked|x64 - {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Checked|Any CPU.Build.0 = Checked|x64 - {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Checked|x64.ActiveCfg = Checked|x64 - {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Checked|x64.Build.0 = Checked|x64 - {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Checked|x86.ActiveCfg = Checked|x86 - {94B59BA0-491F-4B59-ADFF-A057EC3EC835}.Checked|x86.Build.0 = Checked|x86 + {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Checked|Any CPU.Build.0 = Debug|Any CPU + {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Checked|x64.ActiveCfg = Debug|Any CPU + {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Checked|x64.Build.0 = Debug|Any CPU + {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Checked|x86.ActiveCfg = Debug|Any CPU + {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Checked|x86.Build.0 = Debug|Any CPU {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Debug|Any CPU.Build.0 = Debug|Any CPU {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -82,12 +92,12 @@ Global {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Release|x64.Build.0 = Release|Any CPU {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Release|x86.ActiveCfg = Release|Any CPU {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Release|x86.Build.0 = Release|Any CPU - {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Checked|Any CPU.Build.0 = Debug|Any CPU - {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Checked|x64.ActiveCfg = Debug|Any CPU - {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Checked|x64.Build.0 = Debug|Any CPU - {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Checked|x86.ActiveCfg = Debug|Any CPU - {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA}.Checked|x86.Build.0 = Debug|Any CPU + {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Checked|Any CPU.Build.0 = Debug|Any CPU + {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Checked|x64.ActiveCfg = Debug|Any CPU + {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Checked|x64.Build.0 = Debug|Any CPU + {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Checked|x86.ActiveCfg = Debug|Any CPU + {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Checked|x86.Build.0 = Debug|Any CPU {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Debug|Any CPU.Build.0 = Debug|Any CPU {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -100,12 +110,12 @@ Global {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Release|x64.Build.0 = Release|Any CPU {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Release|x86.ActiveCfg = Release|Any CPU {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Release|x86.Build.0 = Release|Any CPU - {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Checked|Any CPU.Build.0 = Debug|Any CPU - {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Checked|x64.ActiveCfg = Debug|Any CPU - {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Checked|x64.Build.0 = Debug|Any CPU - {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Checked|x86.ActiveCfg = Debug|Any CPU - {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A}.Checked|x86.Build.0 = Debug|Any CPU + {1B248B4C-7584-4C04-850A-A50EB592052C}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {1B248B4C-7584-4C04-850A-A50EB592052C}.Checked|Any CPU.Build.0 = Debug|Any CPU + {1B248B4C-7584-4C04-850A-A50EB592052C}.Checked|x64.ActiveCfg = Debug|Any CPU + {1B248B4C-7584-4C04-850A-A50EB592052C}.Checked|x64.Build.0 = Debug|Any CPU + {1B248B4C-7584-4C04-850A-A50EB592052C}.Checked|x86.ActiveCfg = Debug|Any CPU + {1B248B4C-7584-4C04-850A-A50EB592052C}.Checked|x86.Build.0 = Debug|Any CPU {1B248B4C-7584-4C04-850A-A50EB592052C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1B248B4C-7584-4C04-850A-A50EB592052C}.Debug|Any CPU.Build.0 = Debug|Any CPU {1B248B4C-7584-4C04-850A-A50EB592052C}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -118,12 +128,12 @@ Global {1B248B4C-7584-4C04-850A-A50EB592052C}.Release|x64.Build.0 = Release|Any CPU {1B248B4C-7584-4C04-850A-A50EB592052C}.Release|x86.ActiveCfg = Release|Any CPU {1B248B4C-7584-4C04-850A-A50EB592052C}.Release|x86.Build.0 = Release|Any CPU - {1B248B4C-7584-4C04-850A-A50EB592052C}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {1B248B4C-7584-4C04-850A-A50EB592052C}.Checked|Any CPU.Build.0 = Debug|Any CPU - {1B248B4C-7584-4C04-850A-A50EB592052C}.Checked|x64.ActiveCfg = Debug|Any CPU - {1B248B4C-7584-4C04-850A-A50EB592052C}.Checked|x64.Build.0 = Debug|Any CPU - {1B248B4C-7584-4C04-850A-A50EB592052C}.Checked|x86.ActiveCfg = Debug|Any CPU - {1B248B4C-7584-4C04-850A-A50EB592052C}.Checked|x86.Build.0 = Debug|Any CPU + {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Checked|Any CPU.Build.0 = Debug|Any CPU + {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Checked|x64.ActiveCfg = Debug|Any CPU + {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Checked|x64.Build.0 = Debug|Any CPU + {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Checked|x86.ActiveCfg = Debug|Any CPU + {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Checked|x86.Build.0 = Debug|Any CPU {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Debug|Any CPU.Build.0 = Debug|Any CPU {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -136,12 +146,12 @@ Global {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Release|x64.Build.0 = Release|Any CPU {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Release|x86.ActiveCfg = Release|Any CPU {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Release|x86.Build.0 = Release|Any CPU - {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Checked|Any CPU.Build.0 = Debug|Any CPU - {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Checked|x64.ActiveCfg = Debug|Any CPU - {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Checked|x64.Build.0 = Debug|Any CPU - {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Checked|x86.ActiveCfg = Debug|Any CPU - {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40}.Checked|x86.Build.0 = Debug|Any CPU + {768B77B0-EA45-469D-B39E-545EB72F5A43}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {768B77B0-EA45-469D-B39E-545EB72F5A43}.Checked|Any CPU.Build.0 = Debug|Any CPU + {768B77B0-EA45-469D-B39E-545EB72F5A43}.Checked|x64.ActiveCfg = Debug|Any CPU + {768B77B0-EA45-469D-B39E-545EB72F5A43}.Checked|x64.Build.0 = Debug|Any CPU + {768B77B0-EA45-469D-B39E-545EB72F5A43}.Checked|x86.ActiveCfg = Debug|Any CPU + {768B77B0-EA45-469D-B39E-545EB72F5A43}.Checked|x86.Build.0 = Debug|Any CPU {768B77B0-EA45-469D-B39E-545EB72F5A43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {768B77B0-EA45-469D-B39E-545EB72F5A43}.Debug|Any CPU.Build.0 = Debug|Any CPU {768B77B0-EA45-469D-B39E-545EB72F5A43}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -154,12 +164,12 @@ Global {768B77B0-EA45-469D-B39E-545EB72F5A43}.Release|x64.Build.0 = Release|Any CPU {768B77B0-EA45-469D-B39E-545EB72F5A43}.Release|x86.ActiveCfg = Release|Any CPU {768B77B0-EA45-469D-B39E-545EB72F5A43}.Release|x86.Build.0 = Release|Any CPU - {768B77B0-EA45-469D-B39E-545EB72F5A43}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {768B77B0-EA45-469D-B39E-545EB72F5A43}.Checked|Any CPU.Build.0 = Debug|Any CPU - {768B77B0-EA45-469D-B39E-545EB72F5A43}.Checked|x64.ActiveCfg = Debug|Any CPU - {768B77B0-EA45-469D-B39E-545EB72F5A43}.Checked|x64.Build.0 = Debug|Any CPU - {768B77B0-EA45-469D-B39E-545EB72F5A43}.Checked|x86.ActiveCfg = Debug|Any CPU - {768B77B0-EA45-469D-B39E-545EB72F5A43}.Checked|x86.Build.0 = Debug|Any CPU + {8671F164-F78C-44FA-93B7-A310F67890FE}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {8671F164-F78C-44FA-93B7-A310F67890FE}.Checked|Any CPU.Build.0 = Debug|Any CPU + {8671F164-F78C-44FA-93B7-A310F67890FE}.Checked|x64.ActiveCfg = Debug|Any CPU + {8671F164-F78C-44FA-93B7-A310F67890FE}.Checked|x64.Build.0 = Debug|Any CPU + {8671F164-F78C-44FA-93B7-A310F67890FE}.Checked|x86.ActiveCfg = Debug|Any CPU + {8671F164-F78C-44FA-93B7-A310F67890FE}.Checked|x86.Build.0 = Debug|Any CPU {8671F164-F78C-44FA-93B7-A310F67890FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8671F164-F78C-44FA-93B7-A310F67890FE}.Debug|Any CPU.Build.0 = Debug|Any CPU {8671F164-F78C-44FA-93B7-A310F67890FE}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -172,12 +182,12 @@ Global {8671F164-F78C-44FA-93B7-A310F67890FE}.Release|x64.Build.0 = Release|Any CPU {8671F164-F78C-44FA-93B7-A310F67890FE}.Release|x86.ActiveCfg = Release|Any CPU {8671F164-F78C-44FA-93B7-A310F67890FE}.Release|x86.Build.0 = Release|Any CPU - {8671F164-F78C-44FA-93B7-A310F67890FE}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {8671F164-F78C-44FA-93B7-A310F67890FE}.Checked|Any CPU.Build.0 = Debug|Any CPU - {8671F164-F78C-44FA-93B7-A310F67890FE}.Checked|x64.ActiveCfg = Debug|Any CPU - {8671F164-F78C-44FA-93B7-A310F67890FE}.Checked|x64.Build.0 = Debug|Any CPU - {8671F164-F78C-44FA-93B7-A310F67890FE}.Checked|x86.ActiveCfg = Debug|Any CPU - {8671F164-F78C-44FA-93B7-A310F67890FE}.Checked|x86.Build.0 = Debug|Any CPU + {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Checked|Any CPU.Build.0 = Debug|Any CPU + {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Checked|x64.ActiveCfg = Debug|Any CPU + {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Checked|x64.Build.0 = Debug|Any CPU + {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Checked|x86.ActiveCfg = Debug|Any CPU + {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Checked|x86.Build.0 = Debug|Any CPU {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -190,12 +200,12 @@ Global {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Release|x64.Build.0 = Release|Any CPU {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Release|x86.ActiveCfg = Release|Any CPU {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Release|x86.Build.0 = Release|Any CPU - {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Checked|Any CPU.Build.0 = Debug|Any CPU - {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Checked|x64.ActiveCfg = Debug|Any CPU - {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Checked|x64.Build.0 = Debug|Any CPU - {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Checked|x86.ActiveCfg = Debug|Any CPU - {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1}.Checked|x86.Build.0 = Debug|Any CPU + {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Checked|Any CPU.Build.0 = Debug|Any CPU + {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Checked|x64.ActiveCfg = Debug|Any CPU + {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Checked|x64.Build.0 = Debug|Any CPU + {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Checked|x86.ActiveCfg = Debug|Any CPU + {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Checked|x86.Build.0 = Debug|Any CPU {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Debug|Any CPU.Build.0 = Debug|Any CPU {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -208,12 +218,12 @@ Global {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Release|x64.Build.0 = Release|Any CPU {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Release|x86.ActiveCfg = Release|Any CPU {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Release|x86.Build.0 = Release|Any CPU - {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Checked|Any CPU.Build.0 = Debug|Any CPU - {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Checked|x64.ActiveCfg = Debug|Any CPU - {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Checked|x64.Build.0 = Debug|Any CPU - {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Checked|x86.ActiveCfg = Debug|Any CPU - {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E}.Checked|x86.Build.0 = Debug|Any CPU + {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Checked|Any CPU.Build.0 = Debug|Any CPU + {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Checked|x64.ActiveCfg = Debug|Any CPU + {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Checked|x64.Build.0 = Debug|Any CPU + {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Checked|x86.ActiveCfg = Debug|Any CPU + {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Checked|x86.Build.0 = Debug|Any CPU {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Debug|Any CPU.Build.0 = Debug|Any CPU {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -226,12 +236,12 @@ Global {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Release|x64.Build.0 = Release|Any CPU {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Release|x86.ActiveCfg = Release|Any CPU {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Release|x86.Build.0 = Release|Any CPU - {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Checked|Any CPU.Build.0 = Debug|Any CPU - {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Checked|x64.ActiveCfg = Debug|Any CPU - {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Checked|x64.Build.0 = Debug|Any CPU - {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Checked|x86.ActiveCfg = Debug|Any CPU - {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1}.Checked|x86.Build.0 = Debug|Any CPU + {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Checked|Any CPU.Build.0 = Debug|Any CPU + {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Checked|x64.ActiveCfg = Debug|Any CPU + {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Checked|x64.Build.0 = Debug|Any CPU + {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Checked|x86.ActiveCfg = Debug|Any CPU + {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Checked|x86.Build.0 = Debug|Any CPU {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Debug|Any CPU.Build.0 = Debug|Any CPU {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -244,12 +254,12 @@ Global {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Release|x64.Build.0 = Release|Any CPU {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Release|x86.ActiveCfg = Release|Any CPU {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Release|x86.Build.0 = Release|Any CPU - {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Checked|Any CPU.Build.0 = Debug|Any CPU - {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Checked|x64.ActiveCfg = Debug|Any CPU - {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Checked|x64.Build.0 = Debug|Any CPU - {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Checked|x86.ActiveCfg = Debug|Any CPU - {EA8DBC12-60BC-433E-ABFF-A89DFA795283}.Checked|x86.Build.0 = Debug|Any CPU + {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Checked|Any CPU.Build.0 = Debug|Any CPU + {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Checked|x64.ActiveCfg = Debug|Any CPU + {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Checked|x64.Build.0 = Debug|Any CPU + {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Checked|x86.ActiveCfg = Debug|Any CPU + {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Checked|x86.Build.0 = Debug|Any CPU {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -262,12 +272,12 @@ Global {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Release|x64.Build.0 = Release|Any CPU {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Release|x86.ActiveCfg = Release|Any CPU {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Release|x86.Build.0 = Release|Any CPU - {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Checked|Any CPU.Build.0 = Debug|Any CPU - {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Checked|x64.ActiveCfg = Debug|Any CPU - {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Checked|x64.Build.0 = Debug|Any CPU - {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Checked|x86.ActiveCfg = Debug|Any CPU - {25D66424-2EAF-464D-8460-10C04EDEF3C3}.Checked|x86.Build.0 = Debug|Any CPU + {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Checked|Any CPU.Build.0 = Debug|Any CPU + {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Checked|x64.ActiveCfg = Debug|Any CPU + {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Checked|x64.Build.0 = Debug|Any CPU + {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Checked|x86.ActiveCfg = Debug|Any CPU + {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Checked|x86.Build.0 = Debug|Any CPU {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Debug|Any CPU.Build.0 = Debug|Any CPU {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -280,12 +290,12 @@ Global {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Release|x64.Build.0 = Release|Any CPU {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Release|x86.ActiveCfg = Release|Any CPU {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Release|x86.Build.0 = Release|Any CPU - {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Checked|Any CPU.Build.0 = Debug|Any CPU - {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Checked|x64.ActiveCfg = Debug|Any CPU - {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Checked|x64.Build.0 = Debug|Any CPU - {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Checked|x86.ActiveCfg = Debug|Any CPU - {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF}.Checked|x86.Build.0 = Debug|Any CPU + {866D295E-424A-4747-9417-CD7746936138}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {866D295E-424A-4747-9417-CD7746936138}.Checked|Any CPU.Build.0 = Debug|Any CPU + {866D295E-424A-4747-9417-CD7746936138}.Checked|x64.ActiveCfg = Debug|Any CPU + {866D295E-424A-4747-9417-CD7746936138}.Checked|x64.Build.0 = Debug|Any CPU + {866D295E-424A-4747-9417-CD7746936138}.Checked|x86.ActiveCfg = Debug|Any CPU + {866D295E-424A-4747-9417-CD7746936138}.Checked|x86.Build.0 = Debug|Any CPU {866D295E-424A-4747-9417-CD7746936138}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {866D295E-424A-4747-9417-CD7746936138}.Debug|Any CPU.Build.0 = Debug|Any CPU {866D295E-424A-4747-9417-CD7746936138}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -298,12 +308,12 @@ Global {866D295E-424A-4747-9417-CD7746936138}.Release|x64.Build.0 = Release|Any CPU {866D295E-424A-4747-9417-CD7746936138}.Release|x86.ActiveCfg = Release|Any CPU {866D295E-424A-4747-9417-CD7746936138}.Release|x86.Build.0 = Release|Any CPU - {866D295E-424A-4747-9417-CD7746936138}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {866D295E-424A-4747-9417-CD7746936138}.Checked|Any CPU.Build.0 = Debug|Any CPU - {866D295E-424A-4747-9417-CD7746936138}.Checked|x64.ActiveCfg = Debug|Any CPU - {866D295E-424A-4747-9417-CD7746936138}.Checked|x64.Build.0 = Debug|Any CPU - {866D295E-424A-4747-9417-CD7746936138}.Checked|x86.ActiveCfg = Debug|Any CPU - {866D295E-424A-4747-9417-CD7746936138}.Checked|x86.Build.0 = Debug|Any CPU + {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Checked|Any CPU.Build.0 = Debug|Any CPU + {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Checked|x64.ActiveCfg = Debug|Any CPU + {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Checked|x64.Build.0 = Debug|Any CPU + {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Checked|x86.ActiveCfg = Debug|Any CPU + {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Checked|x86.Build.0 = Debug|Any CPU {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Debug|Any CPU.Build.0 = Debug|Any CPU {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -316,12 +326,12 @@ Global {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Release|x64.Build.0 = Release|Any CPU {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Release|x86.ActiveCfg = Release|Any CPU {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Release|x86.Build.0 = Release|Any CPU - {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Checked|Any CPU.Build.0 = Debug|Any CPU - {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Checked|x64.ActiveCfg = Debug|Any CPU - {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Checked|x64.Build.0 = Debug|Any CPU - {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Checked|x86.ActiveCfg = Debug|Any CPU - {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5}.Checked|x86.Build.0 = Debug|Any CPU + {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Checked|Any CPU.Build.0 = Debug|Any CPU + {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Checked|x64.ActiveCfg = Debug|Any CPU + {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Checked|x64.Build.0 = Debug|Any CPU + {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Checked|x86.ActiveCfg = Debug|Any CPU + {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Checked|x86.Build.0 = Debug|Any CPU {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Debug|Any CPU.Build.0 = Debug|Any CPU {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -334,20 +344,19 @@ Global {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Release|x64.Build.0 = Release|Any CPU {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Release|x86.ActiveCfg = Release|Any CPU {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Release|x86.Build.0 = Release|Any CPU - {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Checked|Any CPU.Build.0 = Debug|Any CPU - {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Checked|x64.ActiveCfg = Debug|Any CPU - {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Checked|x64.Build.0 = Debug|Any CPU - {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Checked|x86.ActiveCfg = Debug|Any CPU - {0B5FD0C2-367D-4AD6-8001-80AD79B2441C}.Checked|x86.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {94B59BA0-491F-4B59-ADFF-A057EC3EC835} = {B1678CCD-95C8-4419-B9F9-14A03061BE4B} - {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1} = {B1678CCD-95C8-4419-B9F9-14A03061BE4B} {1FF4CC8E-49C3-42A0-A6E0-2E5908455FBA} = {FB99AC59-1744-4F12-A4B0-0D54FCA048BF} + {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A} = {D893B9AA-57C5-49E3-97B1-12CC62D84307} + {1B248B4C-7584-4C04-850A-A50EB592052C} = {E1AEBD5D-AE4E-4F61-B9ED-AEF950B0CC33} + {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40} = {E1AEBD5D-AE4E-4F61-B9ED-AEF950B0CC33} + {768B77B0-EA45-469D-B39E-545EB72F5A43} = {E1AEBD5D-AE4E-4F61-B9ED-AEF950B0CC33} + {8671F164-F78C-44FA-93B7-A310F67890FE} = {D893B9AA-57C5-49E3-97B1-12CC62D84307} + {4FC33B9B-1BCF-4D16-B886-DCA8F2B823C1} = {B1678CCD-95C8-4419-B9F9-14A03061BE4B} {79F7BE0E-01AA-4AFB-B047-CF7C0B38F81E} = {FB99AC59-1744-4F12-A4B0-0D54FCA048BF} {9C2C2B5C-5E75-4935-8A4A-DE3D3A5DBBC1} = {FB99AC59-1744-4F12-A4B0-0D54FCA048BF} {EA8DBC12-60BC-433E-ABFF-A89DFA795283} = {FB99AC59-1744-4F12-A4B0-0D54FCA048BF} @@ -355,14 +364,12 @@ Global {049B7FD4-ACEF-4BCD-A7A7-75C9BBEC4EBF} = {FB99AC59-1744-4F12-A4B0-0D54FCA048BF} {866D295E-424A-4747-9417-CD7746936138} = {FB99AC59-1744-4F12-A4B0-0D54FCA048BF} {D3A329E3-0FEB-4136-9CB6-B38319B0FFA5} = {FB99AC59-1744-4F12-A4B0-0D54FCA048BF} - {4859BEE3-34B7-48E7-83D4-1ADD8B8F3B3A} = {D893B9AA-57C5-49E3-97B1-12CC62D84307} - {8671F164-F78C-44FA-93B7-A310F67890FE} = {D893B9AA-57C5-49E3-97B1-12CC62D84307} {0B5FD0C2-367D-4AD6-8001-80AD79B2441C} = {D893B9AA-57C5-49E3-97B1-12CC62D84307} - {1B248B4C-7584-4C04-850A-A50EB592052C} = {E1AEBD5D-AE4E-4F61-B9ED-AEF950B0CC33} - {90CDAD9F-3ACC-43B0-9696-0C849FCD8C40} = {E1AEBD5D-AE4E-4F61-B9ED-AEF950B0CC33} - {768B77B0-EA45-469D-B39E-545EB72F5A43} = {E1AEBD5D-AE4E-4F61-B9ED-AEF950B0CC33} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D4031401-FEB5-4CCF-91C1-38F5646B2BFD} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\System.Private.CoreLib\src\System.Private.CoreLib.Shared.projitems*{94b59ba0-491f-4b59-adff-a057ec3ec835}*SharedItemsImports = 5 + EndGlobalSection EndGlobal diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/SyntaxExtensions.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/SyntaxExtensions.cs index 20400c4721369e..3fea4b0fae2e21 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/SyntaxExtensions.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/SyntaxExtensions.cs @@ -33,7 +33,17 @@ public static Location FindTypeExpressionOrNullLocation(this AttributeArgumentSy switch (attributeTarget.Identifier.Kind()) { case SyntaxKind.ReturnKeyword: - return ((IMethodSymbol)targetSymbol).GetReturnTypeAttributes().First(attributeSyntaxLocationMatches); + if (targetSymbol is IMethodSymbol method) + { + // Sometimes an attribute is put on a symbol that is nested within the containing symbol. + // For example, the ContainingSymbol for an AttributeSyntax on a local function have a ContainingSymbol of the method. + // Since this method is internal and the callers don't care about attributes on local functions, + // we just allow this method to return null in those cases. + return method.GetReturnTypeAttributes().FirstOrDefault(attributeSyntaxLocationMatches); + } + // An attribute on the return value of a delegate type's Invoke method has a ContainingSymbol of the delegate type. + // We don't care about the attributes in this case for the callers, so we'll just return null. + return null; case SyntaxKind.AssemblyKeyword: return targetSymbol.ContainingAssembly.GetAttributes().First(attributeSyntaxLocationMatches); case SyntaxKind.ModuleKeyword: @@ -43,7 +53,8 @@ public static Location FindTypeExpressionOrNullLocation(this AttributeArgumentSy } } // Sometimes an attribute is put on a symbol that is nested within the containing symbol. - // For example, the ContainingSymbol for an AttributeSyntax on a parameter have a ContainingSymbol of the method. + // For example, the ContainingSymbol for an AttributeSyntax on a parameter have a ContainingSymbol of the method + // and an AttributeSyntax on a local function have a ContainingSymbol of the containing method. // Since this method is internal and the callers don't care about attributes on parameters, we just allow // this method to return null in those cases. return targetSymbol.GetAttributes().FirstOrDefault(attributeSyntaxLocationMatches); diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs index 1da4dbd49afeca..c2b8cb92f8ca25 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs @@ -12,7 +12,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; [assembly: System.Resources.NeutralResourcesLanguage("en-US")] @@ -64,7 +63,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Collect all methods adorned with LibraryImportAttribute var attributedMethods = context.SyntaxProvider .ForAttributeWithMetadataName( - context, TypeNames.LibraryImportAttribute, static (node, ct) => node is MethodDeclarationSyntax, static (context, ct) => context.TargetSymbol is IMethodSymbol methodSymbol diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.csproj b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.csproj index ab8172b6979078..a7f9cc8ae96afb 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.csproj +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.csproj @@ -22,7 +22,7 @@ - + @@ -37,17 +37,9 @@ - - - - - - - - - + - + diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IncrementalGeneratorInitializationContextExtensions.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IncrementalGeneratorInitializationContextExtensions.cs index 0b008795884810..20a1818a33e54a 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IncrementalGeneratorInitializationContextExtensions.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IncrementalGeneratorInitializationContextExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; @@ -27,12 +28,21 @@ public static void RegisterDiagnostics(this IncrementalGeneratorInitializationCo public static void RegisterConcatenatedSyntaxOutputs(this IncrementalGeneratorInitializationContext context, IncrementalValuesProvider nodes, string fileName) where TNode : SyntaxNode { - IncrementalValueProvider generatedMethods = nodes + IncrementalValueProvider> generatedMethods = nodes .Select( static (node, ct) => node.NormalizeWhitespace().ToFullString()) - .Collect() - .Select(static (generatedSources, ct) => + .Collect(); + + context.RegisterSourceOutput(generatedMethods, + (context, generatedSources) => { + // Don't generate a file if we don't have to, to avoid the extra IDE overhead once we have generated + // files in play. + if (generatedSources.IsEmpty) + { + return; + } + StringBuilder source = new(); // Mark in source that the file is auto-generated. source.AppendLine("// "); @@ -40,13 +50,9 @@ public static void RegisterConcatenatedSyntaxOutputs(this IncrementalGene { source.AppendLine(generated); } - return source.ToString(); - }); - context.RegisterSourceOutput(generatedMethods, - (context, source) => - { - context.AddSource(fileName, source); + // Once https://github.com/dotnet/roslyn/issues/61326 is resolved, we can avoid the ToString() here. + context.AddSource(fileName, source.ToString()); }); } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ElementsMarshalling.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ElementsMarshalling.cs index f51e0d522a321c..aebe94581377b5 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ElementsMarshalling.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ElementsMarshalling.cs @@ -160,6 +160,25 @@ public NonBlittableElementsMarshalling( protected abstract InvocationExpressionSyntax GetUnmanagedValuesSource(TypePositionInfo info, StubCodeContext context); protected abstract InvocationExpressionSyntax GetManagedValuesDestination(TypePositionInfo info, StubCodeContext context); + protected IEnumerable DeclareLastIndexMarshalledIfNeeded( + TypePositionInfo info, + StubCodeContext context) + { + // We need to declare the last index marshalled variable if the collection is not multi-dimensional. + // If it is multidimensional, we will just clear each allocated span. + if (UsesLastIndexMarshalled(info, context)) + { + yield return LocalDeclarationStatement( + VariableDeclaration( + PredefinedType(Token(SyntaxKind.IntKeyword)), + SingletonSeparatedList( + VariableDeclarator( + Identifier(MarshallerHelpers.GetLastIndexMarshalledIdentifier(info, context)), + null, + EqualsValueClause(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0))))))); + } + } + protected StatementSyntax GenerateByValueOutMarshalStatement(TypePositionInfo info, StubCodeContext context) { // If the parameter is marshalled by-value [Out], then we don't marshal the contents of the collection. @@ -180,8 +199,10 @@ protected StatementSyntax GenerateMarshalStatement(TypePositionInfo info, StubCo // ReadOnlySpan = // Span = + // .Clear() // << marshal contents >> - return Block( + var statements = new List() + { LocalDeclarationStatement(VariableDeclaration( GenericName( Identifier(TypeNames.System_ReadOnlySpan), @@ -198,14 +219,27 @@ protected StatementSyntax GenerateMarshalStatement(TypePositionInfo info, StubCo VariableDeclarator( Identifier(nativeSpanIdentifier)) .WithInitializer(EqualsValueClause( - GetUnmanagedValuesDestination(info, context)))))), - GenerateContentsMarshallingStatement( + GetUnmanagedValuesDestination(info, context)))))) + }; + // If it is a multidimensional array, we will just clear each allocated span. + if (!UsesLastIndexMarshalled(info, context)) + { + // .Clear() + statements.Add(ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(nativeSpanIdentifier), + IdentifierName("Clear"))))); + } + statements.Add(GenerateContentsMarshallingStatement( info, context, MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(MarshallerHelpers.GetManagedSpanIdentifier(info, context)), IdentifierName("Length")), StubCodeContext.Stage.Marshal)); + return Block(statements); } public StatementSyntax GenerateUnmarshalStatement(TypePositionInfo info, StubCodeContext context) @@ -315,21 +349,78 @@ protected StatementSyntax GenerateByValueOutUnmarshalStatement(TypePositionInfo StubCodeContext.Stage.Unmarshal)); } - protected StatementSyntax GenerateContentsMarshallingStatement( + private Dictionary<(TypePositionInfo, StubCodeContext), bool> _usesLastIndexMarshalledCache = new(); + + protected bool UsesLastIndexMarshalled(TypePositionInfo info, StubCodeContext context) + { + var cleanupContext = context with { CurrentStage = StubCodeContext.Stage.Cleanup }; + if (_usesLastIndexMarshalledCache.TryGetValue((info, cleanupContext), out bool result)) + return result; + bool usesLastIndexMarshalled = !(ShouldCleanupAllElements(info, context) || !GenerateElementStages(info, cleanupContext, out _, out _, StubCodeContext.Stage.Cleanup).Any()); + _usesLastIndexMarshalledCache.Add((info, cleanupContext), usesLastIndexMarshalled); + return usesLastIndexMarshalled; + } + + protected static bool ShouldCleanupAllElements(TypePositionInfo info, StubCodeContext context) + { + // AdditionalTemporaryStateLivesAcrossStages implies that it is an outer collection + // Out parameters means that the contents are created by the P/Invoke and assumed to have successfully created all elements + return !context.AdditionalTemporaryStateLivesAcrossStages || info.ByValueContentsMarshalKind == ByValueContentsMarshalKind.Out || info.RefKind == RefKind.Out; + } + + protected StatementSyntax GenerateElementCleanupStatement(TypePositionInfo info, StubCodeContext context) + { + string nativeSpanIdentifier = MarshallerHelpers.GetNativeSpanIdentifier(info, context); + ExpressionSyntax indexConstraintName; + if (ShouldCleanupAllElements(info, context)) + { + indexConstraintName = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(nativeSpanIdentifier), + IdentifierName("Length")); + } + else + { + indexConstraintName = IdentifierName(MarshallerHelpers.GetLastIndexMarshalledIdentifier(info, context)); + } + + StatementSyntax contentsCleanupStatements = GenerateContentsMarshallingStatement(info, context, + indexConstraintName, + StubCodeContext.Stage.Cleanup); + + if (contentsCleanupStatements.IsKind(SyntaxKind.EmptyStatement)) + { + return EmptyStatement(); + } + + return Block( + LocalDeclarationStatement(VariableDeclaration( + GenericName( + Identifier(TypeNames.System_Span), + TypeArgumentList(SingletonSeparatedList(_unmanagedElementType))), + SingletonSeparatedList( + VariableDeclarator( + Identifier(nativeSpanIdentifier)) + .WithInitializer(EqualsValueClause( + GetUnmanagedValuesDestination(info, context)))))), + contentsCleanupStatements); + } + + private List GenerateElementStages( TypePositionInfo info, StubCodeContext context, - ExpressionSyntax lengthExpression, - params StubCodeContext.Stage[] stagesToGeneratePerElement) + out LinearCollectionElementMarshallingCodeContext elementSetupSubContext, + out TypePositionInfo localElementInfo, + params StubCodeContext.Stage[] stages) { string managedSpanIdentifier = MarshallerHelpers.GetManagedSpanIdentifier(info, context); string nativeSpanIdentifier = MarshallerHelpers.GetNativeSpanIdentifier(info, context); - var elementSetupSubContext = new LinearCollectionElementMarshallingCodeContext( + elementSetupSubContext = new LinearCollectionElementMarshallingCodeContext( StubCodeContext.Stage.Setup, managedSpanIdentifier, nativeSpanIdentifier, context); - TypePositionInfo localElementInfo = _elementInfo with + localElementInfo = _elementInfo with { InstanceIdentifier = info.InstanceIdentifier, RefKind = info.IsByRef ? info.RefKind : info.ByValueContentsMarshalKind.GetRefKindForByValueContentsKind(), @@ -338,11 +429,22 @@ protected StatementSyntax GenerateContentsMarshallingStatement( }; List elementStatements = new(); - foreach (StubCodeContext.Stage stage in stagesToGeneratePerElement) + foreach (StubCodeContext.Stage stage in stages) { var elementSubContext = elementSetupSubContext with { CurrentStage = stage }; - elementStatements.AddRange(_elementMarshaller.Generate(localElementInfo, elementSubContext)); + elementStatements.AddRange( _elementMarshaller.Generate(localElementInfo, elementSubContext)); } + return elementStatements; + + } + + protected StatementSyntax GenerateContentsMarshallingStatement( + TypePositionInfo info, + StubCodeContext context, + ExpressionSyntax lengthExpression, + params StubCodeContext.Stage[] stagesToGeneratePerElement) + { + var elementStatements = GenerateElementStages(info, context, out var elementSetupSubContext, out var localElementInfo, stagesToGeneratePerElement); if (elementStatements.Any()) { @@ -357,8 +459,16 @@ protected StatementSyntax GenerateContentsMarshallingStatement( } // Iterate through the elements of the native collection to marshal them - return MarshallerHelpers.GetForLoop(lengthExpression, elementSetupSubContext.IndexerIdentifier) - .WithStatement(marshallingStatement); + ForStatementSyntax forLoop = MarshallerHelpers.GetForLoop(lengthExpression, elementSetupSubContext.IndexerIdentifier) + .WithStatement(Block(marshallingStatement)); + // If we're tracking LastIndexMarshalled, increment that too + if (context.CurrentStage == StubCodeContext.Stage.Marshal && UsesLastIndexMarshalled(info, context)) + { + forLoop = forLoop.AddIncrementors( + PrefixUnaryExpression(SyntaxKind.PreIncrementExpression, + IdentifierName(MarshallerHelpers.GetLastIndexMarshalledIdentifier(info, context)))); + } + return forLoop; } return EmptyStatement(); diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs index c245f8db61bcb1..1ad3747d654b4b 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs @@ -147,6 +147,11 @@ public static string GetNumElementsIdentifier(TypePositionInfo info, StubCodeCon return context.GetAdditionalIdentifier(info, "numElements"); } + public static string GetLastIndexMarshalledIdentifier(TypePositionInfo info, StubCodeContext context) + { + return context.GetAdditionalIdentifier(info, "lastIndexMarshalled"); + } + internal static bool CanUseCallerAllocatedBuffer(TypePositionInfo info, StubCodeContext context) { return context.SingleFrameSpansNativeContext && (!info.IsByRef || info.RefKind == RefKind.In); @@ -292,5 +297,37 @@ public static IEnumerable GetDependentElementsOfMarshallingInf } } } + + public static StatementSyntax SkipInitOrDefaultInit(TypePositionInfo info, StubCodeContext context) + { + (TargetFramework fmk, _) = context.GetTargetFramework(); + if (info.ManagedType is not PointerTypeInfo + && info.ManagedType is not ValueTypeInfo { IsByRefLike: true } + && fmk is TargetFramework.Net) + { + // Use the Unsafe.SkipInit API when available and + // managed type is usable as a generic parameter. + return ExpressionStatement( + InvocationExpression( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + ParseName(TypeNames.System_Runtime_CompilerServices_Unsafe), + IdentifierName("SkipInit"))) + .WithArgumentList( + ArgumentList(SingletonSeparatedList( + Argument(IdentifierName(info.InstanceIdentifier)) + .WithRefOrOutKeyword(Token(SyntaxKind.OutKeyword)))))); + } + else + { + // Assign out params to default + return ExpressionStatement( + AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + IdentifierName(info.InstanceIdentifier), + LiteralExpression( + SyntaxKind.DefaultLiteralExpression, + Token(SyntaxKind.DefaultKeyword)))); + } + } } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StatefulMarshallingStrategy.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StatefulMarshallingStrategy.cs index ad487bf6d4015e..c3c9026783839c 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StatefulMarshallingStrategy.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StatefulMarshallingStrategy.cs @@ -311,7 +311,7 @@ internal sealed class StatefulLinearCollectionBlittableElementsMarshalling : Bli public StatefulLinearCollectionBlittableElementsMarshalling( ICustomTypeMarshallingStrategy innerMarshaller, MarshallerShape shape, TypeSyntax managedElementType, TypeSyntax unmanagedElementType, ExpressionSyntax numElementsExpression) - : base (managedElementType, unmanagedElementType) + : base(managedElementType, unmanagedElementType) { _innerMarshaller = innerMarshaller; _shape = shape; @@ -451,7 +451,7 @@ public StatefulLinearCollectionNonBlittableElementsMarshalling( IMarshallingGenerator elementMarshaller, TypePositionInfo elementInfo, ExpressionSyntax numElementsExpression) - : base (unmanagedElementType, elementMarshaller, elementInfo) + : base(unmanagedElementType, elementMarshaller, elementInfo) { _innerMarshaller = innerMarshaller; _shape = shape; @@ -459,7 +459,28 @@ public StatefulLinearCollectionNonBlittableElementsMarshalling( } public TypeSyntax AsNativeType(TypePositionInfo info) => _innerMarshaller.AsNativeType(info); - public IEnumerable GenerateCleanupStatements(TypePositionInfo info, StubCodeContext context) => _innerMarshaller.GenerateCleanupStatements(info, context); + public IEnumerable GenerateCleanupStatements(TypePositionInfo info, StubCodeContext context) + { + StatementSyntax elementCleanup = GenerateElementCleanupStatement(info, context); + + if (!elementCleanup.IsKind(SyntaxKind.EmptyStatement)) + { + yield return elementCleanup; + } + + if (!_shape.HasFlag(MarshallerShape.Free)) + yield break; + + string marshaller = StatefulValueMarshalling.GetMarshallerIdentifier(info, context); + // .Free(); + yield return ExpressionStatement( + InvocationExpression( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(marshaller), + IdentifierName(ShapeMemberNames.Free)), + ArgumentList())); + } + public IEnumerable GenerateGuaranteedUnmarshalStatements(TypePositionInfo info, StubCodeContext context) => _innerMarshaller.GenerateGuaranteedUnmarshalStatements(info, context); public IEnumerable GenerateMarshalStatements(TypePositionInfo info, StubCodeContext context) @@ -480,6 +501,7 @@ public IEnumerable GenerateMarshalStatements(TypePositionInfo i // ReadOnlySpan = .GetManagedValuesSource() // Span = .GetUnmanagedValuesDestination() + // .Clear() // << marshal contents >> yield return GenerateMarshalStatement(info, context); } @@ -487,8 +509,8 @@ public IEnumerable GenerateMarshalStatements(TypePositionInfo i public IEnumerable GenerateNotifyForSuccessfulInvokeStatements(TypePositionInfo info, StubCodeContext context) => _innerMarshaller.GenerateNotifyForSuccessfulInvokeStatements(info, context); public IEnumerable GeneratePinnedMarshalStatements(TypePositionInfo info, StubCodeContext context) => _innerMarshaller.GeneratePinnedMarshalStatements(info, context); public IEnumerable GeneratePinStatements(TypePositionInfo info, StubCodeContext context) => _innerMarshaller.GeneratePinStatements(info, context); - public IEnumerable GenerateSetupStatements(TypePositionInfo info, StubCodeContext context) => _innerMarshaller.GenerateSetupStatements(info, context); - + public IEnumerable GenerateSetupStatements(TypePositionInfo info, StubCodeContext context) + => DeclareLastIndexMarshalledIfNeeded(info, context).Concat(_innerMarshaller.GenerateSetupStatements(info, context)); public IEnumerable GenerateUnmarshalStatements(TypePositionInfo info, StubCodeContext context) { string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(info, context); diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StatelessMarshallingStrategy.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StatelessMarshallingStrategy.cs index 213eec0855f30d..906712e7a02655 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StatelessMarshallingStrategy.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StatelessMarshallingStrategy.cs @@ -251,6 +251,10 @@ public StatelessFreeMarshalling(ICustomTypeMarshallingStrategy innerMarshaller, public IEnumerable GenerateCleanupStatements(TypePositionInfo info, StubCodeContext context) { + foreach (StatementSyntax statement in _innerMarshaller.GenerateCleanupStatements(info, context)) + { + yield return statement; + } // .Free(); yield return ExpressionStatement( InvocationExpression( @@ -372,11 +376,19 @@ public IEnumerable GenerateMarshalStatements(TypePositionInfo i public IEnumerable GeneratePinStatements(TypePositionInfo info, StubCodeContext context) => Array.Empty(); public IEnumerable GenerateSetupStatements(TypePositionInfo info, StubCodeContext context) { + string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(info, context); yield return LocalDeclarationStatement( VariableDeclaration( PredefinedType(Token(SyntaxKind.IntKeyword)), SingletonSeparatedList( - VariableDeclarator(MarshallerHelpers.GetNumElementsIdentifier(info, context))))); + VariableDeclarator(numElementsIdentifier)))); + // Use the numElements local to ensure the compiler doesn't give errors for using an uninitialized variable. + // The value will never be used unless it has been initialized, so this is safe. + yield return MarshallerHelpers.SkipInitOrDefaultInit( + new TypePositionInfo(SpecialTypeInfo.Int32, NoMarshallingInfo.Instance) + { + InstanceIdentifier = numElementsIdentifier + }, context); } public IEnumerable GenerateUnmarshalCaptureStatements(TypePositionInfo info, StubCodeContext context) => Array.Empty(); @@ -502,7 +514,7 @@ public StatelessLinearCollectionNonBlittableElementsMarshalling( IMarshallingGenerator elementMarshaller, TypePositionInfo elementInfo, ExpressionSyntax numElementsExpression) - : base (unmanagedElementType, elementMarshaller, elementInfo) + : base(unmanagedElementType, elementMarshaller, elementInfo) { _marshallerTypeSyntax = marshallerTypeSyntax; _nativeTypeSyntax = nativeTypeSyntax; @@ -512,7 +524,21 @@ public StatelessLinearCollectionNonBlittableElementsMarshalling( public TypeSyntax AsNativeType(TypePositionInfo info) => _nativeTypeSyntax; - public IEnumerable GenerateCleanupStatements(TypePositionInfo info, StubCodeContext context) => Array.Empty(); + public IEnumerable GenerateCleanupStatements(TypePositionInfo info, StubCodeContext context) + { + StatementSyntax elementCleanup = GenerateElementCleanupStatement(info, context); + + if (!elementCleanup.IsKind(SyntaxKind.EmptyStatement)) + { + if (!UsesLastIndexMarshalled(info, context)) + { + // numElementsIdentifier is declared in setup + string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(info, context); + yield return ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName(numElementsIdentifier), _numElementsExpression)); + } + yield return elementCleanup; + } + } public IEnumerable GenerateGuaranteedUnmarshalStatements(TypePositionInfo info, StubCodeContext context) { @@ -588,11 +614,24 @@ public IEnumerable GenerateMarshalStatements(TypePositionInfo i public IEnumerable GenerateSetupStatements(TypePositionInfo info, StubCodeContext context) { + string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(info, context); yield return LocalDeclarationStatement( VariableDeclaration( PredefinedType(Token(SyntaxKind.IntKeyword)), SingletonSeparatedList( - VariableDeclarator(MarshallerHelpers.GetNumElementsIdentifier(info, context))))); + VariableDeclarator(numElementsIdentifier)))); + // Use the numElements local to ensure the compiler doesn't give errors for using an uninitialized variable. + // The value will never be used unless it has been initialized, so this is safe. + yield return MarshallerHelpers.SkipInitOrDefaultInit( + new TypePositionInfo(SpecialTypeInfo.Int32, NoMarshallingInfo.Instance) + { + InstanceIdentifier = numElementsIdentifier + }, context); + + foreach (var statement in DeclareLastIndexMarshalledIfNeeded(info, context)) + { + yield return statement; + } } public IEnumerable GenerateUnmarshalCaptureStatements(TypePositionInfo info, StubCodeContext context) => Array.Empty(); diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs index 19680588e7bad4..fb4fcb4f7a0491 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs @@ -593,7 +593,7 @@ private MarshallingInfo CreateNativeMarshallingInfo( } int maxIndirectionDepthUsedLocal = maxIndirectionDepthUsed; - Func getMarshallingInfoForElement = (ITypeSymbol elementType) => GetMarshallingInfo(elementType, new Dictionary(), 1, ImmutableHashSet.Empty, ref maxIndirectionDepthUsedLocal); + Func getMarshallingInfoForElement = (ITypeSymbol elementType) => GetMarshallingInfo(elementType, useSiteAttributes, indirectionLevel + 1, inspectedElements, ref maxIndirectionDepthUsedLocal); if (ManualTypeMarshallingHelper.TryGetLinearCollectionMarshallersFromEntryType(entryPointType, type, _compilation, getMarshallingInfoForElement, out CustomTypeMarshallers? collectionMarshallers)) { maxIndirectionDepthUsed = maxIndirectionDepthUsedLocal; diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj index d2dd79ffb36dcd..f9304841f0b346 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/VariableDeclarations.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/VariableDeclarations.cs index 4c06dedb5eefd1..dbc73df2f8d2c0 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/VariableDeclarations.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/VariableDeclarations.cs @@ -29,34 +29,7 @@ public static VariableDeclarations GenerateDeclarationsForManagedToNative(BoundG if (info.RefKind == RefKind.Out) { - (TargetFramework fmk, _) = context.GetTargetFramework(); - if (info.ManagedType is not PointerTypeInfo - && info.ManagedType is not ValueTypeInfo { IsByRefLike: true } - && fmk is TargetFramework.Net) - { - // Use the Unsafe.SkipInit API when available and - // managed type is usable as a generic parameter. - initializations.Add(ExpressionStatement( - InvocationExpression( - MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - ParseName(TypeNames.System_Runtime_CompilerServices_Unsafe), - IdentifierName("SkipInit"))) - .WithArgumentList( - ArgumentList(SingletonSeparatedList( - Argument(IdentifierName(info.InstanceIdentifier)) - .WithRefOrOutKeyword(Token(SyntaxKind.OutKeyword))))))); - } - else - { - // Assign out params to default - initializations.Add(ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - IdentifierName(info.InstanceIdentifier), - LiteralExpression( - SyntaxKind.DefaultLiteralExpression, - Token(SyntaxKind.DefaultKeyword))))); - } + initializations.Add(MarshallerHelpers.SkipInitOrDefaultInit(info, context)); } // Declare variables for parameters diff --git a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs index b5a7b209486d9a..c1ace3807df844 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -1314,9 +1314,9 @@ public static void Free(void* ptr) { } static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out System.Runtime.InteropServices.NFloat result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out System.Runtime.InteropServices.NFloat result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out System.Runtime.InteropServices.NFloat result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(System.Runtime.InteropServices.NFloat value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Runtime.InteropServices.NFloat value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Runtime.InteropServices.NFloat value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(System.Runtime.InteropServices.NFloat value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Runtime.InteropServices.NFloat value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Runtime.InteropServices.NFloat value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static System.Runtime.InteropServices.NFloat System.Numerics.ISubtractionOperators.operator checked -(System.Runtime.InteropServices.NFloat left, System.Runtime.InteropServices.NFloat right) { throw null; } static System.Runtime.InteropServices.NFloat System.Numerics.IUnaryNegationOperators.operator checked -(System.Runtime.InteropServices.NFloat value) { throw null; } public static System.Runtime.InteropServices.NFloat Tan(System.Runtime.InteropServices.NFloat x) { throw null; } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionMarshallingFails.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionMarshallingFails.cs new file mode 100644 index 00000000000000..17586de9cef39c --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionMarshallingFails.cs @@ -0,0 +1,539 @@ +// 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; +using System.Runtime.InteropServices.Marshalling; +using SharedTypes; +using Xunit; + +namespace LibraryImportGenerator.IntegrationTests +{ + partial class NativeExportsNE + { + public partial class MarshallingFails + { + [LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.Utf8)] + public static partial void Utf8StringSpan(ReadOnlySpan s); + + [LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.Utf8)] + public static partial void Utf8StringArray(string[] s); + + [LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.Utf8)] + public static partial void MarshalSingleDimensionalArray( + [MarshalUsing(typeof(EnforceLastElementMarshalledCleanupBoolStruct), ElementIndirectionDepth = 1)] + BoolStruct[] c); + + [LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.Utf8)] + public static partial void MarshalMultidimensionalArray_CheckOuterArrayIsIndexTracked( + [MarshalUsing(typeof(EnforceLastElementMarshalledCleanupBoolStructArray), ElementIndirectionDepth = 1)] + BoolStruct[][] c); + + [LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.Utf8)] + public static partial void MarshalMultidimensionalArray_CheckInnerArraysAreCleared( + [MarshalUsing(typeof(EnforceClearedMemoryCleanup), ElementIndirectionDepth = 2)] + BoolStruct[][] c); + + [LibraryImport("DoesNotExist")] + public static partial void MarshalArray_Ref( + [MarshalUsing(typeof(EnforceLastElementMarshalledCleanupBoolStruct), ElementIndirectionDepth = 1)] + [MarshalUsing(ConstantElementCount = 10)] + ref BoolStruct[] c); + + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "negate_bool_struct_array_out")] + public static partial void NegateBoolsOut( + BoolStruct[] boolStruct, + int numValues, + [MarshalUsing(typeof(EnforceAllElementsCleanedUpBoolStruct), ElementIndirectionDepth = 1)] + [MarshalUsing(CountElementName = nameof(numValues))] + out BoolStruct[] pBoolStructOut); + + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "negate_bool_struct_array_out_2d")] + public static partial void NegateBoolsOut2D( + BoolStruct[][] boolStruct, + int length, + int[] widths, + [MarshalUsing(typeof(EnforceAllElementsCleanedUpBoolStruct), ElementIndirectionDepth = 2)] + [MarshalUsing(CountElementName = nameof(widths), ElementIndirectionDepth = 1)] + [MarshalUsing(CountElementName = nameof(length))] + out BoolStruct[][] pBoolStructOut); + + + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "fill_range_array")] + [return: MarshalAs(UnmanagedType.U1)] + public static partial bool FillRangeArray( + [MarshalUsing(typeof(EnforceAllElementsCleanedUpIntStruct), ElementIndirectionDepth = 1)] + [Out] + IntStructWrapper[] array, + int length, + int start); + + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "fill_range_array_2d")] + [return: MarshalAs(UnmanagedType.U1)] + public static partial bool FillRangeArray2D( + [MarshalUsing(typeof(EnforceAllElementsCleanedUpIntStruct), ElementIndirectionDepth = 2)] + [MarshalUsing(CountElementName = nameof(widths), ElementIndirectionDepth = 1)] + [Out] + IntStructWrapper[][] array, + int length, + int[] widths, + int start); + + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "negate_bool_struct_array_ref")] + public static partial void NegateBoolsRef( + [MarshalUsing(typeof(EnforceLastElementMarshalledCleanupBoolStruct), ElementIndirectionDepth = 1)] + [MarshalUsing(CountElementName = nameof(numValues))] + ref BoolStruct[] boolStruct, + int numValues); + + [LibraryImport("DoesNotExist", EntryPoint = "negate_bool_struct_array_ref_2d")] + public static partial void NegateBoolsRef2D_LastElementMarshalling( + [MarshalUsing(typeof(EnforceLastElementMarshalledCleanupBoolStructArray), ElementIndirectionDepth = 1)] + [MarshalUsing(CountElementName = nameof(length))] + ref BoolStruct[][] boolStruct, + int length); + + [LibraryImport("DoesNotExist", EntryPoint = "negate_bool_struct_array_ref_2d")] + public static partial void NegateBoolsRef2D_ClearMarshalling( + [MarshalUsing(typeof(EnforceClearedMemoryCleanup), ElementIndirectionDepth = 2)] + [MarshalUsing(CountElementName = nameof(widths), ElementIndirectionDepth = 1)] + [MarshalUsing(CountElementName = nameof(length))] + ref BoolStruct[][] boolStruct, + int length, + int[] widths); + } + } + + public class CollectionMarshallingFails + { + [Fact] + public void UTFStringConversionFailures() + { + bool threw = false; + try + { + var a = new string[] { new string((char)0xaaaa, 1_000_000_000), "Hello" }; // Conversion of the very long string to utf8 is going to fail + NativeExportsNE.MarshallingFails.Utf8StringSpan(a); + } + catch (ArgumentException) { threw = true; } + catch (OutOfMemoryException) { threw = true; } + Assert.True(threw); + + threw = false; + try + { + var a = new string[] { new string((char)0xaaaa, 1_000_000_000), "Hello" }; // Conversion of the very long string to utf8 is going to fail + NativeExportsNE.MarshallingFails.Utf8StringArray(a); + } + catch (ArgumentException) { threw = true; } + catch (OutOfMemoryException) { threw = true; } + Assert.True(threw); + } + + private T[][] GetMultiDimensionalArray(int dim1, int dim2) + { + var arr = new T[dim1][]; + for (int i = 0; i < dim1; i++) + { + arr[i] = new T[dim2]; + } + return arr; + } + + [Fact] + public void SingleDimensionalArray_EnsureLastIndexArrayIsTracked() + { + var arr = new BoolStruct[10]; + foreach (var throwOn in new int[] { 0, 1, 5, 9 }) + { + EnforceLastElementMarshalledCleanupBoolStruct.ThrowOnNthMarshalledElement(throwOn); + Assert.Throws(() => + { + NativeExportsNE.MarshallingFails.MarshalSingleDimensionalArray(arr); + }); + } + } + + [Fact] + public void MultidimensionalArray_CheckOuterArrayIsIndexTracked() + { + var arr = GetMultiDimensionalArray(10, 10); + foreach (var throwOn in new int[] { 0, 1, 5, 9 }) + { + EnforceLastElementMarshalledCleanupBoolStructArray.ThrowOnNthMarshalledElement(throwOn); + Assert.Throws(() => + { + NativeExportsNE.MarshallingFails.MarshalMultidimensionalArray_CheckOuterArrayIsIndexTracked(arr); + }); + } + } + + [Fact] + public void MultidimensionalArray_CheckInnerArraysAreCleared() + { + var arr = GetMultiDimensionalArray(10, 10); + foreach (var throwOn in new int[] { 0, 1, 45, 99 }) + { + EnforceClearedMemoryCleanup.ThrowOnNthMarshalledElement(throwOn); + Assert.Throws(() => + { + NativeExportsNE.MarshallingFails.MarshalMultidimensionalArray_CheckInnerArraysAreCleared(arr); + }); + } + } + + [Fact] + public void SingleDimensionalOutArray_EnsureAllCleaned() + { + var arr = new BoolStruct[10]; + foreach (var throwOn in new int[] { 0, 1, 5, 9 }) + { + EnforceAllElementsCleanedUpBoolStruct.ThrowOnNthUnmarshalledElement(throwOn); + EnforceAllElementsCleanedUpBoolStruct.ExpectedCleanupNumber = 10; + Assert.Throws(() => + { + NativeExportsNE.MarshallingFails.NegateBoolsOut(arr, arr.Length, out var boolsOut); + }); + EnforceAllElementsCleanedUpBoolStruct.AssertAllHaveBeenCleaned(); + } + // Run without throwing - this is okay only because the native code doesn't actually use the array, it creates a whole new one + EnforceAllElementsCleanedUpBoolStruct.ThrowOnNthUnmarshalledElement(-1); + EnforceAllElementsCleanedUpBoolStruct.ExpectedCleanupNumber = 10; + NativeExportsNE.MarshallingFails.NegateBoolsOut(arr, arr.Length, out var boolsOut); + EnforceAllElementsCleanedUpBoolStruct.AssertAllHaveBeenCleaned(); + } + + [Fact] + public void MultiDimensionalOutArray_EnsureAllCleaned() + { + var arr = GetMultiDimensionalArray(10, 10); + var widths = new int[10] { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 }; + foreach (var throwOn in new int[] { 0, 1, 45, 99 }) + { + EnforceAllElementsCleanedUpBoolStruct.ThrowOnNthUnmarshalledElement(throwOn); + EnforceAllElementsCleanedUpBoolStruct.ExpectedCleanupNumber = 100; + Assert.Throws(() => + { + NativeExportsNE.MarshallingFails.NegateBoolsOut2D(arr, arr.Length, widths, out BoolStruct[][] boolsOut); + }); + EnforceAllElementsCleanedUpBoolStruct.AssertAllHaveBeenCleaned(); + } + // Run without throwing - this is okay only because the native code doesn't actually use the array, it creates a whole new one + EnforceAllElementsCleanedUpBoolStruct.ThrowOnNthUnmarshalledElement(-1); + EnforceAllElementsCleanedUpBoolStruct.ExpectedCleanupNumber = 100; + NativeExportsNE.MarshallingFails.NegateBoolsOut2D(arr, arr.Length, widths, out BoolStruct[][] boolsOut); + EnforceAllElementsCleanedUpBoolStruct.AssertAllHaveBeenCleaned(); + } + + [Fact] + public void SingleDimensionalOutAttributedArray_EnsureAllCleaned() + { + var arr = new IntStructWrapper[10]; + foreach (var throwOn in new int[] { 0, 1, 5, 9 }) + { + EnforceAllElementsCleanedUpIntStruct.ThrowOnNthUnmarshalledElement(throwOn); + EnforceAllElementsCleanedUpIntStruct.ExpectedCleanupNumber = 10; + Assert.Throws(() => + NativeExportsNE.MarshallingFails.FillRangeArray(arr, 10, 0) + ); + EnforceAllElementsCleanedUpIntStruct.AssertAllHaveBeenCleaned(); + } + // Run without throwing - this is okay only because the native code doesn't actually use the array, it creates a whole new one + EnforceAllElementsCleanedUpIntStruct.ThrowOnNthUnmarshalledElement(-1); + EnforceAllElementsCleanedUpIntStruct.ExpectedCleanupNumber = 10; + NativeExportsNE.MarshallingFails.FillRangeArray(arr, 0, 9); + EnforceAllElementsCleanedUpIntStruct.AssertAllHaveBeenCleaned(); + } + + [Fact] + public void MultiDimensionalOutAttributedArray_EnsureAllCleaned() + { + var arr = GetMultiDimensionalArray(10, 10); + var widths = new int[10] { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 }; + foreach (var throwOn in new int[] { 0, 1, 45, 99 }) + { + EnforceAllElementsCleanedUpIntStruct.ThrowOnNthUnmarshalledElement(throwOn); + EnforceAllElementsCleanedUpIntStruct.ExpectedCleanupNumber = 100; + Assert.Throws(() => + NativeExportsNE.MarshallingFails.FillRangeArray2D(arr, 10, widths, 0) + ); + EnforceAllElementsCleanedUpIntStruct.AssertAllHaveBeenCleaned(); + } + // Run without throwing - this is okay only because the native code doesn't actually use the array, it creates a whole new one + EnforceAllElementsCleanedUpIntStruct.ThrowOnNthUnmarshalledElement(-1); + EnforceAllElementsCleanedUpIntStruct.ExpectedCleanupNumber = 100; + NativeExportsNE.MarshallingFails.FillRangeArray2D(arr, 10, widths, 0); + EnforceAllElementsCleanedUpIntStruct.AssertAllHaveBeenCleaned(); + } + + [Fact] + public void SingleDimensionalRefArray_EnsureLastIndexArrayIsTracked() + { + var arr = new BoolStruct[10]; + foreach (var throwOn in new int[] { 0, 1, 5, 9 }) + { + EnforceLastElementMarshalledCleanupBoolStruct.ThrowOnNthMarshalledElement(throwOn); + Assert.Throws(() => + { + NativeExportsNE.MarshallingFails.NegateBoolsRef(ref arr, arr.Length); + }); + } + } + + [Fact] + public void MultiDimensionalRefArray_EnsureOuterArrayLastIndexArrayIsTracked() + { + var arr = GetMultiDimensionalArray(10, 10); + foreach (var throwOn in new int[] { 0, 1, 5, 9 }) + { + EnforceLastElementMarshalledCleanupBoolStructArray.ThrowOnNthMarshalledElement(throwOn); + Assert.Throws(() => + { + NativeExportsNE.MarshallingFails.NegateBoolsRef2D_LastElementMarshalling(ref arr, arr.Length); + }); + } + } + + [Fact] + public void MultiDimensionalRefArray_EnsureInnerArraysAreCleared() + { + var arr = GetMultiDimensionalArray(10, 10); + var widths = new int[10] { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 }; + foreach (var throwOn in new int[] { 0, 1, 45, 99 }) + { + EnforceClearedMemoryCleanup.ThrowOnNthMarshalledElement(throwOn); + Assert.Throws(() => + { + NativeExportsNE.MarshallingFails.NegateBoolsRef2D_ClearMarshalling(ref arr, arr.Length, widths); + }); + } + } + } + + /// + /// Use to ensure that the generated code frees N elements. Make sure to set to the number of elements that are expected to be freed, then after calling the LibraryImport method, call . + /// + [CustomMarshaller(typeof(IntStructWrapper), MarshalMode.ElementOut, typeof(EnforceAllElementsCleanedUpIntStruct))] + static unsafe class EnforceAllElementsCleanedUpIntStruct + { + private static MarshallingExceptionManager s_IntStructWrapperMarshalling = new(0, default); + + public static void ThrowOnNthMarshalledElement(int n) + { + s_IntStructWrapperMarshalling.ThrowOnNthMarshalledElement(n); + } + public static void ThrowOnNthUnmarshalledElement(int n) + { + s_IntStructWrapperMarshalling.ThrowOnNthUnmarshalledElement(n); + } + + public static IntStructWrapper ConvertToManaged(nint unmanaged) => s_IntStructWrapperMarshalling.ConvertToManaged(unmanaged); + + /// + /// The number of elements that are expected to be cleaned up / freed. + /// + public static int ExpectedCleanupNumber { get; set; } = 0; + + public static void AssertAllHaveBeenCleaned(int remaining = 0) + { + if (ExpectedCleanupNumber - remaining != 0) + s_IntStructWrapperMarshalling.Throw($"Incorrected number of elements freed. Expected {ExpectedCleanupNumber - remaining} more elements to be freed."); + } + + public static void Free(nint obj) + { + if (ExpectedCleanupNumber-- < 0) + s_IntStructWrapperMarshalling.Throw($"Freed too many objects"); + } + } + + /// + /// Use to ensure that the generated code frees N elements. Make sure to set to the number of elements that are expected to be freed, then after calling the LibraryImport method, call . + /// + [CustomMarshaller(typeof(BoolStruct), MarshalMode.ElementOut, typeof(EnforceAllElementsCleanedUpBoolStruct))] + internal static class EnforceAllElementsCleanedUpBoolStruct + { + private static MarshallingExceptionManager s_BoolStructMarshalling = new(0, default); + + public static void ThrowOnNthMarshalledElement(int n) + { + s_BoolStructMarshalling.ThrowOnNthMarshalledElement(n); + } + public static void ThrowOnNthUnmarshalledElement(int n) + { + s_BoolStructMarshalling.ThrowOnNthUnmarshalledElement(n); + } + + public static BoolStruct ConvertToManaged(nint unmanaged) => s_BoolStructMarshalling.ConvertToManaged(unmanaged); + + /// + /// The number of elements that are expected to be cleaned up / freed. + /// + public static int ExpectedCleanupNumber { get; set; } = 0; + + public static void AssertAllHaveBeenCleaned(int remaining = 0) + { + if (ExpectedCleanupNumber - remaining != 0) + s_BoolStructMarshalling.Throw($"Incorrected number of elements freed. Expected {ExpectedCleanupNumber - remaining} more elements to be freed."); + } + + public static void Free(nint obj) + { + if (ExpectedCleanupNumber-- < 0) + s_BoolStructMarshalling.Throw($"Freed too many objects"); + } + } + + /// + /// Use to ensure that the generated code only frees elements that have been marshalled. It will create a dummy pointer for marshalled elements, + /// throw an exception when marshaling the Nth element, and ensure all freed memory is the dummy pointer. This will not properly marshal elements, + /// so the pinvoke should not be run if it will access marshalled objects. Make sure to call ThrowOnNthMarshalledElement such that marshalling + /// the array will fail before the pinvoke is run. + /// + [CustomMarshaller(typeof(BoolStruct), MarshalMode.ElementIn, typeof(EnforceLastElementMarshalledCleanupBoolStruct))] + [CustomMarshaller(typeof(BoolStruct), MarshalMode.ElementRef, typeof(EnforceLastElementMarshalledCleanupBoolStruct))] + [CustomMarshaller(typeof(BoolStruct), MarshalMode.ElementOut, typeof(EnforceLastElementMarshalledCleanupBoolStruct))] + [CustomMarshaller(typeof(char), MarshalMode.ElementIn, typeof(EnforceLastElementMarshalledCleanupBoolStruct))] + static class EnforceLastElementMarshalledCleanupBoolStruct + { + private static MarshallingExceptionManager s_BoolStructMarshalling = new(_dummyPtr, default); + + public static void ThrowOnNthMarshalledElement(int n) => s_BoolStructMarshalling.ThrowOnNthMarshalledElement(n); + + static nint _dummyPtr => 0xA1FA1FA; + + public static nint ConvertToUnmanaged(BoolStruct managed) => s_BoolStructMarshalling.ConvertToUnmanaged(managed); + + public static void Free(nint obj) + { + if (obj != _dummyPtr) + s_BoolStructMarshalling.Throw($"Freed unmarshalled pointer: {obj}"); + } + + public static nint ConvertToUnmanaged(char managed) => throw new NotImplementedException(); + + public static BoolStruct ConvertToManaged(nint unmanaged) => throw new NotImplementedException(); + } + + /// + /// Use to ensure that the generated code only frees elements that have been marshalled. It will create a dummy pointer for marshalled elements, + /// throw an exception when marshaling the Nth element, and ensure all freed memory is the dummy pointer. This will not properly marshal elements, + /// so the pinvoke should not be run if it will access marshalled objects. Make sure to call ThrowOnNthMarshalledElement such that marshalling + /// the array will fail before the pinvoke is run. + /// + [CustomMarshaller(typeof(BoolStruct[]), MarshalMode.ElementIn, typeof(EnforceLastElementMarshalledCleanupBoolStructArray))] + [CustomMarshaller(typeof(BoolStruct[]), MarshalMode.ElementRef, typeof(EnforceLastElementMarshalledCleanupBoolStructArray))] + static class EnforceLastElementMarshalledCleanupBoolStructArray + { + private static MarshallingExceptionManager s_BoolStructArrayMarshalling = new(_dummyPtr, default); + + public static void ThrowOnNthMarshalledElement(int n) => s_BoolStructArrayMarshalling.ThrowOnNthMarshalledElement(n); + + static nint _dummyPtr => 0xA1FA1FA; + + public static nint ConvertToUnmanaged(BoolStruct[] managed) => s_BoolStructArrayMarshalling.ConvertToUnmanaged(managed); + + public static void Free(nint obj) + { + if (obj != _dummyPtr) + s_BoolStructArrayMarshalling.Throw($"Freed unmarshalled pointer: {obj}"); + } + + public static BoolStruct[] ConvertToManaged(nint unmanaged) => throw new NotImplementedException(); + } + + + /// + /// Use to ensure that an array is cleared before elements are marshalled. It will create a dummy pointer for marshalled elements, throw an exception when marshaling the Nth element, + /// and ensure all freed memory is either the dummy pointer or null. This will not properly marshal elements, so the pinvoke should not be run if it will access marshalled objects. + /// Make sure to call ThrowOnNthMarshalledElement such that marshalling the array will fail before the pinvoke is run. + /// + [CustomMarshaller(typeof(BoolStruct), MarshalMode.ElementIn, typeof(EnforceClearedMemoryCleanup))] + [CustomMarshaller(typeof(BoolStruct), MarshalMode.ElementRef, typeof(EnforceClearedMemoryCleanup))] + static class EnforceClearedMemoryCleanup + { + private static MarshallingExceptionManager s_exceptionManager = new(_dummyPtr, default); + + public static void ThrowOnNthMarshalledElement(int n) => s_exceptionManager.ThrowOnNthMarshalledElement(n); + + public static int ThrowOnElementNumber { get; set; } = -1; + + static nint _dummyPtr => 0xA1FA1FA; + + public static nint ConvertToUnmanaged(BoolStruct managed) => s_exceptionManager.ConvertToUnmanaged(managed); + + public static BoolStruct ConvertToManaged(nint unmanaged) => throw new NotImplementedException(); + + public static void Free(nint obj) + { + if (obj != _dummyPtr && obj != 0) + s_exceptionManager.Throw($"Freed unmarshalled pointer: {obj}"); + } + } + + internal class MarshallingExceptionManager + { + private int _marshalledCount = 0; + private int _unmarshalledCount = 0; + private int _throwOnMarshallingElement = -1; + private int _throwOnUnmarshallingElement = -1; + private readonly nint _marshalledValue; + private readonly TManaged _unmarshalledValue; + + public MarshallingExceptionManager(nint marshalledValue, TManaged unmarshalledValue) + { + _marshalledValue = marshalledValue; + _unmarshalledValue = unmarshalledValue; + } + + /// + /// Force marshalling to fail on the nth element. + /// + /// + public void ThrowOnNthMarshalledElement(int n) + { + _marshalledCount = 0; + _throwOnMarshallingElement = n; + } + + /// + /// Force unmarshalling to fail on the nth element. + /// + public void ThrowOnNthUnmarshalledElement(int n) + { + _unmarshalledCount = 0; + _throwOnUnmarshallingElement = n; + } + + public nint ConvertToUnmanaged(TManaged managed) + { + if (_marshalledCount++ == _throwOnMarshallingElement) + { + _marshalledCount = 0; + _throwOnMarshallingElement = -1; + throw new ArgumentException("Marshalling failed"); + } + return _marshalledValue; + } + + public TManaged ConvertToManaged(nint unmanaged) + { + if (_unmarshalledCount++ == _throwOnUnmarshallingElement) + { + _unmarshalledCount = 0; + _throwOnUnmarshallingElement = -1; + throw new ArgumentException("Unmarshalling failed"); + } + return _unmarshalledValue; + } + public void Throw(string message) => throw new InvalidMarshallingException(message); + + [Serializable] + private sealed class InvalidMarshallingException : Exception + { + public InvalidMarshallingException(string? message) : base(message) + { + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs index 48d592735280c5..51214883bd4b3e 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs @@ -25,6 +25,9 @@ public partial class Stateless [LibraryImport(NativeExportsNE_Binary, EntryPoint = "sum_int_array")] public static partial int SumWithBuffer([MarshalUsing(typeof(ListMarshallerWithBuffer<,>))] List values, int numValues); + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "sum_int_ptr_array")] + public static unsafe partial int SumWithFreeTracking([MarshalUsing(typeof(ListMarshaller<,>)), MarshalUsing(typeof(IntWrapperMarshallerWithFreeCounts), ElementIndirectionDepth = 1)] List values, int numValues); + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "double_values")] public static partial int DoubleValues([MarshalUsing(typeof(ListMarshallerWithPinning<,>))] List values, int length); @@ -99,6 +102,9 @@ public partial class Stateful [LibraryImport(NativeExportsNE_Binary, EntryPoint = "sum_int_array")] public static partial int Sum([MarshalUsing(typeof(ListMarshallerStateful<,>))] List values, int numValues); + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "sum_int_ptr_array")] + public static unsafe partial int SumWithFreeTracking([MarshalUsing(typeof(ListMarshallerStateful<,>)), MarshalUsing(typeof(IntWrapperMarshallerWithFreeCounts), ElementIndirectionDepth = 1)] List values, int numValues); + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "sum_int_array_ref")] public static partial int SumInArray([MarshalUsing(typeof(ListMarshallerStateful<,>))] in List values, int numValues); @@ -369,6 +375,30 @@ public void NonBlittableElementCollection_GuaranteedUnmarshal() Assert.True(NativeExportsNE.Collections.Stateful.ListGuaranteedUnmarshal.Marshaller.ToManagedFinallyCalled); } + [Fact] + public void ElementsFreed() + { + List list = new List + { + new IntWrapper { i = 1 }, + new IntWrapper { i = 10 }, + new IntWrapper { i = 24 }, + new IntWrapper { i = 30 }, + }; + + int startingCount = IntWrapperMarshallerWithFreeCounts.NumCallsToFree; + + NativeExportsNE.Collections.Stateless.SumWithFreeTracking(list, list.Count); + + Assert.Equal(startingCount + list.Count, IntWrapperMarshallerWithFreeCounts.NumCallsToFree); + + startingCount = IntWrapperMarshallerWithFreeCounts.NumCallsToFree; + + NativeExportsNE.Collections.Stateful.SumWithFreeTracking(list, list.Count); + + Assert.Equal(startingCount + list.Count, IntWrapperMarshallerWithFreeCounts.NumCallsToFree); + } + private static List GetBoolStructsToAnd(bool result) => new List { new BoolStruct diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/FunctionPointerTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/FunctionPointerTests.cs index fde6b235ed4561..7557cf2e51921f 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/FunctionPointerTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/FunctionPointerTests.cs @@ -115,5 +115,17 @@ static int CallbackUnmanagedStdcall(int a, int b) return Callback(a, b); } } + + [UnmanagedCallersOnly] + public static int Increment (int i) { + return i + 1; + } + + [Fact] + public unsafe void CalliUnmanaged() + { + delegate* unmanaged callbackProc = (delegate* unmanaged)&Increment; + Assert.Equal(6, callbackProc(5)); + } } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/LibraryImportGenerator.Tests.csproj b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/LibraryImportGenerator.Tests.csproj index ad8f6d61e84d58..ae35ddbf18fbaa 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/LibraryImportGenerator.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/LibraryImportGenerator.Tests.csproj @@ -1,16 +1,18 @@ - + $(NetCoreAppCurrent) true true false + None + true - + diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs index f23a9f22c61cf3..17ba7dea2507cd 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs @@ -641,33 +641,30 @@ public async Task ValidateSnippetsWithMultipleSources(string id, string[] source TestUtils.AssertPostSourceGeneratorCompilation(newComp); } - public static IEnumerable CodeSnippetsToCompileToValidateAllowUnsafeBlocks() + public static IEnumerable CodeSnippetsToVerifyNoTreesProduced() { - yield return new object[] { ID(), CodeSnippets.TrivialClassDeclarations, TestTargetFramework.Net, true }; - - { - string source = @" + string source = @" using System.Runtime.InteropServices; public class Basic { } "; - yield return new object[] { ID(), source, TestTargetFramework.Standard, false }; - yield return new object[] { ID(), source, TestTargetFramework.Framework, false }; - yield return new object[] { ID(), source, TestTargetFramework.Net, false }; - } + yield return new object[] { ID(), source, TestTargetFramework.Standard }; + yield return new object[] { ID(), source, TestTargetFramework.Framework }; + yield return new object[] { ID(), source, TestTargetFramework.Net }; } [Theory] - [MemberData(nameof(CodeSnippetsToCompileToValidateAllowUnsafeBlocks))] - public async Task ValidateRequireAllowUnsafeBlocksDiagnosticNoTrigger(string id, string source, TestTargetFramework framework, bool allowUnsafe) + [MemberData(nameof(CodeSnippetsToVerifyNoTreesProduced))] + public async Task ValidateNoGeneratedOuptutForNoImport(string id, string source, TestTargetFramework framework) { TestUtils.Use(id); - Compilation comp = await TestUtils.CreateCompilation(source, framework, allowUnsafe: allowUnsafe); + Compilation comp = await TestUtils.CreateCompilation(source, framework, allowUnsafe: false); TestUtils.AssertPreSourceGeneratorCompilation(comp); var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.LibraryImportGenerator()); Assert.Empty(generatorDiags); - TestUtils.AssertPostSourceGeneratorCompilation(newComp); + // Assert we didn't generate any syntax trees, even empty ones + Assert.Same(comp, newComp); } } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/NativeMarshallingAttributeAnalyzerTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/NativeMarshallingAttributeAnalyzerTests.cs index 582568718722b5..5b8769e19c9ee2 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/NativeMarshallingAttributeAnalyzerTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/NativeMarshallingAttributeAnalyzerTests.cs @@ -272,8 +272,18 @@ public class X { void Foo([MarshalAs(UnmanagedType.I4)] int i) { + [return:MarshalAs(UnmanagedType.I4)] + [SkipLocalsInit] + static int Local() + { + return 0; + } } } + + [return:MarshalAs(UnmanagedType.I4)] + delegate int Y(); + """; await VerifyCS.VerifyAnalyzerAsync(source); diff --git a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/Arrays.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/Arrays.cs index b5c393d3b10fb1..342e2917fa6ae5 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/Arrays.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/Arrays.cs @@ -128,6 +128,27 @@ public static byte FillRange([DNNE.C99Type("struct int_struct_wrapper*")] IntStr return 1; } + [UnmanagedCallersOnly(EntryPoint = "fill_range_array_2d")] + [DNNE.C99DeclCode("struct int_struct_wrapper;")] + public static byte FillRange2D([DNNE.C99Type("struct int_struct_wrapper**")] IntStructWrapperNative** numValues, int length, int* widths, int start) + { + if (numValues == null) + { + return 0; + } + + for (int i = 0; i < length; i++) + { + numValues[i] = (IntStructWrapperNative*)Marshal.AllocCoTaskMem(sizeof(IntStructWrapperNative) * widths[i]); + for (int j = 0; j < widths[i]; j++, start++) + { + numValues[i][j] = new IntStructWrapperNative { value = start }; + } + } + + return 1; + } + [UnmanagedCallersOnly(EntryPoint = "double_values")] [DNNE.C99DeclCode("struct int_struct_wrapper { int value; };")] public static void DoubleValues([DNNE.C99Type("struct int_struct_wrapper*")] IntStructWrapperNative* numValues, int length) @@ -291,6 +312,25 @@ public static void NegateBoolStructsRef( } } + [UnmanagedCallersOnly(EntryPoint = "negate_bool_struct_array_ref_2d")] + [DNNE.C99DeclCode("struct bool_struct;")] + public static void NegateBoolStructsRef2D( + [DNNE.C99Type("struct bool_struct**")] BoolStructMarshaller.BoolStructNative*** array, + int length, + int* widths) + { + for (int i = 0; i < length; i++) + { + for (int j = 0; j < widths[i]; j++) + { + BoolStructMarshaller.BoolStructNative boolStruct = *(array[i][j]); + (*array)[i][j].b1 = (byte)(boolStruct.b1 != 0 ? 0 : 1); + (*array)[i][j].b2 = (byte)(boolStruct.b2 != 0 ? 0 : 1); + (*array)[i][j].b3 = (byte)(boolStruct.b3 != 0 ? 0 : 1); + } + } + } + [UnmanagedCallersOnly(EntryPoint = "negate_bool_struct_array_out")] [DNNE.C99DeclCode("struct bool_struct;")] public static void NegateBoolStructsOut( @@ -301,6 +341,21 @@ public static void NegateBoolStructsOut( *outArray = NegateBoolStructsImpl(array, length); } + [UnmanagedCallersOnly(EntryPoint = "negate_bool_struct_array_out_2d")] + [DNNE.C99DeclCode("struct bool_struct;")] + public static void NegateBoolStructsOut2D( + [DNNE.C99Type("struct bool_struct**")] BoolStructMarshaller.BoolStructNative** array, + int length, + int* widths, + [DNNE.C99Type("struct bool_struct***")] BoolStructMarshaller.BoolStructNative*** outArray) + { + *outArray = (BoolStructMarshaller.BoolStructNative**)Marshal.AllocCoTaskMem(sizeof(BoolStructMarshaller.BoolStructNative**) * length); + for (int i = 0; i < length; i++) + { + (*outArray)[i] = NegateBoolStructsImpl(array[i], widths[i]); + } + } + [UnmanagedCallersOnly(EntryPoint = "negate_bool_struct_array_return")] [DNNE.C99DeclCode("struct bool_struct;")] [return: DNNE.C99Type("struct bool_struct*")] diff --git a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/NativeExports.csproj b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/NativeExports.csproj index d420161d1a6ebe..57344f04744328 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/NativeExports.csproj +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/NativeExports.csproj @@ -9,7 +9,7 @@ Major $(OutputRid) - $(OutputRid) + $(PackageRID) <_TargetsAppleOS Condition="'$(TargetOS)' == 'OSX' or '$(TargetOS)' == 'MacCatalyst' or '$(TargetOS)' == 'iOS' or '$(TargetOS)' == 'tvOS' or '$(TargetOS)' == 'iOSSimulator' or '$(TargetOS)' == 'tvOSSimulator'">true diff --git a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/NonBlittable.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/NonBlittable.cs index 6035dc2dc5b1ff..88e594d9f6a2d2 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/NonBlittable.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/NonBlittable.cs @@ -196,6 +196,31 @@ public static void Free(int* unmanaged) } } + [CustomMarshaller(typeof(IntWrapper), MarshalMode.Default, typeof(IntWrapperMarshallerWithFreeCounts))] + public static unsafe class IntWrapperMarshallerWithFreeCounts + { + [ThreadStatic] + public static int NumCallsToFree = 0; + + public static int* ConvertToUnmanaged(IntWrapper managed) + { + int* ret = (int*)Marshal.AllocCoTaskMem(sizeof(int)); + *ret = managed.i; + return ret; + } + + public static IntWrapper ConvertToManaged(int* unmanaged) + { + return new IntWrapper { i = *unmanaged }; + } + + public static void Free(int* unmanaged) + { + NumCallsToFree++; + Marshal.FreeCoTaskMem((IntPtr)unmanaged); + } + } + [CustomMarshaller(typeof(IntWrapper), MarshalMode.Default, typeof(Marshaller))] public static unsafe class IntWrapperMarshallerStateful { @@ -477,14 +502,14 @@ public void FromManaged(List managed, Span buffer) _list = managed; // Always allocate at least one byte when the list is zero-length. - int spaceToAllocate = Math.Max(managed.Count * sizeof(TUnmanagedElement), 1); - if (spaceToAllocate <= buffer.Length) + int countToAllocate = Math.Max(managed.Count, 1); + if (countToAllocate <= buffer.Length) { - _span = buffer[0..spaceToAllocate]; + _span = buffer[0..countToAllocate]; } else { - _allocatedMemory = Marshal.AllocCoTaskMem(spaceToAllocate); + _allocatedMemory = Marshal.AllocCoTaskMem(countToAllocate * sizeof(TUnmanagedElement)); _span = new Span((void*)_allocatedMemory, managed.Count); } } diff --git a/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj b/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj index 3009539cc05335..6c0187cf21ff84 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj +++ b/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj @@ -7,6 +7,10 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser $(Features.Replace('nullablePublicOnly', '') + + wasm.helix.targets + $(WasmXHarnessArgs) --engine-arg=--experimental-wasm-simd diff --git a/src/libraries/System.Runtime.Intrinsics/tests/wasm.helix.targets b/src/libraries/System.Runtime.Intrinsics/tests/wasm.helix.targets new file mode 100644 index 00000000000000..65123620588e8d --- /dev/null +++ b/src/libraries/System.Runtime.Intrinsics/tests/wasm.helix.targets @@ -0,0 +1,49 @@ + + + $(HelixExtensionTargets);_AddHelixRuntimeIntrinsicsItems + <_RuntimeIntrinsicsProjectName>System.Runtime.Intrinsics.Tests + + + + + + + <_AOTBuildArgsSIMD Condition="'$(OS)' != 'Windows_NT'">"WasmXHarnessArgs=$WasmXHarnessArgs --engine-arg=--experimental-wasm-simd" + <_AOTBuildArgsSIMD Condition="'$(OS)' == 'Windows_NT'">"WasmXHarnessArgs=%WasmXHarnessArgs% --engine-arg=--experimental-wasm-simd" + + + + <_AOTBuildArgsSIMD Condition="'$(OS)' != 'Windows_NT'">$(_AOTBuildArgsSIMD) "AOT_BUILD_ARGS=-p:WasmEnableSIMD=true" + + <_AOTBuildArgsSIMD Condition="'$(OS)' != 'Windows_NT'">export $(_AOTBuildArgsSIMD) + <_AOTBuildArgsSIMD Condition="'$(OS)' == 'Windows_NT'">set $(_AOTBuildArgsSIMD) + + + + + + + + $(TestArchiveTestsDir)$(_RuntimeIntrinsicsProjectName).zip + $(HelixCommand) + $(_workItemTimeout) + + + + $(TestArchiveTestsDir)$(_RuntimeIntrinsicsProjectName).zip + $(HelixCommand) + $(_workItemTimeout) + + $(_AOTBuildArgsSIMD) + + + <_RuntimeIntrinsicsHelixItem + Include="@(HelixWorkItem)" + Condition="$([System.String]::new('%(HelixWorkItem.Identity)').EndsWith('-$(_RuntimeIntrinsicsProjectName)'))" /> + + + + + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod/ReflectionAddNewMethod.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod/ReflectionAddNewMethod.cs new file mode 100644 index 00000000000000..c483ce17129def --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod/ReflectionAddNewMethod.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 System; + + +namespace System.Reflection.Metadata.ApplyUpdate.Test +{ + public class ReflectionAddNewMethod + { + public string ExistingMethod(string u, double f) + { + return u + f.ToString();; + } + + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod/ReflectionAddNewMethod_v1.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod/ReflectionAddNewMethod_v1.cs new file mode 100644 index 00000000000000..bd4804ab16aa70 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod/ReflectionAddNewMethod_v1.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Runtime.CompilerServices; +using CancellationToken = System.Threading.CancellationToken; + +namespace System.Reflection.Metadata.ApplyUpdate.Test +{ + public class ReflectionAddNewMethod + { + public string ExistingMethod(string u, double f) + { + return u + f.ToString();; + } + + public double AddedNewMethod(char c, float h, string w, CancellationToken ct = default, [CallerMemberName] string callerName = "") + { + return ((double)Convert.ToInt32(c)) + h; + } + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod.csproj b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod.csproj new file mode 100644 index 00000000000000..f2b921019f7b33 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod.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.ReflectionAddNewMethod/deltascript.json b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod/deltascript.json new file mode 100644 index 00000000000000..a9e2fde9d00786 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod/deltascript.json @@ -0,0 +1,6 @@ +{ + "changes": [ + {"document": "ReflectionAddNewMethod.cs", "update": "ReflectionAddNewMethod_v1.cs"}, + ] +} + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType/ReflectionAddNewType.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType/ReflectionAddNewType.cs index 7d720e44585f3a..9347c5319a12e3 100644 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType/ReflectionAddNewType.cs +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType/ReflectionAddNewType.cs @@ -25,6 +25,8 @@ public class PreviousNestedClass { public event EventHandler E; public void R() { E(this,"123"); } } + + public static void ExistingMethod () {} } [AttributeUsage(AttributeTargets.All, AllowMultiple=true, Inherited=false)] diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType/ReflectionAddNewType_v1.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType/ReflectionAddNewType_v1.cs index 02762f11f05128..a7b8542d35538e 100644 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType/ReflectionAddNewType_v1.cs +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType/ReflectionAddNewType_v1.cs @@ -38,6 +38,14 @@ public class NewNestedClass {}; public static DateTime NewStaticField; public static double NewProp { get; set; } + + public static void ExistingMethod () + { + // modified + NewStaticField2 = new AnotherAddedClass(); + } + + public static AnotherAddedClass NewStaticField2; } [AttributeUsage(AttributeTargets.All, AllowMultiple=true, Inherited=false)] @@ -87,3 +95,22 @@ public interface INewInterface : IExistingInterface { public enum NewEnum { Red, Yellow, Green } + +public class AnotherAddedClass +{ + public struct NewNestedStruct + { + public double D; + public object O; + } + + public NewNestedStruct S; + + public AnotherAddedClass() + { + S = new NewNestedStruct { + D = 1234.0, + O = "1234", + }; + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs index ce69168955a0e7..d48880285c5e53 100644 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Xunit; namespace System.Reflection.Metadata @@ -230,7 +231,6 @@ public void CustomAttributeDelete() }); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/52993", TestRuntimes.Mono)] [ConditionalFact(typeof(ApplyUpdateUtil), nameof (ApplyUpdateUtil.IsSupported))] public void AsyncMethodChanges() { @@ -625,7 +625,91 @@ public static void TestReflectionAddNewType() var i = (System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType.IExistingInterface)o; Assert.Equal("123", i.ItfMethod(123)); + + System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType.ZExistingClass.ExistingMethod (); }); } + + [ConditionalFact(typeof(ApplyUpdateUtil), nameof(ApplyUpdateUtil.IsSupported))] + public static void TestReflectionAddNewMethod() + { + ApplyUpdateUtil.TestCase(static () => + { + var ty = typeof(System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod); + var assm = ty.Assembly; + + var bindingFlags = BindingFlags.Instance | BindingFlags.Public; + var allMethods = ty.GetMethods(bindingFlags); + + int objectMethods = typeof(object).GetMethods(bindingFlags).Length; + Assert.Equal (objectMethods + 1, allMethods.Length); + + ApplyUpdateUtil.ApplyUpdate(assm); + ApplyUpdateUtil.ClearAllReflectionCaches(); + + ty = typeof(System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod); + + allMethods = ty.GetMethods(bindingFlags); + Assert.Equal (objectMethods + 2, allMethods.Length); + + var mi = ty.GetMethod ("AddedNewMethod"); + + Assert.NotNull (mi); + + var retParm = mi.ReturnParameter; + Assert.NotNull (retParm); + Assert.NotNull (retParm.ParameterType); + Assert.Equal (-1, retParm.Position); + + var retCas = retParm.GetCustomAttributes(false); + Assert.NotNull(retCas); + Assert.Equal(0, retCas.Length); + + var parms = mi.GetParameters(); + Assert.Equal (5, parms.Length); + + int parmPos = 0; + foreach (var parm in parms) + { + Assert.NotNull(parm); + Assert.NotNull(parm.ParameterType); + Assert.Equal(parmPos, parm.Position); + Assert.NotNull(parm.Name); + + var cas = parm.GetCustomAttributes(false); + foreach (var ca in cas) { + Assert.NotNull (ca); + } + + parmPos++; + } + + var parmAttrs = parms[4].GetCustomAttributes(false); + Assert.Equal (2, parmAttrs.Length); + bool foundCallerMemberName = false; + bool foundOptional = false; + foreach (var pa in parmAttrs) { + if (typeof (CallerMemberNameAttribute).Equals(pa.GetType())) + { + foundCallerMemberName = true; + } + if (typeof (OptionalAttribute).Equals(pa.GetType())) + { + foundOptional = true; + } + } + Assert.True(foundCallerMemberName); + Assert.True(foundOptional); + + // n.b. this typeof() also makes the rest of the test work on Wasm with aggressive trimming. + Assert.Equal (typeof(System.Threading.CancellationToken), parms[3].ParameterType); + + Assert.True(parms[3].HasDefaultValue); + Assert.True(parms[4].HasDefaultValue); + + Assert.Null(parms[3].DefaultValue); + Assert.Equal(string.Empty, parms[4].DefaultValue); + }); + } } } 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 50941fe7b3ca73..6c5a8469726179 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 @@ -62,6 +62,7 @@ + diff --git a/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs b/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs index 443aae6338ffcd..0d371c4d7886c6 100644 --- a/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs +++ b/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs @@ -218,9 +218,9 @@ namespace System.Numerics static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out System.Numerics.BigInteger result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out System.Numerics.BigInteger result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out System.Numerics.BigInteger result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(System.Numerics.BigInteger value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Numerics.BigInteger value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Numerics.BigInteger value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(System.Numerics.BigInteger value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Numerics.BigInteger value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Numerics.BigInteger value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static System.Numerics.BigInteger System.Numerics.INumber.MaxNumber(System.Numerics.BigInteger x, System.Numerics.BigInteger y) { throw null; } static System.Numerics.BigInteger System.Numerics.INumber.MinNumber(System.Numerics.BigInteger x, System.Numerics.BigInteger y) { throw null; } static int System.Numerics.INumber.Sign(System.Numerics.BigInteger value) { throw null; } @@ -367,9 +367,9 @@ namespace System.Numerics static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out System.Numerics.Complex result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out System.Numerics.Complex result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out System.Numerics.Complex result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } public static System.Numerics.Complex Tan(System.Numerics.Complex value) { throw null; } public static System.Numerics.Complex Tanh(System.Numerics.Complex value) { throw null; } public override string ToString() { throw null; } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs index 4956fac177a403..f55acb76559d63 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -3481,42 +3481,19 @@ public static BigInteger TrailingZeroCount(BigInteger value) ulong result = 0; - if (value._sign >= 0) - { - // When the value is positive, we simply need to do a tzcnt for all bits until we find one set - - uint part = value._bits[0]; + // Both positive values and their two's-complement negative representation will share the same TrailingZeroCount, + // so the sign of value does not matter and both cases can be handled in the same way - for (int i = 1; (part == 0) && (i < value._bits.Length); i++) - { - part = value._bits[i]; - result += (sizeof(uint) * 8); - - i++; - } + uint part = value._bits[0]; - result += uint.TrailingZeroCount(part); - } - else + for (int i = 1; (part == 0) && (i < value._bits.Length); i++) { - // When the value is negative, we need to tzcnt the two's complement representation - // We'll do this "inline" to avoid needing to unnecessarily allocate. - - uint part = ~value._bits[0] + 1; - - for (int i = 1; (part == 0) && (i < value._bits.Length); i++) - { - // Simply process bits, adding the carry while the previous value is zero - - part = ~value._bits[i] + 1; - result += (sizeof(uint) * 8); - - i++; - } - - result += uint.TrailingZeroCount(part); + part = value._bits[i]; + result += (sizeof(uint) * 8); } + result += uint.TrailingZeroCount(part); + return result; } @@ -4489,7 +4466,7 @@ private static bool TryConvertFromTruncating(TOther value, out BigIntege /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(BigInteger value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(BigInteger value, [MaybeNullWhen(false)] out TOther result) { if (typeof(TOther) == typeof(byte)) { @@ -4601,14 +4578,14 @@ static bool INumberBase.TryConvertToChecked(BigInteger value } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(BigInteger value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(BigInteger value, [MaybeNullWhen(false)] out TOther result) { if (typeof(TOther) == typeof(byte)) { @@ -4794,14 +4771,14 @@ static bool INumberBase.TryConvertToSaturating(BigInteger va } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(BigInteger value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(BigInteger value, [MaybeNullWhen(false)] out TOther result) { if (typeof(TOther) == typeof(byte)) { @@ -5190,7 +5167,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs index 7e59c6dd002603..d3edb7d44afb95 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs @@ -35,7 +35,9 @@ stackalloc uint[StackAllocThreshold] value.CopyTo(valueCopy); valueCopy.Slice(value.Length).Clear(); - PowCore(valueCopy, value.Length, temp, power, bits).CopyTo(bits); + Span result = PowCore(valueCopy, value.Length, temp, power, bits); + result.CopyTo(bits); + bits.Slice(result.Length).Clear(); if (tempFromPool != null) ArrayPool.Shared.Return(tempFromPool); @@ -50,19 +52,19 @@ private static Span PowCore(Span value, int valueLength, Span Debug.Assert(value.Length == temp.Length); result[0] = 1; - int bitsLength = 1; + int resultLength = 1; // The basic pow algorithm using square-and-multiply. while (power != 0) { if ((power & 1) == 1) - bitsLength = MultiplySelf(ref result, bitsLength, value.Slice(0, valueLength), ref temp); + resultLength = MultiplySelf(ref result, resultLength, value.Slice(0, valueLength), ref temp); if (power != 1) valueLength = SquareSelf(ref value, valueLength, ref temp); power >>= 1; } - return result; + return result.Slice(0, resultLength); } private static int MultiplySelf(ref Span left, int leftLength, ReadOnlySpan right, ref Span temp) @@ -217,7 +219,11 @@ public static void Pow(ReadOnlySpan value, uint power, Span valueCopy = (size <= StackAllocThreshold ? stackalloc uint[StackAllocThreshold] : valueCopyFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - valueCopy.Clear(); + + // smallish optimization here: + // subsequent operations will copy the elements to the beginning of the buffer, + // no need to clear everything + valueCopy.Slice(value.Length).Clear(); if (value.Length > modulus.Length) { @@ -262,7 +268,11 @@ public static void Pow(ReadOnlySpan value, ReadOnlySpan power, Span valueCopy = (size <= StackAllocThreshold ? stackalloc uint[StackAllocThreshold] : valueCopyFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - valueCopy.Clear(); + + // smallish optimization here: + // subsequent operations will copy the elements to the beginning of the buffer, + // no need to clear everything + valueCopy.Slice(value.Length).Clear(); if (value.Length > modulus.Length) { @@ -305,7 +315,9 @@ private static void PowCore(Span value, int valueLength, if (modulus.Length < ReducerThreshold) { - PowCore(value, valueLength, power, modulus, bits, 1, temp).CopyTo(bits); + Span result = PowCore(value, valueLength, power, modulus, bits, 1, temp); + result.CopyTo(bits); + bits.Slice(result.Length).Clear(); } else { @@ -341,7 +353,9 @@ stackalloc uint[StackAllocThreshold] if (rFromPool != null) ArrayPool.Shared.Return(rFromPool); - PowCore(value, valueLength, power, reducer, bits, 1, temp).CopyTo(bits); + Span result = PowCore(value, valueLength, power, reducer, bits, 1, temp); + result.CopyTo(bits); + bits.Slice(result.Length).Clear(); if (muFromPool != null) ArrayPool.Shared.Return(muFromPool); @@ -361,7 +375,9 @@ private static void PowCore(Span value, int valueLength, if (modulus.Length < ReducerThreshold) { - PowCore(value, valueLength, power, modulus, bits, 1, temp).CopyTo(bits); + Span result = PowCore(value, valueLength, power, modulus, bits, 1, temp); + result.CopyTo(bits); + bits.Slice(result.Length).Clear(); } else { @@ -397,7 +413,9 @@ stackalloc uint[StackAllocThreshold] if (rFromPool != null) ArrayPool.Shared.Return(rFromPool); - PowCore(value, valueLength, power, reducer, bits, 1, temp).CopyTo(bits); + Span result = PowCore(value, valueLength, power, reducer, bits, 1, temp); + result.CopyTo(bits); + bits.Slice(result.Length).Clear(); if (muFromPool != null) ArrayPool.Shared.Return(muFromPool); @@ -523,7 +541,7 @@ private static Span PowCore(Span value, int valueLength, power >>= 1; } - return result; + return result.Slice(0, resultLength); } } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs index 97c0a5558f5e13..f2e2220b6ba08d 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs @@ -1613,7 +1613,7 @@ private static bool TryConvertFrom(TOther value, out Complex result) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(Complex value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(Complex value, [MaybeNullWhen(false)] out TOther result) { // Complex numbers with an imaginary part can't be represented as a "real number" // so we'll throw an OverflowException for this scenario for integer types and @@ -1805,14 +1805,14 @@ static bool INumberBase.TryConvertToChecked(Complex value, [Not } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(Complex value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(Complex value, [MaybeNullWhen(false)] out TOther result) { // Complex numbers with an imaginary part can't be represented as a "real number" // and there isn't really a well-defined way to "saturate" to just a real value. @@ -1949,14 +1949,14 @@ static bool INumberBase.TryConvertToSaturating(Complex value, [ } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(Complex value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(Complex value, [MaybeNullWhen(false)] out TOther result) { // Complex numbers with an imaginary part can't be represented as a "real number" // so we'll only consider the real part for the purposes of truncation. @@ -2085,7 +2085,7 @@ static bool INumberBase.TryConvertToTruncating(Complex value, [ } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/modpow.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/modpow.cs index 54af6812151704..68f67892fcf76c 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/modpow.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/modpow.cs @@ -274,6 +274,31 @@ public static void ModPowAxiom() } } + [Fact] + public static void RegressionIssueRuntime70330() + { + byte[] tempByteArray1 = { 226, 32 }; + byte[] tempByteArray2 = { 113 }; + byte[] tempByteArray3 = { 15, 8, 201, 158, 96, 200, 233, 243, 184, 0, 33, 203, 210, 80, 174, 198, 244, 177, 223, 221, 168, 243, 233, 133, 103, 252, 219, 195, 187, 227, 215, 54, 66, 248, 37, 186, 232, 45, 227, 147, 100, 14, 121, 244, 56, 89, 181, 120, 205, 4, 59, 48, 65, 239, 221, 28, 30, 68, 55, 99, 237, 38, 56, 213, 40, 234, 136, 218, 42, 244, 222, 198, 205 }; + VerifyIdentityString( + Print(tempByteArray3) + Print(tempByteArray2) + Print(tempByteArray1) + "tModPow", + Print(tempByteArray3) + Print(tempByteArray2) + Print(tempByteArray1) + "bPow" + " bRemainder" + ); + } + + [Fact] + public static void RegressionIssuePerformance2575() + { + byte[] tempByteArray1 = { 0x93, 0x30, 0x70, 0xD8, 0x74, 0x0A, 0x70, 0x79, 0x18, 0x5A, 0xCD, 0x2D, 0x39, 0xBF, 0x36, 0xC6, 0x24, 0xDE, 0x4B, 0xD5, 0xC7, 0xB4, 0x56, 0x23, 0xB2, 0xB4, 0xB7, 0x43, 0xE5, 0x05, 0xDD, 0xAF, 0x97, 0x81, 0x67, 0xCB, 0x67, 0xE9, 0x53, 0x5A, 0x00, 0x42, 0x9B, 0x20, 0x56, 0xFA, 0xBE, 0x27, 0x6A, 0x14, 0x36, 0x17, 0x49, 0xC5, 0xAC, 0xDF, 0xDA, 0x4C, 0x26, 0xC0, 0x52, 0x2C, 0x93, 0x13, 0x7D, 0x1E, 0x96, 0xB8, 0x58, 0x6B, 0x5F, 0x3A, 0x8B, 0xF1, 0xD5, 0x84, 0x18, 0x36, 0xCC, 0xB7, 0xF5, 0x90, 0xD9, 0xD1, 0xCE, 0xC5, 0x65, 0xD2, 0xD5, 0x87, 0xF0, 0x1B, 0xC3, 0x92, 0x07, 0xD3, 0xAF, 0x88, 0xA2, 0x38, 0x64, 0x06, 0xCE, 0xFE, 0xB5, 0xFC, 0x8C, 0x58, 0xEF, 0x27, 0xC6, 0xA4, 0x7F, 0x6E, 0xCA, 0xC2, 0x53, 0xC2, 0x44, 0xB7, 0xB8, 0xC3, 0xE2, 0xD0, 0x7A, 0x43, 0x76, 0xF8, 0x00 }; + byte[] tempByteArray2 = { 0x02, 0xD4, 0x90, 0x93, 0x94, 0x52, 0xEF, 0xC1, 0xDA, 0x1B, 0xD2, 0x39, 0x0D, 0xE3, 0xAD, 0xC1, 0x4C, 0x9B, 0x54, 0xC8, 0x44, 0x7F, 0xED, 0x43, 0xEA, 0x7F, 0xA1, 0x23, 0xFE, 0x84, 0x71, 0x85, 0x93, 0x6E, 0xEC, 0x53, 0x35, 0x10, 0xEB, 0x1C, 0xDA, 0x01, 0x78, 0xA6, 0x71, 0x7E, 0xAB, 0xE0, 0x35, 0x0F, 0x2E, 0xA8, 0x21, 0x30, 0xA5, 0x83, 0xE7, 0x4C, 0xA9, 0x14, 0xB0, 0xCC, 0xC3, 0x56, 0xAA, 0x4C, 0xA5, 0x9E, 0xF6, 0x5A, 0x8B, 0x3C, 0xAF, 0x38, 0xED, 0x43, 0x06, 0x46, 0xD2, 0x6C, 0xD3, 0xC1, 0xED, 0xEE, 0x55, 0xC8, 0x63, 0x63, 0xC9, 0x69, 0x6B, 0xE8, 0x67, 0xD6, 0x6B, 0x0D, 0x3E, 0x22, 0xFC, 0x24, 0xD8, 0x0C, 0xEA, 0xDB, 0x83, 0xF2, 0xF9, 0xE9, 0x43, 0x61, 0x7C, 0xA0, 0x7A, 0x3D, 0x41, 0xEF, 0xDF, 0xD4, 0x00, 0xE1, 0xE9, 0x42, 0xD5, 0x8C, 0xEE, 0xA3, 0xD5, 0xD8, 0x00 }; + byte[] tempByteArray3 = { 0xDF, 0x05, 0xE4, 0x4A, 0xAD, 0x93, 0xC6, 0xD7, 0x00 }; + byte[] tempByteArray4 = { 0xC6, 0x57, 0x30, 0x3F, 0xCE, 0x21, 0xA0, 0x3D }; + VerifyIdentityString( + Print(tempByteArray3) + Print(tempByteArray2) + Print(tempByteArray1) + "tModPow", + Print(tempByteArray4) + ); + } + [Fact] public static void ModPowBoundary() { diff --git a/src/libraries/System.Runtime.Numerics/tests/BigIntegerTests.GenericMath.cs b/src/libraries/System.Runtime.Numerics/tests/BigIntegerTests.GenericMath.cs index d4e06061325aa0..3e19f1cf22e97e 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigIntegerTests.GenericMath.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigIntegerTests.GenericMath.cs @@ -366,6 +366,9 @@ public static void TrailingZeroCountTest() Assert.Equal((BigInteger)63, BinaryIntegerHelper.TrailingZeroCount(Int64MaxValuePlusOne)); Assert.Equal((BigInteger)0, BinaryIntegerHelper.TrailingZeroCount(UInt64MaxValue)); + + Assert.Equal((BigInteger)1000, BinaryIntegerHelper.TrailingZeroCount(BigInteger.Pow(2, 1000))); + Assert.Equal((BigInteger)1000, BinaryIntegerHelper.TrailingZeroCount(-BigInteger.Pow(2, 1000))); } [Fact] diff --git a/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs b/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs index 7e48c51bc50aad..95891b7bcfa3a3 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs @@ -25,6 +25,7 @@ public partial class BinaryFormatterTests : FileCleanupTestBase [ConditionalTheory(typeof(Environment), nameof(Environment.Is64BitProcess))] [SkipOnCoreClr("Long running tests: https://github.com/dotnet/runtime/issues/11191", ~RuntimeConfiguration.Release)] [ActiveIssue("https://github.com/dotnet/runtime/issues/35915", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoInterpreter))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/75281", typeof(PlatformDetection), nameof(PlatformDetection.IsPpc64leProcess))] [InlineData(2 * 6_584_983 - 2)] // previous limit [InlineData(2 * 7_199_369 - 2)] // last pre-computed prime number public void SerializeHugeObjectGraphs(int limit) diff --git a/src/libraries/System.Runtime.Serialization.Schema/Directory.Build.props b/src/libraries/System.Runtime.Serialization.Schema/Directory.Build.props index 4a2bc78c16e30a..e7d357018da7bf 100644 --- a/src/libraries/System.Runtime.Serialization.Schema/Directory.Build.props +++ b/src/libraries/System.Runtime.Serialization.Schema/Directory.Build.props @@ -1,8 +1,9 @@ - + + Microsoft true browser;ios;tvos;maccatalyst - \ No newline at end of file + diff --git a/src/libraries/System.Runtime.Serialization.Schema/src/System.Runtime.Serialization.Schema.csproj b/src/libraries/System.Runtime.Serialization.Schema/src/System.Runtime.Serialization.Schema.csproj index d1d56008051099..b193422d135535 100644 --- a/src/libraries/System.Runtime.Serialization.Schema/src/System.Runtime.Serialization.Schema.csproj +++ b/src/libraries/System.Runtime.Serialization.Schema/src/System.Runtime.Serialization.Schema.csproj @@ -1,7 +1,7 @@ $(NetCoreAppCurrent) - Microsoft + true true Provides support for importing and exporting xsd schemas for DataContractSerializer. @@ -25,4 +25,4 @@ System.Runtime.Serialization.Schema.XsdDataContractImporter - \ No newline at end of file + diff --git a/src/libraries/System.Runtime.Serialization.Schema/src/System/Runtime/Serialization/Schema/CodeExporter.cs b/src/libraries/System.Runtime.Serialization.Schema/src/System/Runtime/Serialization/Schema/CodeExporter.cs index 45476be4423f10..78a0301fe3ad50 100644 --- a/src/libraries/System.Runtime.Serialization.Schema/src/System/Runtime/Serialization/Schema/CodeExporter.cs +++ b/src/libraries/System.Runtime.Serialization.Schema/src/System/Runtime/Serialization/Schema/CodeExporter.cs @@ -520,8 +520,8 @@ private static CodeTypeDeclaration CreateTypeDeclaration(string typeName, DataCo CodeAttributeDeclaration generatedCodeAttribute = new CodeAttributeDeclaration(typeof(GeneratedCodeAttribute).FullName!); AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName(); - generatedCodeAttribute.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(assemblyName.Name))); - generatedCodeAttribute.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(assemblyName.Version?.ToString()))); + generatedCodeAttribute.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(assemblyName.Name!))); + generatedCodeAttribute.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(assemblyName.Version?.ToString()!))); // System.Diagnostics.DebuggerStepThroughAttribute not allowed on enums // ensure that the attribute is only generated on types that are not enums @@ -820,7 +820,7 @@ private void ExportClassDataContract(DataContract classDataContract, ContractCod { ContractCodeDomInfo baseContractCodeDomInfo = GetContractCodeDomInfo(classDataContract.BaseContract); Debug.Assert(baseContractCodeDomInfo.IsProcessed, "Cannot generate code for type if code for base type has not been generated"); - type.BaseTypes.Add(baseContractCodeDomInfo.TypeReference); + type.BaseTypes.Add(baseContractCodeDomInfo.TypeReference!); AddBaseMemberNames(baseContractCodeDomInfo, contractCodeDomInfo); if (baseContractCodeDomInfo.ReferencedTypeExists) { @@ -1153,7 +1153,7 @@ private void ExportISerializableDataContract(DataContract classDataContract, Con { ContractCodeDomInfo baseContractCodeDomInfo = GetContractCodeDomInfo(classDataContract.BaseContract); GenerateType(classDataContract.BaseContract, baseContractCodeDomInfo); - type.BaseTypes.Add(baseContractCodeDomInfo.TypeReference); + type.BaseTypes.Add(baseContractCodeDomInfo.TypeReference!); if (baseContractCodeDomInfo.ReferencedTypeExists) { Type? actualType = (Type?)baseContractCodeDomInfo.TypeReference?.UserData[s_codeUserDataActualTypeKey]; @@ -1238,7 +1238,7 @@ private void ExportCollectionDataContract(DataContract collectionContract, Contr Debug.Assert(contractCodeDomInfo.TypeDeclaration != null); CodeTypeDeclaration generatedType = contractCodeDomInfo.TypeDeclaration; - generatedType.BaseTypes.Add(baseTypeReference); + generatedType.BaseTypes.Add(baseTypeReference!); CodeAttributeDeclaration collectionContractAttribute = new CodeAttributeDeclaration(GetClrTypeFullName(typeof(CollectionDataContractAttribute))); collectionContractAttribute.Arguments.Add(new CodeAttributeArgument(ImportGlobals.NameProperty, new CodePrimitiveExpression(dataContractName))); collectionContractAttribute.Arguments.Add(new CodeAttributeArgument(ImportGlobals.NamespaceProperty, new CodePrimitiveExpression(collectionContract.XmlName.Namespace))); @@ -1673,7 +1673,7 @@ private static CodeThisReferenceExpression ThisReference private static CodePrimitiveExpression NullReference { - get { return new CodePrimitiveExpression(null); } + get { return new CodePrimitiveExpression(null!); } } private CodeParameterDeclarationExpression SerializationInfoParameter @@ -1784,12 +1784,12 @@ private CodeMemberMethod GetSchemaStaticMethod new CodeTypeReferenceExpression(GetCodeTypeReference(typeof(XmlSerializableServices))), nameof(XmlSerializableServices.AddDefaultSchema), new CodeArgumentReferenceExpression(paramDeclaration.Name), - new CodeFieldReferenceExpression(null, TypeNameFieldName) + new CodeFieldReferenceExpression(null!, TypeNameFieldName) ) ); getSchemaStaticMethod.Statements.Add( new CodeMethodReturnStatement( - new CodeFieldReferenceExpression(null, TypeNameFieldName) + new CodeFieldReferenceExpression(null!, TypeNameFieldName) ) ); return getSchemaStaticMethod; diff --git a/src/libraries/System.Runtime.Serialization.Schema/tests/System/Runtime/Serialization/Schema/SchemaUtils.cs b/src/libraries/System.Runtime.Serialization.Schema/tests/System/Runtime/Serialization/Schema/SchemaUtils.cs index cea8d7333ccb7c..d0a4eda2369747 100644 --- a/src/libraries/System.Runtime.Serialization.Schema/tests/System/Runtime/Serialization/Schema/SchemaUtils.cs +++ b/src/libraries/System.Runtime.Serialization.Schema/tests/System/Runtime/Serialization/Schema/SchemaUtils.cs @@ -14,8 +14,7 @@ internal class SchemaUtils static XmlWriterSettings writerSettings = new XmlWriterSettings() { Indent = true }; #region Test Data - internal static XmlSchemaSet PositiveSchemas = SchemaUtils.ReadStringsIntoSchemaSet( - new string[] { + private static string[] _positiveSchemas = new string[] { @" @@ -30,10 +29,10 @@ internal class SchemaUtils ", - }); + }; + internal static XmlSchemaSet PositiveSchemas => ReadStringsIntoSchemaSet(_positiveSchemas); - internal static XmlSchemaSet IsReferenceSchemas = SchemaUtils.ReadStringsIntoSchemaSet( - new string[] { + private static string[] _isReferenceSchemas = new string[] { @" @@ -53,10 +52,10 @@ internal class SchemaUtils ", - }); + }; + internal static XmlSchemaSet IsReferenceSchemas => ReadStringsIntoSchemaSet(_isReferenceSchemas); - internal static XmlSchemaSet MixedSchemas = SchemaUtils.ReadStringsIntoSchemaSet( - new string[] { + private static string[] _mixedSchemas = new string[] { @" @@ -67,7 +66,8 @@ internal class SchemaUtils ", - }); + }; + internal static XmlSchemaSet MixedSchemas => ReadStringsIntoSchemaSet(_mixedSchemas); internal static string[] NegativeSchemaStrings = new string[] { diff --git a/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryReaderTests.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryReaderTests.cs index e15f9fc2df544b..6880fc48f02270 100644 --- a/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryReaderTests.cs +++ b/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryReaderTests.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.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Linq; @@ -193,8 +194,10 @@ public static void BinaryXml_ReadPrimitiveTypes() AssertReadContentFromBinary(8.20788039913184E-304, XmlBinaryNodeType.DoubleText, new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 }); AssertReadContentFromBinary(guid, XmlBinaryNodeType.GuidText, guid.ToByteArray()); AssertReadContentFromBinary(new TimeSpan(0x0807060504030201), XmlBinaryNodeType.TimeSpanText, new byte[] { 01, 02, 03, 04, 05, 06, 07, 08 }); - AssertReadContentFromBinary(new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b), XmlBinaryNodeType.DecimalText - , new byte[] { 0x0, 0x0, 0x1b, 0x80, 0x4, 0x3, 0x2, 0x1, 0x23, 0x22, 0x21, 0x20, 0x13, 0x12, 0x11, 0x10 }); + AssertReadContentFromBinary(new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b), XmlBinaryNodeType.DecimalText, + new byte[] { 0x0, 0x0, 0x1b, 0x80, 0x4, 0x3, 0x2, 0x1, 0x23, 0x22, 0x21, 0x20, 0x13, 0x12, 0x11, 0x10 }); + AssertReadContentFromBinary(new DateTime(2022, 8, 26, 12, 34, 56, DateTimeKind.Utc), XmlBinaryNodeType.DateTimeText, + new byte[] { 0x00, 0x18, 0xdf, 0x61, 0x5f, 0x87, 0xda, 0x48 }); // Double can be represented as float or inte as long as no detail is lost AssertReadContentFromBinary((double)0x0100, XmlBinaryNodeType.Int16Text, new byte[] { 0x00, 0x01 }); @@ -205,6 +208,16 @@ public static void BinaryXml_ReadPrimitiveTypes() public static void BinaryXml_Array_RoundTrip() { int[] ints = new int[] { -1, 0x01020304, 0x11223344, -1 }; + float[] floats = new float[] { 1.2345f, 2.3456f }; + double[] doubles = new double[] { 1.2345678901, 2.3456789012 }; + decimal[] decimals = new[] { + new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b), + new decimal(0x50515253, 0x40414243, 0x31323334, false, scale: 0x1c) + }; + DateTime[] datetimes = new[] { + new DateTime(2022, 8, 26, 12, 34, 56, DateTimeKind.Utc), + new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local) + }; TimeSpan[] timespans = new[] { TimeSpan.FromTicks(0x0102030405060708), TimeSpan.FromTicks(0x1011121314151617) }; // Write more than 4 kb in a single call to ensure we hit path for reading (and writing happens on 512b) large arrays long[] longs = Enumerable.Range(0x01020304, 513).Select(i => (long)i | (long)(~i << 32)).ToArray(); @@ -217,6 +230,10 @@ public static void BinaryXml_Array_RoundTrip() using var writer = XmlDictionaryWriter.CreateBinaryWriter(ms); writer.WriteStartElement("root"); writer.WriteArray(null, "ints", null, ints, 1, 2); + writer.WriteArray(null, "floats", null, floats, 0, floats.Length); + writer.WriteArray(null, "doubles", null, doubles, 0, doubles.Length); + writer.WriteArray(null, "decimals", null, decimals, 0, decimals.Length); + writer.WriteArray(null, "datetimes", null, datetimes, 0, datetimes.Length); writer.WriteArray(null, "timespans", null, timespans, 0, timespans.Length); writer.WriteArray(null, "longs", null, longs, 0, longs.Length); writer.WriteArray(null, "guids", null, guids, 0, guids.Length); @@ -230,6 +247,10 @@ public static void BinaryXml_Array_RoundTrip() using var reader = XmlDictionaryReader.CreateBinaryReader(ms, XmlDictionaryReaderQuotas.Max); reader.ReadStartElement("root"); int intsRead = reader.ReadArray("ints", string.Empty, actualInts, 1, 3); + float[] actualFloats = reader.ReadSingleArray("floats", string.Empty); + double[] actualDoubles = reader.ReadDoubleArray("doubles", string.Empty); + decimal[] actualDecimals = reader.ReadDecimalArray("decimals", string.Empty); + DateTime[] actualDateTimes = reader.ReadDateTimeArray("datetimes", string.Empty); TimeSpan[] actualTimeSpans = reader.ReadTimeSpanArray("timespans", string.Empty); long[] actualLongs = reader.ReadInt64Array("longs", string.Empty); Guid[] actualGuids = reader.ReadGuidArray("guids", string.Empty); @@ -240,6 +261,10 @@ public static void BinaryXml_Array_RoundTrip() Assert.Equal(2, intsRead); AssertExtensions.SequenceEqual(ints, actualInts); AssertExtensions.SequenceEqual(actualLongs, longs); + AssertExtensions.SequenceEqual(actualFloats, floats); + AssertExtensions.SequenceEqual(actualDoubles, doubles); + AssertExtensions.SequenceEqual(actualDecimals, decimals); + AssertExtensions.SequenceEqual(actualDateTimes, datetimes); AssertExtensions.SequenceEqual(actualTimeSpans, timespans); AssertExtensions.SequenceEqual(actualGuids, guids); } diff --git a/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs index 719a06d6e48cf9..3ad4d32400e371 100644 --- a/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs +++ b/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Linq; @@ -320,6 +321,179 @@ public static void FragmentTest() Assert.False(FragmentHelper.CanFragment(writer)); } + [Fact] + public static void BinaryWriter_PrimitiveTypes() + { + using MemoryStream ms = new(); + using XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(ms); + writer.WriteStartElement("root"); + + AssertBytesWritten(x => x.WriteValue((byte)0x78), XmlBinaryNodeType.Int8Text, new byte[] { 0x78 }); + AssertBytesWritten(x => x.WriteValue((short)0x1234), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0x12 }); + AssertBytesWritten(x => x.WriteValue(unchecked((short)0xf234)), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0xf2 }); + AssertBytesWritten(x => x.WriteValue((int)0x12345678), XmlBinaryNodeType.Int32Text, new byte[] { 0x78, 0x56, 0x34, 0x12 }); + AssertBytesWritten(x => x.WriteValue((long)0x0102030412345678), XmlBinaryNodeType.Int64Text, new byte[] { 0x78, 0x56, 0x34, 0x12, 04, 03, 02, 01 }); + + // Integer values should be represented using smalles possible type + AssertBytesWritten(x => x.WriteValue((long)0), XmlBinaryNodeType.ZeroText, Span.Empty); + AssertBytesWritten(x => x.WriteValue((long)1), XmlBinaryNodeType.OneText, Span.Empty); + AssertBytesWritten(x => x.WriteValue((int)0x00000078), XmlBinaryNodeType.Int8Text, new byte[] { 0x78 }); + AssertBytesWritten(x => x.WriteValue(unchecked((int)0xfffffff0)), XmlBinaryNodeType.Int8Text, new byte[] { 0xf0 }); + AssertBytesWritten(x => x.WriteValue((int)0x00001234), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0x12 }); + AssertBytesWritten(x => x.WriteValue(unchecked((int)0xfffff234)), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0xf2 }); + AssertBytesWritten(x => x.WriteValue((long)0x12345678), XmlBinaryNodeType.Int32Text, new byte[] { 0x78, 0x56, 0x34, 0x12 }); + AssertBytesWritten(x => x.WriteValue(unchecked((long)0xfffffffff2345678)), XmlBinaryNodeType.Int32Text, new byte[] { 0x78, 0x56, 0x34, 0xf2 }); + + float f = 1.23456788f; + ReadOnlySpan floatBytes = new byte[] { 0x52, 0x06, 0x9e, 0x3f }; + double d = 1.0 / 3.0; + ReadOnlySpan doubleBytes = new byte[] { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f }; + Guid guid = new Guid(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }); + DateTime datetime = new DateTime(2022, 8, 26, 12, 34, 56, DateTimeKind.Utc); + Span datetimeBytes = stackalloc byte[8]; + BinaryPrimitives.WriteInt64LittleEndian(datetimeBytes, datetime.ToBinary()); + + AssertBytesWritten(x => x.WriteValue(f), XmlBinaryNodeType.FloatText, floatBytes); + AssertBytesWritten(x => x.WriteValue(new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b)), XmlBinaryNodeType.DecimalText, + new byte[] { 0x0, 0x0, 0x1b, 0x80, 0x4, 0x3, 0x2, 0x1, 0x23, 0x22, 0x21, 0x20, 0x13, 0x12, 0x11, 0x10 }); + AssertBytesWritten(x => x.WriteValue(guid), XmlBinaryNodeType.GuidText, guid.ToByteArray()); + AssertBytesWritten(x => x.WriteValue(new TimeSpan(0x0807060504030201)), XmlBinaryNodeType.TimeSpanText, new byte[] { 01, 02, 03, 04, 05, 06, 07, 08 }); + AssertBytesWritten(x => x.WriteValue(datetime), XmlBinaryNodeType.DateTimeText, datetimeBytes); + + // Double can be represented as float or int as long as no detail is lost + AssertBytesWritten(x => x.WriteValue((double)f), XmlBinaryNodeType.FloatText, floatBytes); + AssertBytesWritten(x => x.WriteValue((double)0x0100), XmlBinaryNodeType.Int16Text, new byte[] { 0x00, 0x01 }); + AssertBytesWritten(x => x.WriteValue(d), XmlBinaryNodeType.DoubleText, doubleBytes); + + + void AssertBytesWritten(Action action, XmlBinaryNodeType nodeType, ReadOnlySpan expected) + { + writer.WriteStartElement("a"); + + // Reset stream so we only compare the actual value written (including end element) + writer.Flush(); + ms.Position = 0; + ms.SetLength(0); + + action(writer); + + writer.Flush(); + ms.TryGetBuffer(out ArraySegment segement); + Assert.Equal(nodeType, (XmlBinaryNodeType)segement[0]); + AssertExtensions.SequenceEqual(expected, segement.AsSpan(1)); + writer.WriteEndElement(); + } + } + + + [Fact] + public static void BinaryWriter_Arrays() + { + using var ms = new MemoryStream(); + using var writer = XmlDictionaryWriter.CreateBinaryWriter(ms); + writer.WriteStartElement("root"); + int offset = 1; + int count = 2; + + bool[] bools = new bool[] { false, true, false, true }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, bools, offset, count), XmlBinaryNodeType.BoolTextWithEndElement, + count, new byte[] { 1, 0 }); + + short[] shorts = new short[] { -1, 0x0102, 0x1122, -1 }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, shorts, offset, count), XmlBinaryNodeType.Int16TextWithEndElement, + count, new byte[] { 2, 1, 0x22, 0x11 }); + + int[] ints = new int[] { -1, 0x01020304, 0x11223344, -1 }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, ints, offset, count), XmlBinaryNodeType.Int32TextWithEndElement, + count, new byte[] { 4, 3, 2, 1, 0x44, 0x33, 0x22, 0x11 }); + + long[] longs = new long[] { -1, 0x0102030405060708, 0x1122334455667788, -1 }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, longs, offset, count), XmlBinaryNodeType.Int64TextWithEndElement, + count, new byte[] { 8, 7, 6, 5, 4, 3, 2, 1, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 }); + + float[] floats = new float[] { -1.0f, 1.23456788f, 1.23456788f, -1.0f }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, floats, offset, count), XmlBinaryNodeType.FloatTextWithEndElement, + count, new byte[] { 0x52, 0x06, 0x9e, 0x3f, 0x52, 0x06, 0x9e, 0x3f }); + + double[] doubles = new double[] { -1.0, 1.0 / 3.0, 1.0 / 3.0, -1.0 }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, doubles, offset, count), XmlBinaryNodeType.DoubleTextWithEndElement, + count, new byte[] { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f }); + + decimal[] decimals = new[] { + new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b), + new decimal(0x50515253, 0x40414243, 0x31323334, false, scale: 0x1c) + }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, decimals, 0, decimals.Length), XmlBinaryNodeType.DecimalTextWithEndElement, + decimals.Length, new byte[] { 0x0, 0x0, 0x1b, 0x80, 0x4, 0x3, 0x2, 0x1, + 0x23, 0x22, 0x21, 0x20, 0x13, 0x12, 0x11, 0x10, + 0x0, 0x0, 0x1c, 0x00, 0x34, 0x33, 0x32, 0x31, + 0x53, 0x52, 0x51, 0x50, 0x43, 0x42, 0x41, 0x40 }); + + DateTime[] datetimes = new[] { + new DateTime(2022, 8, 26, 12, 34, 56, DateTimeKind.Utc), + new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local) + }; + Span datetimeBytes = stackalloc byte[8 * datetimes.Length]; + for (int i = 0; i < datetimes.Length; i++) + { + BinaryPrimitives.WriteInt64LittleEndian(datetimeBytes.Slice(8 * i), datetimes[i].ToBinary()); + } + AssertBytesWritten(x => x.WriteArray(null, "a", null, datetimes, 0, datetimes.Length), XmlBinaryNodeType.DateTimeTextWithEndElement, + datetimes.Length, datetimeBytes); + + TimeSpan[] timespans = new[] { new TimeSpan(0x0807060504030201), new TimeSpan(0x1817161514131211) }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, timespans, 0, timespans.Length), XmlBinaryNodeType.TimeSpanTextWithEndElement, + timespans.Length, new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 }); + + Guid[] guids = new Guid[] + { + new Guid(new ReadOnlySpan(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 })), + new Guid(new ReadOnlySpan(new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160 })) + }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, guids, 0, guids.Length), XmlBinaryNodeType.GuidTextWithEndElement, + guids.Length, new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160 }); + + // Write more than 512 bytes in a single call to trigger different writing logic in XmlStreamNodeWriter.WriteBytes + long[] many_longs = Enumerable.Range(0x01020304, 127).Select(i => (long)i | (long)(~i << 32)).ToArray(); + Span many_longBytes = stackalloc byte[8 * many_longs.Length]; + for (int i = 0; i < many_longs.Length; i++) + { + BinaryPrimitives.WriteInt64LittleEndian(many_longBytes.Slice(8 * i), many_longs[i]); + } + AssertBytesWritten(x => x.WriteArray(null, "a", null, many_longs, 0, many_longs.Length), XmlBinaryNodeType.Int64TextWithEndElement, + many_longs.Length, many_longBytes); + + void AssertBytesWritten(Action action, XmlBinaryNodeType nodeType, int count, ReadOnlySpan expected) + { + // Reset stream so we only compare the actual value written (including end element) + writer.Flush(); + ms.Position = 0; + ms.SetLength(0); + + action(writer); + + writer.Flush(); + ms.TryGetBuffer(out ArraySegment segement); + + var actual = segement.AsSpan(); + Assert.Equal(XmlBinaryNodeType.Array, (XmlBinaryNodeType)actual[0]); + Assert.Equal(XmlBinaryNodeType.ShortElement, (XmlBinaryNodeType)actual[1]); + int elementLength = actual[2]; + Assert.InRange(elementLength, 0, 0x8f); // verify count is single byte + Assert.Equal(XmlBinaryNodeType.EndElement, (XmlBinaryNodeType)actual[3 + elementLength]); + + actual = actual.Slice(4 + elementLength); + // nodetype and count + Assert.Equal(nodeType, (XmlBinaryNodeType)actual[0]); + Assert.Equal(checked((sbyte)count), (sbyte)actual[1]); + + AssertExtensions.SequenceEqual(expected, actual.Slice(2)); + } + } + private static bool ReadTest(MemoryStream ms, Encoding encoding, ReaderWriterFactory.ReaderWriterType rwType, byte[] byteArray) { ms.Position = 0; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 5d04d15a81dc9b..aeb4fb35061c55 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -818,9 +818,9 @@ public static void SetByte(System.Array array, int index, byte value) { } static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out byte result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out byte result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out byte result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(byte value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(byte value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(byte value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(byte value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(byte value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(byte value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static byte System.Numerics.INumber.CopySign(byte value, byte sign) { throw null; } static byte System.Numerics.INumber.MaxNumber(byte x, byte y) { throw null; } static byte System.Numerics.INumber.MinNumber(byte x, byte y) { throw null; } @@ -1000,9 +1000,9 @@ public CannotUnloadAppDomainException(string? message, System.Exception? innerEx static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out char result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out char result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out char result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(char value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(char value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(char value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(char value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(char value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(char value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static bool System.Numerics.INumberBase.TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out char result) { throw null; } static bool System.Numerics.INumberBase.TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out char result) { throw null; } static char System.Numerics.IShiftOperators.operator <<(char value, int shiftAmount) { throw null; } @@ -2016,9 +2016,9 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out decimal result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out decimal result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out decimal result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(decimal value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(decimal value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(decimal value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(decimal value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(decimal value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(decimal value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static decimal System.Numerics.INumber.MaxNumber(decimal x, decimal y) { throw null; } static decimal System.Numerics.INumber.MinNumber(decimal x, decimal y) { throw null; } void System.Runtime.Serialization.IDeserializationCallback.OnDeserialization(object? sender) { } @@ -2266,9 +2266,9 @@ public DivideByZeroException(string? message, System.Exception? innerException) static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out double result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out double result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out double result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(double value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(double value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(double value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(double value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(double value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(double value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static double System.Numerics.ISubtractionOperators.operator -(double left, double right) { throw null; } static double System.Numerics.IUnaryNegationOperators.operator -(double value) { throw null; } static double System.Numerics.IUnaryPlusOperators.operator +(double value) { throw null; } @@ -2908,9 +2908,9 @@ public enum GCNotificationStatus static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out System.Half result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out System.Half result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out System.Half result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(System.Half value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Half value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Half value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(System.Half value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Half value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Half value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } public static System.Half Tan(System.Half x) { throw null; } public static System.Half Tanh(System.Half x) { throw null; } public static System.Half TanPi(System.Half x) { throw null; } @@ -3209,9 +3209,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out System.Int128 result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out System.Int128 result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out System.Int128 result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(System.Int128 value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Int128 value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Int128 value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(System.Int128 value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Int128 value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Int128 value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static System.Int128 System.Numerics.INumber.MaxNumber(System.Int128 x, System.Int128 y) { throw null; } static System.Int128 System.Numerics.INumber.MinNumber(System.Int128 x, System.Int128 y) { throw null; } public override string ToString() { throw null; } @@ -3334,9 +3334,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out short result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out short result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out short result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(short value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(short value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(short value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(short value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(short value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(short value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static short System.Numerics.INumber.MaxNumber(short x, short y) { throw null; } static short System.Numerics.INumber.MinNumber(short x, short y) { throw null; } static short System.Numerics.IShiftOperators.operator <<(short value, int shiftAmount) { throw null; } @@ -3467,9 +3467,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out int result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out int result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out int result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(int value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(int value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(int value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(int value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(int value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(int value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static int System.Numerics.INumber.MaxNumber(int x, int y) { throw null; } static int System.Numerics.INumber.MinNumber(int x, int y) { throw null; } static int System.Numerics.IShiftOperators.operator <<(int value, int shiftAmount) { throw null; } @@ -3600,9 +3600,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out long result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out long result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out long result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(long value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(long value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(long value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(long value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(long value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(long value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static long System.Numerics.INumber.MaxNumber(long x, long y) { throw null; } static long System.Numerics.INumber.MinNumber(long x, long y) { throw null; } static long System.Numerics.IShiftOperators.operator <<(long value, int shiftAmount) { throw null; } @@ -3735,9 +3735,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out nint result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out nint result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out nint result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(nint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(nint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(nint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(nint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(nint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(nint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static nint System.Numerics.INumber.MaxNumber(nint x, nint y) { throw null; } static nint System.Numerics.INumber.MinNumber(nint x, nint y) { throw null; } static nint System.Numerics.IShiftOperators.operator <<(nint value, int shiftAmount) { throw null; } @@ -3804,7 +3804,7 @@ public partial interface IObserver void OnError(System.Exception error); void OnNext(T value); } - public partial interface IParsable where TSelf : System.IParsable + public partial interface IParsable where TSelf : System.IParsable? { static abstract TSelf Parse(string s, System.IFormatProvider? provider); static abstract bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TSelf result); @@ -3817,7 +3817,7 @@ public partial interface ISpanFormattable : System.IFormattable { bool TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider); } - public partial interface ISpanParsable : System.IParsable where TSelf : System.ISpanParsable + public partial interface ISpanParsable : System.IParsable where TSelf : System.ISpanParsable? { static abstract TSelf Parse(System.ReadOnlySpan s, System.IFormatProvider? provider); static abstract bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TSelf result); @@ -4664,9 +4664,9 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out sbyte result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out sbyte result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out sbyte result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(sbyte value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(sbyte value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(sbyte value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(sbyte value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(sbyte value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(sbyte value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static sbyte System.Numerics.INumber.MaxNumber(sbyte x, sbyte y) { throw null; } static sbyte System.Numerics.INumber.MinNumber(sbyte x, sbyte y) { throw null; } static sbyte System.Numerics.IShiftOperators.operator <<(sbyte value, int shiftAmount) { throw null; } @@ -4863,9 +4863,9 @@ public SerializableAttribute() { } static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out float result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out float result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out float result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(float value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(float value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(float value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(float value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(float value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(float value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static float System.Numerics.ISubtractionOperators.operator -(float left, float right) { throw null; } static float System.Numerics.IUnaryNegationOperators.operator -(float value) { throw null; } static float System.Numerics.IUnaryPlusOperators.operator +(float value) { throw null; } @@ -6221,9 +6221,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out System.UInt128 result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out System.UInt128 result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out System.UInt128 result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(System.UInt128 value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(System.UInt128 value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(System.UInt128 value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(System.UInt128 value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(System.UInt128 value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(System.UInt128 value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static System.UInt128 System.Numerics.INumber.CopySign(System.UInt128 value, System.UInt128 sign) { throw null; } static System.UInt128 System.Numerics.INumber.MaxNumber(System.UInt128 x, System.UInt128 y) { throw null; } static System.UInt128 System.Numerics.INumber.MinNumber(System.UInt128 x, System.UInt128 y) { throw null; } @@ -6346,9 +6346,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out ushort result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out ushort result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out ushort result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(ushort value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(ushort value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(ushort value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(ushort value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(ushort value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(ushort value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static ushort System.Numerics.INumber.CopySign(ushort value, ushort sign) { throw null; } static ushort System.Numerics.INumber.MaxNumber(ushort x, ushort y) { throw null; } static ushort System.Numerics.INumber.MinNumber(ushort x, ushort y) { throw null; } @@ -6479,9 +6479,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out uint result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out uint result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out uint result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(uint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(uint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(uint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(uint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(uint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(uint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static uint System.Numerics.INumber.CopySign(uint value, uint sign) { throw null; } static uint System.Numerics.INumber.MaxNumber(uint x, uint y) { throw null; } static uint System.Numerics.INumber.MinNumber(uint x, uint y) { throw null; } @@ -6612,9 +6612,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out ulong result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out ulong result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out ulong result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(ulong value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(ulong value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(ulong value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(ulong value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(ulong value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(ulong value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static ulong System.Numerics.INumber.CopySign(ulong value, ulong sign) { throw null; } static ulong System.Numerics.INumber.MaxNumber(ulong x, ulong y) { throw null; } static ulong System.Numerics.INumber.MinNumber(ulong x, ulong y) { throw null; } @@ -6744,9 +6744,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out nuint result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out nuint result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out nuint result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(nuint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(nuint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(nuint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(nuint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(nuint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(nuint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static nuint System.Numerics.INumber.CopySign(nuint value, nuint sign) { throw null; } static nuint System.Numerics.INumber.MaxNumber(nuint x, nuint y) { throw null; } static nuint System.Numerics.INumber.MinNumber(nuint x, nuint y) { throw null; } @@ -10218,19 +10218,19 @@ public static partial class BitOperations [System.CLSCompliantAttribute(false)] public static int TrailingZeroCount(nuint value) { throw null; } } - public partial interface IAdditionOperators where TSelf : System.Numerics.IAdditionOperators + public partial interface IAdditionOperators where TSelf : System.Numerics.IAdditionOperators? { static abstract TResult operator +(TSelf left, TOther right); static virtual TResult operator checked +(TSelf left, TOther right) { throw null; } } - public partial interface IAdditiveIdentity where TSelf : System.Numerics.IAdditiveIdentity + public partial interface IAdditiveIdentity where TSelf : System.Numerics.IAdditiveIdentity? { static abstract TResult AdditiveIdentity { get; } } - public partial interface IBinaryFloatingPointIeee754 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IBinaryFloatingPointIeee754 + public partial interface IBinaryFloatingPointIeee754 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IBinaryFloatingPointIeee754? { } - public partial interface IBinaryInteger : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IBinaryInteger + public partial interface IBinaryInteger : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IBinaryInteger? { static virtual (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right) { throw null; } int GetByteCount(); @@ -10257,42 +10257,42 @@ public partial interface IBinaryInteger : System.IComparable, System.ICom int WriteLittleEndian(byte[] destination, int startIndex) { throw null; } int WriteLittleEndian(System.Span destination) { throw null; } } - public partial interface IBinaryNumber : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IBinaryNumber + public partial interface IBinaryNumber : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IBinaryNumber? { static virtual TSelf AllBitsSet { get { throw null; } } static abstract bool IsPow2(TSelf value); static abstract TSelf Log2(TSelf value); } - public partial interface IBitwiseOperators where TSelf : System.Numerics.IBitwiseOperators + public partial interface IBitwiseOperators where TSelf : System.Numerics.IBitwiseOperators? { static abstract TResult operator &(TSelf left, TOther right); static abstract TResult operator |(TSelf left, TOther right); static abstract TResult operator ^(TSelf left, TOther right); static abstract TResult operator ~(TSelf value); } - public partial interface IComparisonOperators : System.Numerics.IEqualityOperators where TSelf : System.Numerics.IComparisonOperators + public partial interface IComparisonOperators : System.Numerics.IEqualityOperators where TSelf : System.Numerics.IComparisonOperators? { static abstract TResult operator >(TSelf left, TOther right); static abstract TResult operator >=(TSelf left, TOther right); static abstract TResult operator <(TSelf left, TOther right); static abstract TResult operator <=(TSelf left, TOther right); } - public partial interface IDecrementOperators where TSelf : System.Numerics.IDecrementOperators + public partial interface IDecrementOperators where TSelf : System.Numerics.IDecrementOperators? { static virtual TSelf operator checked --(TSelf value) { throw null; } static abstract TSelf operator --(TSelf value); } - public partial interface IDivisionOperators where TSelf : System.Numerics.IDivisionOperators + public partial interface IDivisionOperators where TSelf : System.Numerics.IDivisionOperators? { static virtual TResult operator checked /(TSelf left, TOther right) { throw null; } static abstract TResult operator /(TSelf left, TOther right); } - public partial interface IEqualityOperators where TSelf : System.Numerics.IEqualityOperators + public partial interface IEqualityOperators where TSelf : System.Numerics.IEqualityOperators? { - static abstract TResult operator ==(TSelf left, TOther right); - static abstract TResult operator !=(TSelf left, TOther right); + static abstract TResult operator ==(TSelf? left, TOther? right); + static abstract TResult operator !=(TSelf? left, TOther? right); } - public partial interface IExponentialFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.IExponentialFunctions + public partial interface IExponentialFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.IExponentialFunctions? { static abstract TSelf Exp(TSelf x); static abstract TSelf Exp10(TSelf x); @@ -10301,13 +10301,13 @@ public partial interface IExponentialFunctions : System.Numerics.IFloatin static virtual TSelf Exp2M1(TSelf x) { throw null; } static virtual TSelf ExpM1(TSelf x) { throw null; } } - public partial interface IFloatingPointConstants : System.Numerics.INumberBase where TSelf : System.Numerics.IFloatingPointConstants + public partial interface IFloatingPointConstants : System.Numerics.INumberBase where TSelf : System.Numerics.IFloatingPointConstants? { static abstract TSelf E { get; } static abstract TSelf Pi { get; } static abstract TSelf Tau { get; } } - public partial interface IFloatingPointIeee754 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IFloatingPointIeee754 + public partial interface IFloatingPointIeee754 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IFloatingPointIeee754? { static abstract TSelf Epsilon { get; } static abstract TSelf NaN { get; } @@ -10325,7 +10325,7 @@ public partial interface IFloatingPointIeee754 : System.IComparable, Syst static virtual TSelf ReciprocalSqrtEstimate(TSelf x) { throw null; } static abstract TSelf ScaleB(TSelf x, int n); } - public partial interface IFloatingPoint : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IFloatingPoint + public partial interface IFloatingPoint : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IFloatingPoint? { static virtual TSelf Ceiling(TSelf x) { throw null; } static virtual TSelf Floor(TSelf x) { throw null; } @@ -10355,7 +10355,7 @@ public partial interface IFloatingPoint : System.IComparable, System.ICom int WriteSignificandLittleEndian(byte[] destination, int startIndex) { throw null; } int WriteSignificandLittleEndian(System.Span destination) { throw null; } } - public partial interface IHyperbolicFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.IHyperbolicFunctions + public partial interface IHyperbolicFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.IHyperbolicFunctions? { static abstract TSelf Acosh(TSelf x); static abstract TSelf Asinh(TSelf x); @@ -10364,12 +10364,12 @@ public partial interface IHyperbolicFunctions : System.Numerics.IFloating static abstract TSelf Sinh(TSelf x); static abstract TSelf Tanh(TSelf x); } - public partial interface IIncrementOperators where TSelf : System.Numerics.IIncrementOperators + public partial interface IIncrementOperators where TSelf : System.Numerics.IIncrementOperators? { static virtual TSelf operator checked ++(TSelf value) { throw null; } static abstract TSelf operator ++(TSelf value); } - public partial interface ILogarithmicFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.ILogarithmicFunctions + public partial interface ILogarithmicFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.ILogarithmicFunctions? { static abstract TSelf Log(TSelf x); static abstract TSelf Log(TSelf x, TSelf newBase); @@ -10379,33 +10379,42 @@ public partial interface ILogarithmicFunctions : System.Numerics.IFloatin static virtual TSelf Log2P1(TSelf x) { throw null; } static virtual TSelf LogP1(TSelf x) { throw null; } } - public partial interface IMinMaxValue where TSelf : System.Numerics.IMinMaxValue + public partial interface IMinMaxValue where TSelf : System.Numerics.IMinMaxValue? { static abstract TSelf MaxValue { get; } static abstract TSelf MinValue { get; } } - public partial interface IModulusOperators where TSelf : System.Numerics.IModulusOperators + public partial interface IModulusOperators where TSelf : System.Numerics.IModulusOperators? { static abstract TResult operator %(TSelf left, TOther right); } - public partial interface IMultiplicativeIdentity where TSelf : System.Numerics.IMultiplicativeIdentity + public partial interface IMultiplicativeIdentity where TSelf : System.Numerics.IMultiplicativeIdentity? { static abstract TResult MultiplicativeIdentity { get; } } - public partial interface IMultiplyOperators where TSelf : System.Numerics.IMultiplyOperators + public partial interface IMultiplyOperators where TSelf : System.Numerics.IMultiplyOperators? { static virtual TResult operator checked *(TSelf left, TOther right) { throw null; } static abstract TResult operator *(TSelf left, TOther right); } - public partial interface INumberBase : System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.INumberBase + public partial interface INumberBase : System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.INumberBase? { static abstract TSelf One { get; } static abstract int Radix { get; } static abstract TSelf Zero { get; } static abstract TSelf Abs(TSelf value); - static virtual TSelf CreateChecked(TOther value) where TOther : System.Numerics.INumberBase { throw null; } - static virtual TSelf CreateSaturating(TOther value) where TOther : System.Numerics.INumberBase { throw null; } - static virtual TSelf CreateTruncating(TOther value) where TOther : System.Numerics.INumberBase { throw null; } + static virtual TSelf CreateChecked(TOther value) +#nullable disable + where TOther : System.Numerics.INumberBase { throw null; } +#nullable restore + static virtual TSelf CreateSaturating(TOther value) +#nullable disable + where TOther : System.Numerics.INumberBase { throw null; } +#nullable restore + static virtual TSelf CreateTruncating(TOther value) +#nullable disable + where TOther : System.Numerics.INumberBase { throw null; } +#nullable restore static abstract bool IsCanonical(TSelf value); static abstract bool IsComplexNumber(TSelf value); static abstract bool IsEvenInteger(TSelf value); @@ -10429,16 +10438,34 @@ public partial interface INumberBase : System.IEquatable, System.I static abstract TSelf MinMagnitudeNumber(TSelf x, TSelf y); static abstract TSelf Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider); static abstract TSelf Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider); - protected static abstract bool TryConvertFromChecked(TOther value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TSelf? result) where TOther : System.Numerics.INumberBase; - protected static abstract bool TryConvertFromSaturating(TOther value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TSelf? result) where TOther : System.Numerics.INumberBase; - protected static abstract bool TryConvertFromTruncating(TOther value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TSelf? result) where TOther : System.Numerics.INumberBase; - protected static abstract bool TryConvertToChecked(TSelf value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther? result) where TOther : System.Numerics.INumberBase; - protected static abstract bool TryConvertToSaturating(TSelf value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther? result) where TOther : System.Numerics.INumberBase; - protected static abstract bool TryConvertToTruncating(TSelf value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther? result) where TOther : System.Numerics.INumberBase; - static abstract bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out TSelf result); - static abstract bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out TSelf result); + protected static abstract bool TryConvertFromChecked(TOther value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result) +#nullable disable + where TOther : System.Numerics.INumberBase; +#nullable restore + protected static abstract bool TryConvertFromSaturating(TOther value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result) +#nullable disable + where TOther : System.Numerics.INumberBase; +#nullable restore + protected static abstract bool TryConvertFromTruncating(TOther value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result) +#nullable disable + where TOther : System.Numerics.INumberBase; +#nullable restore + protected static abstract bool TryConvertToChecked(TSelf value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TOther result) +#nullable disable + where TOther : System.Numerics.INumberBase; +#nullable restore + protected static abstract bool TryConvertToSaturating(TSelf value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TOther result) +#nullable disable + where TOther : System.Numerics.INumberBase; +#nullable restore + protected static abstract bool TryConvertToTruncating(TSelf value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TOther result) +#nullable disable + where TOther : System.Numerics.INumberBase; +#nullable restore + static abstract bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result); + static abstract bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result); } - public partial interface INumber : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.INumber + public partial interface INumber : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.INumber? { static virtual TSelf Clamp(TSelf value, TSelf min, TSelf max) { throw null; } static virtual TSelf CopySign(TSelf value, TSelf sign) { throw null; } @@ -10448,33 +10475,33 @@ public partial interface INumber : System.IComparable, System.IComparable static virtual TSelf MinNumber(TSelf x, TSelf y) { throw null; } static virtual int Sign(TSelf value) { throw null; } } - public partial interface IPowerFunctions : System.Numerics.INumberBase where TSelf : System.Numerics.IPowerFunctions + public partial interface IPowerFunctions : System.Numerics.INumberBase where TSelf : System.Numerics.IPowerFunctions? { static abstract TSelf Pow(TSelf x, TSelf y); } - public partial interface IRootFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.IRootFunctions + public partial interface IRootFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.IRootFunctions? { static abstract TSelf Cbrt(TSelf x); static abstract TSelf Hypot(TSelf x, TSelf y); static abstract TSelf RootN(TSelf x, int n); static abstract TSelf Sqrt(TSelf x); } - public partial interface IShiftOperators where TSelf : System.Numerics.IShiftOperators + public partial interface IShiftOperators where TSelf : System.Numerics.IShiftOperators? { static abstract TResult operator <<(TSelf value, TOther shiftAmount); static abstract TResult operator >>(TSelf value, TOther shiftAmount); static abstract TResult operator >>>(TSelf value, TOther shiftAmount); } - public partial interface ISignedNumber : System.Numerics.INumberBase where TSelf : System.Numerics.ISignedNumber + public partial interface ISignedNumber : System.Numerics.INumberBase where TSelf : System.Numerics.ISignedNumber? { static abstract TSelf NegativeOne { get; } } - public partial interface ISubtractionOperators where TSelf : System.Numerics.ISubtractionOperators + public partial interface ISubtractionOperators where TSelf : System.Numerics.ISubtractionOperators? { static virtual TResult operator checked -(TSelf left, TOther right) { throw null; } static abstract TResult operator -(TSelf left, TOther right); } - public partial interface ITrigonometricFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.ITrigonometricFunctions + public partial interface ITrigonometricFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.ITrigonometricFunctions? { static abstract TSelf Acos(TSelf x); static abstract TSelf AcosPi(TSelf x); @@ -10491,16 +10518,16 @@ public partial interface ITrigonometricFunctions : System.Numerics.IFloat static abstract TSelf Tan(TSelf x); static abstract TSelf TanPi(TSelf x); } - public partial interface IUnaryNegationOperators where TSelf : System.Numerics.IUnaryNegationOperators + public partial interface IUnaryNegationOperators where TSelf : System.Numerics.IUnaryNegationOperators? { static virtual TResult operator checked -(TSelf value) { throw null; } static abstract TResult operator -(TSelf value); } - public partial interface IUnaryPlusOperators where TSelf : System.Numerics.IUnaryPlusOperators + public partial interface IUnaryPlusOperators where TSelf : System.Numerics.IUnaryPlusOperators? { static abstract TResult operator +(TSelf value); } - public partial interface IUnsignedNumber : System.Numerics.INumberBase where TSelf : System.Numerics.IUnsignedNumber + public partial interface IUnsignedNumber : System.Numerics.INumberBase where TSelf : System.Numerics.IUnsignedNumber? { } } diff --git a/src/libraries/System.Runtime/tests/NlsTests/System.Runtime.Nls.Tests.csproj b/src/libraries/System.Runtime/tests/NlsTests/System.Runtime.Nls.Tests.csproj index b93223c66887c6..849edc8cd5c2c1 100644 --- a/src/libraries/System.Runtime/tests/NlsTests/System.Runtime.Nls.Tests.csproj +++ b/src/libraries/System.Runtime/tests/NlsTests/System.Runtime.Nls.Tests.csproj @@ -6,6 +6,9 @@ $(NetCoreAppCurrent)-windows $(NoWarn),SYSLIB0013 + + + diff --git a/src/libraries/System.Runtime/tests/NlsTests/runtimeconfig.template.json b/src/libraries/System.Runtime/tests/NlsTests/runtimeconfig.template.json deleted file mode 100644 index f93c6039127bd1..00000000000000 --- a/src/libraries/System.Runtime/tests/NlsTests/runtimeconfig.template.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "configProperties": { - "System.Globalization.UseNls": true - } -} diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index accc11d4277ca8..fc6859854dc2e6 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -111,6 +111,8 @@ + + diff --git a/src/libraries/System.Runtime/tests/System/Attributes.cs b/src/libraries/System.Runtime/tests/System/Attributes.cs index d0b474f3110821..0b75b48ea133e7 100644 --- a/src/libraries/System.Runtime/tests/System/Attributes.cs +++ b/src/libraries/System.Runtime/tests/System/Attributes.cs @@ -331,10 +331,11 @@ public static void customAttributeCount() { List customAttributes = typeof(GetCustomAttribute).Module.CustomAttributes.ToList(); // [System.Security.UnverifiableCodeAttribute()] + // [System.Runtime.CompilerServices.RefSafetyRulesAttribute((Int32)11)] // [TestAttributes.FooAttribute()] // [TestAttributes.ComplicatedAttribute((Int32)1, Stuff = 2)] // [System.Diagnostics.DebuggableAttribute((Boolean)True, (Boolean)False)] - Assert.Equal(4, customAttributes.Count); + Assert.Equal(5, customAttributes.Count); } [Fact] diff --git a/src/libraries/System.Runtime/tests/System/Int128Tests.cs b/src/libraries/System.Runtime/tests/System/Int128Tests.cs index 15f1a39a02396a..d0dee7dc57cf7c 100644 --- a/src/libraries/System.Runtime/tests/System/Int128Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int128Tests.cs @@ -116,6 +116,12 @@ public static IEnumerable ToString_TestData() yield return new object[] { (Int128)(-4567), defaultSpecifier, defaultFormat, "-4567" }; yield return new object[] { (Int128)0, defaultSpecifier, defaultFormat, "0" }; yield return new object[] { (Int128)4567, defaultSpecifier, defaultFormat, "4567" }; + yield return new object[] { new Int128(0x0000_0000_0000_0001, 0x0000_0000_0000_0003), defaultSpecifier, defaultFormat, "18446744073709551619" }; + yield return new object[] { new Int128(0x0000_0000_0000_0001, 0x0000_0000_0000_000A), defaultSpecifier, defaultFormat, "18446744073709551626" }; + yield return new object[] { new Int128(0x0000_0000_0000_0005, 0x0000_0000_0000_0001), defaultSpecifier, defaultFormat, "92233720368547758081" }; + yield return new object[] { new Int128(0x0000_0000_0000_0005, 0x6BC7_5E2D_6310_0000), defaultSpecifier, defaultFormat, "100000000000000000000" }; + yield return new object[] { new Int128(0x0000_0000_0000_0036, 0x35C9_ADC5_DEA0_0000), defaultSpecifier, defaultFormat, "1000000000000000000000" }; + yield return new object[] { new Int128(0x0013_4261_72C7_4D82, 0x2B87_8FE8_0000_0000), defaultSpecifier, defaultFormat, "100000000000000000000000000000000000" }; yield return new object[] { Int128.MaxValue, defaultSpecifier, defaultFormat, "170141183460469231731687303715884105727" }; } @@ -452,5 +458,15 @@ public static void TestNegativeNumberParsingWithHyphen() CultureInfo ci = CultureInfo.GetCultureInfo("sv-SE"); Assert.Equal(-15868, Int128.Parse("-15868", NumberStyles.Number, ci)); } + + [Fact] + public static void Runtime75416() + { + Int128 a = (Int128.MaxValue - 10) * +100; + Assert.Equal(a, -1100); + + Int128 b = (Int128.MaxValue - 10) * -100; + Assert.Equal(b, +1100); + } } } diff --git a/src/libraries/System.Runtime/tests/System/Numerics/GenericMathDimHelpers.cs b/src/libraries/System.Runtime/tests/System/Numerics/GenericMathDimHelpers.cs index d6b34be3114410..058bba1d255b5c 100644 --- a/src/libraries/System.Runtime/tests/System/Numerics/GenericMathDimHelpers.cs +++ b/src/libraries/System.Runtime/tests/System/Numerics/GenericMathDimHelpers.cs @@ -60,9 +60,9 @@ private BinaryNumberDimHelper(int value) static BinaryNumberDimHelper INumberBase.Parse(string s, NumberStyles style, IFormatProvider? provider) => throw new NotImplementedException(); static BinaryNumberDimHelper ISpanParsable.Parse(ReadOnlySpan s, IFormatProvider? provider) => throw new NotImplementedException(); static BinaryNumberDimHelper IParsable.Parse(string s, IFormatProvider? provider) => throw new NotImplementedException(); - static bool INumberBase.TryConvertToChecked(BinaryNumberDimHelper value, out TOther? result) where TOther : default => throw new NotImplementedException(); - static bool INumberBase.TryConvertToSaturating(BinaryNumberDimHelper value, out TOther? result) where TOther : default => throw new NotImplementedException(); - static bool INumberBase.TryConvertToTruncating(BinaryNumberDimHelper value, out TOther? result) where TOther : default => throw new NotImplementedException(); + static bool INumberBase.TryConvertToChecked(BinaryNumberDimHelper value, out TOther result) where TOther : default => throw new NotImplementedException(); + static bool INumberBase.TryConvertToSaturating(BinaryNumberDimHelper value, out TOther result) where TOther : default => throw new NotImplementedException(); + static bool INumberBase.TryConvertToTruncating(BinaryNumberDimHelper value, out TOther result) where TOther : default => throw new NotImplementedException(); static bool INumberBase.TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out BinaryNumberDimHelper result) => throw new NotImplementedException(); static bool INumberBase.TryParse(string? s, NumberStyles style, IFormatProvider? provider, out BinaryNumberDimHelper result) => throw new NotImplementedException(); static bool ISpanParsable.TryParse(ReadOnlySpan s, IFormatProvider? provider, out BinaryNumberDimHelper result) => throw new NotImplementedException(); @@ -96,4 +96,176 @@ private BinaryNumberDimHelper(int value) static bool IComparisonOperators.operator >=(BinaryNumberDimHelper left, BinaryNumberDimHelper right) => throw new NotImplementedException(); } + public struct FloatingPointDimHelper : IFloatingPoint + { + public float Value; + + public FloatingPointDimHelper(float value) + { + Value = value; + } + + static FloatingPointDimHelper IFloatingPoint.Round(FloatingPointDimHelper x, int digits, MidpointRounding mode) + => new FloatingPointDimHelper(float.Round(x.Value, digits, mode)); + + // + // The below are all not used for existing Dim tests, so they stay unimplemented + // + + static FloatingPointDimHelper IFloatingPointConstants.E => throw new NotImplementedException(); + static FloatingPointDimHelper IFloatingPointConstants.Pi => throw new NotImplementedException(); + static FloatingPointDimHelper IFloatingPointConstants.Tau => throw new NotImplementedException(); + static FloatingPointDimHelper ISignedNumber.NegativeOne => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.One => throw new NotImplementedException(); + static int INumberBase.Radix => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.Zero => throw new NotImplementedException(); + static FloatingPointDimHelper IAdditiveIdentity.AdditiveIdentity => throw new NotImplementedException(); + static FloatingPointDimHelper IMultiplicativeIdentity.MultiplicativeIdentity => throw new NotImplementedException(); + + static FloatingPointDimHelper INumberBase.Abs(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsCanonical(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsComplexNumber(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsEvenInteger(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsFinite(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsImaginaryNumber(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsInfinity(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsInteger(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNaN(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNegative(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNegativeInfinity(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNormal(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsOddInteger(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsPositive(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsPositiveInfinity(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsRealNumber(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsSubnormal(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsZero(FloatingPointDimHelper value) => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.MaxMagnitude(FloatingPointDimHelper x, FloatingPointDimHelper y) => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.MaxMagnitudeNumber(FloatingPointDimHelper x, FloatingPointDimHelper y) => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.MinMagnitude(FloatingPointDimHelper x, FloatingPointDimHelper y) => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.MinMagnitudeNumber(FloatingPointDimHelper x, FloatingPointDimHelper y) => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.Parse(string s, NumberStyles style, IFormatProvider? provider) => throw new NotImplementedException(); + static FloatingPointDimHelper ISpanParsable.Parse(ReadOnlySpan s, IFormatProvider? provider) => throw new NotImplementedException(); + static FloatingPointDimHelper IParsable.Parse(string s, IFormatProvider? provider) => throw new NotImplementedException(); + + static bool INumberBase.TryConvertFromChecked(TOther value, out FloatingPointDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertFromSaturating(TOther value, out FloatingPointDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertFromTruncating(TOther value, out FloatingPointDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertToChecked(FloatingPointDimHelper value, out TOther result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertToSaturating(FloatingPointDimHelper value, out TOther result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertToTruncating(FloatingPointDimHelper value, out TOther result) => throw new NotImplementedException(); + static bool INumberBase.TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out FloatingPointDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryParse(string? s, NumberStyles style, IFormatProvider? provider, out FloatingPointDimHelper result) => throw new NotImplementedException(); + static bool ISpanParsable.TryParse(ReadOnlySpan s, IFormatProvider? provider, out FloatingPointDimHelper result) => throw new NotImplementedException(); + static bool IParsable.TryParse(string? s, IFormatProvider? provider, out FloatingPointDimHelper result) => throw new NotImplementedException(); + int IComparable.CompareTo(object? obj) => throw new NotImplementedException(); + int IComparable.CompareTo(FloatingPointDimHelper other) => throw new NotImplementedException(); + bool IEquatable.Equals(FloatingPointDimHelper other) => throw new NotImplementedException(); + int IFloatingPoint.GetExponentByteCount() => throw new NotImplementedException(); + int IFloatingPoint.GetExponentShortestBitLength() => throw new NotImplementedException(); + int IFloatingPoint.GetSignificandBitLength() => throw new NotImplementedException(); + int IFloatingPoint.GetSignificandByteCount() => throw new NotImplementedException(); + string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => throw new NotImplementedException(); + bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => throw new NotImplementedException(); + bool IFloatingPoint.TryWriteExponentBigEndian(Span destination, out int bytesWritten) => throw new NotImplementedException(); + bool IFloatingPoint.TryWriteExponentLittleEndian(Span destination, out int bytesWritten) => throw new NotImplementedException(); + bool IFloatingPoint.TryWriteSignificandBigEndian(Span destination, out int bytesWritten) => throw new NotImplementedException(); + bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destination, out int bytesWritten) => throw new NotImplementedException(); + + static FloatingPointDimHelper IUnaryPlusOperators.operator +(FloatingPointDimHelper value) => throw new NotImplementedException(); + static FloatingPointDimHelper IAdditionOperators.operator +(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static FloatingPointDimHelper IUnaryNegationOperators.operator -(FloatingPointDimHelper value) => throw new NotImplementedException(); + static FloatingPointDimHelper ISubtractionOperators.operator -(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static FloatingPointDimHelper IIncrementOperators.operator ++(FloatingPointDimHelper value) => throw new NotImplementedException(); + static FloatingPointDimHelper IDecrementOperators.operator --(FloatingPointDimHelper value) => throw new NotImplementedException(); + static FloatingPointDimHelper IMultiplyOperators.operator *(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static FloatingPointDimHelper IDivisionOperators.operator /(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static FloatingPointDimHelper IModulusOperators.operator %(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static bool IEqualityOperators.operator ==(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static bool IEqualityOperators.operator !=(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static bool IComparisonOperators.operator <(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static bool IComparisonOperators.operator >(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static bool IComparisonOperators.operator <=(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static bool IComparisonOperators.operator >=(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + } + + public struct ExponentialFunctionsDimHelper : IExponentialFunctions + { + public float Value; + + public ExponentialFunctionsDimHelper(float value) + { + Value = value; + } + + static ExponentialFunctionsDimHelper IExponentialFunctions.Exp10(ExponentialFunctionsDimHelper x) => new ExponentialFunctionsDimHelper(float.Exp10(x.Value)); + static ExponentialFunctionsDimHelper INumberBase.One => new ExponentialFunctionsDimHelper(1f); + static ExponentialFunctionsDimHelper ISubtractionOperators.operator -(ExponentialFunctionsDimHelper left, ExponentialFunctionsDimHelper right) + => new ExponentialFunctionsDimHelper(left.Value - right.Value); + + // + // The below are all not used for existing Dim tests, so they stay unimplemented + // + + static ExponentialFunctionsDimHelper IFloatingPointConstants.E => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IFloatingPointConstants.Pi => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IFloatingPointConstants.Tau => throw new NotImplementedException(); + static int INumberBase.Radix => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper INumberBase.Zero => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IAdditiveIdentity.AdditiveIdentity => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IMultiplicativeIdentity.MultiplicativeIdentity => throw new NotImplementedException(); + + static ExponentialFunctionsDimHelper INumberBase.Abs(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IExponentialFunctions.Exp(ExponentialFunctionsDimHelper x) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IExponentialFunctions.Exp2(ExponentialFunctionsDimHelper x) => throw new NotImplementedException(); + static bool INumberBase.IsCanonical(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsComplexNumber(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsEvenInteger(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsFinite(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsImaginaryNumber(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsInfinity(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsInteger(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNaN(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNegative(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNegativeInfinity(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNormal(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsOddInteger(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsPositive(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsPositiveInfinity(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsRealNumber(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsSubnormal(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsZero(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper INumberBase.MaxMagnitude(ExponentialFunctionsDimHelper x, ExponentialFunctionsDimHelper y) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper INumberBase.MaxMagnitudeNumber(ExponentialFunctionsDimHelper x, ExponentialFunctionsDimHelper y) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper INumberBase.MinMagnitude(ExponentialFunctionsDimHelper x, ExponentialFunctionsDimHelper y) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper INumberBase.MinMagnitudeNumber(ExponentialFunctionsDimHelper x, ExponentialFunctionsDimHelper y) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper INumberBase.Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper INumberBase.Parse(string s, NumberStyles style, IFormatProvider? provider) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper ISpanParsable.Parse(ReadOnlySpan s, IFormatProvider? provider) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IParsable.Parse(string s, IFormatProvider? provider) => throw new NotImplementedException(); + static bool INumberBase.TryConvertFromChecked(TOther value, out ExponentialFunctionsDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertFromSaturating(TOther value, out ExponentialFunctionsDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertFromTruncating(TOther value, out ExponentialFunctionsDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertToChecked(ExponentialFunctionsDimHelper value, out TOther result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertToSaturating(ExponentialFunctionsDimHelper value, out TOther result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertToTruncating(ExponentialFunctionsDimHelper value, out TOther result) => throw new NotImplementedException(); + static bool INumberBase.TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out ExponentialFunctionsDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryParse(string? s, NumberStyles style, IFormatProvider? provider, out ExponentialFunctionsDimHelper result) => throw new NotImplementedException(); + static bool ISpanParsable.TryParse(ReadOnlySpan s, IFormatProvider? provider, out ExponentialFunctionsDimHelper result) => throw new NotImplementedException(); + static bool IParsable.TryParse(string? s, IFormatProvider? provider, out ExponentialFunctionsDimHelper result) => throw new NotImplementedException(); + bool IEquatable.Equals(ExponentialFunctionsDimHelper other) => throw new NotImplementedException(); + string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => throw new NotImplementedException(); + bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => throw new NotImplementedException(); + + static ExponentialFunctionsDimHelper IUnaryPlusOperators.operator +(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IAdditionOperators.operator +(ExponentialFunctionsDimHelper left, ExponentialFunctionsDimHelper right) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IUnaryNegationOperators.operator -(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IIncrementOperators.operator ++(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IDecrementOperators.operator --(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IMultiplyOperators.operator *(ExponentialFunctionsDimHelper left, ExponentialFunctionsDimHelper right) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IDivisionOperators.operator /(ExponentialFunctionsDimHelper left, ExponentialFunctionsDimHelper right) => throw new NotImplementedException(); + static bool IEqualityOperators.operator ==(ExponentialFunctionsDimHelper left, ExponentialFunctionsDimHelper right) => throw new NotImplementedException(); + static bool IEqualityOperators.operator !=(ExponentialFunctionsDimHelper left, ExponentialFunctionsDimHelper right) => throw new NotImplementedException(); + } } diff --git a/src/libraries/System.Runtime/tests/System/Numerics/IExponentialFunctionsTests.cs b/src/libraries/System.Runtime/tests/System/Numerics/IExponentialFunctionsTests.cs new file mode 100644 index 00000000000000..6c2096cf88d77c --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Numerics/IExponentialFunctionsTests.cs @@ -0,0 +1,19 @@ +// 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.Numerics.Tests +{ + public sealed class IExponentialFunctionsTests + { + const float baseValue = 2.1f; + static readonly ExponentialFunctionsDimHelper helperValue = new ExponentialFunctionsDimHelper(baseValue); + + [Fact] + public static void Exp10M1Test() + { + Assert.Equal(float.Exp10M1(baseValue), ExponentialFunctionsHelper.Exp10M1(helperValue).Value); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/Numerics/IFloatingPointTests.cs b/src/libraries/System.Runtime/tests/System/Numerics/IFloatingPointTests.cs new file mode 100644 index 00000000000000..6085d5b29d0e5a --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Numerics/IFloatingPointTests.cs @@ -0,0 +1,43 @@ +// 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.Numerics.Tests +{ + public sealed class IFloatingPointTests + { + const float baseValue = 5.5f; + static readonly FloatingPointDimHelper helperValue = new FloatingPointDimHelper(baseValue); + + [Fact] + public static void AwayFromZeroRoundingTest() + { + Assert.Equal(float.Round(baseValue, MidpointRounding.AwayFromZero), FloatingPointHelper.Round(helperValue, MidpointRounding.AwayFromZero).Value); + } + + [Fact] + public static void ToEvenRoundingTest() + { + Assert.Equal(float.Round(baseValue, MidpointRounding.ToEven), FloatingPointHelper.Round(helperValue, MidpointRounding.ToEven).Value); + } + + [Fact] + public static void ToNegativeInfinityRoundingTest() + { + Assert.Equal(float.Round(baseValue, MidpointRounding.ToNegativeInfinity), FloatingPointHelper.Round(helperValue, MidpointRounding.ToNegativeInfinity).Value); + } + + [Fact] + public static void ToPositiveRoundingTest() + { + Assert.Equal(float.Round(baseValue, MidpointRounding.ToPositiveInfinity), FloatingPointHelper.Round(helperValue, MidpointRounding.ToPositiveInfinity).Value); + } + + [Fact] + public static void ToZeroRoundingTest() + { + Assert.Equal(float.Round(baseValue, MidpointRounding.ToZero), FloatingPointHelper.Round(helperValue, MidpointRounding.ToZero).Value); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs index 40ce236003222a..a5922112c1d98e 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs @@ -7,8 +7,18 @@ namespace System.Reflection.Tests { + public class A + { + public string P { get; set; } + public int F; +#pragma warning disable CS0067 + public event EventHandler E; +#pragma warning restore CS0067 + public void M() { } + } + [Collection(nameof(DisableParallelization))] - public class ReflectionCacheTests + public class ReflectionCacheTests : A { private static bool IsMetadataUpdateAndRemoteExecutorSupported => PlatformDetection.IsMetadataUpdateSupported && RemoteExecutor.IsSupported; @@ -47,6 +57,12 @@ public void GetMembers_MultipleCalls_SameObjects() AssertSameEqualAndHashCodeEqual(fi1, fi2); AssertSameEqualAndHashCodeEqual(ei1, ei2); AssertSameEqualAndHashCodeEqual(ci1, ci2); + + PropertyInfo parentProperty = typeof(A).GetProperty("P"); + PropertyInfo childProperty = s_type.GetProperty("P"); + Assert.NotNull(parentProperty); + Assert.NotNull(childProperty); + Assert.NotEqual(parentProperty, childProperty); } void AssertSameEqualAndHashCodeEqual(object o1, object o2) @@ -87,6 +103,20 @@ public void GetMembers_MultipleCalls_ClearCache_ReflectionCacheTestsType() EventInfo ei1 = s_type.GetEvent(nameof(Event1)); ConstructorInfo ci1 = s_type.GetConstructor(Type.EmptyTypes); + PropertyInfo parentProperty = typeof(A).GetProperty("P"); + PropertyInfo childProperty = s_type.GetProperty("P"); + FieldInfo parentField = typeof(A).GetField("F"); + FieldInfo childField = s_type.GetField("F"); + MethodInfo parentMethod = typeof(A).GetMethod("M"); + MethodInfo childMethod = s_type.GetMethod("M"); + EventInfo parentEvent = typeof(A).GetEvent("E"); + EventInfo childEvent = s_type.GetEvent("E"); + + Assert.NotEqual(parentProperty, childProperty); + Assert.NotEqual(parentField, childField); + Assert.NotEqual(parentMethod, childMethod); + Assert.NotEqual(parentEvent, childEvent); + clearCache(new[] { typeof(ReflectionCacheTests) }); MethodInfo mi2 = s_type.GetMethod(nameof(Method)); @@ -95,6 +125,11 @@ public void GetMembers_MultipleCalls_ClearCache_ReflectionCacheTestsType() EventInfo ei2 = s_type.GetEvent(nameof(Event1)); ConstructorInfo ci2 = s_type.GetConstructor(Type.EmptyTypes); + Assert.NotEqual(parentProperty, childProperty); + Assert.NotEqual(parentField, childField); + Assert.NotEqual(parentMethod, childMethod); + Assert.NotEqual(parentEvent, childEvent); + AssertNotSameSameButEqualAndHashCodeEqual(mi1, mi2); AssertNotSameSameButEqualAndHashCodeEqual(pi1, pi2); AssertNotSameSameButEqualAndHashCodeEqual(fi1, fi2); diff --git a/src/libraries/System.Runtime/tests/System/Runtime/ControlledExecutionTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/ControlledExecutionTests.cs index 469e3cfd60b823..8aa28ad66522c3 100644 --- a/src/libraries/System.Runtime/tests/System/Runtime/ControlledExecutionTests.cs +++ b/src/libraries/System.Runtime/tests/System/Runtime/ControlledExecutionTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Threading; +using System.Threading.Tasks; using Xunit; // Disable warnings for ControlledExecution.Run @@ -9,208 +10,312 @@ namespace System.Runtime.Tests { - public class ControlledExecutionTests + public sealed class ControlledExecutionTests { - private bool _startedExecution, _caughtException, _finishedExecution; + private volatile bool _readyForCancellation; + private bool _caughtException, _finishedExecution; private Exception _exception; - private CancellationTokenSource _cts; private volatile int _counter; - // Tests cancellation on timeout. The ThreadAbortException must be mapped to OperationCanceledException. + // Tests that the Run method finishes normally if no cancellation is requested [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/72703", TestPlatforms.AnyUnix)] - public void CancelOnTimeout() + public void RunWithoutCancelling() { var cts = new CancellationTokenSource(); - cts.CancelAfter(200); - RunTest(LengthyAction, cts.Token); + RunTest(Test, cts.Token); + + Assert.True(_finishedExecution); + Assert.Null(_exception); + + void Test() + { + _finishedExecution = true; + } + } + + // Tests that a nested invocation of the Run method throws an InvalidOperationException + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] + public void TestNestedRunInvocation() + { + bool nestedExecution = false; + + var cts = new CancellationTokenSource(); + RunTest(Test, cts.Token); + + Assert.False(nestedExecution); + Assert.IsType(_exception); + + void Test() + { + ControlledExecution.Run(() => nestedExecution = true, cts.Token); + } + } + + // Tests that an infinite loop may be aborted and that the ThreadAbortException is translated + // to an OperationCanceledException. + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] + public void CancelOutsideOfTryCatchFinally() + { + var cts = new CancellationTokenSource(); + Task.Run(() => CancelWhenTestIsReady(cts)); + RunTest(Test, cts.Token); - Assert.True(_startedExecution); - Assert.True(_caughtException); Assert.False(_finishedExecution); Assert.IsType(_exception); + + void Test() + { + _readyForCancellation = true; + RunInfiniteLoop(); + _finishedExecution = true; + } } - // Tests that catch blocks are not aborted. The action catches the ThreadAbortException and throws an exception of a different type. + // Tests that an infinite loop may be aborted, that the ThreadAbortException is automatically rethrown, + // and that it is eventually translated to an OperationCanceledException. [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] - public void CancelOnTimeout_ThrowFromCatch() + public void CancelInTryAndExitCatchNormally() { var cts = new CancellationTokenSource(); - cts.CancelAfter(200); - RunTest(LengthyAction_ThrowFromCatch, cts.Token); + Task.Run(() => CancelWhenTestIsReady(cts)); + RunTest(Test, cts.Token); - Assert.True(_startedExecution); Assert.True(_caughtException); Assert.False(_finishedExecution); - Assert.IsType(_exception); + Assert.IsType(_exception); + + void Test() + { + try + { + _readyForCancellation = true; + RunInfiniteLoop(); + } + catch + { + // Swallow all exceptions to verify that the ThreadAbortException is automatically rethrown + _caughtException = true; + } + + if (!PlatformDetection.IsWindows) + { + // Rethrowing a ThreadAbortException at the end of catch blocks is not implemented, so force it + // here by calling native code (https://github.com/dotnet/runtime/issues/72703). + Thread.Sleep(0); + } + + _finishedExecution = true; + } } - // Tests that finally blocks are not aborted. The action throws an exception from a finally block. + // Tests that catch blocks are not aborted. The catch block swallows the ThreadAbortException and throws a different exception. [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] - public void CancelOnTimeout_ThrowFromFinally() + public void CancelInTryAndThrowFromCatch() { var cts = new CancellationTokenSource(); - cts.CancelAfter(200); - RunTest(LengthyAction_ThrowFromFinally, cts.Token); + Task.Run(() => CancelWhenTestIsReady(cts)); + RunTest(Test, cts.Token); + + Assert.True(_caughtException); + Assert.IsType(_exception); - Assert.True(_startedExecution); - Assert.IsType(_exception); + void Test() + { + try + { + _readyForCancellation = true; + RunInfiniteLoop(); + } + catch + { + _caughtException = true; + // The catch block must not be aborted + Thread.Sleep(200); + throw new TestException(); + } + } } - // Tests that finally blocks are not aborted. The action throws an exception from a try block. + // Tests that finally blocks are not aborted. The finally block exits normally. [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] - public void CancelOnTimeout_Finally() + public void CancelInFinallyThatSleeps() { var cts = new CancellationTokenSource(); - cts.CancelAfter(200); - RunTest(LengthyAction_Finally, cts.Token); + Task.Run(() => CancelWhenTestIsReady(cts)); + RunTest(Test, cts.Token); - Assert.True(_startedExecution); Assert.True(_finishedExecution); - Assert.IsType(_exception); + Assert.IsType(_exception); + + void Test() + { + try + { + // Make sure to run the non-inlined finally + throw new TestException(); + } + finally + { + _readyForCancellation = true; + WaitUntilAbortIsRequested(); + // The finally block must not be aborted + Thread.Sleep(200); + _finishedExecution = true; + } + } + } + + // Tests that finally blocks are not aborted. The finally block throws an exception. + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] + public void CancelInFinallyThatSleepsAndThrows() + { + var cts = new CancellationTokenSource(); + Task.Run(() => CancelWhenTestIsReady(cts)); + RunTest(Test, cts.Token); + + Assert.IsType(_exception); + + void Test() + { + try + { + // Make sure to run the non-inlined finally + throw new Exception(); + } + finally + { + _readyForCancellation = true; + WaitUntilAbortIsRequested(); + // The finally block must not be aborted + Thread.Sleep(200); + throw new TestException(); + } + } } - // Tests cancellation before calling the Run method + // Tests cancellation before calling the Run method. The action must never start. [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] public void CancelBeforeRun() { var cts = new CancellationTokenSource(); cts.Cancel(); - Thread.Sleep(100); - RunTest(LengthyAction, cts.Token); + RunTest(Test, cts.Token); + Assert.False(_finishedExecution); Assert.IsType(_exception); + + void Test() + { + _finishedExecution = true; + } } // Tests cancellation by the action itself [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] - public void CancelItself() + public void CancelItselfOutsideOfTryCatchFinally() { - _cts = new CancellationTokenSource(); - RunTest(Action_CancelItself, _cts.Token); + var cts = new CancellationTokenSource(); + RunTest(Test, cts.Token); - Assert.True(_startedExecution); Assert.False(_finishedExecution); - Assert.IsType(_exception); - Assert.IsType(_exception.InnerException); - } - - private void RunTest(Action action, CancellationToken cancellationToken) - { - _startedExecution = _caughtException = _finishedExecution = false; - _exception = null; + // CancellationTokenSource.Cancel catches the ThreadAbortException; however, it is rethrown at the end + // of the catch block. + Assert.IsType(_exception); - try + void Test() { - ControlledExecution.Run(action, cancellationToken); - } - catch (Exception e) - { - _exception = e; + cts.Cancel(); + _finishedExecution = true; } } - private void LengthyAction() + // Tests cancellation by the action itself. Finally blocks must be executed except the one that triggered cancellation. + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] + public void CancelItselfFromFinally() { - _startedExecution = true; - // Redirection via thread suspension is supported on Windows only. - // Make a call in the loop to allow redirection on other platforms. - bool sleep = !PlatformDetection.IsWindows; + bool finishedContainingFinally = false; - try + var cts = new CancellationTokenSource(); + RunTest(Test, cts.Token); + + Assert.False(finishedContainingFinally); + Assert.True(_finishedExecution); + // CancellationTokenSource.Cancel catches the ThreadAbortException and wraps it into an AggregateException + // at the end of the method's execution. The ThreadAbortException is not rethrown at the end of the catch + // block, because the Cancel method is called from a finally block. + Assert.IsType(_exception); + Assert.IsType(_exception.InnerException); + + void Test() { - for (_counter = 0; _counter < int.MaxValue; _counter++) + try { - if ((_counter & 0xfffff) == 0 && sleep) + try + { + // Make sure to run the non-inlined finally + throw new Exception(); + } + finally { - Thread.Sleep(0); + // When cancelling itself, the containing finally block must be aborted + cts.Cancel(); + finishedContainingFinally = true; } } + finally + { + _finishedExecution = true; + } } - catch - { - // Swallow all exceptions to verify that the exception is automatically rethrown - _caughtException = true; - } - - _finishedExecution = true; } - private void LengthyAction_ThrowFromCatch() + private void RunTest(Action action, CancellationToken cancellationToken) { - _startedExecution = true; - bool sleep = !PlatformDetection.IsWindows; + _readyForCancellation = _caughtException = _finishedExecution = false; + _exception = null; + _counter = 0; try { - for (_counter = 0; _counter < int.MaxValue; _counter++) - { - if ((_counter & 0xfffff) == 0 && sleep) - { - Thread.Sleep(0); - } - } + ControlledExecution.Run(action, cancellationToken); } - catch + catch (Exception e) { - _caughtException = true; - // The catch block must not be aborted - Thread.Sleep(100); - throw new TimeoutException(); + _exception = e; } - - _finishedExecution = true; } - private void LengthyAction_ThrowFromFinally() + private void CancelWhenTestIsReady(CancellationTokenSource cancellationTokenSource) { - _startedExecution = true; - - try + // Wait until the execution is ready to be canceled + while (!_readyForCancellation) { - // Make sure to run the non-inlined finally - throw new Exception(); - } - finally - { - // The finally block must not be aborted - Thread.Sleep(400); - throw new TimeoutException(); + Thread.Sleep(10); } + cancellationTokenSource.Cancel(); } - private void LengthyAction_Finally() + private static void WaitUntilAbortIsRequested() { - _startedExecution = true; - - try - { - // Make sure to run the non-inlined finally - throw new TimeoutException(); - } - finally + while ((Thread.CurrentThread.ThreadState & ThreadState.AbortRequested) == 0) { - // The finally block must not be aborted - Thread.Sleep(400); - _finishedExecution = true; + Thread.Sleep(10); } } - private void Action_CancelItself() + private void RunInfiniteLoop() { - _startedExecution = true; - - try + while (true) { - // Make sure to run the non-inlined finally - throw new TimeoutException(); - } - finally - { - // The finally block must be aborted - _cts.Cancel(); - _finishedExecution = true; + if ((++_counter & 0xfffff) == 0) + { + Thread.Sleep(0); + } } } + + private sealed class TestException : Exception + { + } } } diff --git a/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs b/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs index ea3b1bfdef1a6c..514febd5410eb3 100644 --- a/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs @@ -90,44 +90,43 @@ public static void Names() public static IEnumerable Platform_TimeZoneNamesTestData() { if (PlatformDetection.IsBrowser || PlatformDetection.IsiOS || PlatformDetection.IstvOS) - return new TheoryData + return new TheoryData { - { TimeZoneInfo.FindSystemTimeZoneById(s_strPacific), "(UTC-08:00) America/Los_Angeles", "PST", "PDT" }, - { TimeZoneInfo.FindSystemTimeZoneById(s_strSydney), "(UTC+10:00) Australia/Sydney", "AEST", "AEDT" }, - { TimeZoneInfo.FindSystemTimeZoneById(s_strPerth), "(UTC+08:00) Australia/Perth", "AWST", "AWDT" }, - { TimeZoneInfo.FindSystemTimeZoneById(s_strIran), "(UTC+03:30) Asia/Tehran", "+0330", "+0430" }, + { TimeZoneInfo.FindSystemTimeZoneById(s_strPacific), "(UTC-08:00) America/Los_Angeles", null, "PST", "PDT" }, + { TimeZoneInfo.FindSystemTimeZoneById(s_strSydney), "(UTC+10:00) Australia/Sydney", null, "AEST", "AEDT" }, + { TimeZoneInfo.FindSystemTimeZoneById(s_strPerth), "(UTC+08:00) Australia/Perth", null, "AWST", "AWDT" }, + { TimeZoneInfo.FindSystemTimeZoneById(s_strIran), "(UTC+03:30) Asia/Tehran", null, "+0330", "+0430" }, - { s_NewfoundlandTz, "(UTC-03:30) America/St_Johns", "NST", "NDT" }, - { s_catamarcaTz, "(UTC-03:00) America/Argentina/Catamarca", "-03", "-02" } + { s_NewfoundlandTz, "(UTC-03:30) America/St_Johns", null, "NST", "NDT" }, + { s_catamarcaTz, "(UTC-03:00) America/Argentina/Catamarca", null, "-03", "-02" } }; else if (PlatformDetection.IsWindows) - return new TheoryData + return new TheoryData { - { TimeZoneInfo.FindSystemTimeZoneById(s_strPacific), "(UTC-08:00) Pacific Time (US & Canada)", "Pacific Standard Time", "Pacific Daylight Time" }, - { TimeZoneInfo.FindSystemTimeZoneById(s_strSydney), "(UTC+10:00) Canberra, Melbourne, Sydney", "AUS Eastern Standard Time", "AUS Eastern Daylight Time" }, - { TimeZoneInfo.FindSystemTimeZoneById(s_strPerth), "(UTC+08:00) Perth", "W. Australia Standard Time", "W. Australia Daylight Time" }, - { TimeZoneInfo.FindSystemTimeZoneById(s_strIran), "(UTC+03:30) Tehran", "Iran Standard Time", "Iran Daylight Time" }, + { TimeZoneInfo.FindSystemTimeZoneById(s_strPacific), "(UTC-08:00) Pacific Time (US & Canada)", null, "Pacific Standard Time", "Pacific Daylight Time" }, + { TimeZoneInfo.FindSystemTimeZoneById(s_strSydney), "(UTC+10:00) Canberra, Melbourne, Sydney", null, "AUS Eastern Standard Time", "AUS Eastern Daylight Time" }, + { TimeZoneInfo.FindSystemTimeZoneById(s_strPerth), "(UTC+08:00) Perth", null, "W. Australia Standard Time", "W. Australia Daylight Time" }, + { TimeZoneInfo.FindSystemTimeZoneById(s_strIran), "(UTC+03:30) Tehran", null, "Iran Standard Time", "Iran Daylight Time" }, - { s_NewfoundlandTz, "(UTC-03:30) Newfoundland", "Newfoundland Standard Time", "Newfoundland Daylight Time" }, - { s_catamarcaTz, "(UTC-03:00) City of Buenos Aires", "Argentina Standard Time", "Argentina Daylight Time" } + { s_NewfoundlandTz, "(UTC-03:30) Newfoundland", null, "Newfoundland Standard Time", "Newfoundland Daylight Time" }, + { s_catamarcaTz, "(UTC-03:00) City of Buenos Aires", null, "Argentina Standard Time", "Argentina Daylight Time" } }; else - return new TheoryData + return new TheoryData { - { TimeZoneInfo.FindSystemTimeZoneById(s_strPacific), "(UTC-08:00) Pacific Time (Los Angeles)", "Pacific Standard Time", "Pacific Daylight Time" }, - { TimeZoneInfo.FindSystemTimeZoneById(s_strSydney), "(UTC+10:00) Eastern Australia Time (Sydney)", "Australian Eastern Standard Time", "Australian Eastern Daylight Time" }, - { TimeZoneInfo.FindSystemTimeZoneById(s_strPerth), "(UTC+08:00) Australian Western Standard Time (Perth)", "Australian Western Standard Time", "Australian Western Daylight Time" }, - { TimeZoneInfo.FindSystemTimeZoneById(s_strIran), "(UTC+03:30) Iran Time", "Iran Standard Time", "Iran Daylight Time" }, - - { s_NewfoundlandTz, "(UTC-03:30) Newfoundland Time (St. John’s)", "Newfoundland Standard Time", "Newfoundland Daylight Time" }, - { s_catamarcaTz, "(UTC-03:00) Argentina Standard Time (Catamarca)", "Argentina Standard Time", "Argentina Summer Time" } + { TimeZoneInfo.FindSystemTimeZoneById(s_strPacific), "(UTC-08:00) Pacific Time (Los Angeles)", null, "Pacific Standard Time", "Pacific Daylight Time" }, + { TimeZoneInfo.FindSystemTimeZoneById(s_strSydney), "(UTC+10:00) Eastern Australia Time (Sydney)", null, "Australian Eastern Standard Time", "Australian Eastern Daylight Time" }, + { TimeZoneInfo.FindSystemTimeZoneById(s_strPerth), "(UTC+08:00) Australian Western Standard Time (Perth)", null, "Australian Western Standard Time", "Australian Western Daylight Time" }, + { TimeZoneInfo.FindSystemTimeZoneById(s_strIran), "(UTC+03:30) Iran Time", "(UTC+03:30) Iran Standard Time (Tehran)", "Iran Standard Time", "Iran Daylight Time" }, + { s_NewfoundlandTz, "(UTC-03:30) Newfoundland Time (St. John’s)", null, "Newfoundland Standard Time", "Newfoundland Daylight Time" }, + { s_catamarcaTz, "(UTC-03:00) Argentina Standard Time (Catamarca)", null, "Argentina Standard Time", "Argentina Summer Time" } }; } // We test the existence of a specific English time zone name to avoid failures on non-English platforms. [ConditionalTheory(nameof(IsEnglishUILanguage))] [MemberData(nameof(Platform_TimeZoneNamesTestData))] - public static void Platform_TimeZoneNames(TimeZoneInfo tzi, string displayName, string standardName, string daylightName) + public static void Platform_TimeZoneNames(TimeZoneInfo tzi, string displayName, string alternativeDisplayName, string standardName, string daylightName) { // Edge case - Optionally allow some characters to be absent in the display name. const string chars = ".’"; @@ -139,8 +138,10 @@ public static void Platform_TimeZoneNames(TimeZoneInfo tzi, string displayName, } } - Assert.Equal($"DisplayName: \"{displayName}\", StandardName: {standardName}\", DaylightName: {daylightName}\"", - $"DisplayName: \"{tzi.DisplayName}\", StandardName: {tzi.StandardName}\", DaylightName: {tzi.DaylightName}\""); + Assert.True(displayName == tzi.DisplayName || alternativeDisplayName == tzi.DisplayName, + $"Display Name: Neither '{displayName}' nor '{alternativeDisplayName}' equal to '{tzi.DisplayName}'"); + Assert.Equal(standardName, tzi.StandardName); + Assert.Equal(daylightName, tzi.DaylightName); } [Fact] @@ -2083,6 +2084,11 @@ public static IEnumerable ConvertTime_DateTimeOffset_InvalidDestinatio yield return new object[] { s_strPacific + "\\Display" }; yield return new object[] { s_strPacific + "\n" }; // no trailing newline yield return new object[] { new string('a', 100) }; // long string + yield return new object[] { "/dev/random" }; + yield return new object[] { "Invalid Id" }; + yield return new object[] { "Invalid/Invalid" }; + yield return new object[] { $"./{s_strPacific}" }; + yield return new object[] { $"{s_strPacific}/../{s_strPacific}" }; } [Theory] diff --git a/src/libraries/System.Runtime/tests/System/UInt128Tests.cs b/src/libraries/System.Runtime/tests/System/UInt128Tests.cs index a02f205e72fa84..57a2f42ce09cda 100644 --- a/src/libraries/System.Runtime/tests/System/UInt128Tests.cs +++ b/src/libraries/System.Runtime/tests/System/UInt128Tests.cs @@ -101,6 +101,14 @@ public static IEnumerable ToString_TestData() { yield return new object[] { (UInt128)0, defaultSpecifier, defaultFormat, "0" }; yield return new object[] { (UInt128)4567, defaultSpecifier, defaultFormat, "4567" }; + yield return new object[] { new UInt128(0x0000_0000_0000_0001, 0x0000_0000_0000_0003), defaultSpecifier, defaultFormat, "18446744073709551619" }; + yield return new object[] { new UInt128(0x0000_0000_0000_0001, 0x0000_0000_0000_000A), defaultSpecifier, defaultFormat, "18446744073709551626" }; + yield return new object[] { new UInt128(0x0000_0000_0000_0005, 0x0000_0000_0000_0001), defaultSpecifier, defaultFormat, "92233720368547758081" }; + yield return new object[] { new UInt128(0x0000_0000_0000_0005, 0x6BC7_5E2D_6310_0000), defaultSpecifier, defaultFormat, "100000000000000000000" }; + yield return new object[] { new UInt128(0x0000_0000_0000_0036, 0x35C9_ADC5_DEA0_0000), defaultSpecifier, defaultFormat, "1000000000000000000000" }; + yield return new object[] { new UInt128(0x0013_4261_72C7_4D82, 0x2B87_8FE8_0000_0000), defaultSpecifier, defaultFormat, "100000000000000000000000000000000000" }; + yield return new object[] { new UInt128(0x7FFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF), defaultSpecifier, defaultFormat, "170141183460469231731687303715884105727" }; + yield return new object[] { new UInt128(0x8000_0000_0000_0000, 0x0000_0000_0000_0000), defaultSpecifier, defaultFormat, "170141183460469231731687303715884105728" }; yield return new object[] { UInt128.MaxValue, defaultSpecifier, defaultFormat, "340282366920938463463374607431768211455" }; } @@ -433,5 +441,15 @@ public static void TryFormat(UInt128 i, string format, IFormatProvider provider, Assert.Equal(expected.ToLowerInvariant(), new string(actual)); } } + + [Fact] + public static void Runtime75416() + { + UInt128 a = (UInt128Tests_GenericMath.Int128MaxValue - 10u) * +100u; + Assert.Equal(a, (UInt128)(Int128)(-1100)); + + UInt128 b = (UInt128Tests_GenericMath.Int128MaxValue - 10u) * (UInt128)(Int128)(-100); + Assert.Equal(b, 1100u); + } } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Pkcs/src/Resources/Strings.resx index 7055b2beec656b..008e12e8d08687 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Resources/Strings.resx @@ -283,4 +283,7 @@ The key in the enveloped message is not valid or could not be decoded. + + PKCS12 (PFX) without a supplied password has exceeded maximum allowed iterations. See https://go.microsoft.com/fwlink/?linkid=2233907 for more information. + 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 be33237e616414..d94830c0d850f8 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 @@ -1,10 +1,14 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);$(NetCoreAppMinimum)-windows;$(NetCoreAppMinimum);netstandard2.1;netstandard2.0;$(NetFrameworkMinimum) + $(DefineConstants);BUILDING_PKCS true true $(NoWarn);CA5384 true + + false + 2 Provides support for PKCS and CMS algorithms. Commonly Used Types: @@ -615,6 +619,8 @@ System.Security.Cryptography.Pkcs.EnvelopedCms Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml.cs Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml + (() => signedCms.RemoveCertificate(cert)); + } + } + + [Fact] + public static void ComputeCounterSignature_PreservesAttributeCertificate() + { + SignedCms signedCms = new SignedCms(); + signedCms.Decode(SignedDocuments.TstWithAttributeCertificate); + int countBefore = CountCertificateChoices(SignedDocuments.TstWithAttributeCertificate); + + using (X509Certificate2 cert = Certificates.RSA2048SignatureOnly.TryGetCertificateWithPrivateKey()) + { + CmsSigner signer = new CmsSigner(cert); + SignerInfo info = signedCms.SignerInfos[0]; + info.ComputeCounterSignature(signer); + } + + byte[] encoded = signedCms.Encode(); + int countAfter = CountCertificateChoices(encoded); + Assert.Equal(countBefore + 1, countAfter); + } + + [Fact] + public static void ComputeSignature_PreservesAttributeCertificate() + { + SignedCms signedCms = new SignedCms(); + signedCms.Decode(SignedDocuments.TstWithAttributeCertificate); + int countBefore = CountCertificateChoices(SignedDocuments.TstWithAttributeCertificate); + + using (X509Certificate2 cert = Certificates.RSA2048SignatureOnly.TryGetCertificateWithPrivateKey()) + { + CmsSigner signer = new CmsSigner(cert); + signedCms.ComputeSignature(signer); + } + + byte[] encoded = signedCms.Encode(); + int countAfter = CountCertificateChoices(encoded); + Assert.Equal(countBefore + 1, countAfter); + } + private static void VerifyWithExplicitPrivateKey(X509Certificate2 cert, AsymmetricAlgorithm key) { using (var pubCert = new X509Certificate2(cert.RawData)) @@ -664,5 +751,36 @@ private static void VerifyCounterSignatureWithExplicitPrivateKey(X509Certificate Assert.Equal(counterSignerPubCert, cms.SignerInfos[0].CounterSignerInfos[0].Certificate); } } + + private static int CountCertificateChoices(byte[] encoded) + { + AsnReader reader = new AsnReader(encoded, AsnEncodingRules.BER); + reader = reader.ReadSequence(); + reader.ReadObjectIdentifier(); + reader = reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0)); + reader = reader.ReadSequence(); + + reader.ReadInteger(); // version + reader.ReadSetOf(); // digestAlgorithms + reader.ReadSequence(); // encapsulatedContentInfo + + Asn1Tag expectedTag = new Asn1Tag(TagClass.ContextSpecific, 0, true); // certificates[0] + + if (reader.PeekTag() == expectedTag) + { + AsnReader certs = reader.ReadSetOf(expectedTag); + int count = 0; + + while (certs.HasData) + { + certs.ReadEncodedValue(); + count++; + } + + return count; + } + + return 0; + } } } diff --git a/src/libraries/System.Security.Cryptography.ProtectedData/src/System.Security.Cryptography.ProtectedData.csproj b/src/libraries/System.Security.Cryptography.ProtectedData/src/System.Security.Cryptography.ProtectedData.csproj index 34ed55a968938f..27084f0ad25143 100644 --- a/src/libraries/System.Security.Cryptography.ProtectedData/src/System.Security.Cryptography.ProtectedData.csproj +++ b/src/libraries/System.Security.Cryptography.ProtectedData/src/System.Security.Cryptography.ProtectedData.csproj @@ -1,9 +1,12 @@ - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);$(NetCoreAppMinimum)-windows;$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) + $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true - true true + true + + false + 1 Provides access to Windows Data Protection Api. Commonly Used Types: @@ -13,13 +16,11 @@ System.Security.Cryptography.ProtectedData - $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) true true - SR.PlatformNotSupported_CryptographyProtectedData - + Link="Common\System\Security\Cryptography\CryptoThrowHelper.Windows.cs" /> - + @@ -50,4 +51,8 @@ System.Security.Cryptography.ProtectedData + + + + diff --git a/src/libraries/System.Security.Cryptography.ProtectedData/src/System/Security/Cryptography/ProtectedData.cs b/src/libraries/System.Security.Cryptography.ProtectedData/src/System/Security/Cryptography/ProtectedData.cs index 61bb88a6cbe53f..0779fc90fd668a 100644 --- a/src/libraries/System.Security.Cryptography.ProtectedData/src/System/Security/Cryptography/ProtectedData.cs +++ b/src/libraries/System.Security.Cryptography.ProtectedData/src/System/Security/Cryptography/ProtectedData.cs @@ -16,14 +16,20 @@ public static partial class ProtectedData public static byte[] Protect(byte[] userData, byte[]? optionalEntropy, DataProtectionScope scope) { - ArgumentNullException.ThrowIfNull(userData); + CheckPlatformSupport(); + + if (userData is null) + throw new ArgumentNullException(nameof(userData)); return ProtectOrUnprotect(userData, optionalEntropy, scope, protect: true); } public static byte[] Unprotect(byte[] encryptedData, byte[]? optionalEntropy, DataProtectionScope scope) { - ArgumentNullException.ThrowIfNull(encryptedData); + CheckPlatformSupport(); + + if (encryptedData is null) + throw new ArgumentNullException(nameof(encryptedData)); return ProtectOrUnprotect(encryptedData, optionalEntropy, scope, protect: false); } @@ -102,5 +108,13 @@ private static bool ErrorMayBeCausedByUnloadedProfile(int errorCode) return errorCode == HResults.E_FILENOTFOUND || errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND; } + + private static void CheckPlatformSupport() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + throw new PlatformNotSupportedException(); + } + } } } diff --git a/src/libraries/System.Security.Cryptography.ProtectedData/tests/ProtectedDataUnsupportedTests.cs b/src/libraries/System.Security.Cryptography.ProtectedData/tests/ProtectedDataUnsupportedTests.cs new file mode 100644 index 00000000000000..641a6df2d38c80 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.ProtectedData/tests/ProtectedDataUnsupportedTests.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. + +using System.Security.Cryptography; + +using Xunit; + +namespace System.Security.Cryptography.ProtectedDataTests +{ + [PlatformSpecific(~TestPlatforms.Windows)] + public static class ProtectedUnsupportedDataTests + { + [Theory] + [InlineData(DataProtectionScope.LocalMachine)] + [InlineData(DataProtectionScope.CurrentUser)] + public static void Protect_PlatformNotSupported(DataProtectionScope scope) + { + Assert.Throws(() => ProtectedData.Protect(null, null, scope)); + } + + [Theory] + [InlineData(DataProtectionScope.LocalMachine)] + [InlineData(DataProtectionScope.CurrentUser)] + public static void Unprotect_PlatformNotSupported(DataProtectionScope scope) + { + Assert.Throws(() => ProtectedData.Unprotect(null, null, scope)); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.ProtectedData/tests/System.Security.Cryptography.ProtectedData.Tests.csproj b/src/libraries/System.Security.Cryptography.ProtectedData/tests/System.Security.Cryptography.ProtectedData.Tests.csproj index 61408ea83bcdb9..da449bd55a8456 100644 --- a/src/libraries/System.Security.Cryptography.ProtectedData/tests/System.Security.Cryptography.ProtectedData.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.ProtectedData/tests/System.Security.Cryptography.ProtectedData.Tests.csproj @@ -1,10 +1,11 @@ true - $(NetCoreAppCurrent)-windows;$(NetFrameworkMinimum) + $(NetCoreAppCurrent);$(NetFrameworkMinimum) + diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertTests.cs index a1e39d7d74b3af..5d6b9caaef0897 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertTests.cs @@ -414,7 +414,7 @@ public static void ExportPublicKeyAsPkcs12() // Read it back as a collection, there should be only one cert, and it should // be equal to the one we started with. - using (ImportedCollection ic = Cert.Import(pkcs12Bytes)) + using (ImportedCollection ic = Cert.Import(pkcs12Bytes, (string?)null, X509KeyStorageFlags.DefaultKeySet)) { X509Certificate2Collection fromPfx = ic.Collection; diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CollectionImportTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CollectionImportTests.cs index d46d734b5cefbb..a292d8e4828af5 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CollectionImportTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CollectionImportTests.cs @@ -22,7 +22,7 @@ public static void ImportNull() [Fact] public static void ImportEmpty_Pkcs12() { - using (ImportedCollection ic = Cert.Import(TestData.EmptyPfx)) + using (ImportedCollection ic = Cert.Import(TestData.EmptyPfx, (string?)null, X509KeyStorageFlags.DefaultKeySet)) { X509Certificate2Collection collection = ic.Collection; Assert.Equal(0, collection.Count); diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs index 73c00e0dd4376f..bf6da54862354f 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs @@ -636,7 +636,7 @@ public static void ImportFromFileTests(X509KeyStorageFlags storageFlags) [Fact] public static void ImportMultiplePrivateKeysPfx() { - using (ImportedCollection ic = Cert.Import(TestData.MultiPrivateKeyPfx)) + using (ImportedCollection ic = Cert.Import(TestData.MultiPrivateKeyPfx, (string?)null, X509KeyStorageFlags.DefaultKeySet)) { X509Certificate2Collection collection = ic.Collection; @@ -752,7 +752,7 @@ public static void ExportUnrelatedPfx() byte[] exported = collection.Export(X509ContentType.Pkcs12); - using (ImportedCollection ic = Cert.Import(exported)) + using (ImportedCollection ic = Cert.Import(exported, (string?)null, X509KeyStorageFlags.DefaultKeySet)) { X509Certificate2Collection importedCollection = ic.Collection; @@ -814,7 +814,7 @@ public static void ExportMultiplePrivateKeys() byte[] exported = collection.Export(X509ContentType.Pkcs12); - using (ImportedCollection ic = Cert.Import(exported)) + using (ImportedCollection ic = Cert.Import(exported, (string?)null, X509KeyStorageFlags.DefaultKeySet)) { X509Certificate2Collection importedCollection = ic.Collection; @@ -853,7 +853,7 @@ public static void CanAddMultipleCertsWithSinglePrivateKey() byte[] buffer = col.Export(X509ContentType.Pfx); - using (ImportedCollection newCollection = Cert.Import(buffer)) + using (ImportedCollection newCollection = Cert.Import(buffer, (string?)null, X509KeyStorageFlags.DefaultKeySet)) { Assert.Equal(2, newCollection.Collection.Count); } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/ExportTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/ExportTests.cs index 4c8beea3b4d33d..6e4314d6bbcf68 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/ExportTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/ExportTests.cs @@ -66,7 +66,7 @@ public static void ExportAsPfx() byte[] pfx = c1.Export(X509ContentType.Pkcs12); Assert.Equal(X509ContentType.Pkcs12, X509Certificate2.GetCertContentType(pfx)); - using (X509Certificate2 c2 = new X509Certificate2(pfx)) + using (X509Certificate2 c2 = new X509Certificate2(pfx, (string?)null)) { byte[] rawData = c2.Export(X509ContentType.Cert); Assert.Equal(TestData.MsCertificate, rawData); @@ -135,7 +135,7 @@ public static void ExportAsPfxWithPrivateKey() byte[] pfxBytes = cert.Export(X509ContentType.Pkcs12); - using (X509Certificate2 fromPfx = new X509Certificate2(pfxBytes)) + using (X509Certificate2 fromPfx = new X509Certificate2(pfxBytes, (string?)null)) { Assert.Equal(cert, fromPfx); Assert.True(fromPfx.HasPrivateKey, "fromPfx.HasPrivateKey"); diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.CustomAppDomainDataLimit.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.CustomAppDomainDataLimit.cs new file mode 100644 index 00000000000000..e61ba53f9359b3 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.CustomAppDomainDataLimit.cs @@ -0,0 +1,160 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.DotNet.RemoteExecutor; +using Microsoft.DotNet.XUnitExtensions; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using Test.Cryptography; +using Xunit; + +namespace System.Security.Cryptography.X509Certificates.Tests +{ + // AppContext and AppDomain are the same in this context. + public class PfxIterationCountTests_CustomAppDomainDataLimit + { + // We need to use virtual in a non-abstract class because RemoteExecutor can't work on abstract classes. + internal virtual X509Certificate Import(byte[] blob) => new X509Certificate(blob); + + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [MemberData(memberName: nameof(PfxIterationCountTests.GetCertsWith_IterationCountNotExceedingDefaultLimit_AndNullOrEmptyPassword_MemberData), MemberType = typeof(PfxIterationCountTests))] + public void Import_AppDomainDataWithValueTwo_ActsAsDefaultLimit_IterationCountNotExceedingDefaultLimit(string name, bool usesPbes2, byte[] blob, long iterationCount, bool usesRC2) + { + _ = iterationCount; + _ = blob; + + if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported) + { + throw new SkipTestException(name + " uses PBES2 which is not supported on this version."); + } + + if (usesRC2 && !PlatformSupport.IsRC2Supported) + { + throw new SkipTestException(name + " uses RC2, which is not supported on this platform."); + } + + RemoteExecutor.Invoke((certName) => + { + AppDomain.CurrentDomain.SetData("System.Security.Cryptography.Pkcs12UnspecifiedPasswordIterationLimit", -2); + + PfxInfo pfxInfo = s_certificatesDictionary[certName]; + + X509Certificate cert = Import(pfxInfo.Blob); + Assert.True(cert.Subject == "CN=test" || cert.Subject == "CN=potato"); + }, name).Dispose(); + } + + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [MemberData(memberName: nameof(PfxIterationCountTests.GetCertsWith_IterationCountExceedingDefaultLimit_MemberData), MemberType = typeof(PfxIterationCountTests))] + public void Import_AppDomainDataWithValueTwo_ActsAsDefaultLimit_IterationCountLimitExceeded_Throws(string name, string password, bool usesPbes2, byte[] blob, long iterationCount, bool usesRC2) + { + _ = password; + _ = iterationCount; + _ = blob; + + if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported) + { + throw new SkipTestException(name + " uses PBES2 which is not supported on this version."); + } + + if (usesRC2 && !PlatformSupport.IsRC2Supported) + { + throw new SkipTestException(name + " uses RC2, which is not supported on this platform."); + } + + RemoteExecutor.Invoke((certName) => + { + AppDomain.CurrentDomain.SetData("System.Security.Cryptography.Pkcs12UnspecifiedPasswordIterationLimit", -2); + + PfxInfo pfxInfo = s_certificatesDictionary[certName]; + + CryptographicException ce = Assert.Throws(() => Import(pfxInfo.Blob)); + Assert.Contains("2233907", ce.Message); + }, name).Dispose(); + } + + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [MemberData(memberName: nameof(PfxIterationCountTests.GetCertsWith_IterationCountNotExceedingDefaultLimit_AndNullOrEmptyPassword_MemberData), MemberType = typeof(PfxIterationCountTests))] + public void Import_AppDomainDataWithValueZero_IterationCountNotExceedingDefaultLimit_Throws(string name, bool usesPbes2, byte[] blob, long iterationCount, bool usesRC2) + { + _ = iterationCount; + _ = blob; + + if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported) + { + throw new SkipTestException(name + " uses PBES2 which is not supported on this version."); + } + + if (usesRC2 && !PlatformSupport.IsRC2Supported) + { + throw new SkipTestException(name + " uses RC2, which is not supported on this platform."); + } + + RemoteExecutor.Invoke((certName) => + { + AppDomain.CurrentDomain.SetData("System.Security.Cryptography.Pkcs12UnspecifiedPasswordIterationLimit", 0); + + PfxInfo pfxInfo = s_certificatesDictionary[certName]; + + CryptographicException ce = Assert.Throws(() => Import(pfxInfo.Blob)); + Assert.Contains("2233907", ce.Message); + }, name).Dispose(); + } + + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [MemberData(memberName: nameof(PfxIterationCountTests.GetCertsWith_IterationCountExceedingDefaultLimit_MemberData), MemberType = typeof(PfxIterationCountTests))] + public void Import_AppDomainDataWithValueMinusOne_IterationCountExceedingDefaultLimit(string name, string password, bool usesPbes2, byte[] blob, long iterationCount, bool usesRC2) + { + _ = password; + _ = blob; + _ = iterationCount; + + if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported) + { + throw new SkipTestException(name + " uses PBES2 which is not supported on this version."); + } + + if (usesRC2 && !PlatformSupport.IsRC2Supported) + { + throw new SkipTestException(name + " uses RC2, which is not supported on this platform."); + } + + RemoteExecutor.Invoke((certName) => + { + AppDomain.CurrentDomain.SetData("System.Security.Cryptography.Pkcs12UnspecifiedPasswordIterationLimit", -1); + + PfxInfo pfxInfo = s_certificatesDictionary[certName]; + + if (OperatingSystem.IsWindows()) + { + // Opting-out with AppDomain data value -1 will still give us error because cert is beyond Windows limit. + // But we will get the CryptoThrowHelper+WindowsCryptographicException. + PfxIterationCountTests.VerifyThrowsCryptoExButDoesNotThrowPfxWithoutPassword(() => Import(pfxInfo.Blob)); + } + else + { + Assert.NotNull(Import(pfxInfo.Blob)); + } + }, name).Dispose(); + } + + public static readonly Dictionary s_certificatesDictionary + = PfxIterationCountTests.s_Certificates.ToDictionary((c) => c.Name); + } + + public class PfxIterationCountTests_CustomLimit_X509Certificate2 : PfxIterationCountTests_CustomAppDomainDataLimit + { + internal override X509Certificate Import(byte[] blob) => new X509Certificate2(blob); + } + + public class PfxIterationCountTests_CustomLimit_X509Certificate2Collection : PfxIterationCountTests_CustomAppDomainDataLimit + { + internal override X509Certificate Import(byte[] blob) + { + X509Certificate2Collection collection = new X509Certificate2Collection(); + collection.Import(blob); + return collection[0]; + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.X509Certificate.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.X509Certificate.cs new file mode 100644 index 00000000000000..15a39d05755b3d --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.X509Certificate.cs @@ -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. + +namespace System.Security.Cryptography.X509Certificates.Tests +{ + public class PfxIterationCountTests_X509Certificate : PfxIterationCountTests + { + internal override X509Certificate Import(byte[] blob) + => new X509Certificate(blob); + + internal override X509Certificate Import(byte[] blob, string password) + => new X509Certificate(blob, password); + + internal override X509Certificate Import(byte[] blob, SecureString password) + => new X509Certificate(blob, password); + + internal override X509Certificate Import(string fileName) + => new X509Certificate(fileName); + + internal override X509Certificate Import(string fileName, string password) + => new X509Certificate(fileName, password); + + internal override X509Certificate Import(string fileName, SecureString password) + => new X509Certificate(fileName, password); + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.X509Certificate2.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.X509Certificate2.cs new file mode 100644 index 00000000000000..6e4697f406548d --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.X509Certificate2.cs @@ -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. + +namespace System.Security.Cryptography.X509Certificates.Tests +{ + public class PfxIterationCountTests_X509Certificate2 : PfxIterationCountTests + { + internal override X509Certificate Import(byte[] blob) + => new X509Certificate2(blob); + + internal override X509Certificate Import(byte[] blob, string password) + => new X509Certificate2(blob, password); + + internal override X509Certificate Import(byte[] blob, SecureString password) + => new X509Certificate2(blob, password); + + internal override X509Certificate Import(string fileName) + => new X509Certificate2(fileName); + + internal override X509Certificate Import(string fileName, string password) + => new X509Certificate2(fileName, password); + + internal override X509Certificate Import(string fileName, SecureString password) + => new X509Certificate2(fileName, password); + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.X509Certificate2Collection.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.X509Certificate2Collection.cs new file mode 100644 index 00000000000000..40b8ac11da8f43 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.X509Certificate2Collection.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Security.Cryptography.X509Certificates.Tests +{ + public class PfxIterationCountTests_X509Certificate2Collection : PfxIterationCountTests + { + internal override X509Certificate Import(byte[] blob) + { + X509Certificate2Collection collection = new X509Certificate2Collection(); + collection.Import(blob); + return collection[0]; + } + + internal override X509Certificate Import(byte[] blob, string password) + { + X509Certificate2Collection collection = new X509Certificate2Collection(); + collection.Import(blob, password, X509KeyStorageFlags.DefaultKeySet); + return collection[0]; + } + + // X509Certificate2Collection.Import does not support SecureString so we just make this work. + internal override X509Certificate Import(byte[] blob, SecureString password) + => new X509Certificate2(blob, password); + + internal override X509Certificate Import(string fileName) + { + X509Certificate2Collection collection = new X509Certificate2Collection(); + collection.Import(fileName); + return collection[0]; + } + + internal override X509Certificate Import(string fileName, string password) + { + X509Certificate2Collection collection = new X509Certificate2Collection(); + collection.Import(fileName, password, X509KeyStorageFlags.DefaultKeySet); + return collection[0]; + } + + // X509Certificate2Collection.Import does not support SecureString so we just make this work. + internal override X509Certificate Import(string fileName, SecureString password) + => new X509Certificate2(fileName, password); + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.cs new file mode 100644 index 00000000000000..234ec5bf3e6224 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxIterationCountTests.cs @@ -0,0 +1,225 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.DotNet.XUnitExtensions; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using Test.Cryptography; +using Xunit; + +namespace System.Security.Cryptography.X509Certificates.Tests +{ + public abstract partial class PfxIterationCountTests + { + private const long DefaultIterationLimit = 600_000; + internal abstract X509Certificate Import(byte[] blob); + internal abstract X509Certificate Import(byte[] blob, string password); + internal abstract X509Certificate Import(byte[] blob, SecureString password); + internal abstract X509Certificate Import(string fileName); + internal abstract X509Certificate Import(string fileName, string password); + internal abstract X509Certificate Import(string fileName, SecureString password); + + [ConditionalTheory] + [MemberData(nameof(GetCertsWith_IterationCountNotExceedingDefaultLimit_AndNullOrEmptyPassword_MemberData))] + public void Import_IterationCounLimitNotExceeded_Succeeds(string name, bool usesPbes2, byte[] blob, long iterationCount, bool usesRC2) + { + if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported) + { + throw new SkipTestException(name + " uses PBES2 which is not supported on this version."); + } + + if (usesRC2 && !PlatformSupport.IsRC2Supported) + { + throw new SkipTestException(name + " uses RC2, which is not supported on this platform."); + } + + if (PfxTests.IsPkcs12IterationCountAllowed(iterationCount, PfxTests.DefaultIterations)) + { + X509Certificate cert = Import(blob); + Assert.True(cert.Subject == "CN=test" || cert.Subject == "CN=potato"); + } + } + + [ConditionalTheory] + [MemberData(nameof(GetCertsWith_IterationCountExceedingDefaultLimit_MemberData))] + public void Import_IterationCountLimitExceeded_Throws(string name, string password, bool usesPbes2, byte[] blob, long iterationCount, bool usesRC2) + { + _ = password; + _ = iterationCount; + + if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported) + { + throw new SkipTestException(name + " uses PBES2 which is not supported on this version."); + } + + if (usesRC2 && !PlatformSupport.IsRC2Supported) + { + throw new SkipTestException(name + " uses RC2, which is not supported on this platform."); + } + + CryptographicException ce = Assert.Throws(() => Import(blob)); + Assert.Contains("2233907", ce.Message); + } + + [ConditionalTheory] + [MemberData(nameof(GetCertsWith_IterationCountExceedingDefaultLimit_MemberData))] + public void ImportWithPasswordOrFileName_IterationCountLimitExceeded(string name, string password, bool usesPbes2, byte[] blob, long iterationCount, bool usesRC2) + { + if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported) + { + throw new SkipTestException(name + " uses PBES2 which is not supported on this version."); + } + + if (usesRC2 && !PlatformSupport.IsRC2Supported) + { + throw new SkipTestException(name + " uses RC2, which is not supported on this platform."); + } + + using (TempFileHolder tempFile = new TempFileHolder(blob)) + { + string fileName = tempFile.FilePath; + if (PfxTests.IsPkcs12IterationCountAllowed(iterationCount, PfxTests.DefaultIterations)) + { + Assert.NotNull(Import(blob, password)); + Assert.NotNull(Import(blob, PfxTests.GetSecureString(password))); + + Assert.NotNull(Import(fileName)); + Assert.NotNull(Import(fileName, password)); + Assert.NotNull(Import(fileName, PfxTests.GetSecureString(password))); + } + else + { + if (OperatingSystem.IsWindows()) + { + // Specifying password or importing from file will still give us error because cert is beyond Windows limit. + // But we will get the CryptoThrowHelper+WindowsCryptographicException. + VerifyThrowsCryptoExButDoesNotThrowPfxWithoutPassword(() => Import(blob, password)); + VerifyThrowsCryptoExButDoesNotThrowPfxWithoutPassword(() => Import(blob, PfxTests.GetSecureString(password))); + + // Using a file will do as above as well. + VerifyThrowsCryptoExButDoesNotThrowPfxWithoutPassword(() => Import(fileName)); + VerifyThrowsCryptoExButDoesNotThrowPfxWithoutPassword(() => Import(fileName, password)); + VerifyThrowsCryptoExButDoesNotThrowPfxWithoutPassword(() => Import(fileName, PfxTests.GetSecureString(password))); + } + } + } + } + + internal static void VerifyThrowsCryptoExButDoesNotThrowPfxWithoutPassword(Action action) + { + CryptographicException ce = Assert.ThrowsAny(action); + Assert.DoesNotContain("2233907", ce.Message); + } + + [ConditionalTheory] + [MemberData(nameof(GetCertsWith_NonNullOrEmptyPassword_MemberData))] + public void Import_NonNullOrEmptyPasswordExpected_Throws(string name, string password, bool usesPbes2, byte[] blob, long iterationCount, bool usesRC2) + { + if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported) + { + throw new SkipTestException(name + " uses PBES2 which is not supported on this version."); + } + + if (usesRC2 && !PlatformSupport.IsRC2Supported) + { + throw new SkipTestException(name + " uses RC2, which is not supported on this platform."); + } + + CryptographicException ce = Assert.ThrowsAny(() => Import(blob)); + + if (PfxTests.IsPkcs12IterationCountAllowed(iterationCount, PfxTests.DefaultIterations)) + { + Assert.NotNull(Import(blob, password)); + Assert.NotNull(Import(blob, PfxTests.GetSecureString(password))); + + + using (TempFileHolder tempFile = new TempFileHolder(blob)) + { + string fileName = tempFile.FilePath; + Assert.NotNull(Import(fileName, password)); + Assert.NotNull(Import(fileName, PfxTests.GetSecureString(password))); + } + } + } + + internal static readonly List s_Certificates = GetCertificates(); + + internal static List GetCertificates() + { + List certificates = new List(); + certificates.Add(new PfxInfo( + nameof(TestData.Pkcs12NoPassword2048RoundsHex), null, 2048 * 3, true, TestData.Pkcs12NoPassword2048RoundsHex.HexToByteArray())); + certificates.Add(new PfxInfo( + nameof(TestData.Pkcs12OpenSslOneCertDefaultEmptyPassword), "", 2048 * 3, true, TestData.Pkcs12OpenSslOneCertDefaultEmptyPassword.HexToByteArray())); + certificates.Add(new PfxInfo( + nameof(TestData.Pkcs12OpenSslOneCertDefaultNoMac), null, 2048, true, TestData.Pkcs12OpenSslOneCertDefaultNoMac.HexToByteArray())); + certificates.Add(new PfxInfo( + nameof(TestData.Pkcs12NoPasswordRandomCounts), null, 938, true, TestData.Pkcs12NoPasswordRandomCounts)); + certificates.Add(new PfxInfo( + nameof(TestData.Pkcs12WindowsDotnetExportEmptyPassword), "", 6000, false, TestData.Pkcs12WindowsDotnetExportEmptyPassword.HexToByteArray())); + certificates.Add(new PfxInfo( + nameof(TestData.Pkcs12MacosKeychainCreated), null, 4097, false, TestData.Pkcs12MacosKeychainCreated.HexToByteArray(), usesRC2: true)); + certificates.Add(new PfxInfo( + nameof(TestData.Pkcs12BuilderSaltWithMacNullPassword), null, 120000, true, TestData.Pkcs12BuilderSaltWithMacNullPassword.HexToByteArray())); + certificates.Add(new PfxInfo( + nameof(TestData.Pkcs12Builder3DESCBCWithNullPassword), null, 30000, false, TestData.Pkcs12Builder3DESCBCWithNullPassword.HexToByteArray())); + certificates.Add(new PfxInfo( + nameof(TestData.Pkcs12Builder3DESCBCWithEmptyPassword), "", 30000, false, TestData.Pkcs12Builder3DESCBCWithEmptyPassword.HexToByteArray())); + certificates.Add(new PfxInfo( + nameof(TestData.Pkcs12WindowsWithCertPrivacyPasswordIsOne), "1", 4000, false, TestData.Pkcs12WindowsWithCertPrivacyPasswordIsOne.HexToByteArray())); + certificates.Add(new PfxInfo( + nameof(TestData.Pkcs12WindowsWithoutCertPrivacyPasswordIsOne), "1", 4000, false, TestData.Pkcs12WindowsWithoutCertPrivacyPasswordIsOne.HexToByteArray())); + certificates.Add(new PfxInfo( + nameof(TestData.Pkcs12NoPassword600KPlusOneRoundsHex), null, 600_001 * 3, true, TestData.Pkcs12NoPassword600KPlusOneRoundsHex.HexToByteArray())); + + return certificates; + } + + public static IEnumerable GetCertsWith_IterationCountNotExceedingDefaultLimit_AndNullOrEmptyPassword_MemberData() + { + foreach (PfxInfo p in s_Certificates.Where( + c => c.IterationCount <= DefaultIterationLimit && + string.IsNullOrEmpty(c.Password))) + { + yield return new object[] { p.Name, p.UsesPbes2, p.Blob, p.IterationCount, p.UsesRC2 }; + } + } + + public static IEnumerable GetCertsWith_IterationCountExceedingDefaultLimit_MemberData() + { + foreach (PfxInfo p in s_Certificates.Where(c => c.IterationCount > DefaultIterationLimit)) + { + yield return new object[] { p.Name, p.Password, p.UsesPbes2, p.Blob, p.IterationCount, p.UsesRC2 }; + } + } + + public static IEnumerable GetCertsWith_NonNullOrEmptyPassword_MemberData() + { + foreach(PfxInfo p in s_Certificates.Where(c => !string.IsNullOrEmpty(c.Password))) + { + yield return new object[] { p.Name, p.Password, p.UsesPbes2, p.Blob, p.IterationCount, p.UsesRC2 }; + } + } + } + + public class PfxInfo + { + internal string Name { get; set; } + internal string? Password { get; set; } + internal long IterationCount { get; set; } + internal bool UsesPbes2 { get; set; } + internal byte[] Blob { get; set; } + internal bool UsesRC2 { get; set; } + + internal PfxInfo(string name, string password, long iterationCount, bool usesPbes2, byte[] blob, bool usesRC2 = false) + { + Name = name; + Password = password; + IterationCount = iterationCount; + UsesPbes2 = usesPbes2; + Blob = blob; + UsesRC2 = usesRC2; + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxTests.cs index b7d8b6de5116ac..5313f830222020 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/PfxTests.cs @@ -1,14 +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 Microsoft.DotNet.XUnitExtensions; using System.Collections.Generic; +using System.Reflection; +using System.Text; using Test.Cryptography; +using Microsoft.DotNet.RemoteExecutor; using Xunit; +using System.Linq; namespace System.Security.Cryptography.X509Certificates.Tests { public static class PfxTests { + private const long UnspecifiedIterations = -2; + private const long UnlimitedIterations = -1; + internal const long DefaultIterations = 600_000; + private const long DefaultIterationsWindows = 600_000; + + // We don't know for sure this is a correct Windows version when this support was added but + // we know for a fact lower versions don't support it. + public static bool Pkcs12PBES2Supported => !PlatformDetection.IsWindows || PlatformDetection.IsWindows10Version1703OrGreater; + public static IEnumerable BrainpoolCurvesPfx { get @@ -448,6 +462,70 @@ public static void CollectionPerphemeralImport_HasKeyName() } } + + [ConditionalTheory] + [MemberData(memberName: nameof(PfxIterationCountTests.GetCertsWith_IterationCountNotExceedingDefaultLimit_AndNullOrEmptyPassword_MemberData), MemberType = typeof(PfxIterationCountTests))] + public static void TestIterationCounter(string name, bool usesPbes2, byte[] blob, int iterationCount, bool usesRC2) + { + _ = iterationCount; + + MethodInfo method = typeof(X509Certificate).GetMethod("GetIterationCount", BindingFlags.Static | BindingFlags.NonPublic); + GetIterationCountDelegate target = method.CreateDelegate(); + + if (usesPbes2 && !Pkcs12PBES2Supported) + { + throw new SkipTestException(name + " uses PBES2 which is not supported on this version."); + } + + if (usesRC2 && !PlatformSupport.IsRC2Supported) + { + throw new SkipTestException(name + " uses RC2, which is not supported on this platform."); + } + + try + { + long count = (long)target(blob); + Assert.Equal(iterationCount, count); + } + catch (Exception e) + { + throw new Exception($"There's an error on certificate {name}, see inner exception for details", e); + } + } + + internal static bool IsPkcs12IterationCountAllowed(long iterationCount, long allowedIterations) + { + if (allowedIterations == UnlimitedIterations) + { + return true; + } + + if (allowedIterations == UnspecifiedIterations) + { + allowedIterations = DefaultIterations; + } + + Assert.True(allowedIterations >= 0); + + return iterationCount <= allowedIterations; + } + + // This is a horrible way to produce SecureString. SecureString is deprecated and should not be used. + // This is only reasonable because it is a test driver. + internal static SecureString? GetSecureString(string password) + { + if (password == null) + return null; + + SecureString secureString = new SecureString(); + foreach (char c in password) + { + secureString.AppendChar(c); + } + + return secureString; + } + // Keep the ECDsaCng-ness contained within this helper method so that it doesn't trigger a // FileNotFoundException on Unix. private static void AssertEccAlgorithm(ECDsa ecdsa, string algorithmId) @@ -476,5 +554,7 @@ private static X509Certificate2 Rewrap(this X509Certificate2 c) c.Dispose(); return newC; } + + internal delegate ulong GetIterationCountDelegate(ReadOnlySpan pkcs12); } } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/AiaTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/AiaTests.cs index 89882f4de5a33c..753bec3b88b6bd 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/AiaTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/AiaTests.cs @@ -53,6 +53,41 @@ public static void EmptyAiaResponseIsIgnored() } } + [Theory] + [InlineData(AiaResponseKind.Pkcs12, true)] + [InlineData(AiaResponseKind.Cert, false)] + public static void AiaAcceptsCertTypesAndIgnoresNonCertTypes(AiaResponseKind aiaResponseKind, bool mustIgnore) + { + CertificateAuthority.BuildPrivatePki( + PkiOptions.AllRevocation, + out RevocationResponder responder, + out CertificateAuthority root, + out CertificateAuthority intermediate, + out X509Certificate2 endEntity, + pkiOptionsInSubject: false, + testName: Guid.NewGuid().ToString()); + + using (responder) + using (root) + using (intermediate) + using (endEntity) + using (X509Certificate2 rootCert = root.CloneIssuerCert()) + { + responder.AiaResponseKind = aiaResponseKind; + + using (ChainHolder holder = new ChainHolder()) + { + X509Chain chain = holder.Chain; + chain.ChainPolicy.CustomTrustStore.Add(rootCert); + chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; + chain.ChainPolicy.VerificationTime = endEntity.NotBefore.AddMinutes(1); + chain.ChainPolicy.UrlRetrievalTimeout = DynamicRevocationTests.s_urlRetrievalLimit; + + Assert.NotEqual(mustIgnore, chain.Build(endEntity)); + } + } + } + [Fact] [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "CA store is not available")] public static void DisableAiaOptionWorks() diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj b/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj index a284d8edf478ca..84f30311a63b8e 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj @@ -49,6 +49,11 @@ + + + + + diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/TempFileHolder.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/TempFileHolder.cs index 4ce70ccbf4412b..a6efa3a1f19173 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/TempFileHolder.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/TempFileHolder.cs @@ -20,6 +20,13 @@ public TempFileHolder(ReadOnlySpan content) } } + public TempFileHolder(byte[] content) + { + FilePath = Path.GetTempFileName(); + + File.WriteAllBytes(FilePath, content); + } + public void Dispose() { try diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/TestData.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/TestData.cs index 92d8606e3093b4..0e0202fa119df0 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/TestData.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/TestData.cs @@ -2943,5 +2943,1221 @@ internal static DSAParameters GetDSA1024Params() "MAoGCCqGSM49BAMCA0cAMEQCIHafyKHQhv+03DaOJpuotD+jNu0Nc9pUI9OA8pUY\n" + "3+qJAiBsqKjtc8LuGtUoqGvxLLQJwJ2QNY/qyEGtaImlqTYg5w==\n" + "-----END CERTIFICATE-----"; + + public static readonly byte[] PfxWithNoPassword = ( + "308205DB0201033082059706092A864886F70D010701A0820588048205843082" + + "0580308202F106092A864886F70D010701A08202E2048202DE308202DA308202" + + "D6060B2A864886F70D010C0A0102A08202AE308202AA3024060A2A864886F70D" + + "010C010330160410286B4EFF0202AFE16583E50C3EB8F38F020207D004820280" + + "C3452B3850AE69FCC0AC426FF3A0421477813259C128D643452219EEF71EBCAB" + + "5E7054B7A195E3F945222864CA37D8F67DDFA9136A93CD7FEAD86F00D4179F1C" + + "557253253C6235295499729C564DE2CE30E131C0D9B3E1BDBB211F8FA9E78B7B" + + "088C63137DF44CF50C293E082E7C57A8D0CB0404D1F5B9D1491F4EF9045181D3" + + "8D528C61F49EB3F1CF11ABB60270CBC10AC4BAF115A5AB52EA22FE4406743695" + + "7DDC1BFEE0C6BDC097BDF092AC6D11CABAE497FC10564E7E7797BC6028CBD75B" + + "1A2339329D439F6557B3CEB77489467FC8990EE832D48E2FF65A7BCB20E2DBBD" + + "F81C762F688E2EE43822CF9DEDE11914DD982FAB2AC141496D912396F6F67E3C" + + "D04D0617F8EC2BE6D35AF9860C384DB8C21FD0B00494FA3188983E6200DE90B3" + + "E6E662C5B07AB202A1B9C3F10F03B88E677EC7BDC2873AA4DFF873DCD714AD39" + + "42C33E63442A855C709F58063D836F8CA77DB9D208F3DB2552D7AC611409E8BA" + + "942BE520D9B4951AB844892D123DF550BA4F22D255069AD77E6C14D730128FAD" + + "3736551CDBEED2022417A948B1BD2567C39DFB561F251C45D2F4B5711B4AC82A" + + "09226403432FEEEEAC5F54590CC2EC7925768651ACF42C32A114ED133718FB1A" + + "9FB0DCC35C10640226F7587BD4EA67C10EF963113D03988C82B3A0B43B1B60ED" + + "BE0A7CBCB6422CD9695BF206A190E8EA6F87E2FABB33549081EFA7A8B04CA044" + + "9DF52E133556781511CDFCBDC55B487DA51C8D476FB635896FD0C71754B2EEE9" + + "A395F355F4A56CCB8CE75D65F73AE8DE80215EA7CA9129FD90EC48ECF26828A6" + + "0E471FA5F34471AD1C21AD200E16C4E4E99FA55B5CF75BC4BF68D1A975E8276F" + + "5EBD47EEB21DB06092F51AEE6BDAA2CDBC9387620A2B3983247E9AA93252D49A" + + "3115301306092A864886F70D01091531060404000000003082028706092A8648" + + "86F70D010706A0820278308202740201003082026D06092A864886F70D010701" + + "3024060A2A864886F70D010C010330160410127070F8C0D10011CBE0CC8489A3" + + "0C71020207D080820238C151B340B678AE8CAD6C6B11E0661E91FD1F4107F0DB" + + "687F039362D88AFF382E3557A75157D8A6D93F0777AD41D7520D32916677699A" + + "EC3DAEE462344BF18410EE07E83811EC26569FF9CD8D13A77F387D6E7C5C21C1" + + "6BC9936DB1B8AB614A8B4B6F9975E4A0A5DFCE29EF14E833FB2526805901A782" + + "724AF6BA2A80E93A4C4BA07B1C1319169E200A4B7AB100AE2CA135512919120A" + + "1D1AB57EF6DED00144F87D051391676D205196ABB4B211698BF137436D6E39D6" + + "719737B66AD2E76D5766D36E87108D79145021C77328A9F2ADDF44EC2A95EEA3" + + "86DCA32FB53D0AA92FB5C5BB7B49CB1F1755ABF195C7274702681C616C21BB05" + + "817B5FD344FC25CC6145A4FFE36F4D5BC131434E6C44BD14769EB08FBDA0D1A0" + + "6DCF2D061FA4A2FCC45A30125680507AACEE7903ABA0C1D36395925A82741797" + + "CF93A11F249D7E7D8228F8F6AFD03FB317D1F2BDB319C0AEF15E19E9DB1067B1" + + "A6CC12CA458C33BAB31C3F275C45A956F71CFF939F393EC7D20E13B397E64263" + + "702B54DD228D0E1275B39A77B3B1A28EFD5C7DF2643CAF7AAF8574988CDF4112" + + "E057F715331F6E75462E6C948BFB92C5BC81B84FBB47FB97AEB3D8C228388B94" + + "CDFA0E2A48F05A32EA9F2CDFAE2B0CEFF815531B148C358ECE1D23F7B793A1FF" + + "ECE491E990BDF231756600B87FA2F7F3AD2C2AA2F6DB42FC6D62766F28F60436" + + "FFA4993C87BE6631D7CE6C06C5B7AE7218450C504ADB401B9FBA5FB2FC6A8289" + + "B42D51B4E1AF159AE7F3A63BA8644C8C5A99F108FC25A27DE54E268AF8A259D9" + + "F6E3303B301F300706052B0E03021A0414AF311074EBABE699402460BFFFE14E" + + "4D7314FCB4041469835268466D1390373566F7034C4736346CD17D020207D0").HexToByteArray(); + + internal static readonly byte[] Pkcs12NoPasswordRandomCounts = + Convert.FromBase64String(@" + MIIvdAIBAzCCLtQGCSqGSIb3DQEHAaCCLsUEgi7BMIIuvTCCLrkGCSqGSIb3DQEHAaCCLqoEgi6m + MIIuojCCAvkGCyqGSIb3DQEMCgECoIIC6DCCAuQwXgYJKoZIhvcNAQUNMFEwMAYJKoZIhvcNAQUM + MCMEELD+7LV5Y9tyUiJnNeZVLwQCASowDAYIKoZIhvcNAgsFADAdBglghkgBZQMEASoEEBzHfelA + 4f5vP3LmQVtFodcEggKAfy/+/5lLpe/Ti1qZB1kbVrY1nheb2TVTfqLyjqUxsQlo4ElbAdgvlkJi + NpmGu//pniqKxODkZujRMTty8VfdnLpe8gVGCltSiDSXF3ttcsv7yKYkJiU5GN4cWwz8qud6P6cn + zEHtnJJ4AkNJT/Lkjy7ktTbww+ha/1IbteKWFW7tarKkjrx2PxqiizLKhydPvLrr7v0tqgv9K2/G + mFvJF9i/ynVHS2NtD11kHysBj3DjMTSEddBp7th1gXNevQ4NcxNL9hG0g4tCYS/Tq6WRMJip5PSb + 7tsYxtWJU4vb3JYKbadVnOwiP7VAUOEU8YJUiF6lBtWvNTwpmRg8S/Jg4EWqOX32DjSl5jx/E+q6 + ljMANkInnAI7uPa87GACaaCfKI7sPtlwjz2JQtj5HUrjGu5ff0yY2fl98FneuO5n6atD1X7460vc + KdeDAhXsWh/oBGrmREYvPFI/9vE8Lm8i5FKzZZTY+XiTsQMeLeFLmzikqCubfcQhtYOJLCZUb3LD + HALSKFdtKpBJAp0rrh6NkR5ss3jFQmclctJeYYFGPfHhM6g75jqR/3X8JUucduJ8+2k25B5qqiQ2 + xMVYwLdCTU8bW2KIt3VCROEO1n+kLM94Byju1f57vn0ZkRhKtVS2AvmxdxAD2mrVE6Mj6Hw2VWHi + Bnqer8BrmOhVOoKuRj49Nz5PFGoncdayJJe6h9/HDB+RofLe8tKPbLDHbDm1jmJaZoBHbypESW5f + KUKHhjsH1awzNMs180o71hsiL4NuUqUk9nEAvx3QqolHIuGe2YnLlRBWzOB0lCqmMBV2D91O1SIX + RfZ0T+0AA87jGvjK+sCxVNQvqwgEYZTQAn11HDCCAcsGCyqGSIb3DQEMCgEDoIIBujCCAbYGCiqG + SIb3DQEJFgGgggGmBIIBojCCAZ4wggEHoAMCAQICCQDG4/UcO7qKXjANBgkqhkiG9w0BAQsFADAR + MQ8wDQYDVQQDEwZwb3RhdG8wHhcNMjMwNDE4MjAyNTA4WhcNMjMwNDE4MjAyNTA4WjARMQ8wDQYD + VQQDEwZwb3RhdG8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAL3Hf+x7UDSJ6CzFBJ+riwvJ + TepV4Zjy/NY+09FbR9L6rmzF8fp6IyT9c5osmCK/Q81e3fptYFeSowcm4v628tM8Aw9L8+H8OO+w + xfwiQeyRXCFCwl+rRgKVuvkvtupejL6aZdaZmvF1GP/Jho13zY1ILBMzgtw+MXnZVsWi4iYxAgMB + AAEwDQYJKoZIhvcNAQELBQADgYEAE8FaAa0klFrcONIsUk6A9M45lo/iXZjnxuq/X05tDlUd/nDW + Am6bYrSxTC/o3LcYkRg+GVy8Gs1AaeeG/MFXe2zm9N7DuOqKtslKvmjt8L0L1E3zygUc4DUl87y8 + p0tn8bKI/RoflMNU/mHr8U3pKg9SaENLZq0TlBXLrr8IGyQwggL5BgsqhkiG9w0BDAoBAqCCAugw + ggLkMF4GCSqGSIb3DQEFDTBRMDAGCSqGSIb3DQEFDDAjBBB6JnCtAnPpOcMgsMFOBUggAgE9MAwG + CCqGSIb3DQILBQAwHQYJYIZIAWUDBAEqBBAFove+QNh2kcK4jBcdRQZqBIICgEZS8QJsHwDdktJ6 + 4TalaTiYmQ734FJDwHA8v0qhMEh7gWGf/0X2dRi2s27lU78g0C3YtJNnEFRtICTpco6mJu1r/+nq + 9QG6OTdwfiB8vKPpkQaLVQr4bl9rA0lAOP9Lqyv9cjFg97GLJCrsPkeykV0FafBRyeQdMjDR60yO + X2AcV3XFPrzphibsAazyC0IymSfUvytHK54PI4z9RWGwFncBZjItkbNPxfVoMRnfBpLOYpz/nfFu + eky6qItXtXkz0rfC4qNPEpPEd4E2ecoRWJxCK6ItS2R5zfrTeQ0LqlkptBvcxC2k3TfSl+TVTee7 + g0+z8bc9JypL9qr/M4Ir8qLbdGT4aAigRrS8+s336p3J4qJkdFreS+FtHHlmms6E28g2h6HwynCG + Mc/z8TikKm/ltJ4+FiB6g+NywPgJbMFBjsaNF+wPlGMRO83M+3zysQCPrhtdo2gsjnJEqBN2kXu8 + 4k05UAHTpsrZn7FmAG7Kt553y/xJqY8U0rwswlcy402CKtS26kQ7Vv0Ic3YfsaNypNwYo1h5b/tl + t/8c0Sws8JoEj4RW/dflaMmZ5qDdZ1Smiwgx3l4VC9lpm2AmTlvV3MBdsnAKMfQuilqK14rOftaG + lzKIEI34F/Z7qUKCTL6LgTsHxt6L/aHxeraXSMplLb7E6rRlau7BYZBNbtZ6hHRzqBIui9m7iQ23 + 3wYxpK7etlFz6fAHyFFxsawMrjgFq24L3dugCa2wuUPesNwcSn2Zte6FS+JUOgaOqWAsSwhDPn+u + Fws/t23vpKm1cBtNAE4kl6IWI4/QBfxnAHabNE66LZfFUSFXsk+wKJO8PKsfSLZ6oVZSkRcOHQ4W + EN4wggL5BgsqhkiG9w0BDAoBAqCCAugwggLkMF4GCSqGSIb3DQEFDTBRMDAGCSqGSIb3DQEFDDAj + BBDJY4TwjOXIl9C20wMX0JQSAgE9MAwGCCqGSIb3DQILBQAwHQYJYIZIAWUDBAEqBBBegG+K9bCn + WqkCVKlZOYRrBIICgO1ic3SIALh0ujMqyrqazrx+E53ShenbUAx5fgSka7pINpFjtFikScuN6bAc + fs2lQTgH0+alWGr+HLoHa0GsO4Z/TPYZqswD8NFoVFKz5gK2zXMfFrObp/0HVj8hLMfOKiMH6tWo + yL0gDDn3t1kz9v2eglf8ifjS1jFANpX4s/hpEFdCfzqjsQwy7ToZdn2BB2CbUy9A3GFoi/z1O+7N + angPPr4r6PE1jOH63TFIRwxmH6kLGGc253UxQI4P+v5g/hh19dRByjIQvk2TyWYRugkAx/B4RNzD + NAWYFvae79uWhKQrfzUWhGhtfmWxqcZnwDb69aCdzBBXOKz7n2ZCK6Ex8qK2gm29foqjMQnX8YQZ + EB5dRX1r+jIBHwBQFyz4fofByVdjb4SA7g7RAmoCRYsJYupz9Gnesg0De3iADbTx97oco60PXdjL + OHhGqjrMpgAKSVS+tULc33CSUHq+Zsevs/hSB0Aqh6YH+3kWp/LdRB72FUHQSpt5/aFm84M8guc7 + lD11Fcgpj/y1zUmyFlyvRgvGMeFhkGTJPkkSpFXq+M09xAuljtQ+QCTF1Q/jx9cI9glconr+umd2 + VB0Mq/ppRrbiCGFNLOOfHk6na1Z2FAv8bywZ701WyMaZRvgaYca8ueKEh77MQsymG/5A1kKueiRW + 9Gx6tP0OJDWUCaOeGMJQFdP/e52FC+EdKkAZajngWThSX/ZO2Mp8oxufHRGcGHIdzFCzh5iaVxb9 + 7pG3rV6NS9wSz8eAMixmEmf44P8rOhiHbyxoaCB+6iYluHtQuw2fU7JlIPFrK/o++rGtDq+GHQw2 + zXUIbrpw1MzbtUgW/hTRlqjasXtlrgDzSZYwggL5BgsqhkiG9w0BDAoBAqCCAugwggLkMF4GCSqG + SIb3DQEFDTBRMDAGCSqGSIb3DQEFDDAjBBAQSmfTMz55MV7GR5lEF9SXAgE9MAwGCCqGSIb3DQIL + BQAwHQYJYIZIAWUDBAEqBBDLBwbNgTYNIoSJTI9zbdvuBIICgHux5dOkqx9pQCQ8nDYiqUJAkfl3 + SEF+FRJb2xUErUshDNwFeC8NHkJ6R/KkBAtqRtak44/EuN8/RPrZa4huIFxu71hABETWit+fyirU + HnqAS/mz66pe3IxRr5qFY5/WxUCsYj3lYjfVyLKrCwBZhtYyrXQJg+0qWYs6+16ij7pifosDb90e + UgD5U9jqwMIwAdjiTxQ6BSq9AeIlhdI9Y1z13kfcXXhWpNu6EWlcYB2VNPZ4r32CRJe+fR9AWXCb + iajbIItWjhiwRlVLao8KcFMAq5N5CaDEZj3RwVb7Cf24Pb6j+dF+EotSW2Wem3ZAPeKOTI0Ykk2f + MtZPD5znGZBJtbBM6iuoqm3+axrnK6f8XAyKdKEHnA3lqGtQR8GimqwCFkBv/gUVk9eVCvL6yErp + zoJpA6r0fLo1NQGSW17TR0NMyfyPThqRrUtTZlDCWzgJbfNWs/oaAplX63naXsUtS3duYQSTHO95 + 4e89OrrSa/eXSYn30j767GEkIUGBFaPcFqV86FMBiVbDOmvEYBK6ORdBqYrjLAD88EYX9e/pcmgn + svWLwWlYe+uMAGMOxsuvQcp3vqlyPCxuYrh6LdKVBrK4m9wQAIyF3niDrGOJpmVztKdDaB51jDav + nK16vc5JpG3BlnnpjBEEYJGUS18ms6RTH4iLpk/Ui4A32sdhM44P/nnUoEtaDrKoUxMNS6UcDaEf + 5N7eJ+ccEDGym3tGxVCT7i6uKrQKEyKf7FI0mf9NgPk1Lz/DBCyvnRVgx7qEdhmbhEUZgQ7ccmBj + V4cFR7+NfnxUABCyJyZu+gLFqaDfALhQvwmCIrAgOgx8FyeMw8MWdFFUk0WV7K0nUh4wggL5Bgsq + hkiG9w0BDAoBAqCCAugwggLkMF4GCSqGSIb3DQEFDTBRMDAGCSqGSIb3DQEFDDAjBBA8NwJFoLrq + bxCmSinxHNnGAgE9MAwGCCqGSIb3DQILBQAwHQYJYIZIAWUDBAEqBBB3zPuXtX75pJJA2Rvq+DyC + BIICgF/qdMfgy4cf03s8T2UbLt1bsLvsJgptXjgrGYWfc6lgidaWxGPf+66HwqaCA4sTPH/U98jO + Ok8ljcVApAAlAONsQKlOYm1G/V8AxgF1RD02GQo148+kHq4swNcdXoAcowuvEihsGKk1jszXH2L0 + iahiAMwRimEglTaGBJd1cJLhVe45A7RHPl8hKZECvvSx2kB1h26pqBIYRoGSamAwABRKYc43YlBG + eczB8Ry+sZUwrhntjSFeGa61TGM9jWr04GjnnTWN+IxRXz9Epe0NWFPMcOXg+gIPFf6RyULYdSO0 + kNb3jolrz7nYKO4qTHwuqYrQGJC/3sfqc+s2SCNZnIhrQf3Q2L3jhqxnc/u7gdxqXGwFCxkeG/q+ + TW8Yt4ncqETFqmQs8J6IMPncuoRHmYT+9JZjBnCakFjsrwOBQD69K1VMJ1ZbfZY66YAA+tLUuDB5 + khn5zmeqbyjRDaG+sZZtvHYhob+Hbz0o1Uv9vXQ3t2mVENHPj2LI6Fk+AGNKp5O4DYBVsrak8huL + hc7W9Z9/SAAUFfkf8P4fp18WgTnWr8Wv7UBSHmwTWwzjEERCI+KENH/zODdlwQH2I8/bsS8vNC91 + WWXrBIF1BU1jsHSYbqkDvOugll8bY9LB04isZfcUucdIJH9U7FRbAR3hXeC47C9C6kZvKVqwW5ag + KcFKE212LRK19NQ8UBNwQdxL3WIlT/32AHVVzkBfL5mUqkwTQWaZrD+6+wrVKsG28ECrJvYUQlQg + L4+uErK5RWgfTOJHgrlWB0bd+AJ7oZ2agys9eL8Nh9/pzR8vp3xrrwofFRZxc+8rzHRvcJhjpUAQ + uWK1DJtx8SBLWXJh6QLXiGIwggL5BgsqhkiG9w0BDAoBAqCCAugwggLkMF4GCSqGSIb3DQEFDTBR + MDAGCSqGSIb3DQEFDDAjBBCRnVLg3B7r7n0iuf43dPAcAgE9MAwGCCqGSIb3DQILBQAwHQYJYIZI + AWUDBAEqBBCNlDLKYmIdyzfRqK8rUN3IBIICgGM4XE2PVGrFipIfzqKwU17TmIDKKPPBbk24RCo9 + 0Q9yRIH3yyLoV8rd3xONA9eUU5JePhwDEgDS3z1Vl6E4qAVXZ86E+APODs2ZyhYO3IA7NmUjvlH1 + s01OjmVIQdn/GHhfbYl5vseuedFtYmKGMmyUxCOVtYBro1P5RKZF0boeA4Dq+9tl6awVptEN18yL + a24HmWKfleY9uYQZ26sX3LFmzhCZI6gSpdcg9Fv+WzoFZn/XbhY9g4IK7OMzEYnqw9JGBQRl/kNR + 1sSkxPiB8CHWfeNkQz6hfUrubFI9+rZCzMn+j1LC7ERFKff4bX+qBU766+jB7DK0vL2Q6QqLYvGR + IrYGn6B2X2X7sOA8CYIvFKVawy7Ec9ZLi3cxriFgZMdmkB9mzzenq+wB8gkFqnnbw+CjTWQTwB4P + SZEUsls1uHgoClg+UMvpNXJ0YosdfmrOswSwG82kPF8R6ZT89o7E/+8SGKz7khF0U9Tdn+ZygoL6 + OxuMln0Jwg//hp0QejHYxVxRy9D5GHVH4FuLpei8Jwpisz3Rvb5+59SUU3VKlkxuPD2R7OJuQVkL + N+GXeEOJ59gOI38FKef7p/AJU8PdTt4nxCTjLpbBsNkG9NcY9ORpEY3FCjEJK6jLiid04FE94A1F + 0Xbsr6fWFIbCrYG+pZiashoiboAyUTEiwFuNoI0ao1sNaOkEkFiEDjxvlGZ7KH8Cv+wxZ8qDpcUR + Uhd2VEmpN1/RbWlXfqgGANFo/jipLgNXOCCMrBCpkkPnscoKoWAQNVSbEjcelAslXiCqumR+EBr6 + Hx3W0prA2SyGlhHDjJGA1c3a9oPpJ2oDX7+RNThRAnhiQ7xNeGPHgHowggL5BgsqhkiG9w0BDAoB + AqCCAugwggLkMF4GCSqGSIb3DQEFDTBRMDAGCSqGSIb3DQEFDDAjBBDYU4dscAnvUzG7FUBYdA2c + AgE9MAwGCCqGSIb3DQILBQAwHQYJYIZIAWUDBAEqBBAwsNB30zk+oAIl92c/+T0cBIICgA3tpZTk + auJrxGyE7auf2lrpB1nCtHGHsxHpm0TOj+7ovX8/TZcR/j/JTQrAs85/MHRmZu9MZ/PNRTMpgcP/ + 9qtZQ451OaRtiamanzKXklZdCQYEKsgT18PlLDLtmGJu7vlDJqwwCRTl9hix7seVXpM7f3zeh2eA + z941NN7MREVEtGgvTP0SL9w6S8V3KhU0h35roH81A8dh7TLjisAMKkfMePLg7AzGX++npX5Q59zm + Pf+eZCk7oqhGpZNCruLXgF363h94MoTweifZjtZp2kBD/V3258Ix3f6Z+SiGvTqxyhSYiVUAFHDP + zmLsj/lJw8Rwk7GcGjgbeF+hrkvvl95mtNCl29ocbKn+eJjmkF2g5YGUtdk+JZd0vZ6f1DhvpO0b + ONq96Do5ltTUM0YmyFUilAJx+vYXtG1t2iXjN+ASGfYGI81jbksK8HPB7Bg80ILREl7D0EsKgB1o + cUfPT7FqW56EJQpTyaDarKSkHXMNu7g1SQCe4X55qOJPpXgJb6mNaa1Kf2f8y1n0qtd6FYNHBpcF + 3dFxcDV/WLQRqpgIRk8EB1nEoT+yEJfMfQn/KCMM2IfW7+yrsfS/KUSKnN5B6JUBO2+OzlpJDQ2u + rB9oKFXN4Jx/x8wJHgmQRf6R111w7166mddBgJfwKs/1hphyoCefr9nuemyP0RE0ukU5r+aOv2mR + CS2DmPpPT6VufeeruhxsL2+fcuVfF7hjX5HUbMhbgIyiDnzIrkEZxU0qqJSwbLSzHrmLqWuSIkXI + AKbQXVBhZoIxnMIafNVJ+SXfaqyl8O/xn8hi1XcZbNs46cSOfbtGdzfqxwmPfwRewVaXN4UisOeM + xMA0jalkD8wwggL5BgsqhkiG9w0BDAoBAqCCAugwggLkMF4GCSqGSIb3DQEFDTBRMDAGCSqGSIb3 + DQEFDDAjBBDJYnZTe7lgNIfACZNNAJX2AgE9MAwGCCqGSIb3DQILBQAwHQYJYIZIAWUDBAEqBBDN + DMvGFo4NtfB+zBihcVVXBIICgP0SMgHhWsdkjRny48Ha7ji8ysQ1KZKU6umNfjMZ5A78Ic8Zsk0K + AXHHtX1zDwFOkx+/laRpYlNH8lMYyo4elf8oBfsXzvYfF9ROz08L0EqHIL0gWm4XTShkHGyrB5RW + XurVNRKIy6lA/OAB2lf1rFGxEAgOGvoROc3pIJGH/KQa9XRUhD/NK1U3CmhoF5GcHOUoxsW5zmxT + CHR8gZIG+U3W7kF9H87ZFuM9VNk9zbQr9pBKzPWr8JxrA7MikjTZli+rIyOgeuuMeE+pDw7d2pS7 + /rbnUY/X2l4LvR3vjaGCTxctx/0inpyY5gmN1DSybq1sypTz0QpqFUJY9kAKI+7BA+KsCmWPjFpo + HrUfjQ2vqmbcSFyKItGBMEXT5tJb9+rL5HNvFmxTt8LCfa7/0KAONLCe6+iaY/rLwA5a71RC7WKp + oYyedIMkZi6Bv097gXJa+H9l7VPVJCsO71PPKWHZEbm/F4xXiaf7s91/abt2kHGGq5IfFx6mtuJE + 0+RqoyZwBL2tOob+BBUTW8p9/nBmGkTjY9Eqt9Kp8tZrPcFOwrD2PqSN93n8KmFDE9/B5Aq3ejis + ctNf+aq6gAw7/QmvM6VFIZJbq3b38xax4W/e+uUb4xgpi4hsj/w0xh7yxBjYv8HtW8+mBJzUpArj + BoRsnyn/SHJHpnEOM+nCi+p2/qF1lvHDCNfoW+/6utwX95ic1ogMeH3TCvGUmGf+uc3L79OC5HlD + yCkA/yCd7YD65T8EmjZhwa2RPx4DTErQ1jJPkJPhM+k5kSxcXUQjF2A7jo6Av6FGFESmu1N22fTH + 2yK9osG4vbvhKYEBtYAzi9GFZPqwIh2uG6YTdhbNfKAwggL5BgsqhkiG9w0BDAoBAqCCAugwggLk + MF4GCSqGSIb3DQEFDTBRMDAGCSqGSIb3DQEFDDAjBBA8SVWRj2wPS4KfI+loUA3XAgE9MAwGCCqG + SIb3DQILBQAwHQYJYIZIAWUDBAEqBBBr5TnL/rG4n7kGGunNQWOWBIICgIu2UuhmU1gAQGAXvgcJ + zPa8FhjQE1OoGfh5oySL2uqn6VH4PTc87/j/p+yGwFVFe5hfe0YwqhSiDrLEXYMNb8PisHILlQzW + fDHCwLSYX6oUPLGjUOnuf9uGv9/WwzxjHVPt0lKawYNLTTSZ2blURC4TxIFSBLauUVEAsfQRHghf + vcNcMQr1kUlIsRS1mTc7qiOsJTG9I2HAxEakp0+O6uxRS27QHxRHthFnXV5ebfpvTuuFIUOBUl+i + lBr7p6xsTJ7waFUrMFIFoFFZv1iJwzW90ldW2KEzuR1duv3WWUrlHrsmXKA3wT9bHNF4bhLHseXg + lyw5rVF5+/2DicQWHYa2e7oRsw0NtMbZLscW5NhMh1BShFMP85k2LFY9D9K47UxXX0oPQ2D4bMSR + YuiYTERKgvCFgjZSIW9FKttkpJdL3MixJHSrNo8pdVcR/CZMc30d/JKp/BPT4DNmpL2K3iTYnYL4 + LckgbBcKGa4y3wkyQxfZNwcD0fIk75s/snSuYrZOgbeHznfVprrf9T9/Zo3nDYBGcMGg+p3lwB9F + +/cCtJLGdxIusy+ZVrqwuWvPRRG71mvh1DHbkBF9k60ms2ihSc1eJjmwC/HETa443k57RBYoLUqb + flPpxRVUIjJu6Foyz1TO2d8b/RMKivhJp4CjFMYnX5iPYy4prICTS9vDFfuGukmlF3AReoD93WYl + 4O+tT4X5bnJTAGe/uh1I1WmdnZwQ01+O/xqHMFKIFmJcvKN7wyHyydPkSdoVKf8+h6xZamm9WptR + oTcExFwUxR9JUqKjCwcomFIZkaqh7ZSP3FExj6tbPur4h1v52DwCHVK0cDotkn3di4qR6PYX0kMw + ggL5BgsqhkiG9w0BDAoBAqCCAugwggLkMF4GCSqGSIb3DQEFDTBRMDAGCSqGSIb3DQEFDDAjBBCd + Z1J37v5rAgCtcJsxya59AgE9MAwGCCqGSIb3DQILBQAwHQYJYIZIAWUDBAEqBBCYWX5/999vcvvu + FZE5J2JWBIICgOtuuzHGjQYlFZ0O5cG8Xytr7DuLa/wJBtwRTFMpwNVsRAjp0L/vZkG+Fi6QpQDd + 4dXqItfgHPdOjmvNbA2UPbvuYQ4kMKfCP1ELxTbDVunTWfu4agPfjKvJ25eGR+Sn8ny+sEluE6n1 + MPu92QKuTGKvBTwoXIY3NJwA2V6Hg3EEGH10jHAn1xvvboHgOvZ7XYqE+ZSVlL+g+L6Yjkk3UyrF + NXaCnqWP3rn6IV48KIj3d/iR6Q7RFkNmzhF2prmJuhlGz56kpoIbdVUvRGmPccSq8iMALMtZolaN + JFD3zH7e0Af4xkOqEaw3Y91g/u3NxdaGBVnOecPqyVv7uMBw3k+v6xRn7gNpS9USR6cAgrvm+fo7 + KHVy/M8c0WrsyS1dHCYohe1OPONbKZfVdoABu9qFVoj/O2rawMtKSMyVlGLSPNrTFmz0K2ka40w6 + 7xnvoRGHcHtRVSRnJXqPfpfgZlZzdcddbe0hQd7nmx2XPshAFr11Oxv295Ew+6RJYtSouyq10vY+ + a7qWlAsN0NVufk1Ych8ltxe9b/EpnjfPh1vKh9g9DjrhIbSIjGvH3NxAT7I87BfbWb+NqiEJd9Lf + 89nSM0ynXCq5ALYesO/s+OIFJ1npLEr0cE+yyejX+Io/CvMoV1W5loYF5k8FiHdKxCCRabP/biLF + +oeaOCyPWlDNIpYywqRtZca4C/WDYxS/ukQmrerH6UR4GQ3+oOBQpYglworiu6dZ081vmxzRhE5E + 5vJRD1ymIo6GvocOA0i9MyC2NlTf3M0b/byn43n1LrEkrHZgVb0ZqCF3WkIjnsosfkNwmbtICKD4 + vykU+kaBy58/lrqw95+/4QEhjN8cU14wggL5BgsqhkiG9w0BDAoBAqCCAugwggLkMF4GCSqGSIb3 + DQEFDTBRMDAGCSqGSIb3DQEFDDAjBBASEnWW6rKuQyZM2kxWjR1YAgE9MAwGCCqGSIb3DQILBQAw + HQYJYIZIAWUDBAEqBBAGLCV0XI3bVBD6CZSK/AfBBIICgJsaPg9gul3iekO8dPhzuiaC2h65zsxb + RPYjMF+gAGn4MzjCTqZcy6pscUOa/efe4KsvRFw0rpmHWW0CN9bXqnim0h5TtWkivwlzJBNjlZ1+ + Yrnj90sj+rpASQXJNdS1+61zBrRpRFwC/WJYsNj5NLh4la6hDipYET9dQY2pNDwUhULTfjmNTkZB + Hp2Ju2Pxh/8p1+usfCpcxpEQ+byWswibPr17ggfYtmel1fxDHKPlfgqwfU3bK2t43hl3QfVz4Y9u + ceU4VM0owGyyMbEXMjb8D6sd/at/XrASRFH3aieJQbA87ffj281/NYn1k1OVt6ZqvpOkyX0tu2nk + 4CrgY97gn+CMgZrEskvrXHqD8lA8y4NGv/YUGjRwoqgepZETnt7FxGZXAVdrvukxExAtFJSnmaV0 + VEzlkfYzzDFOqGJDkBLCLTrc5yJ6BBTwIlFzYuxSb2YE88hinhoKuMdJZFQ0KXVI0iddL5xoWEpl + aa7BI4B2v/r7n2uo8RtHRBhjhBQ5TmGsNog5yJt0Y2XStfEgD75po8jS4/SjTPaxuMlsBdiK9ZQ8 + kjEiIDqx1V+zGNjsgQC4wSbApCoo8OYiwbeP1HE7Mi9s/XXWwyJDA7uZxN8So0F2+yniyH6rYAnk + 50vJGEZX2QoTw99kz0dSvBhgxACzP4fq5olI3700f2wvHojlG2qkaaGHAeqpLKD65613hOCkgJfT + nVHPcONbbU4ECi60003krVSazYNPJghR+7PdPKl0wajVzdiYvGBa6mbgN1gkG0jqA26yeP60oUR+ + YWXjcTCpAnQHoksnuyvESjKdZZJRhFt3HgyFwtcXl2dJ/AU0Mn343guo8ZlAUCAwggL5BgsqhkiG + 9w0BDAoBAqCCAugwggLkMF4GCSqGSIb3DQEFDTBRMDAGCSqGSIb3DQEFDDAjBBCMQWIRFEXSbPX/ + jRCzhjGwAgE9MAwGCCqGSIb3DQILBQAwHQYJYIZIAWUDBAEqBBCHQ5CNps6DXhgk/tC17MpGBIIC + gAERI1Hgiu8K+df+O7pKcw3oaWWdIVECK5mLSIDMEFg/bPvcwTYtQeC49VhOtFVlYGlAfDmGsISX + f0PRGGOPz3Cc2obMhA8UtPJkt/LV0Mzkyf4+apaW9DSfQFI9dEZzdVGym7oxjPoHatc7nCuAcFN+ + GMP9SMMSXx7swabccBmfDCRMBrsp2F3RuYsd2w5Cvq8BrrnczXZIFgUlztM2UxOJT35cGjIHnNEP + fpZYftLjbB+uLuFfMfd3zzqwdNtwGfCQ15YAaXF88/klN5gWr+8btcOlRNfsEFv8gvZ9mjm8AmmK + biGVPPaFvDQG5WlRu1XdK2LnRG1hfzIZ7fO2w/lVJIUj6a+pzzzT+lCSZILztOcfAJOar6qYYN5F + 10fNISl8/AZ8YdiwEOEwS+Z/AKoYe0XdFAhs6YMC+3aqtFTM/523bjCIqrjM9S9khS8yL0oZXCBK + du+YN2tZbslVpWc80R4n+JZXAMWjp+0KKW5tg0VdURBs6ftqpPCk7ZohMD8TGmh8PRG3Z2hVohL3 + aXTLfE0HrPzjYjwJHYuD/kR4UARp7B4D2en2RLmsUFu/dG+/G8kZ0yDRgCYqx4tfMjfIShDCzKk7 + u9ccX/HOyWNc4VYlILTkYepIPU1qezHTBbZvXlcZIUwqKoT8Joz2uJ3Dsx1f/9tXSTjGOIFHYq8A + QFtp/edOC3gwX4W/Ep4F2DKlmh80rrAy3a59wxsZzPINXWRCk5u8i1DaGDVW0jaJt9uRrv3GJPFD + ZzO+EI7lK8C/aH9Vj7JE6isz3JjW5ml39vk4BQyUi9h3Y86ZjUMqKtY+bPm6gge4ET0qmAU2VBIw + nj9l3Cejjdp7E2NjZOswggL5BgsqhkiG9w0BDAoBAqCCAugwggLkMF4GCSqGSIb3DQEFDTBRMDAG + CSqGSIb3DQEFDDAjBBDwOs7VEjNL2wOWOEByveYiAgE9MAwGCCqGSIb3DQILBQAwHQYJYIZIAWUD + BAEqBBANcxniDlBevXtVGETyVpalBIICgAzqfhOnh6utR+5JQa+onB5DQWQCwC9xgE728LO4zrIg + NGGBtA1Yv+tvMIzAmwppYyyUeJebANC8xICh1R/Jw1zVxildGsDe+CcIqCSV9XzwLiORQU9uq8El + 8hOSKp+jxQU41NhK70CqUCT2icOjDikUGzDGxXsY8VnKZyddWb10Mrnyafqp5PORkC3KL1cAsfnN + 2hJEVbVFwAezurxYShMsPnhrCUzq+RBMxds7KWn6+kcgkKN4tjbqHOCGv8ufljrl3I44LfVb8tsB + ymKdYnqYV0q2Bn72DYWAHYzn791J5sCWcPBPd1VMmYi7U+ERkmmY8fyfCF7tNSS3jjj6r++FJ5DS + nsfVjmiuZrFNstZkINL814fgt59qGy0LpdWCNkiR00Ntv6CQG7S5qaHxTOEmbs4E3H53x+QO/FCa + 7UtI01zSV8FmBtZDgOmXTkAX8mgNqfCEqMs9ezyZ8P/5K9KnspmVVZs4hpdmimp2Cnuf7ASD3TN7 + iNGgk/RNu6F1tniyhUqPIiMLe+DZDnNewmPaf55JaD5KTHUt4V13M790HvBtjFA39KfuZF0YsKsl + CdRW4h4/r9FO7gOflakBrqlOfhcaz/SdV9+nO9nIe/IhMe73r6dcOsd2tKX1dqpQaD6FDQE5/eR5 + gcFsUxoCAy0XWTg1H+Y+nspAyMjIAQ5ai9ymLsclIoeA2upO3nlY3xSMF1fngOngTJcSCPTklVkE + SKvIoebknFgBTpBgSwYf5752UmrnyylqXdajPjG5tfOuRSe2evoEJAsLy0WZzuanJ6AJizXP4Aqc + wHXpI+ZTW4SXCekbiGOK/69D2DO0VamzljGRsu+v6oiaEgpsIjYwggL5BgsqhkiG9w0BDAoBAqCC + AugwggLkMF4GCSqGSIb3DQEFDTBRMDAGCSqGSIb3DQEFDDAjBBB2L1pvvp0GM+oyvQaukcrRAgE9 + MAwGCCqGSIb3DQILBQAwHQYJYIZIAWUDBAEqBBC1LDhdsGRaHR0kgO37xDl8BIICgPuaW4/Gr5d3 + ZHHkh9VKu8ETi5zscFLAtTcFpb4MJ2c3UnKQG+RjFMezp0svRSW84//X0pvzlfoKxng8VoNBCUwR + N/aJsEqC5UnBqiHWKL2wkiVl4gziRUZu+eosgm/9Gk1xd1Qxs7nwyTdMYfgnvfXof+MCs8IjSfB9 + //RJLPqVRu9mK1ZHXY7sb1iiXxoVjPhOvDF2L2uUt47ADSQSNp1iYhAA7g++mXiSDBTRa3Vy83cd + Tvq0bVx4NHgKUXss+b9kA1cM23DqQaqpouqDlXxTW2mC9490rCUQLyRTCj4xfrdgPypntFfBHuQJ + Uj891OEHiHzTnptw2anXjk8DfSkdAy3q38MxHCSxHKy/m8FgUUBkGGoL7wb7MD+vV6Mi1lyziNCD + mCPKIwaJe3+JEhdCO7GVZfVz/lsfhSZBHHkomfXe0LFwGFbvRxSyb9xiAF8NpIkFyazTc0odsaZ7 + JsfO4QWkVudzbTM1ByFCNetvZJPtfy2jZhSwXrKnF/tTEBFh5VA6UOkGEpvKmUXW+Kkisg8rZ/95 + G186Nc7BC7Y7OIlNG7ZBeVwjOUi9yD3YawQ/w4OC0TxKM+CtcJuepV2fCu6oMRueT7wxR4k/sl/k + XZr3AREf6s/JAtUJ4DtGcHnlOKuEnSxrwrAgChjxIjkf6IjSpgp9cUHg5NpTRAk9isnkYYlHK2bf + xPoiJ9Z7hosT56xRuFf9RRvNLjw0IiVKY/SnZILvMK5Vj/IAONgeDTXMGPaxn6/JCDghD0Hq4i/C + lciZ54XoyrbJLUd78TSsf8OA1RyNpoAFl00NidyjaNjvJfHtlWE+6nNXt3pQ7CpxPA4bKH3rnJ+V + qm+41t8wggL5BgsqhkiG9w0BDAoBAqCCAugwggLkMF4GCSqGSIb3DQEFDTBRMDAGCSqGSIb3DQEF + DDAjBBD4bw4y5yha/aU2REAUVi1HAgE9MAwGCCqGSIb3DQILBQAwHQYJYIZIAWUDBAEqBBBSBk3R + ONt1SqljlDVYuUrlBIICgM1rgQ9V7mZSMAk8fCCpVgWmsRIsAIBkqrFfInr4sDQgg6XZSXBySVXD + Jx6cKY4UR6z4731n9Up+7FLA+wC74W+fyPjWTuJPZcR0YGtbj1N3RKf9mOuNxqir/mamf8EMW0ix + zX53l3DWn26U8F5nwE2DsJh894I9/BSCRNwMpJF4d4G4nf52oAe04/iD21/1a4TShmnlt4+wwccX + 9em8Z71ZUE2FAQyTOQVBburLKFLCxGHZnFlZ7ZL9APuXPuvcOXwq3aa/K7Eq+fqliVqZORnWrfo2 + ORKzU3rNkobgFiyJCNDV+YRswoIdqjI+P/RlQJvJUBko7CJEWgRzTwi2PkYbxrmYxdBK4EV6O2Ws + DBmmY8zDfOqDMyzUn1Z56kZZqyg2O0fRFWBB+ouleLaX2QqbXfBEkjZYDcLfT7X7iaqWSMQK+S4u + 1BhkjoJuoHF3zP7BYLomcNBINVz7ZpP5cPLYwhKnC167cywc6OBOQ5PwGbSWcbM/HIvSxZvX/Djc + iinyo9BaxJXXfjX2yW2uDovMATlcNiQQO/jjCuTcfQXtLarQNDBjztsuhTkErEiUpKU+GD1cuqzV + Nlf47d/Nz++9xix9A9NPbCw4LZ2ZWPbelC2cfnok+o4kiwFg/iLul/rwIa291OwCW5WsAH8XbGtg + FjYsfGZ7WwnAw5T/Qkmxh0kWPMSsWWbpHJsxT+RzRbRbDWx/0ky3AjHbr75KQqCIOw06vHxBrf/U + wPLUVabMq7+InwPmKu7aHn+lQVulbLF0WbctOhgftsKnh8etipi1Fvbxn1hhqcv5ZRk8wHf0ZKEe + zK6jiI9f/ZOwmYk/nWxdcdmywxIP3+mgJAB+YaowgZYwTzALBglghkgBZQMEAgMEQAnxDpUZgkDr + nJ5wJc7Dkwj4LYOne2DnHpz/51y7BYiWx/Ib2Nq07pRd6KcOv5bCLtRg1LtASYZwGVwnrK3Agx8E + QEjnyQlMNis7fiLOs+ZthpmtwptebOMdcoB5JdmK4Q1QsnkVetK04cL1hh87unT4vAbwn02BSg47 + mSdzI1U6Td0CASo="); + + internal const string Pkcs12NoPassword2048RoundsHex = + "308209CF0201033082098506092A864886F70D010701A0820976048209723082" + + "096E308203E206092A864886F70D010706A08203D3308203CF020100308203C8" + + "06092A864886F70D010701305706092A864886F70D01050D304A302906092A86" + + "4886F70D01050C301C040887A8C4D7DB154CC402020800300C06082A864886F7" + + "0D02090500301D060960864801650304012A041028529AA9F7079F5031C52A23" + + "8A23CD4780820360ED47A5097F8A6C7B6EDD1989CD19C67B8A59DBF6CD22F7B8" + + "36A4B8D9BAAD54666FA9F022CDF366BC1B4A47604C4A0B67C09F2DBB8F75D185" + + "B62DF310817E24FE71A47246831C58670A27A5693CAFC52EB730B7BC0CF00145" + + "A9E4D6784D3A49C524546415EA6448D991C5C83FACAA4C1FECF198DA5B8CA9B1" + + "09346B723238359CC18D1CC28B185AA3860B082086F9FD909F3BA3D94C9238BB" + + "CF8AF18C0A56E6A88E7E0F4F15675770C812F805604743B1E8E683FFEB3FA4C9" + + "9D2C6B00A858050DCFE12C0DB3471B678657C7E28C074277165D714FC4A6E193" + + "C118C676FD2B2F97B36D8EDAFC3332609F192EF98D3ACAF1EDBCB03A2DBB9B0C" + + "8AEF748A5BF66F1EDD9C7C8A81638D644F9ADC99DDD6BC7FA7FDEDFF044D47C1" + + "6F62280D80526D05F17D5DBB44209A3F27CF88B881227D5685DB6BAA22914D0C" + + "E0B01D9997F9A429B4294940BA75FBB08C7C7EEFCD77B7BAD0DBF3E418F4B551" + + "ACAEC537DDA914DD0BE933181A86B433640C1E10609D96D093544D119727D1F8" + + "49060303189EA33B8FF8950493909B0963EA4BC8999BE78D2D2F1315F96B24C1" + + "DE6A925C0DE3C862788B2AFC56BF343E63E1274D0B8652C6C49033E1B21AA283" + + "0AB2A1406291D215B799D8CB5CB6CA0D3B0458C51EBFB01207E96DA04FDE7C24" + + "F32E48A0B4E48EDE150B13D8B2B1D3B736BC4BA54F800DA9F980F7CB9F23B7DF" + + "8C343F980ADB0752E2978D4C1069337A631E15D945D8EB858FA6D1548C230269" + + "21D0413F13FD4C94A1BB8897040AC3CBF13DF42FFCA7D0CF76FF08C68893E45A" + + "061700E4BC3B005FDF4E8A04323B98D07ED037E4A341E49E43F2D2BABB0CE6B9" + + "4D260BF720A7B864DFB12B22D28F4992C91A16E30346252191EB60A06C23BB6E" + + "1FEAABFFCCD57C0A2516E8015E9B329C97D99704E653EBE04BBF26199B11A2B7" + + "E30FCBCD2C57A1389B0747E1B9896C6CED2C86D721DEE424120E0347A05B57F9" + + "19FA1FA334512C1EA43205E5B1A56BD1F490C5EC3CFEA17CF1E8EC4686DD6150" + + "0C420D03E6E807491B24ECB6E64F210BC8029399C153E62705E80FF358E1A705" + + "DE3EC20DD3AB6651EB51E7F7F2729ECB9BEF94B9E1A685555DC075453072AA06" + + "5C8C821033DB91D1DE64CB085E09721580FB01AEE2C105E62177D88E25B4EA54" + + "4605636CA3261107DF7CB8FB2661CBA0B855726F8F3F087B95EB257B523B007B" + + "45B9C71E030FBDCF3082058406092A864886F70D010701A08205750482057130" + + "82056D30820569060B2A864886F70D010C0A0102A08205313082052D30570609" + + "2A864886F70D01050D304A302906092A864886F70D01050C301C04083D689F19" + + "C613307C02020800300C06082A864886F70D02090500301D0609608648016503" + + "04012A04107F45F9CB7E9F7A54479C6B260310BF0E048204D0C37C420A86E386" + + "22F3EA79EFDA069F9A0542207ACC6EC2B7D497D06C2F0E20FC362F637C032D86" + + "72B9A0A393F0DE872B19668D37C35CBA4DA6407D80A1A61998BEDBB01713D41D" + + "741C5D4A03B877B2CB6F66295B3C7E3955AEE9F8BA250086355CA8D340850825" + + "28F3544F9DFFD19D764DA15E6D178E7D8D843E0E8FB0FBF5CD9D6203644FD6D7" + + "88754AF0372BEC11938406A27D76EFEC6974F92982F2F2D212256DF3122CE631" + + "7C079B14D9B14D8E8AE31EBFB76754DB6D5497DC3F7DEDB73DC2E65F722BB3DE" + + "D1D217628DDC42EFD646501B8B8E085996D87F11E3B29B85FBAD0AADF71A391D" + + "92F11DA38286F7C57D12F3923D334D3EE186098B88A77869514DE08FF1D09675" + + "4D6FB2978CDFCE051F7A0AF5E27BC8B0DDFD9230FB292EDF4F8893BA7C1CF8C1" + + "B91B527E13B93D0B32E7A130D829D928281655FF93A60220194777EA4903B577" + + "EE7D12FD585D69ECBE52192BC9A0731444122C13ACD45926DFE13E7F151BBC71" + + "2787DE41A1319B974DF20F0E28A2B8C247553C6A98B52F545723ED67F36DB626" + + "7BEDBE8198AEADF2AE0B177F3F2B61A53AD5EBD99F10BBF8F578AEBC780EF766" + + "F512118263A7775BBAB8D1E7D2B8E55892A59BA4E4DA74C63A90319589096AED" + + "516AC29F6DD666BE867C6E411F0A63D8EBF0A4F8F591D8E16F106DE0A63F001D" + + "373B0A72B3BFBFE27B1463F8A01CF5A4BC81988DCEE84A76ACFF3F27BB205708" + + "848F0555686E0A650280EFC7517BB433742961DE1E0B08D43EC64BBB6B2847A0" + + "01FAA219D470E553A8BA05479DDB4B3C0FE50BE45C7126084AD249B521733477" + + "473B687BBB75D47C3AEF41C72BF7612FB4ED8D9EF51CBEAD26279AE07C3A8151" + + "7E65FC8A77712D8EEE8994F1632157C3F1403DB0FD2A95C0A89D323BD9205AF0" + + "E228DABEA7EEA182B8D257BEF9D704CA0E9FAB1027F49BCB8D75C2969447EC4D" + + "59EE74B762092664AB68DB5390C8E76AD91399DA5473222DF32C96D519FDB21E" + + "3D2EB1C6D98B3A3525D1E92504E29A0DFCE59B0B982D0A8EB845A8BA4DF22CEF" + + "CEEED25773E51C5C1FC0F96A697E24995342FF82D7573479D342B7E41AD2FBEC" + + "BB6F728F8D3D964C09F9FD39239B58AA91269DEA87755C6CAF9F6F876A1EBCBF" + + "9983DF2D6BBFF81D180435B65DE95C7BD41062F123B7CCF31897CC7028F6DDED" + + "EA747F069AECA55637B9848674AB1CC4A61944C274B500E37924F7E394DCCECB" + + "93052BCA45AD66851E9CDD4194470BC2F91667C6E1F696B89A219C45CDAF1752" + + "CC8D95875EF343E90765CA366CDFC55F6D72D2590FBC47BFFA9F45BAD5C4D50B" + + "A89F117F20A934D84C972A15AC4DE8F7D105B1E0AE214BFCA16BC44C4D4A096E" + + "699057ECB1E3D9D6933CC8AB9D9E93A5194BB39FE289E6FC121A564FC049C99D" + + "6B6BFE5B11E8292CEF1E4E80453F805D5BA49BB26EBD84847F1CF238335005FE" + + "2552A972DF84618E59201798DC1FAB60188DCBF18C6B30CEC921D40B76B9BBD7" + + "458819427D0444A2421D69CE1E23D335EB6C327F8145DD3FF25EE740F7896A24" + + "AEAB3E65FA9209C18BCE8A1D28EEF053611170E3B1CB43F583D6084AD40C0035" + + "E053CC6133E67B18F12D508BA43B79E1B2D1EA0CC2B91002BFA2D286DB6CDEB3" + + "796449B33D6DA4E47CD326B8C123228E68D822DF1B8A50520EAB8A2319227415" + + "4F4EA2C604A2274AB881D6B4EC36E0C3F5104A0BBE51E061E4F09A425F797BA7" + + "545D45CB4567A39D923125302306092A864886F70D0109153116041444BBC698" + + "3FD4DB3AD40E15D39784227F2A2E60A330413031300D06096086480165030402" + + "010500042053B736831F5398BCA4DD020714A2E9B46FA9BE273F81DB5A13D27F" + + "482282DE4B04089ECC39D621BAA9ED02020800"; + + internal const string Pkcs12NoPassword600KPlusOneRoundsHex = + "308209D20201033082098706092A864886F70D010701A0820978048209743082" + + "0970308203E306092A864886F70D010706A08203D4308203D0020100308203C9" + + "06092A864886F70D010701305806092A864886F70D01050D304B302A06092A86" + + "4886F70D01050C301D04082BF1679DE99B425702030927C1300C06082A864886" + + "F70D02090500301D060960864801650304012A0410E60AE5FBE308F1D6E6820E" + + "18F85D357380820360E334B918A3971F37CFCEAAEFC215BD0773462CEF830E5F" + + "73D28190E78FA023A2EB377952BEE5927A0FBAF841C96059E4B0111880DCD6CE" + + "619B4B07DCE651642F29AE978AFD15EABE55D5E2AC94439294F15F4E4A304D88" + + "54B04555E30D251D7BD6B53F0584C5293E78F9427861EBF90BA0E41C00A4210D" + + "5208DD27C45E5C964988F00206399AA256D19103E391765F0E4E89319AA8FDAA" + + "F79856ECF1E6F5DDC9629E778446A0DB25FCF541D4815E66967124BB751ADF2F" + + "10DD658BCD76CD7854561E161FDF4300F84C5E04B91B3FE52436BBE2D7DFA2D1" + + "5D9C6E853D4F221F833FF85A985DA2010050FDC17B6013DF16B71B1FE181401B" + + "447A7CD78ADB894F310119600EBD79872CEE05254F141FF4AE317B4B19089943" + + "1FF692246DDC9D042C517FBD389EE2AC9A8DDD7214BBC010FD7A90A325DE4224" + + "D833A55B8118C5BBB003304956C98A4CC1A91EEF29BBE6DCC4F5353EE53065B4" + + "2B0011E291B81C9A8D43CF436F2A47DEB40C21547CA2E416A61740E9DC5107A0" + + "1913612DB720770788894EC2531BE2676F3CB9AE5C73D5AF35261F41D474A565" + + "0DCCE0A9A63EA6A4BE80E0726EF4C1A5805DC8888330D2973402BD0BB4017BA9" + + "D83355DB97A9EDD04D727A1A3FF796267E56494E2A89B0705EEF88805BB23D98" + + "05207982C2B43CD3FB83E8C65EAAB0DA8E5FB85D9821CACA20BD21311BF0E3EC" + + "4F309375D7AC89E9B32B1B2EFB51D45FD5F88AA066A05A775311857A192B7DFB" + + "200FF7F276C284655E30164543A53B3463BF6F8B037452544BA86764B4ACE00D" + + "F2C26B18D9F457153585432201F578F74BFE9A4F8010C3B4973C865CE175485A" + + "3EEE47538C73EDF5E3DB34D814B6525351D03648948561760CCE3F4DFE423ACA" + + "78E1F82C78476528BD35C8D02C17AD41E9796F048BFC4CCC15FE11139C1A104E" + + "469368C654FEFE69139014ED6B30E6CAB30DC7FCF1490BC91C50F1716CF348FA" + + "7EED577F7AE25219163BC1BF378271F9E676CB99227BBAE64FF8D9BBA2FBA69C" + + "6A045CD70675809F9C3EC1DD30A4542C36E4B5B52F982E8369300C7D036144E8" + + "004804F186F2F54385BBD5ADE939F2CEABA8D785DF62D54348CA76D94669C350" + + "07BE6C9A9A9CB263A9A098157B39C6B8E1368D9D7F9D9524D5CD3B9FA1EFEB9D" + + "44AFA24F2536C3A70E19DF07B7B0A68385D8572753A251648534B953717CAEBE" + + "19EABF8E36F039A2503082058506092A864886F70D010701A082057604820572" + + "3082056E3082056A060B2A864886F70D010C0A0102A08205323082052E305806" + + "092A864886F70D01050D304B302A06092A864886F70D01050C301D04088064D4" + + "32AC75560902030927C1300C06082A864886F70D02090500301D060960864801" + + "650304012A041028618CDEA80BF226578A2DA0DEFB48F9048204D070F86CDE3E" + + "25FB88321EB1D0B9E8BA8736C1954E7EC2AD3BB3B4DBFAF6ECE268FF693008CA" + + "DF785473709F6ECE9AA8F93019C5EEF6E557DD5587C3B3A85B77A3FB1871F58B" + + "0316B4A4210BC2856E5B4AF46B3BFD114AD8FEC7195CAA4D8FF578EC0A945A63" + + "1000981A56525F8AFC6F140F5DEE2193C7DE62579890ACAAF056A32CC6E7CCFE" + + "2839DAF60FD881C0D34BC450DD7F0F9378A695F27F547A73044444163670294E" + + "578FEAF5627865F297147ECBD16E45C128697063D16FF4A773ECFE2978124C06" + + "A7728C6495CC35C957A635F213D237C8D64B425ECEE868CD1B901CCBC3AADC1D" + + "CDFBB433195EDA5CBC6C11D048D7DB7C5A0443828483DADA0C63FC4FB3234A3A" + + "6F749428F371121389BE52DD5F9532B8CB86E096ABAEF67865ED7A6D9AA0AFB3" + + "98361237819188AE5592D65B450A9F99191D1E4007FA4C7067E55B495A224435" + + "E6FBB891D5CC84D929166B807C8114E26D6125C7B19B0ECAF55676F61127A75B" + + "56740D00BA582A4412301E3220EE58FC1A55B578EFB90F398B9940CC23581A29" + + "804244A0C37D4661113DF6B43FD77CE00B0E9C4AACB28292FB20DCB12FA461B1" + + "07E853C057867C5ADC6C8D21A812B05A20ADD09F6E6ABEFDC45DD1CF7C64D7AA" + + "0408CA24D1B7014F2C960E8ADD356FD13B75448287677B23F48F7B527380709D" + + "4ABB09A5EB3100068F55CC4A194166BE75F32C52F3031F69EECD36A5400C0769" + + "E5C5DFAB81F1480C916872169EC34B49646C9AAC76909EB8B49CFB9ADF07ACB7" + + "5B051AFF623955F1CB0D5E11154576497306D92CF1A19C02D0B10D219C956DBB" + + "47DA7F24C554D0AA6D71C5DB4E966606B863445F39DD955AA062DF78BCD0C509" + + "000BA379A4BA1FEAB5A9D3EB5FF40B3C85C9450B1E06F79A2670DE7EA799B741" + + "28B67A2B1062391F78B00958497948F8F1133096EEE10E761080CC55928D1F13" + + "AF835DC7098181CA2D27DEEC292C40CB969AF15BC8F82E31A224BB24F04BDDD1" + + "49C57688FC1867E6CA995A4D8B2D72FEA76ADB9E36775EC6994EC346150B26F7" + + "40491EA804C176D9C8852308AF4849A88F2D1C93E948C848D22B0122CFB5C7C7" + + "E848DE044480DCBB6E282285BD00CC8445E6992190B13764337CD226613F2CF5" + + "EB5750A7FBE079B68FD3950E8D3CAC9E579BAC61AA7D52F9F4194C4DA59F07BE" + + "40EAB8F3104D0C70E92AA0273DD3C0CE1901BC9780B5762C833974D193539627" + + "61AD01E3BF23C6C7941372CD042BB05A897E18519C497236DF21ADBFD5507414" + + "E50725AAE16A5078464719FA61C426BA4DF9B42EC2D84824F2505E5745446BF4" + + "CDD8444FEC1E505665C46339054D8CFD6A54CA9C1275C96BBFAF456BEB77491F" + + "71D2D002B2F7805D44DFDAE37C0C4FAD680A7B0DB89AD006208FE01284050AE3" + + "3A21CF566FD2627B1A86A1C2402658083B08998D37234B00CAD3C1F91A54CDA8" + + "57A777BCE878E9902297E4A8128AA2D7D58AD5F491717F9B3C10775F55783641" + + "D052BBF2B69B15D5CB78488731C091E5B7D3EF768D8CA1F2089F30ACBE014566" + + "F566A69CDC09268F2D25B9E9E60FF08742EDE0E85735FB4D4FAD8464B163542F" + + "CE3BBEA4F6DC2869F48B8212013226BDC8B520F74349E85244C7DE0D84F9C4AC" + + "DF8B7C5929267A2CF2AAB77FA7C46BC6A983FC70991002EDCBCDC647ECF289E1" + + "BA930216325D5919290260BA192E8F3E1EF136D4BA18DABF7F2A6F87701EEF98" + + "B66CEF645CA90BF8B75AEC3125302306092A864886F70D010915311604143809" + + "23C6F251CF50C47C7670DEE626FD1708A70E30423031300D0609608648016503" + + "04020105000420AD0EB570ACFB8357A8E99B17672353CFBA69C76FFE5B6BC113" + + "05577F12AE24040408D04E60444B79672302030927C1"; + + internal const string Pkcs12OpenSslOneCertDefaultEmptyPassword = + "308209CF0201033082098506092A864886F70D010701A0820976048209723082" + + "096E308203E206092A864886F70D010706A08203D3308203CF020100308203C8" + + "06092A864886F70D010701305706092A864886F70D01050D304A302906092A86" + + "4886F70D01050C301C04088FC6C10979E6D39802020800300C06082A864886F7" + + "0D02090500301D060960864801650304012A04109EF63F4518B8635FE54FA2D5" + + "2EC0C19880820360B9DEB42B1F8B8F4E58C9CB35F871A99821775275D604A14B" + + "498689CCFA1D8855C1BCB7C07528145C3D77903BCC27E9133041782018D6C1DE" + + "F094328772836DB17E5271F0A0B7D8A9BAAFA1086E95483DA5CE8519B4B911ED" + + "88FF0FC47AEB5924180D8B35083C5B3B44D2C8B7DBB2ECF5FAC710862B090D5F" + + "801932A0E56286DF0CAA10D7CB8243B88D7C2F619D06B233DF6D16060D392F28" + + "2C564ED685ED8227619694ED34050B76C8E28ED38B69C0AF9672060545C81B99" + + "EB6463BA8A786422322180C31D64FCEA249DB78C51CA5B84DBD8522A8AF531EC" + + "930A0A03BFA5CC7F84EE9F98819784D8E22237D524369F02D620B5AAD9C336C4" + + "737285305DC53A8C383453E0D278450CAE3DB2807DE4B0D5C6BAE4BE40370ACC" + + "5CB8D03644E553B0E98FDAAAAC00FDFDD102CF8ED916E22E686F30B575660B4B" + + "3C5BE7A1D3CC1D5217A8614F3BC80727671B13E5776988F7784A7E886C23C733" + + "A0C6A633D7FE8D7EC9ED9B2706B40373940BA8F9BD1BB7F8CD700384D06EC809" + + "21EE29DC45CD7D6EBFA1BBDECFAAA26AFDACB9E8C86450EF404BD3B60716E2E5" + + "1E943E1E1E595113C0EE3687493F252110ADCD4A44562BB3E2A6C682500C6591" + + "F72542BF6953BDB94CA977925B636DCEA6A6BA634CE8E4A44387CD20566DD120" + + "E3096E99ABF83D88D12F6CC4117E422460414DD0F89982A080FBCA7669568865" + + "0E9A07087EAC27753AA5E0AB126093CC9391B7510541820C6DBFF07B790F6DD9" + + "699530BCF5286C9537A9024C0778BBB854EA5A9034B293A78F4FBD2F3C572538" + + "2C703E8CCDFC7C54CB28005E4C511F8C8EEE84E65415CD46680547A7B7BB72BE" + + "CE5432EB1C6EFEF308092C35667BEB7509594F75A6D06D35C5C12FBF1C331E59" + + "6A5BC1E4DA753E319254068B7EC7824BD8389E5379773D14BF664D1A75991CD3" + + "E4AD815CC9A131FA66765530E55362983D4AC316D055BE68E8052101EB36980F" + + "2E72F5A3442BC1302F1E9A721C7A8D7B56A6DB36477712FAB04F1C17F073F8B5" + + "279B441E35BBC7986E67C1D9C8AB6DB30AC045ADDEBACC29BE1AB6FC57EA41C0" + + "5F7CC21BD1AACC1D7B9F2B5155893CC11D512A6DFA5FCE72BEA259D381B57019" + + "6ABE1C186B1B445B1B0B03DA596C3A5AC0252D026CB3F5DE42C06E81AA7A7029" + + "F249EFDA7581862AA29FE1F4E8ECD9F1BFB673119ADB0D369A28A2E3962BC265" + + "3BB1453BBDDE09CF3082058406092A864886F70D010701A08205750482057130" + + "82056D30820569060B2A864886F70D010C0A0102A08205313082052D30570609" + + "2A864886F70D01050D304A302906092A864886F70D01050C301C0408CB7AC3CE" + + "BA6EA01002020800300C06082A864886F70D02090500301D0609608648016503" + + "04012A04100E253ECAD79E0856D73A2CE9A3D48101048204D0D617F9EFF71270" + + "B09B3287BD6014E3DE6C8AD23BA19156ADE125B0324CA30B758DB8EEA9689A25" + + "1586E6E50A0C3A5DD975A2BAA6B571FF1C9C78E361F0ADBF4E9B4F7BB57B2728" + + "621E3B113C5ED339ABB89AD31F803AE2C976C1F0B96ADD7D2FD54E27C490D3AE" + + "220202531960B57B029379188C3613D6B1AD6F03526E1BB23AEC273C8F6E017C" + + "01358356EEB8D1273B5B114129DBAD60E367D70C92B65D38D0EB56130864F708" + + "3A43B7E3118362D21CD032D21CE94E6266809A69087313767E0D9BE736333AB2" + + "9D737E313302C6BEC5124023A22C395EF4A0AD165D344B329A09E5E4593F4848" + + "323FAE51B283EF0DBDFB951BDE62A0C7FC2E6419A412E86680F6414E154DA560" + + "2B4B46B836AABB3DEA03B1FB12110DCC6BA1C0FFE902CA892E4827955E1488B7" + + "AACA0FA0EBE8B8ADDF425CD9AA65B407503D0089D74B809310C737923386B86D" + + "DA357654C4A60285C31BF9C2BA22862D7774EE2FD8AA92008F678CBA8260F04A" + + "561081FA8818CC384307AD0AB4C3BFF86D1BEECC136D0595CC6E816B51C0888D" + + "EB4251FDFA5C02FC6D9EE0C60114406FB7391A149756497A2D88207075E2A528" + + "E021B68820F68F89F4F6A5709A18D440E09B5BF239D4ED36E8C3358DC7890033" + + "D9A0761A0A062734B709144A5B1CE9296480E66E0800B62FAA881F70AB89823C" + + "D513BAC42346758EB3EC982B7F8B4808DCCEB64CF854A18FB0E2D0D3B29FDF63" + + "B60C21A614621183F643BA56BBD0713AA5444EABD1415FF9CC2ACA80B70256CA" + + "CB059949760257128AFCAD51E8B9F574DCEA9BBA42344BACA4D96F7EFAA5AF64" + + "EF01119D34E971D4837C3633C202FA47892C59B37A88310F9CD2C523F0A4F952" + + "BF925A80F2DF0A813283739416DA1885EB3DE5AF04FE6C828C1AEEACBBDA7B82" + + "A00F3505A56C88FBA0537B1B4F7B7F54C1551A5F5279DEB9DFCA2C2EEEBF1D75" + + "F60DE8777703B7AB67411E5DD4478E7430E34CF1D0E0FBA7AFB124D40FA3E04E" + + "5D737D9C1CFF0F8B1CE7E2D4F99C340BB4360FCC285610A44247021BF1CD338A" + + "DBD4C9CFE0C1685AED1A4EE01CD34A9BD446F0E69F16EB395A6AF8E4710A13A2" + + "75D2B102AE38DC0204A6331155E70A0B73440759FCB89CC5E0A2012082696A16" + + "7B97FD90FFE53349D9883FACECC2B7237B28468C6637ABDC47EB595B82662703" + + "C9A8BB3AD8B7DEFFA323EBA092C8BEC5CCE3C5BDD10AABAA872A7620B4888351" + + "EDAC82D0E7DB5585C5B8119E300F8CF8B24C62EB397B9069038AFD69FBF1305D" + + "83FF499E203873F45559868DCFD813A5FB5BF4EC695A7463C6B0A2A8487B09FE" + + "B58BDC0BBBAA377909DC88F03FC3E058D80E22E54840E8803BCB78440C44E6E0" + + "1A17519F78C2C89D965C9B984C96FCFE5DB22ABE51241F88A15BF0DF20936072" + + "EBCDFA465182F41A0025E877A308045D09FF988F122AE2639EC14601ACA00E59" + + "81C3E1B6C8123B8EBB2CF9936D9CF3C4A0A8A694E26E9FF6EED0B0544BEA6F08" + + "EEF5FFA27DC0FA3D169F51C42DCBC65C3BA935F6EEF2D78DD352206C0F5E0F92" + + "AB8A4284D67088C0587443242035C23786BD0A3C48452ECB44F4E07422DDC849" + + "099587763991C299C5BFE8BD14D6904516B31254C1176F191962B81FB5BF16AF" + + "B0E25980A0D069D9FABB11A053FA5D8CC71F7F825A2C96A87D7A6F21FC6D8DA5" + + "059F848F6AB5B85A04D1257218EE480444148F4B0A1BEA8CB4622A96EB9B4EE2" + + "A76F14B1CE25EDCFA23125302306092A864886F70D01091531160414B524C1AA" + + "CC976B19833DCC9739FEE5B240AD3A0030413031300D06096086480165030402" + + "010500042029A4ACD27A78AA6BF3B9114E19D1315E76BD450EDFDFE88F4E5E98" + + "2DDBFC734B0408D154718FB757EF5102020800"; + + internal const string Pkcs12OpenSslOneCertDefaultNoMac = + "308209180201033082091106092A864886F70D010701A0820902048208FE3082" + + "08FA3082036E06092A864886F70D010701A082035F0482035B30820357308203" + + "53060B2A864886F70D010C0A0103A082031B30820317060A2A864886F70D0109" + + "1601A082030704820303308202FF308201E7A00302010202146543F3FA4FA3B8" + + "75A1BFDDB3CC23ECBFEA736A00300D06092A864886F70D01010B0500300F310D" + + "300B06035504030C0474657374301E170D3233303432303137323530385A170D" + + "3234303431393137323530385A300F310D300B06035504030C04746573743082" + + "0122300D06092A864886F70D01010105000382010F003082010A0282010100D6" + + "C77692CB15D98788AF8C00AA0C626AE45015C8300364C0303F09A1D48DFF5599" + + "A3B8DEC49365E28274E2E33288ECE042322CF8B2697E760D0FB4E8894C209859" + + "9D7BCB39DE74596638D11C87F8245EB245AA0FFDDABC45F804158776B6A1980F" + + "13F4C87F2BDFADEFDA91C3628544BF5F328E5F1215B5F277F6C7B89413720B22" + + "9FA9DFD132A7CC187F88154D6C091841461D2E7C35F75C1A40BB99EDC3C3873D" + + "51C2F68F804F58C0A4FCBA8001F6B92825581B4A9391C6A770614F6BD82B39AF" + + "13094EE7E49DCAB7FA2C751533B358C874148BAB88B915FC99BB8C8F907A4317" + + "3FA14F3B26EF4FE5B014D107DF73E5A2FC0E9ED651CB376A78CB1177E5D83B02" + + "03010001A3533051301D0603551D0E0416041479B60678BEFC1E3784E8A780B2" + + "30A7E826DFD627301F0603551D2304183016801479B60678BEFC1E3784E8A780" + + "B230A7E826DFD627300F0603551D130101FF040530030101FF300D06092A8648" + + "86F70D01010B050003820101000ED62D8DCEF486F04C6047AB0C2CB25FDF1923" + + "7E46D1AD9C16139B8C0A0236EDB46582AE45EDF2E242947A81AEE2AF4BA9AA9B" + + "3297FDD76B1C7F235288802DA590213B4A5E7986B828E072665D2855860B86F8" + + "F625F2BF6111938C54563DBE8AAD21DA2A883F901CC2D7CD08DB113F3468384C" + + "388A6B07E4C301839D4F0E8BD38920FEEA4DCAC3ED817BE1D0F3BBAC45E772C5" + + "41B0819DA4F0EBAFB952C082A57714758394C07416EAB0B53ACBEFD2F2E2138E" + + "B91CE02F4F8E008C976CDBED10340B79B9F9FF8CC94C65EAC274C4A08E1347B0" + + "21268E581CDF1730AE9DF3B959508262DED10B040A1D8346F0F544A2BBBA7454" + + "C2B2A7218B21B64D82B8C04A0F3125302306092A864886F70D01091531160414" + + "D222A8E9C8CF876416F9A2132202326C1AE63C203082058406092A864886F70D" + + "010701A0820575048205713082056D30820569060B2A864886F70D010C0A0102" + + "A08205313082052D305706092A864886F70D01050D304A302906092A864886F7" + + "0D01050C301C0408E0AF915D08BBD72002020800300C06082A864886F70D0209" + + "0500301D060960864801650304012A04100CA536131D2DDA95F84830C4E47E84" + + "4A048204D0075FAF61AAE642CAE276A7AED857DEB08094880EA949F763F54DA3" + + "B1C0CED0D383B3B305F5D4C033B33BCFA08385F58BE862048F739B59C30AB3C2" + + "1B8C877D2056D27CD1A015AB8EAF86725CB5F2892C667CC02DE0AB215D4859F4" + + "A4DFF4C6B8187B4DB5DD2BE8217482148D0135E1B6B7784E7C8515FF4155D662" + + "5E742D45D3DDDA357BB7FAA90601773C96468F2E023242CD6B087CD0C504C124" + + "8D747BF0465F33EA2904682678CFE57B4FC9DD8162230F7C6980B399D793B796" + + "08668B7FEF17430E128AADFAF37F7610924845327A631F51B4AFDBA3D08C2408" + + "8C6059FE0495003E210F53E616CAAD669954A7E517DFF203746DA1A6693BD23F" + + "3DFF096DF050DB68AF7E4213F2A40D03C274C3E1F224B66A3481001C445E99DF" + + "F5EAE97A9E7C34D035A89DDAAF982794E2BAB630EB61F96399B891F460463052" + + "CD6176E6561DDC60C9FE476D125758AD3EAF9747818A4F963181928E79FC667D" + + "396EFFD95CFF25DEAC00E0B1A4092E0F0AF9132116A78C75B3306469F64B536B" + + "FD6130B756899A7FBC79EB11BB0F124A8BF08AD818923ED9D1F93A79C1C1DEC6" + + "58B8859794A3C02F9299901F4303CFFBECD354BB77FB5BF63146B8F85C4C0F81" + + "85A7A6C8A28C51C33340DBB2204AFC70D4D79AD2C35514E4BEB413D519A12288" + + "29E7195A730A1A65FF9585E32F91F232DA23BDEB2DFDBEA5377AB9E053198899" + + "B140F816979D53F738601782C7CC24E881CF337F5CF50C24EBE2ADAD74FCD523" + + "2E620B8F144252BEE0B0FCC08AB239526B1DBE7F6C3D1FC8184F60B8B30C64F9" + + "B42F5B674C475A4D088C81C8D9102118273C6DB632CF7E127E037D6B317DE6FA" + + "C7B2752F1173851E3C099F40F207B0EF57BF80C73C224B70D0D975C15A8A2193" + + "2E34EF1113C48EB8258968B37D2358216E6302192C6FB6A68B325D7B731CDABC" + + "4D1FE7A4EA25AF60F5B224FB8BC0A3DB8ABED63FC1B91FB43AA4ACCDB32B46F8" + + "1441CBE62B1C38A4FC1E76E2419579890A10906044E18CC1D06A1866EAE98B78" + + "2BC5503BE5E24638C79FD804809F69D8019B77F498A36CEAF1582DAE245CE949" + + "BE493751FDD232A02AEF72DAF9B527AA29BF8C0B0384DB29BECE1A1E3B81E681" + + "00C047A22E73ADD5ED5B19BD922279BF8E0D4834B69408B5A9844B8421D6420B" + + "3BA243BEFBC2AFA74DA94D34B58BD827D59025438EF4E982E0BFA133F5EDB181" + + "FB5E3723CAF8BF0AFDDF20DF75A433850DFBBDAAB3FA7B61962A66A0AEEC1476" + + "B12A016CCC2B12C16AA8547D46B33F3B6008A65C165125BAD61345D9777E40DF" + + "232190FB47934E3B935B963DDE4717F4F4D1E932D1D7020226C0A2472327E16A" + + "F8E16DCEF37C52E58A65FA44842E5A323DD768322DD0F22BA1CDF9E54A3C3ABB" + + "7010A9E4C3D856719CD025C1EC7AE2DBCB8DE8ED68AC542797978C47310BB3B4" + + "D34A4244B3D0D01338A2EF8A43DF1180C01BA03AB9A382C888912BBBE541F8A8" + + "7C0CDE2EC2405F67AC22049410E2AFD9A8E4B8A836055C399A5422DB375A7520" + + "99A947C933DEFFC26532F399D08978443C6D040FD29C5842B6E0A8235D44012C" + + "6783AA66ACA31FCCA155AD6429BEDCABA71398B287AFFCE96AC8ECC36CD222C6" + + "7140E144656A0D8FCBDDBF5E96390DBB7C7C093EEBD8DFF0BDB68568BFE77D7A" + + "A7A3B6C94CD9737B8FF3C4F9684062E175AE9EB8DAB5B8956EAFB3A5EC84C9C7" + + "376F5F4B588E60875F3654A15899FD3734455366083125302306092A864886F7" + + "0D01091531160414D222A8E9C8CF876416F9A2132202326C1AE63C20"; + + internal const string Pkcs12WindowsDotnetExportEmptyPassword = + "30820A06020103308209C206092A864886F70D010701A08209B3048209AF3082" + + "09AB3082060C06092A864886F70D010701A08205FD048205F9308205F5308205" + + "F1060B2A864886F70D010C0A0102A08204FE308204FA301C060A2A864886F70D" + + "010C0103300E0408865AFCE404C9F4C7020207D0048204D89F814ACB055451B7" + + "7FF858162304D57C3B3393283C3DE8F81B4A2264501B47665EDF7A830B064DA5" + + "DBCBB95A1DE88E07E50681AA732A800FC006617317B0D461450EB0A8CAF73F7A" + + "26B96510A47BE05034301B33954B6DC2E581D00369DACC98C189FE2ABCDEF057" + + "3E5A04D0199EC35F0D401CF314F714D6720220315E13ACE3B982258A94642407" + + "88BF9899E4B631B577293E8C8BA288D459DD6B158AB1C8C585D8ECAD23E3F49A" + + "99EE4B64027DB0A5E5241EC4F2B7D614A2F213F50CB97BCC8C64E1194CE6103E" + + "E21CEB1F83C8A8C4D3CB4F0E76CC8482F34AB8F3D42386E061C72C4A9D040076" + + "09C86331483F0E19A3C94BD2848E708C4B6D07DFF70F839BD7D39A269B4985C2" + + "025FC6230D08999B5121C71FD9E11E5CDB694C6C5C1EEC5D92E9296A8BA59902" + + "82666A494BDFA916EE9FDA34EA64A0877868DDB61A5F63E3EFC54DE52629A2E3" + + "FEB9D302331C4693711AA3B3C8DBEC5E44F4EF28E40C6B2C6F3C6861F270C87A" + + "210F5BAD2C05E43D2D93D5D349D7A60A5002E11F78E402294E77C946DA9F8F59" + + "F51E6BFFD1E322D95E3E49CEC25FE338F579B910B70268EAE76763A368125663" + + "E0B14F84C502A70EDF05D9C4A2257742E2F52C80E01A6A9FFC21E2E231DD94A0" + + "93D2819AF8261C7A2DB6659CC3FB237B8992FDA7242836A9F549555FDF55208D" + + "1E5E2B13B18D776A606941C9DE54B560101289141BD864160EB2C382DEC0AF28" + + "B9037FF80ECBD0189F1A2D22CC395637AF046D62E90220EE6630957422EFAD6F" + + "2F6FDF425B8B9F713368CCA5D3CE9C12DD6EF61B12074FAFAF87F3E59A42D8FF" + + "41BC8CAF09B5E79E3AFAD9832DED992F7A8441307379DA4225EBA0ED9E357664" + + "615FB53B349EB5AB1C2EF450B6338442D938A0D9C52CE38862015F11F1F805C5" + + "426E4AA33E6FF964A4E7F39F8088DB8A98821E737D1D974A70E3CEEC960B733F" + + "68A8C1A243F438DD6D4DD25924251242711343E6767359AF38655455D3058110" + + "73CC04DC656DFDDF7C3E30614D994C315E2CFAF63307C0327753817BDAA08C92" + + "5307B136DAC0BB5FA128AC7879501D00269E7C7B779DD5624F81C28040A2FE42" + + "0FED94C6086AB945D4A81F6F9547B73BF03A0F6E8E36FA629B7730A42D574EA1" + + "D6DD37AD7624AED6A5667500A3CC2CA45333A8E2872B390950EEBAFA7D98C3DC" + + "D16CD6F932C21D3C302DE8B161B85AC67A524855664A3473A541EAA9336E2FF6" + + "B4978BCCBE0C4D7D874EC424AA450B5C2802BD1DA7FF07493F947D2F0E202D3D" + + "6D43F57DA836EAED898B280BB374A23AD10E0F4399F549A76B166F80D0CF4ACA" + + "AEA53B7A7A13E85162FB48F54A7930D564E99F6345B030106D625FF92EC96E89" + + "921442A0F248C1681757D1C8638EEA75A1A5A2CA74F4DD8EE1EF114A56E040FD" + + "6DB11B4C6B542D8294C08B0C65B02DB59D883F52B3A5F0165E37EEA7999BBEF3" + + "669510A206A533F5A82DC52D4F6B54977D5F8AE493AE49BA10663862CF1B8675" + + "D42F8DC6BA3D748CFAF4DD75C870D782CC00F4D463EAF06E2BAFED47748225F5" + + "1756D089EDD71C090CD87225EE9D501848E4682460A23ABE5A4D0589ADA5105B" + + "14439ECAA937517FCDB35CA6BCEBD1190E71A793979F070B37A9AB2D003A00CD" + + "38D8EF194207FE53FD95D7C43F7C58718F0CE80588DB13B6828A1984D9DEBF0C" + + "1072FD2171AF46156A1685CD841830212B7C78CC1A40C9F92DB0017DCE853E1A" + + "83A9FDF792AD2935750D74298EC8051C3181DF301306092A864886F70D010915" + + "3106040401000000305B06092A864886F70D010914314E1E4C007B0037003000" + + "4200410033004500340036002D0033004200410036002D003400360033003000" + + "2D0039004200380043002D003200410039004200380034003300390041004200" + + "320032007D306B06092B0601040182371101315E1E5C004D006900630072006F" + + "0073006F0066007400200045006E00680061006E006300650064002000430072" + + "007900700074006F0067007200610070006800690063002000500072006F0076" + + "0069006400650072002000760031002E00303082039706092A864886F70D0107" + + "06A0820388308203840201003082037D06092A864886F70D010701301C060A2A" + + "864886F70D010C0103300E0408196DD4C6F976C7AE020207D08082035049FA3E" + + "9C159024F1821379145C29EFEE5AFEADC5F364CD86547A125BF22CDD99010BD6" + + "FD3A5CF0F028DC3E37E1DD509F83F2C13F59AFE2D3908B33EDC93488DBC0D091" + + "6A209039CC4A9B8E37F614FA6E877969796EE55B58699A65A98449F8F0ABA078" + + "50497075821CEA2BAE375092254407B1CAB99E8AC3AB1F582D13217E651E328A" + + "DBE6054A3FA4E0A6911B57A5F2651FC772D5A180FDA6846C089C6F0CFF7D3EFD" + + "22D04551B59F58BB605D9F6B99EF7F027082E200052A19AB9606DCD522386A98" + + "BF17FB599252052A69440DAC7D57187000D58E8902911A51422DE3BE79001A42" + + "0EBEE4D263A265D49D41432D6040D4D28734DD38288DD84ED32EBE281C361400" + + "7DA2D4751F423538EF6608F017C3E2B0A0E143ED066AA65A933D62F5872EC26B" + + "1C5CF29E264146DD6EC4A2A90DA88FCCC9E0E20F27373CAF09E9CBFB0D057094" + + "A9FB914BC194941A873D03139FFCA79405300FB46CBEA0070115271268C2196F" + + "C39DE444E483D6E36A973DC95775AAB3C81F766065A76EDDEA274C38F103B218" + + "5B57FBBB82F1316A6C023A51BF873DFE71E62259AA2FB6A5C4E4152D45D70284" + + "E08927B559F1706505277EC088BF91D3ACCC4E541AF1B7701DE69483B1CB64F5" + + "DFDCF259761E080D5C58ACD1BA8E3E13C52E4346D4743F4D7C5FC9FD1BD64D52" + + "0539B7E627A33ED2F5840A9D878AF88B32C5D5B8A8279662895A6DD129E8E511" + + "C5123885514D3E3FFC61C000DABC1648BC816746B3FCD00F967BF8F12B1FB18A" + + "E87A9972DD2B9D83A69E06FCACC37EF3CB6CEFE37D6EC68C40ABCDCFF9A91716" + + "6BFFFA54A54016089E4C008B0FCB9EDDBF2F426E6232D04FBA7C41F0BD044D52" + + "70B4B82D865D75BF8475970459E1BC28E9207189586944D84D1CDBB74EF25773" + + "9A2F070A9871B1072822A6614732A9D90B36E6B1F8B9DB190E0B65059601AFF2" + + "D19BF35C1C900E6D575DDBC1DA505FFBC19CE98E90741A898A207D254E236A89" + + "1826B66AB6E1D26AFAE6F59A70477F17D490DD1CD5673FEB65BB4C52CAAB02BC" + + "ADFDE6E483DB58C24BC3E596E451BB70FB425CD2010861D60CE726F9067659CA" + + "907134542C069E52AD359E2C300FE19AB08E343614AEBE1B5584D258F1713927" + + "4FB671EBCBA8B11CD4E35564B518A840D0DE69B54E39F762F3821E5C6EF5C2FE" + + "9CD9D62EB1031AD5D191B95F2B303B301F300706052B0E03021A04144BB56BD6" + + "06B405745EC53A2F4AE4598F557ABFF60414C984B699CDE84E8ADC55A7291E5D" + + "FF09EAAD2394020207D0"; + + internal const string Pkcs12WindowsWithCertPrivacyPasswordIsOne = + "30820A06020103308209C206092A864886F70D010701A08209B3048209AF3082" + + "09AB3082060C06092A864886F70D010701A08205FD048205F9308205F5308205" + + "F1060B2A864886F70D010C0A0102A08204FE308204FA301C060A2A864886F70D" + + "010C0103300E0408D10B4BC509135A33020207D0048204D8BC69D1EFA1BFF345" + + "14685D3FD6F4407BA4106DDBAD150E899E2C6BAE0B291797542F07376E797891" + + "16D8124259D24C3E753781187F3CF49FCF1994554A28E12A2AA76046692D3673" + + "00A6FA70D09BD1A0299FF481A2060879A4B34FD35ABA4C1EE87AADD95A42CA75" + + "50433F2BCF46548580AE0FBF839AC5C6BA984C9ED00EE8C164F701985B116C3D" + + "4771CFBC95B41CE2C61EA951A0B2B8297ED1A10E94F80640E3BC49AB004DD110" + + "7DB967921976B49AE1DC70AC7F3F1D85A4AD84D911E63BAC9DA9A620EE4CD74C" + + "512D240DD4937AD6718432C1CC39B00354945F80BC59B3A9C3A2CE80D17E4388" + + "88BD9A0C6665E1C4D31C676DB4135386704366E1650E457803E784C37658BEBD" + + "64C23338E6D5416587FD8460E2B04B2F9C884845481B93B6E3A829DA9C364ED8" + + "FCFE8DDF2053B8F8A6A3978B6F1760758C0124C87F04BF9D411DC3478E0A45CF" + + "A0BF342255F798D34C059B401B29962DCA862577C43D7BDD41A69BA220FD58E9" + + "5CD79B0F6FC43730E59E7C8709732B88C68F5BCD7156A75359D7AE38A71AC69A" + + "605BEC35038DEB4167EB989FA4D1B4ECBD25981C0C4D5D3109CF8C12863DF59E" + + "904DA26B0B03AF0CCB1D83F07A02338F413B6177DFAFBB4ABEC97FCF7804655C" + + "0697FAD4182358064A55AAC998B035D559C4496575676413152A38F079568272" + + "2E80AA090D9D974F8CCB59159F184B448ADCC3FE9E3032D64DFEC9405ED27030" + + "09A5BA3AA1A6A9D190DE7D068BBBE5F5A9220788B8052A81C1AE4343E1E17FE0" + + "0B8EB2D4537A97A9726E901FCC8F5B660AE84FF7EF1D2633FCE243DE56053C64" + + "52B1B0C8E73112B34C4DEDF9ADE21BFF9217B341017945D73B6B2670926AA4C0" + + "FF1EEE23EE56C8650CF0861A338F645B4859DB37281B4D655BE084D5FEAD1B8D" + + "15C77FF3EA96CBBD098C4E05F718FA81BBD8D423D491DA5A42C7919D14BC0C66" + + "D7F2FFF8102F2207D7866B3952A15B3EF96E59B8B0AFE59668AE4BBF8A75C0F4" + + "39F3096C3B9B172074C37CE8C0335BA90CED4C78A35EAE6BEC26FCEC69AA6470" + + "518A9131E0BDCE14604CF7C1DFB7C6CBC90C0147EC64417418F877045F0C947E" + + "16168035DC404E2EBD1C6B9BA0347F011AFAF908FF0A1D8FEB85DE87BA2F8CC5" + + "BBE29F4AD3FA4F2F16BB282DC801B817DBA5D00A71A83ADCD228DC03DDC477C1" + + "1693D7C9F179D1ACB1C13F6CFFBFBF7504A31BDF719B35E7A25B116EFAE68F96" + + "6AF7A9B545DCD3C9FE9FDA171E28E46A5FA749F81FD8D87FD52D4795F4E28554" + + "3B3C2289264EA9F5B1DBF0797F7795638E2BEA075F3AAF04C119329F43D9788A" + + "98E22784CE45D3F0659CFF73C55379156A56F75D3CE6BBA7F11F949CD61F9ED6" + + "9D84ADC7A41D67E64C2FCF532CFE4BA2D31400A8DBE70C4344790FFB9F353245" + + "8967D45FE4C6BAA588534AB8A43BB1417DE16D7413DB778698BDB75906BA8E22" + + "7C9221B2B44D358908BBE0C0F2F004BBCA5EA8986169C1EB59B6C8C9C254037F" + + "D44375DF38AE1AF5BC23485CB6FDCEC1C0CF0B8692B4E0A00EA549D4E4697924" + + "0CF5A90137D4E12ECC7F1966E6817CB1F06673EA7D51F462289E08B080257281" + + "30D738CAABFDA23821DCD9CF25452857E3EF237B661338E44A0C8C474944F265" + + "0FE693A3B677EC99A647CAD574E85605800B1BF305B779DF5ACFE21329E1B4D8" + + "09B9CC96363574E387403DE8B57A636D731108EF9501075547E412D22475C053" + + "AC21B74B24ED7AE7C23E1235E93F58D73181DF301306092A864886F70D010915" + + "3106040401000000305B06092A864886F70D010914314E1E4C007B0037003900" + + "4100300039004200300036002D0044003100440030002D003400430046003600" + + "2D0041003800460044002D003800420046003100360043004400320039003700" + + "340032007D306B06092B0601040182371101315E1E5C004D006900630072006F" + + "0073006F0066007400200045006E00680061006E006300650064002000430072" + + "007900700074006F0067007200610070006800690063002000500072006F0076" + + "0069006400650072002000760031002E00303082039706092A864886F70D0107" + + "06A0820388308203840201003082037D06092A864886F70D010701301C060A2A" + + "864886F70D010C0103300E0408CC0493121813F19A020207D08082035041716C" + + "8BF56E445181DCC9560D5AE27F850E9F87928D642B9C3264C2991F3BC05818FF" + + "30EF2D88C20F7E8F3453BF925BBCBBDE8273F5EDAA719D979B468DB9A02B38FA" + + "FB3A852F1CB828B69AD64B92DB29B0275255E0E9E2A1D82208B9388ED4075E7F" + + "18EFB6FEE20A3F1A440245BBC01C71DEA4AEC041C6BC2B7B108E85C1636F8C98" + + "2D2F860EF6B87C8C2705C14F4EBD351A36112BB26EF542EB4CD89DAEA112817D" + + "667B44AC99FBCEFC2164AC9D15A5CE589A61E0AD01F5C4467BD0835E23B41D48" + + "8122061A2E547CFCF38ACBEAEC0DA94290BB0CD52EFE15F5E0AAA89000E04437" + + "2B63787DC5C88392258C58C12BA1E63EB6A8D69ACF662BABF63714D99410C419" + + "D167820182988F5EE810A4C3F76D95057F0772F80E4E6B5F69CC58226DAC747A" + + "34311F509FC06F8055A83895E08520A88F74AC118C8F5FFA94DD6DDA74FAF779" + + "2B2A9B105079654A209D981E4C9954B3EC64E45681629DFC08E03B3D5FDF9461" + + "2A82365F2B959FADC808244ECC80A31C931E0E74DB4AF6864BD7AA90C33EE7E3" + + "432C41C756479927E1D80E241C59CF7BD5F1C2C3ED3376B2C1595C8C28A30937" + + "4E092FC99863A02D3C88E55AE43C3DDF83D8729D5CCAD761AF84C16E44F575AA" + + "67AE08CE9F8BC09C0A67C4C53B964A747C2505BDCF20690D8094F5AD58667CE4" + + "23BEE3383D9FCCC522F17304EF55B27B2DFF3CC4347537B7FCA30ADC16274EF2" + + "2DB9BFDECD5F44D55F8246D21EB64BBCA141BC5A8B62F999CBF06553490B6265" + + "68C3F61B060B0A04AA7A758D5C33155AA453564BA9CABE511C6D4C351A49F914" + + "3B80877542D9E65DEE2DE4AC2BC7A4997EE657FA4B885E3606D1F84D6977E758" + + "EFA84F7097A18330A3A222E7EAE434AD8D472127C7457CEC10602C4E0A4F0B72" + + "1968D8E0019EB5D77EF5B1EFF02D4A6CEE9BB28EDDD72D623FE9295126082631" + + "BE3A95DB414758B414448CB401EF0763BCCE74C83D93D45C2512977C35DEE3E9" + + "8A9D7AB96CAA0EFE7DDB8BB7AA77427151B88087FC97579A39D8A2AEF93741A6" + + "2C02E951ED1C3A172FC366253000AD457CE3194DCAC4DE13297D138A2EB007D3" + + "667F72FD0CE169799D85B85E474D06A28EBFB4C166911EAD43A70DB60A7FEC30" + + "4A6931591F13F3D029409548002D873551ABB2873BA3E16411E7E55CB693088C" + + "2B5DBB5F6861679BD1C2817A73303B301F300706052B0E03021A04145FA3D2F2" + + "D00A66EA4DFB51D5DE4D316276BCCDC00414FDBF473E8C4EEE8CC4D8DF166E6F" + + "6409A8F0EEDE020207D0"; + + internal const string Pkcs12WindowsWithoutCertPrivacyPasswordIsOne = + "308209CD0201033082098906092A864886F70D010701A082097A048209763082" + + "09723082060C06092A864886F70D010701A08205FD048205F9308205F5308205" + + "F1060B2A864886F70D010C0A0102A08204FE308204FA301C060A2A864886F70D" + + "010C0103300E04083EE6D2E1BADA3F12020207D0048204D8B84EA7E2CF71A281" + + "7828C34AA3005FEE52146EA713B5C3AA7958278B35FDF64AE8B998E3D87EA25B" + + "54B5319CF4390C812F7EE71D10CD3C70851AD81C690DE0326D0FAD71FF3D5038" + + "223988DAE31EC0060B6C2C5C9C74B08BE8EE3B78FED2A04AF83D38AD11AE8DF7" + + "8F7E5E1E447CDDDD0D9DF2AB10C4B8A90AF6E1152B6A7BE2BEDFFB4BCD8FDF3B" + + "D100114E75760396304BBA8C2AA19C430C9913028B422D5A74AE1429940D9B55" + + "DB9225604C5C4FD9B162A921E62BBCBC268470B84BC3BBB383A2C9BE7E178C0B" + + "2593D617D1466B95E57E90E30ADB7E08F6B0753B8B9981C49537B9B80D98BEFF" + + "350017130F360095156BD5F2F8963572F1B78A9B08B67642BB09FAEE48877259" + + "3383A830F3EB080210F39F5C28EA7892B8E6D8D6831A1DB33EE2B7A5BE758C65" + + "8837A6D1CB4383B47FDE181BB1F043C403567EDB66EFD95F585BC8DB248CD0B1" + + "909763DC9E3F46203C6FC61004962FA6799248E5E7F5B7A131D720F24056BD42" + + "30D1F5E6827B3025A66DF97436A02D441678797BAEB5135931B02A182C045B16" + + "9E8F86CB5871FD3DFC3FB573392230B70414A18BC792AC988993AE9AA2DAC5DE" + + "E09C87F60AA13637EC006ACA2525078E353497E1B242A5FA4BE57C764B24C4B0" + + "F5956D6067CD150ACEC57D4CAE982E782449588D5184961952A2871EC6FBCEB7" + + "E38DAED63C897808D7C2D499428E1EE04BDA40A287552493954A45941E684028" + + "2C964AB94D5A81E833EF28B3B7DF3698CE84595415001DE52ED85A6DB49D4329" + + "562C73F2AD51118A9E409FF96677C117B0528E725F55433798DB598F2B5B558D" + + "77F7703D1BFE6C24CBBD1CDC5961FFC45C139FE9855F93BA52A0183FF1C18742" + + "73CEBA04CDFD3270ADF3E20590E65E019DD6DE4FBD7C62DCEFDEC21103DC1C65" + + "078B76E9CC1DE6D33C4C6688A14CE938ACFEB25C8CD134C1BE439BFFCF8B9399" + + "8E8DBD7CFBD86D6CED0E1D1D8712913EABB7DD493209C61C39B757B329130562" + + "0E38EFC575C2FFC43A86300D5665C0B56DEE483762A623DFDEFF4627BCBD00CD" + + "3DC181AB22CBD3D602DFD379A223F6AC2134E47507501EEEAF33B06001C45B67" + + "C6DE14026B88CA0BBD44F941729FD3B9A078D801B98AAEC83A2431C1A7A1EB7B" + + "99051B4447256B2EC42A4E2917D6E7BE65FAB7866EB797F6ED4A988C57D4DCB9" + + "8C9236348A45BA717796E7715E734E2B585176100C5FFF37778EF76EE952CCE1" + + "5540E9DEC3E84E740694BCBE6E4108414319DC8E760852313C86B4EE03B8DF16" + + "8D00615FE20442C4687400E2436135C938E9167027799ED8890CC08788BD2A28" + + "41F0F2C1CF8CC2CB2950D3B2C93394017300C714BA941D356F8FA95FB5F82262" + + "B703F77A48E6EBE95DB2E7325F9248758578735EEE3BC0CC169FDA777A9FB591" + + "9A0007E0C191668F65714C359A872C14527B1A455305CE275135C7EB9F2E6AFA" + + "5F7FBB4994D0C2E8BF42EA9BC3581A7D70D3829B7D8204F2BC5AD9D24D7F3A09" + + "E0A0933D6532ADF03C500E11865C3138834AC110C513239E83606921929853F8" + + "D87FD51EEF9288178891AA5BC8A496E9BE44D4BFB83A3D435625DF3FF752E7FE" + + "FB8509D28E3A5E8BD5115FF0BBC2AB54DFB9DC8250478CC7E7A0624060B446A6" + + "4777501DBE0FF81EAAAC1F108BDD7E122FAE1CE7F326EBC697A016CDD1E364AC" + + "39FE901DF5D9E7A49225017DEA5374091232A47BE1147753CD60199268C70CA5" + + "ACFF5023B35FE76C92738BEBB1533FCB3181DF301306092A864886F70D010915" + + "3106040401000000305B06092A864886F70D010914314E1E4C007B0037003900" + + "4100300039004200300036002D0044003100440030002D003400430046003600" + + "2D0041003800460044002D003800420046003100360043004400320039003700" + + "340032007D306B06092B0601040182371101315E1E5C004D006900630072006F" + + "0073006F0066007400200045006E00680061006E006300650064002000430072" + + "007900700074006F0067007200610070006800690063002000500072006F0076" + + "0069006400650072002000760031002E00303082035E06092A864886F70D0107" + + "01A082034F0482034B3082034730820343060B2A864886F70D010C0A0103A082" + + "031B30820317060A2A864886F70D01091601A082030704820303308202FF3082" + + "01E7A00302010202140A49BFCB1DA91FFFDDECE6FBEA3EE7098A4FE4B6300D06" + + "092A864886F70D01010B0500300F310D300B06035504030C0474657374301E17" + + "0D3233303432303137323434315A170D3234303431393137323434315A300F31" + + "0D300B06035504030C047465737430820122300D06092A864886F70D01010105" + + "000382010F003082010A0282010100C53F2D0A6732510BD381F04F9EBFA1914E" + + "197754762CB256EB13B690D5F9A89E27D32FB92F706CB2615285BAECE16C93E1" + + "B6E2E205818A69EE99C2FF456F6EF117F1B414E43F71C0E78BEA69565683C9B6" + + "1CBB1247055FB88C758288CD70B22A1FC72E0B0EDE8A8A7B7AD50271B915DB06" + + "F2F7F6AC149439FA12049CFED26C916A80FA53E8DFCFFD7F2FD4FBF8A70B2BEC" + + "11342C977636C40EEB88F56B07337B2174EF51AD505B4283B876E98B93E8DEAF" + + "5CF4415BC828CDF39C34340921E16437BC7E19EEA44B73B26B594CBDA5729601" + + "C758F3476A221AE043E74975BD90D154643E319F9C253DCA9263A1C2526F7096" + + "A75C524A24B02F6DB13ECA5C43F9370203010001A3533051301D0603551D0E04" + + "1604143939D5CBE561A3E16AF98C385C4DE21C7745A7DB301F0603551D230418" + + "301680143939D5CBE561A3E16AF98C385C4DE21C7745A7DB300F0603551D1301" + + "01FF040530030101FF300D06092A864886F70D01010B05000382010100B4CA91" + + "1663AA6EB8B55B8BDEDDD4A07B1B0D0C79A819035BCAC086A01C5DFC64D2749C" + + "49FD67B20095288E1C54BC3A74FB7384DA4EF82CF5E66114373093DF1729DFF5" + + "95520975BE15C319B747F2209623245113C39B4A61909A6962A4CDDE832F6EE7" + + "DE15308DACAC4A6CFB1C0B0DA61AA9FCEE70441DC554F14A170BEFAD2BFDE032" + + "99C781BC115305E56E0BFF667B518DB0F16FCFA0B730B61EE01CF65EFE499C6A" + + "F5A67F9E20F7DFCD96A4B86ECA84F3E60AE94931B7A06C238BDE733BFAF131A0" + + "BD7D69D92AC75A4FA2EE300C1119DD05BF44BA05BDF1923DCAC9E372E5F2C155" + + "9EADDD2DB07B9BE2A984D2463E12262F058170C60BFC2646713C347D04311530" + + "1306092A864886F70D0109153106040401000000303B301F300706052B0E0302" + + "1A0414B7DC97F46A80902C720E8D237F10ED6FCC44ADB90414695EB196470870" + + "037CD616FBE42CC7469BA795DC020207D0"; + + internal const string Pkcs12MacosKeychainCreated = + "308209B60201033082097D06092A864886F70D010701A082096E0482096A3082" + + "0966308203BF06092A864886F70D010706A08203B0308203AC020100308203A5" + + "06092A864886F70D010701301C060A2A864886F70D010C0106300E0408365868" + + "CD387F42DB0202080080820378CB4B33E06FC54306174EC0AEB21E7DAB66B368" + + "4BFD1E6A2A01F894F38EAE1899CA23205BCD50A9525B125AD532084E6BD4AB4A" + + "5F80AD6FAFADF47AEDB18C9533E527833B972D8F8719141F9982E47AF3E7560E" + + "C34A3F248393659197480475AFE35DC804DA34C14043C67E0EB969FCEE09A933" + + "AC1DC8407F667744C395AB4FFCAFBE4E5553204228B32340692C68BEB99C4519" + + "DDF88D30E8CEF133AAFA77BE355A333C1BC395DFA9581B7F78A59B2089289CF1" + + "207266F8B237141013CA65EEF7DB2681ADC4FA7E36ABFAB2D424AAF83FED6039" + + "FB74BAA00D7D743E6DE29184656754EF99E927960717B414EA3390D62E3773E5" + + "BC82D8B0477ABB2338685951CB6471665E6EF8A7AF869F7A204DB5DAA1C34153" + + "EE9BEFADDE03082ECC0496EB245FE6DAD783F286AE114BAFC0DE20D435F1833C" + + "1FEE5021161D327127198D3BF568297C302DC2C37769DE8E7A499AE6CEF5186B" + + "168544996F5F4159676A2B9B887A335FDEDB14699C03629E204DF1FB5396B00B" + + "B40E20F8898BFCC46E400E32085C827346FC8E69F463938C9224E16EF2E892AD" + + "C37488389501A79A35EB573E915D62ADB2897543989D5105594A3CF3C38E0472" + + "FE8A66FB507498C09FE2BB191935C46A00C9DAAD4FC842DC3218182548D36B0E" + + "EA4114E5997F7FF6210352FE8FA2B0E5D0871DB8B65AF97F3748BD413FC55DD3" + + "B522BBA38113801D03577200126F18868314C11A0F2FB1BD5A4C60A7DAE49FBB" + + "B14A4B256B367289FAA891A1CABD58951531224B5BCDA6B61A17C7033BAB3D80" + + "E08CC0385C98E7B388E072060030B60796BF56A71B31163AB9827F3E3ED043F4" + + "F3DA619D8AB27E7887983152AF110EE552B5BC722FF0F021CEC1206A5C6D2B12" + + "3C5DF369DCC5A1C3C722B3A6767881F5142EE63D0A67866E8ED447884BD4A349" + + "A6DD2641AAE38624CE400E3D71A6420C0685F22422608FA143921EF661252626" + + "F54D743214531AD1F8D91B547E0EC24B9B01A97FD7FB2FF8DCBA530C221DE1F2" + + "0067196F2C1FAFF892E8E49803B33BAA291FC516E3FD5933E5DD0D2016F724EE" + + "E4E8644D7D93D80C9A6BDA84A10F79F50B52E6F76EA9274BCCE40D09A85F6072" + + "654645F4FA044719BEE24A04C2380A6596E17F1DF7213AD98019561E21DBC14A" + + "64D4AF49676113FBBAB9DF2C51E71048B505D1741B325B7AD2D202DC19D541B6" + + "DEDC7B57279C08EBFF5F588086295CE78840D8504D91577C35E9B49F164DAC31" + + "90704B2A7E3082059F06092A864886F70D010701A08205900482058C30820588" + + "30820584060B2A864886F70D010C0A0102A08204EE308204EA301C060A2A8648" + + "86F70D010C0103300E040814D5FF817FE6B0EE02020800048204C8E933D4E9FE" + + "25B62F989659649395ABCA2E20D6784D7F60A36074EFF5E6BB6C319D7E595E1A" + + "F676D24709D8C0A2DB9BB5AD2A0FC0342C77F098C2BA51CB4A1E1B3E26CBC64D" + + "6655704BAF173EA6C38740A9DEDCCF378D7C95588F3F767A9BFF8B0B6DFE668F" + + "1475A860F86E782469F21486C001BFF476319C6E5F9FEDFF8BCBFB4F7012A68D" + + "C85EE96590FC71DD8ABB0C967724564082533DADE23522D73D56707E22211A71" + + "EDD5F62041A79500840D808C61644B1746865D84A6021C20A0818DC173B16263" + + "01F462E54115E15726C1C897CEC39466117AE2AB5C15EAAD645F04E59E7F5E4D" + + "360AC5B4CE7F06C197C3FE699CDAF1B27A197897F97D0FD62D5F9F8FD3CD0D66" + + "13D01CD2DE930C147A7CFE4787763DC0BD864185F9DD9D50A231079B92ED32A4" + + "D290B85142FACCFBC2FE1A9823FDB1E4D5AAE57826F39DBEA82E2A8D7BC8D6CD" + + "B9FC1407BE84447ED48EB17FD3529091FB78398D56CB8C797CFB2574ECDD9732" + + "023315A77D3BCE8EA9DC29FA97F23A09BD5C546A524BDB3C7F4870AC7CACE935" + + "F13C1466ADEF7E66A405B28F4A59589149524A429E03854E62297CE26A4DB720" + + "9BFA48AB52F3646FB5421494C287C30EA2E2311EAF42C561CA144A5C7C592004" + + "88C8D85E9A89EB36C94CB18A2AC306ADC55197C21106137CBBA023DEB14561C8" + + "B6C2B789BB542816CE48F98CC68D65B1DFC406E2E99A80816D069EA1BF2117DB" + + "50D4ACA51B19AFF8669347DCAFB227D827137E633AD37727C352E1CFE184EC5F" + + "D567B865C63BDDC66D16BDAAB053EDD9210EE167F28D9E885262DD8B142A0143" + + "1356A942FE56D9B49E343F9459597C02E2EEEC4F59BBAA06BD9D6E9906ACC2AF" + + "85129F72F78FF97D2FCD0B6A6CC6DD6E2A5A92903B5A35C04DE62259A128CD8E" + + "A162057FE51667D2AA4D661AE36728A424D359409678956EFF604CB254E0A1C6" + + "822C1CC349AA77541D101FC5A0D487C33FBBC318E14FB1B13B74F6C41E9C53EA" + + "ABCFF7FBEF0D370E85A5918BC170AD62BFEFB8B46273901FB9615A808B912183" + + "25E79F666DCBF60B2FFEA25680E1AF19EDC0768AB6C1BAA67304F2FD25AED2B4" + + "BBFEB8BD4FABE09AC3B1298B4CAED0772C5B8EDE2063E0A65C424AE2BA344B56" + + "9E8DAA789B5799480E293EFBD625F8C3482F81C25ED9A44B41DE951D3B0565C7" + + "4889C29C101B9921952D2D7776212AF33C6F60C55A3BCA457C2045C6F4B0ADE9" + + "F4B63CE3148F16153C4425373A335EC789BF3EACAB052BA091A75ABDF4E01B7C" + + "F33805702A53A11E5FFD0752E2B6E14CE2840B94FFE53405EE0EE5C43C19D001" + + "7147891D4FC5DE5B949A8C602481D8C1F9F8B2CC3BF2271BB12B306452EC55BE" + + "EE81E13710746EEA3534F106F14DBE9FBD4E00C2FA2A331D2C8FC54607F6A772" + + "D8EE0B3E349448B7AB4E5CC783E72C3A3AC82AAD33F7CBB24403CE167A14798C" + + "E23284BC8A3F90BEBECDC4FB142037CBFD0BE595A6DCB2FEB4B80CDD4136374A" + + "6347898251859BEE1DBF18F3972493474E1DA9B60943D49978A8F4A73470BEB6" + + "E534D3E74726CD08BB6E9A04594FC8AD7F586F52AF8313D41D4DF45826A300CE" + + "2E6225CC0FD912F6D56A46A6BE4DB2CFCFB16CBDAEF928F6ABFCC212B12E3A51" + + "33864B1280EDA8E003F9FDB30A06058B35B31B133D67743C934C9CBF3CBDC06B" + + "959340EBACF1D7A781AC1318635697C2D27C00EDCF067A6CCBCADEEFD2CEF3C6" + + "B4E750318182305B06092A864886F70D010914314E1E4C007B00370039004100" + + "300039004200300036002D0044003100440030002D0034004300460036002D00" + + "41003800460044002D0038004200460031003600430044003200390037003400" + + "32007D302306092A864886F70D010915311604143939D5CBE561A3E16AF98C38" + + "5C4DE21C7745A7DB30303021300906052B0E03021A05000414AABF612B1D42C9" + + "7B2A777B2B544302FDEE1129EF0408B1FF31BA6E2D1A2D020101"; + + internal const string Pkcs12BuilderSaltWithMacNullPassword = + "308214060201033082136506092A864886F70D010701A0821356048213523082" + + "134E3082134A06092A864886F70D010706A082133B3082133702010030821330" + + "06092A864886F70D010701305F06092A864886F70D01050D3052303106092A86" + + "4886F70D01050C30240410978E6F227D033AAB5C51682EFFBA56570202271030" + + "0C06082A864886F70D020B0500301D060960864801650304012A0410905BE763" + + "FCE5DCA6ADF7F8E1261A5237808212C059073968AE0766D615903AA16B242ED1" + + "765D4776FCBCAD0EBB59DB954E9D0365C5183E758854B80D5CCBEFD131264AF3" + + "D864832A2C25B8B93DFEB8EF2D4CD204B2ADA80B2B41C4C58E5B48B55EC65DA0" + + "FB083D61288687A896374B6C851B6251C0E661751F7EDF47478B9041EC7F5B54" + + "DB9FB6FB8F4FA58CB9F8AF80D7E33B4D0F36880E2D348B5A84BBEBA738B3A10C" + + "7CF9026327A1D47E695515D2EF2B73ECB46B42DA27917607E3504D7DFA40A000" + + "CB265E4FD9C9DA026104220E3ED100EBCD434AAE29FF89A883B25111DAE8501F" + + "68AE219BA7725B7EA2600841F879E9D57664940D74C36FB25E29ED104DFDDF2A" + + "3F104B6A89A878A85C50133B68C22C00FD9791170287828EE50CDA25EC656C83" + + "7932D5904090337F092B24853D7F4E27108654C5764F04817D8205F661527326" + + "9E7DA0B4982108A91E5344B54A0FC07056326483B0D6D445601888D84B22C8CC" + + "589338DED377ECC841B08B02FA5ED7A1E8841B5BB2E0C5C3CB750EEB49D1BDFE" + + "D10516924049FDB0FDE9B20768ADF2DA0425AC8B4DE6EF89CD6AE5270513D7D6" + + "B78B97FBFFDFC2CAEFC428DEDAB3D44C9917AE8F688F7E3EED875A5203821E22" + + "DBBD45A12E6B3C296F8B37B7782AAE09CE48837E772BAFB0B6063644FF3B3DBD" + + "861115C3B91C14669E5E92CCA38879F6260D387B6884C368F7246DC74387C404" + + "7086ABA92BE2D3783106C3B6BBDAFB7F9788E1315751E411A5DCCFCBB58B67E8" + + "ED207FB88B5E92302B97256C3CCA280D3670A7DEE71639EEFC0546D651F2F400" + + "A45C8B7643DCD814349CA3099A6CF5384CDAB9A9668FED88C40EC7ABD1E0079A" + + "F4CFAC8799506D5330A5A97B2988073893A98611327B9806D5B7E0B2BB46FB0E" + + "A2304A2F6A21357D0A3282A149E6303B8A06D9ABEB0518EE665D05DFD1207CE2" + + "4AAF471334D61524FEB2BCAF02D09C400E6EF7748BC71DC7F2098E82EB18A367" + + "1061F2BDFDB063D1823791C182B22AC24E7128287C036CBFD5998B2BA25B1601" + + "937EF2403AAF3B582662D50558D12F3BFE0C4A4174EE2856171FAAB8DD94A065" + + "8D5016CD4F6224B06E9E7A179BECA8FA90F9419982CB5799AA024F8CD4C33CB4" + + "2BD2BAD207D8EE12F739028B04D7015DA37DEDE30A2855FA99A5D33F68460E36" + + "C7E28CF4C87A13B2E92EA349D394ABE6A9C0BD0A2E9BFBDF8961DAAD2F5BB547" + + "7E088EEDFE2858E816815594DF0D6DF581E41F8E758089344CBF536FCAAE2859" + + "2E170F4A11455DB0D3D7D2D5B8E2D951603B9FAF3AA1F76F9CEB0A4988D0EE6C" + + "F0A6FC537C74F6F1A66C38790BBDEF0DE139DD931F1DA04806474931CE5F5F3B" + + "6B419D2DE5B390F94BB1EBCEBAD49E56A7333C6803C0B30FE9A354B81112A93A" + + "57CA90CE91E9FB92163E87A223D02C917A85F7AED5B4074750B79ECD116D86F0" + + "0C7E053C1D113B3281423F8A3CA78FE67E3DE3E6CD7D6BDAE0E4A69EE3B3EA76" + + "7EB2A9AB24124AE29B00CFA484B8CF26E6C7228CB95261F96E6EB8A0B0EB153D" + + "B6BF8B5D1352CEC2C7FA6C4F5DB10AE2023F85C6A6225127E769205B047AF02C" + + "3F6F18F1B1A590B26F29F1632E47B8231BD40C9BA6AC7140B5493CF3CE58F85B" + + "0ACD857B8EE850B82522757FC12B142C1C8A9E76F68C38C779593207B146D598" + + "64C676CDAA4FE83A3FE6B2974B89D4D04CE9E1ED3A95A1402C1242422A6782E4" + + "EC846091719BAB4A4BA6DF294B3E35764D3E41F2057061354694AB5432B02470" + + "EF92768C838B8E472B5CD48E4BDCACC08157AE2BAFC50C309C30A9B52E12B221" + + "961F251E84C8A69342D14B23AAE57E5295EEDB55A2B3AEE0494CD59CB9B392DD" + + "A1EC7C72282336385561505B1F772BF592C7A3ED6FCE4CCB8249EF83960B841C" + + "9664ECA0409D1DFDB9D926D331011B3FC906968B364CD0C6D0B8523C24204234" + + "CAB129D1A1CF17FAFF85E16B215052E886EFE988813DF5AEF7A525798023021D" + + "D821D867427BB605D7DBB4A522078EC9E3D6E863CB54EFD79B291058EE264945" + + "6D61B5A93ED32B507F0DDFD60BEB3258BBB8577FAA4763E06F77F135C8C62DE4" + + "CCB766EF06DC5A7AAA3E8AC42008DEBE1F70901FAF8B6937FD18FFA9B9DD856D" + + "2136E0814817DE4CC48B129CA09C5A58D7FFCF8C1978C54DFEE521DF01AD683F" + + "682066B142B86CF8BAF94E42369BFC4A5842D9E68C3CB2DF69AD0595D60D998F" + + "DD26984428BEEFB4EF36376D4EA6940CA9A286A7AC285F00FD2AE8740E7BA3E2" + + "1E85CF48F03231CBF7CBA3836A5ACAB14FDDD15F0F90EA21C593FF6E134721E3" + + "FA2704E4473CDBB58A00F88535D5668F9B30A2C11FF44AAA4715BCF6DBC85676" + + "345435564F43A10C1D8BD14434C06A1A75FEC14F77A9102337443EAD5494F2C7" + + "96D8A940F000C78269BC362A08948324057D448966AE0BF05D980DA1C8C8390E" + + "94C56145A62AA07392006B7D36DB3C0541387E826DA682BBAA5436A5DF631653" + + "78D3A1DA28E2B525DAA104457C02A9E2CCCAD37537396478C0441B426197767B" + + "4A37A45D2B31C76DD2DEBB9940D61065F1488DC371B6388BC59054AFEDD80D07" + + "3BF1DC05F10BCD1A519743258CC9354EC43AE806CE9133EE3CD78E2695E0D7EE" + + "017D16A839C66011536EE17C4FF635739828BB6617DC1C74E57702C4BD91B362" + + "2E1DB8D94AD98682B3A07CE713B0C3392081D028E3E28A66DC949513F4CA5028" + + "6A9169A2CE5B16B541440399BA50A98434D62A0C19FD40CF2D6859DC6673EE85" + + "60B7BADADAB9C61B7A7A0CE3CFD261C572B8D6C3C4FAE1B426E8B6CEBECF6D5F" + + "8FB9DF07DA90D0250606B6924363AB365E081C89898C84881965AB4D897FEA21" + + "EFF53408F637A844618288404F251009BEE40D1FBA009AA5F30489FBE4E51467" + + "9346544A82001365FE1F45C5332225F2CDCF9859CF286A0D3C41BF20E421CDAE" + + "96723EBDDD5C7431CA0CADEB4550857DD2828C054F757AC2D62921A7DD6A8FC3" + + "1EFB1E64440FC33DE646AFAE0050CA5BC2799E24CF0C9493396B0EB011393CC3" + + "B8C07AF714B3EAD4FD85959177DF63E84CEC40A449EDE23C3670492E9CE28AFA" + + "4FE4F5AA916E4EA3CCDC2A381EC5675FEB06B541AE428A913CC9EB2C4ED70B04" + + "CB7B0A03776B313326C7698E6EED937C81653421D500D66B70545671C4D0FF62" + + "01B6E6318B177C4FA29B408CAA3776D57B4C0424F7DD0350FBFB60CE27FE8A41" + + "6FB5F61E43DF1538617B993D4229643B7AF9F2F8DB8F5197FBDACBFC8B228019" + + "2A12AC4053B05CF307DCDEC654A0C795A11BFF7E8386F057FEE687C97A2476D8" + + "BF1A7850D8A97FCCF8DB5BCA0818813A05F68B84D5EE3583F8D57E5C919F2F86" + + "800AA4A6A4DDA2A4C6F7028CD9205B7B3A0105CA5394D9B02D74EAE6EC792534" + + "247201A00C32C1C732F863E6538C810D046025CE81B6E7DE5F721F609A3EDBE0" + + "FF0EB459309BEAEF029907C7F16CDB809937057BE54D1FB1DB18B289765F6863" + + "FD398ECECA0524C20709306BE27CC94B3D2C705F7D676BA7CF00DFCFC170F0C0" + + "2EC263AFE2EFC0E6B1B1E7ADA062B2D6DA2D067833B31BE50120351B3EDF8CBF" + + "7BAAA08AFE2A2C3914A1A1AC537AAF4061507066426270E98E6CFBB877BC2AA4" + + "BD3C60EBB630F9788A960BBECE743AC1AE7FCFF28FDE43F7AB14C8216217353A" + + "E25FCB031C2DF336FB09C22D118FF9751323AC9C82A352447F1E6C7AB1E39547" + + "868A5CF5CB44C8108298620E8524A04310EB5C4463D3A50E25AF1ACA506FC1AC" + + "C642C5D5DE0536EC82C8DB9CB17FC612275AD03BF8FF030DD34C3B0B9BC24929" + + "CABC0249DA09CA034A9280CBC3EF09D4C4EB09924F569728499587A1DDA9B185" + + "7FD8B5CB61BE96047A4A5A28C8B7EE4F9F976E0A3CF25620EE783F6BA53719E3" + + "69C3EBF94BC8B229251C34388B01657F0EA45B5FDF1B0EDC25C3F08463ED559A" + + "392820F79CA86CBC0CA7034BF996E98E4FC0255DEE55EC693BD7E20FA123EEB1" + + "BD5FF110E8D85348036342A3330573C03E9F3949ACEB03AB5010173871BF1DAB" + + "AF12D10596AC8E22C48B45CE0E69C6881A4CD268401993631F47A1BEA6C90DF0" + + "CD26D86D32CAC9307FD864CF44841095F636D38233CA73812E9AB12DFCADF391" + + "D9D8733A6D039399871B479B734DCE9A358F5FAFCB39AB43215F8A0635EBC0A0" + + "943EEBA6E7726E57E2F041467FED30D543785B47476DFC0A1BB36C61C726C4B7" + + "1720428889B9F8CA2A82168469EF7E1EEC52E61B8676FCEBFE49E1DBF13761DA" + + "A66DD693BA7E311B042E3BB978DFC50F7177244F8AA32CC09234B7F83367967D" + + "17856EDF91D1D1357B1B4BAC1191BD692AD06BF215CE76B9530B4993E5613D7A" + + "D7FD354D89FAFF2D397E0AEDC3A21D1DA8965CD2C39CA833D218F1B7BFD02643" + + "885881BF15C24916ED1979DFFAAD9B9B6F4A6F4D03C84588CAE47AC7EE9CB9C8" + + "6DCA2DF1D2F92AF46CD5F20E3F7D262C8996FE0CF224FD8D7F848FC2D03E1BFF" + + "01E23DAC01A2A27575CEACD93DB8B330EC4047B82F303177233BFA8094E8861E" + + "0637C9ED617CEFCF60938B78354E80F7E405474BDC35548553D8AF005DE611BD" + + "B52425A99CA1475C63ED881AC909F9B35C83B8572F4A0B51E4E6A66D0381F1D0" + + "09D71577B22D58314023FC3DAD9E10D95D3E340B77A962B93B13E4F595B4BA82" + + "FEF8CB6A7E3F92FBF811CC41D75AD49FBAC4EEA9066F2DACF868CDA63851E1E8" + + "8C939CA85A70D0375D4C9788AFAF31150DEC9AA7401577239C094A8A6F242103" + + "06FF01F613E50249E7CB850109A8CC7FF8A07B8DDA5EE2FB964779F7EB7FD093" + + "47CB0065F2A6B473B974BAFC40A3733D13FECF8C1F4DCCE5FD1FE8791A3B0979" + + "3B84F5A05A80A8B8A7F37197CF4CE48152D052431636863AF42F3240D5B8D4F8" + + "4BB2027B756B54CE93C0902A2DABE8758D118D5E6DD5F60AA44BEE1150ED7551" + + "4610402F58FFAB5C2E6FA177E7C250D8409F6917A5C37A7C051FF62A74EB03CC" + + "74017451A96CF9356E08C75DE4C86AFBF6D2A170BB09251EA914D65FBE1686E3" + + "82F96A7A21840E7AB172D611704D7E32B0C2F4829C8E7576E9BE3332112868A0" + + "1479B6C114ED7983FB9F30B44F299F58AE2925587BCD66F5B9F3E221CE1DF9DA" + + "749D6397854FE5D972805C693FB18943BAA8FBC55B8CF24747217F803F74B2C5" + + "E7637AC7D576E23F644E3E6CF24113E64E1CCFABC56C4BA104BA69A14A9A69F8" + + "B6B1DA8AFD9C8F6730F5BADD3F63F557E63B7BF98DC3DA725AF8485047F61559" + + "03BAD87ED8CDA9EC21BE96B60326F9465119E3F069FA147A25FC11343D531954" + + "CE81C15B31838B8EE1CD25B11AE4A8A7FFC68DA57A828318BBED76596A550C68" + + "72982E099C070812E20C099EBF882B86AB722F02F0EFEAE73CD885E6C36F02F8" + + "1C8DF15AE3C01F0370B02706B0BF4AB292E1488450CE70EDA02292323979415C" + + "91DD1391FEFD5EF4EA539BBFAF1E361826454A2E6CC32324D6C3A5F5D7D88372" + + "3258E59706A650F19B5A6A1C2EDACD23EE3CCCB1F09B9910DC3256DA7A851B8E" + + "A110319F11CCFE7EAD2DAD4D73312A64D6A15D9EBC5DDC912562FB494194B5C7" + + "72290595529184953D1DB67035CE1DBC6C05E15131E2F4114A6D6A0B54866DD4" + + "DCFC00475FCC5C44B3494D23A703388314E20EAFF4C4A034EEA686D589A7E128" + + "CF12599CE46BF7F6E1415CCEB27E3DCCD6715949DED6C4CAC4479F442D4554D0" + + "D0181F637098C9F72B0751F508394C06682A22BE7D587788D010D24786C5E156" + + "A07785A78F737D924580CEA11B845548711310D9D29B04364965252CBCC68A99" + + "B0E8FF248B8F706CC61EB5F0B1142F17FA96E3DC4F43449419ED9C348EBE07AE" + + "66ED2CF037AB4C7B814C73C9F7E7A0B82BB4CA5D301AEE6D1B82858568EA263D" + + "3E5536614A7C77F3AECDF941424B09792246A9738AAFED3939EC319569ED4875" + + "A1B09FDD2167950BF95DAD59F8DCE0FE450E765D0B703FFEB330D032884071B2" + + "F3DE1FF0F5E43C7F180E3F0B45744802E8113311E58C2776A4484A8643CF2567" + + "2638C63B40DCCF80F767D26A5E4A830E15397787FA550FFB0D07872EDD38BBCB" + + "7555C9D7637A503C26E4DA6BB41052C0E1DA2A7B7F33C74EFCC529EFEE43977B" + + "8373A2210EBDB9A7126AD0A6742D7AAA7A73E9BE546344FB35043209E3C8BFB0" + + "527AB4456E6D9F873B4C238DA0491D930EA654F71910ACD69973F01713817B0C" + + "1165F22BDFDA789DBE3D01A3375DEC8C40A0A6EB977B10929CEF89B61274D332" + + "ABFBDA987CFCF14EA59C3FA5D7138AA8C32E1CCAEC3D229E1B4A46A2A9F02EBE" + + "5EEAA9D88122BF053F142E42E8A0E1268A19B77CD4B2A45F68B74EE644D3CCBE" + + "FA586076CA0F8FEDAAC1CA11058E4B9896ED529F613E246F2A4CE29ACE49F0D8" + + "FF6C04150DD03F526DFB7AE7DD751FA746C378AB0D658BB4A808955C9C8C3B36" + + "79426C1941F15835D5FD179BAB3D658D4A0462F617DDF5FCD23EF2C899A52D46" + + "60144C01A542DCBB5C15A8086B6E97262090127E5DB9FB999E45B869833E9641" + + "A7795DC55B0D7971A0EA423F306475E80561384169333D7D26299100FCD24B3B" + + "5AD6A215B8323CAD902A6C1B97A19863F4DDF460780D188391FEFDF013EB4693" + + "D3CFA097C480A10502F21CF5C1C2C45E9CDA63604008EAE8C11112167F539F16" + + "236A4B50D76E8C143AF38ECB60CE07572E9F31C5517D1CCC20AD644B3361EF25" + + "84ED7840AB11F901556CEA40435FD9B491EAD820DBC283D4BFBA3B00A8CF2F0B" + + "99627225326C9C7DF70C89E112CF06C9B9B968E28A148B61BE10B7C8670925EE" + + "77EBEC504F822614D0B0F3F0CDCA3EA9308197304F300B060960864801650304" + + "02030440699B89444D5AB2D939E849803579B6C1651E206FB208C67F879AFFD6" + + "32E080D8D15BDD48651241FF03180ADE528193DB946145CFAA069790AF3405E7" + + "81BA7445044055FC4F56A8FA8F63539E4AD754A11A1CE1BCF972388D24023EB4" + + "0E8B8907D86A9F4715D3591307E9DFC832B7756EB214BDADAB09F9E1E61AB184" + + "3EC3297C0D4302022710"; + + internal const string Pkcs12Builder3DESCBCWithNullPassword = + "3082088E0201033082084A06092A864886F70D010701A082083B048208373082" + + "08333082082F06092A864886F70D010706A08208203082081C02010030820815" + + "06092A864886F70D0107013024060A2A864886F70D010C010330160410F19120" + + "18896CFBB4653C4B7C06E67C7F02022710808207E02D736F0AE772F48FF976AE" + + "91E8614648678AB5A318CF7D577713881F80B0D6FBF645446359584803814B99" + + "1A8BFF573CBD1D52AC5237EC7A42ED303474221F91529FDE33DDC2126EB4AAD7" + + "65CCAC633209A0AD43DCDB5C1D4ECFEA856EFFF607F03D66B29D433F268C7E74" + + "993F5F2384F9E0D08C6657CC7AD32549CB8D893A1EE37A24E398A44987D23E79" + + "B3E4F3F48B106FD6193812634F999CDEB1E5E3485D642B4DA850BE1C210F9321" + + "3BB1E01AB38465C5B23FB80AAA6E1421A2F20BE775F658BB52F57B4CAB79197D" + + "E65860D656CE8135E235A3C7E06B511F1BE398C03416BC137E063DC8CF8B7100" + + "628DC62B87A70A282C11BA08A08AB0E03814A9B1940145AC0EC3327396AD6984" + + "4BE0B4883DAFC5A611804FB628B598DD006ACC039A86B23100B102E1F4B3CCFF" + + "06FA1E9841DBA8913B9D1D15D6A16330A94F55B04D9779A90EDC91230A664C41" + + "9AC825594D9F3F39A08074E06E5B671469E3AC9D20D906FA9B99CDAB6696A952" + + "49F7B3CCBB8EC1BB2714C248AE0C3D0934F949D68995A785F782910BB8457B66" + + "1B4EADA3724023BE1DE63144C3661055986A2E4D2F59A2DC018EB8E6178E9DAE" + + "14027352738A4D3579C55F48D0C373C9280689DC509EF602C69EF10566043EF9" + + "F1A0852A618103B6C2039574696D64C8492963B54C5BCBD4B124FF5F93B04DF2" + + "F5C1FB577A2BF8D24D29D7C6630071EAB5A3CCBF7DA9B4F7CC39673CDD25ABB8" + + "9A805838B32A002CEEC02404A78BEC048DE5D8AD470FAAF89F31EA7E42ED1DD1" + + "20B66246AAB7BA1FEA3DCAF7A78BCF679A3FAD5029318D69EDA8F081FC7216A3" + + "EB610BA3F6EEFE78660CDB365055110446F73D0FB468D4F4FFF01C394C9FDD20" + + "47792192AD9A46458E3BB63BA4703A7D8D4E56776D48312C5033497795A396B8" + + "60115AA3FCEB1E3CAED4FE9211787A8B4B0BC1671A3104DA4A7925A1E3D661B4" + + "35760FD3DDF71A5902269288512C637C94DD1DB6B8551EE951019294342A50BC" + + "4310BACBEF7313D82CE7D351FC2E9114BA421FF1EC206BFE1DCA5E772E90BB92" + + "E7DEB1CE899CD9328C09BF299F7ADFC9D43AE4721FD36BAA93E50EA6B8B6DA5C" + + "7A188AEC11EAC6EE755D60BA5FE98ED8B8D2360972C114DB4A17CEB5BF8EC1DE" + + "8EA0D6A4B26F40376B2C5D66657DCE4A2A2F3E519C65035E51981A6A74415AFA" + + "9BEDB27B2A6B175DAA953919580EB1ABF52D15E2CCC725AB0BE5F09864FBD0E3" + + "6EB76E8ECCA70C1A850197BA84698A00EE1AE2EB95DD79F002EC2401DF3AE3F8" + + "1C68FAD724C59E51AB60AB22C60A2E6E0CCE2E9BA5F9E2E503946CFA77CF6713" + + "E9B98253C42F3A68A6487D2CA4ADF6D0CC8A13962F5569AAE34C3500414CB363" + + "DDED7F3D3BD5BF6A0E1B229136F23362FD016595AD0F825E30BEEB9E2F95124A" + + "6033D3BF9CE8758AFD964B10437DEFD03FB28BD46D3153FACF19D6405D260B1B" + + "257B138D0BDF3AC006CE3C22D1626BE10E8613F6912536D07EBC6142D3EE8A3B" + + "9D7321D35DB2A78A1C3376894D5C2A1C3E41BD7BD8190007327505E76247C6D7" + + "3B7E78E098F4504B205D35EC50987B601F76FE6363AC0E2FDC89853CEEF2BFC1" + + "08E4CD798C20AA7B326A81736B5FDB9365B5272DDF0CB692230A5C69A37DA5A4" + + "7241213B95C44B9733700A7D4216043860C649E979E9B8C304C1C02E7981A502" + + "60233E8539A01A361FDE3B83A8B42259FCDAD89FA279CEFBA6F769C562E75E9F" + + "58FCE65C6D11A7F701161092E78DDBAD9D3FD407DDF675A46348DF53E9EF7CD5" + + "FB7CE0A3C1B7EA0138529D061CD3CE3A76FB40138ECCD2A29BED34E27E2494D2" + + "D3787326C11AAE5933BAE7FFA6F5F21D33FED6CDDCDF5CEE3C1D7A8FADCD8EB2" + + "E77A75D01A50B38E445D91A986F8D1C1100E37A9AA5E83823280236482425DEC" + + "F49EE6D898E9D8FA3E0F502B50EF968AA14220E926D1ABDD225624E81AB0DACA" + + "A781857C260B9E712F644A87D5AFE0EB3D73C9999073F3C3835473C3C4716990" + + "DD57540B5A0A2D433F3A7F6CA74D046658FF85A9AFCE68DF76ADFC9CB682F80F" + + "29F1A15257A9ABA43641C68A405A88F499ED3B29D097BBA995F1D29616CC47CD" + + "D8FAAC992A8E88D7A4E033E1D37B5DB2F38DB1E37B2A6B1A094A621BBDB70B67" + + "EB35E63425BEE4C0549D2B30FC9207FFE0BA8C3B39EC8562AB61D0A77F78CAD9" + + "D5C6227C7EDD91316D5202D6AC8CB5503F4FA01BB76A47C59FBEAEF8064709A6" + + "CC2DD5BFAF4C96535A1F699AAC58C213BC1DA0043252C5C2E2852F3B0326D428" + + "A3EB3CEB1372D65271F23935DA069CDB2E2446B26BB98EE04516548482EF7D9E" + + "2A855266521BED66156D491E7FA20D723054F55930D0E02A356659B7513CED09" + + "8666D05B6C3EC9281048F57031992620BB3F582F5963AA167BC7F8C8DB07644B" + + "8AE5984CBA7122A67275E024B3790178C8DE7F260D0D2FCB643B990FDC514F5F" + + "88B9EE89D16B20E26FBB2FE315347E972A25B6DE4DED8D69526C506004C46918" + + "13567486B8CD40E4D710069F137ED0E5EC4A493ACA9545F395935E145CB63354" + + "4D436F9FB5BDF0B154E72867FCD9A2F8675D38FAD9CBD2B1A4EC01B78C229445" + + "FD2B4232DA613A5BD9847CC2F19E624F6B1223E82F6F12CD97740E938C34FEE2" + + "54F775437183BD93D52F96C4C1ACE5ECE2D70F1C42DAA699BB76AEEA5693C7C4" + + "0B5DCB4E6D61E648E5335B06B2E0BFB55F2F338033DF38D8A2C8AD9760F0B21E" + + "8DB0DF9AF8E682E6F62F0C70A9E921FA86A7C9EFFFF2A4302F94604D1F991AA4" + + "F6B1D3B7E46BBE4126C874583281746CDEA73337A904110E94B333CB6BFFF28E" + + "1D0A0516EA5F72876614328AD34B4C1148B0930A2E303B301F300706052B0E03" + + "021A0414C1F4E43998F3594C85C97FC9C5777848189A67420414104FBC73DE62" + + "EFB93E15B5A39DE7E0C1B46E20E102022710"; + + internal const string Pkcs12Builder3DESCBCWithEmptyPassword = + "3082088E0201033082084A06092A864886F70D010701A082083B048208373082" + + "08333082082F06092A864886F70D010706A08208203082081C02010030820815" + + "06092A864886F70D0107013024060A2A864886F70D010C01033016041039A75E" + + "DA3FFCB2391E25BF504AC4BBAA02022710808207E0A9EFBDBE9CCF94EBD69A0A" + + "22DAD13EE5036E45C5A28F2C15888DE9A6E357D3A35E9C466E3101DD33B08AE3" + + "B17103506D371A69D230C1F00DE33487E0B9DE8318DB88397D13A6B8B172CB2D" + + "C57CE7D3D273878ACDEEA06F6D49D06757B7CED3F6958D98620AD7FB303D9CE7" + + "5F0B0880C9F3D2762157B946963FCA3E4C7DD3CF1BD359CA27CEDEF45613D8C4" + + "2ADD13B133997B508A53C56C1581A87C03F0A946C33226D6F40F523B7F920985" + + "470B0B5E5EDD5401C4BE78072D3333CEDF47FEBF83EEFBC7E6F9D9D02AE95384" + + "B68DF04EF7BA859B6D5AFC6FD9C918BEF427C49D5097E7645FC3BBFD9CA29EDB" + + "09F89908937E2B51FFA2F806CA0EE6E09A1CDC9F73D3E8393F1B9DE437709FF9" + + "59E4AEB2524C15269A871A4F6201D15846CA40F7F052AF72909878945894016F" + + "6BFF0B966338D371BAEE16EF7E3151FEE91F3EB6AA48E91B7580FC1ADB3456B5" + + "2382EBAB36548CA3521A583647CAEB75C50EBF485AECD50EDF6DF6B630031A5C" + + "FBDB9340C05F5F94B2622B8A0DB23B2D658D6F5733DD65328B1EDB4779D318E7" + + "CC704C7B7B78B950DEFAE77872CAE2BB1E25BA7BCAB8C04ED12F6EAF48808C79" + + "7E9C04FE4F7444A7B58D7E8A6E5445729C44E9118158C62E2760BA6D014EF026" + + "8EFD70AE83E45FC1EF05B9043C55E5AC4393508CAF2A36932ECDF77495C35036" + + "4F4C92AE4DEF9475A7F274F946EAF3347D05BF49725C86CFF72579006636F096" + + "FA49D5183100271F94C19D292DE0A7C78A8D2AA347396ACAFF36A123135FDB45" + + "604E3B829C00DF082B7010B41DBD62416315C2AC384E7D700779C592487F69F3" + + "917B391246A24F8E76258F8D3219A3A6D1313443CF78BEF667FBFDC507191625" + + "1913B729E11E895B1840635882997E5C428DB006A875E561AC0758F80F702E33" + + "DD985319B2D6BDB1EA0D403A761968494BC77CAAF4CE42B6E9124083134989A2" + + "877077CAB2F8EDFC8EC13C255022C3AF77A40A3BB5CE1B76A57DC5648234346C" + + "A072852934F310D3446CBD129858D00A64462E89E3C61D128F1C9590CE9A6284" + + "CF29EC375DB56B79F13CB404AC4320BF491472FB0FB1D530D68C82E2029C7CE1" + + "2AF38F419C163454EFFC7FE6EE590BCC3543E2B48AE9A43358E76C4B2D90AEC8" + + "CFC82BFFCCE4DEAAA23E4944C3034B4C1341FCA68246F328B65341442571FEC6" + + "F3671DA0284FF6789452C081DA021CDD3FDBAA34E82FE0C68B75321488018EBB" + + "FE787787A89058696E4F92E23211085EADF706462391F1F631CD87CAD48D72FF" + + "1EDE98CAE24DA0C776F342FECE74B1EEBCA4B8FCDEF4BC9AB952C04F79D5B235" + + "680782399746091BFC93577B863CCF499D93736CAB5500A4C45DE78FAD5E57D2" + + "E22D1A5F7C85FDEBE52F0431F980E90B44C0250079BCB09D4C2E3CCFB6E9930E" + + "B9EA497E7035FDF826600F795E3CC9EB0CE66DC84131B8EE3D55829DF48F6739" + + "0BD11F92B92D20C86B93FC9F9DDEE2C0D7DFF25275BFD25D7C2FF5BED8B4789E" + + "BC5AE940D802C0A86BC0CBE2958BC93B7D80C3272945032AC9B0FAD174A636CD" + + "68500BAB5A53A399A9630F8B523A1639A7B313F3F0517708EFD57E4E57319955" + + "48887831D86087F6D4B5180559CBC461C29327D5161518DD88E0761BF0CE05A2" + + "7FE504C84F6ACBA1C87A1284ACF18D5EE2B17B63549C35951CCC582BB8975EA7" + + "B49E7C061933B53724D25EE9F8F04F2A36B8B1F293A1A76B822C3BB0546C029B" + + "91F3FE656778236EA95B978DAF9987AD80757696DE2393149A260B5EF0C6437D" + + "DA8A63B8A486E14F42B8D1A69725CDE49E68C867B5F87D9E3CBDB4D64045C0CC" + + "50ED426A5F78CA1216AD394F3A4CD33FF6AC219AF8ED4555079511E49C288E96" + + "1165A20134EC21A5100FA597133B2B81646E245E5C94C26247112982667CE49A" + + "5633663629BC8280A4115100F2A3B4384469D3A0A7DE8D35D2FB984EE0190A0F" + + "C70CE790CF27CD3F4D261F941BEA6478CE155C5A409204F1298FCA20B1D3F095" + + "ADE129236F101BBE9D766DAFFD9D12296AAA618FE1FA8E0EB0B4D873AF67B1DC" + + "A3AC63D935952FF833621D1E7E06AE0C5749E60941D9B29D3E68968DF21E31AF" + + "DAC972B604CE613E98BBFB40BBEADA6AE85F716BD655066B1CCBEB11CB48C5A7" + + "56B4B7C96BE9DCD3DC8B74CAADC4CA7038DA00B867B35185355EB0D2C2FC76D4" + + "2842B307D92B707CB61F9BC8BF6AB1FC9EE8D0F5A8C3A1EECED5D6FB1D13A38F" + + "B2D17E0513AA4F126B5D5FC9029E574179C447D04D5A22063B60E88C48A4F52C" + + "949F49841DBF73A2F884239F18BBE35453BF67611036781876920157358303B2" + + "BF9D17473EBAD888D5A612BDE9AC41778B33F660C7C2922B654CAB899F16F017" + + "ABDE8660ABF43652ED8AB2B9177CB407E79B3B94FDD61407618B77FE980A727B" + + "BBC8B3034B0FF138C6B32B0AC9B0A851FEA1A8383EE1C571A55E0ABBD63B55BC" + + "3A5F87A3ABC9D69BFAC4FE5AFF937F536A66D9EECC8CEB491BFD3505564AA8DB" + + "0EB624817F7D6E848E505C320349697F13026A024F8D5C74E0DE9065974CD0C1" + + "D8D6D7C55C0721EFF5A960C6A44F7FCFC432998859C397C33171A3E539BB2FB3" + + "C0939BFC3D63BA3D03EF18F11CCFD4F95AB2C99AC69CCFAB692659193FF81FB6" + + "5A2A4ED12C75E2B3E12C5B76420FCB34C94CB5140AAE2F96F5CE0C6C2784CF30" + + "88F10B7DE2B2FE8F486E7A5A8858891A98DC460C51730909688BC9E1DB92DD3E" + + "B6BED869FC7D708FEA4BA6FC5DEADAF86DEBECC75A4FB0FB61FD19C05F371EBC" + + "EA880808ABAA83CE2B447F7DE021900D6777A7377FB990B85DDCFE64A36C8FD5" + + "09463C6E50BCA36EB3F8BCB00D8A415D2D0DB5AE08303B301F300706052B0E03" + + "021A0414A57105D833610A6D07EBFBE51E5486CD3F8BCE0D0414DB32290CC077" + + "37E9D9446E37F104FA876C861C0102022710"; } } diff --git a/src/libraries/System.Security.Cryptography.Xml/ref/System.Security.Cryptography.Xml.csproj b/src/libraries/System.Security.Cryptography.Xml/ref/System.Security.Cryptography.Xml.csproj index 9f5143a0efab8a..cae7cd09370988 100644 --- a/src/libraries/System.Security.Cryptography.Xml/ref/System.Security.Cryptography.Xml.csproj +++ b/src/libraries/System.Security.Cryptography.Xml/ref/System.Security.Cryptography.Xml.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/libraries/System.Security.Cryptography.Xml/src/System.Security.Cryptography.Xml.csproj b/src/libraries/System.Security.Cryptography.Xml/src/System.Security.Cryptography.Xml.csproj index 20a3d77a4641f7..d11fcb26258b50 100644 --- a/src/libraries/System.Security.Cryptography.Xml/src/System.Security.Cryptography.Xml.csproj +++ b/src/libraries/System.Security.Cryptography.Xml/src/System.Security.Cryptography.Xml.csproj @@ -6,6 +6,9 @@ $(NoWarn);nullable $(NoWarn);CA1850 true + + false + 1 Provides classes to support the creation and validation of XML digital signatures. The classes in this namespace implement the World Wide Web Consortium Recommendation, "XML-Signature Syntax and Processing", described at http://www.w3.org/TR/xmldsig-core/. Commonly Used Types: diff --git a/src/libraries/System.Security.Cryptography.Xml/tests/SignedXmlTest.cs b/src/libraries/System.Security.Cryptography.Xml/tests/SignedXmlTest.cs index 54f00d1154c754..8841cf2f821f00 100644 --- a/src/libraries/System.Security.Cryptography.Xml/tests/SignedXmlTest.cs +++ b/src/libraries/System.Security.Cryptography.Xml/tests/SignedXmlTest.cs @@ -1593,6 +1593,7 @@ public void VerifyHMAC_HMACOutputLength_Invalid() [Theory] [InlineData(false)] [InlineData(true)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74115")] public void VerifyXmlResolver(bool provideResolver) { HttpListener listener; diff --git a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs index 8fa22de309915e..5be736fec669ef 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -47,6 +47,7 @@ namespace System.Security.Cryptography public abstract partial class Aes : System.Security.Cryptography.SymmetricAlgorithm { protected Aes() { } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public static new System.Security.Cryptography.Aes Create() { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] [System.ObsoleteAttribute("Cryptographic factory methods accepting an algorithm name are obsolete. Use the parameterless Create factory method on the algorithm type instead.", DiagnosticId="SYSLIB0045", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] @@ -98,6 +99,7 @@ public override void GenerateKey() { } [System.ObsoleteAttribute("Derived cryptographic types are obsolete. Use the Create method on the base type instead.", DiagnosticId="SYSLIB0021", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] public sealed partial class AesCryptoServiceProvider : System.Security.Cryptography.Aes { + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public AesCryptoServiceProvider() { } public override int BlockSize { get { throw null; } set { } } public override int FeedbackSize { get { throw null; } set { } } @@ -134,6 +136,7 @@ public void Encrypt(byte[] nonce, byte[] plaintext, byte[] ciphertext, byte[] ta } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] [System.ObsoleteAttribute("Derived cryptographic types are obsolete. Use the Create method on the base type instead.", DiagnosticId="SYSLIB0021", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public sealed partial class AesManaged : System.Security.Cryptography.Aes { public AesManaged() { } @@ -1781,6 +1784,7 @@ public override void Reset() { } public abstract partial class Rijndael : System.Security.Cryptography.SymmetricAlgorithm { protected Rijndael() { } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public static new System.Security.Cryptography.Rijndael Create() { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] [System.ObsoleteAttribute("Cryptographic factory methods accepting an algorithm name are obsolete. Use the parameterless Create factory method on the algorithm type instead.", DiagnosticId="SYSLIB0045", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] @@ -1788,6 +1792,7 @@ protected Rijndael() { } } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] [System.ObsoleteAttribute("The Rijndael and RijndaelManaged types are obsolete. Use Aes instead.", DiagnosticId="SYSLIB0022", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public sealed partial class RijndaelManaged : System.Security.Cryptography.Rijndael { public RijndaelManaged() { } diff --git a/src/libraries/System.Security.Cryptography/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.cs b/src/libraries/System.Security.Cryptography/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.cs index 6ebaa978de95e2..f53e582af6e295 100644 --- a/src/libraries/System.Security.Cryptography/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.cs +++ b/src/libraries/System.Security.Cryptography/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.cs @@ -14,7 +14,13 @@ internal sealed partial class SafePasswordHandle : SafeHandleZeroOrMinusOneIsInv { internal int Length { get; private set; } - public SafePasswordHandle(string? password) + /// + /// This is used to track if a password was explicitly provided. + /// A null/empty password is a valid password. + /// + internal bool PasswordProvided { get; } + + public SafePasswordHandle(string? password, bool passwordProvided) : base(ownsHandle: true) { if (password != null) @@ -22,9 +28,11 @@ public SafePasswordHandle(string? password) handle = Marshal.StringToHGlobalUni(password); Length = password.Length; } + + PasswordProvided = passwordProvided; } - public SafePasswordHandle(ReadOnlySpan password) + public SafePasswordHandle(ReadOnlySpan password, bool passwordProvided) : base(ownsHandle: true) { // "".AsSpan() is not default, so this is compat for "null tries NULL first". @@ -47,9 +55,11 @@ public SafePasswordHandle(ReadOnlySpan password) Length = password.Length; } + + PasswordProvided = passwordProvided; } - public SafePasswordHandle(SecureString? password) + public SafePasswordHandle(SecureString? password, bool passwordProvided) : base(ownsHandle: true) { if (password != null) @@ -57,6 +67,8 @@ public SafePasswordHandle(SecureString? password) handle = Marshal.SecureStringToGlobalAllocUnicode(password); Length = password.Length; } + + PasswordProvided = passwordProvided; } protected override bool ReleaseHandle() @@ -94,7 +106,7 @@ internal ReadOnlySpan DangerousGetSpan() SafeHandleCache.GetInvalidHandle( () => { - var handle = new SafePasswordHandle((string?)null); + var handle = new SafePasswordHandle((string?)null, false); handle.handle = (IntPtr)(-1); return handle; }); diff --git a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx index 3cc428cfbeed11..fa55fa3a86431c 100644 --- a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx @@ -822,10 +822,7 @@ Unknown error. - - SubtleCrypto returned an unknown error: '{0}'. - - - Only CipherMode.CBC is supported on this platform. + + PKCS12 (PFX) without a supplied password has exceeded maximum allowed iterations. See https://go.microsoft.com/fwlink/?linkid=2233907 for more information. diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index 986ea2c977f87d..fefe1086c00aab 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -1,4 +1,4 @@ - + true $(DefineConstants);INTERNAL_ASYMMETRIC_IMPLEMENTATIONS @@ -73,6 +73,13 @@ Common\System\Security\Cryptography\Asn1\DirectoryStringAsn.xml.cs Common\System\Security\Cryptography\Asn1\DirectoryStringAsn.xml + + Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml + Common\System\Security\Cryptography\Asn1\DssParms.xml @@ -225,6 +232,59 @@ Common\System\Security\Cryptography\Asn1\X509ExtensionAsn.manual.cs Common\System\Security\Cryptography\Asn1\X509ExtensionAsn.xml + + Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.manual.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml + + + @@ -556,13 +619,9 @@ Link="Common\Interop\Browser\Interop.Libraries.cs" /> - - - - + @@ -576,7 +635,6 @@ - @@ -588,7 +646,6 @@ - @@ -597,9 +654,6 @@ - - - @@ -727,66 +781,6 @@ Link="Common\System\Security\Cryptography\ECOpenSsl.ImportExport.cs" /> - - Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.manual.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml - - - Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.manual.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml - @@ -1108,66 +1042,6 @@ Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.X509Chain.cs" /> - - Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.manual.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml - key, - ReadOnlySpan iv, - int blockSize, - int paddingSize, - int feedbackSize, - bool encrypting) - { - ValidateCipherMode(cipherMode); - - Debug.Assert(blockSize == BlockSizeBytes); - Debug.Assert(paddingSize == blockSize); - - return Interop.BrowserCrypto.CanUseSubtleCrypto ? - new AesSubtleCryptoTransform(key, iv, encrypting) : - new AesManagedTransform(key, iv, encrypting); - } - - private static void ValidateCipherMode(CipherMode cipherMode) - { - if (cipherMode != CipherMode.CBC) - throw new PlatformNotSupportedException(SR.PlatformNotSupported_CipherModeBrowser); - } - } -} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NonBrowser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NonBrowser.cs deleted file mode 100644 index 78b74ac82abe75..00000000000000 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NonBrowser.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Security.Cryptography -{ - internal sealed partial class AesImplementation - { - internal static readonly KeySizes[] s_legalKeySizes = { new KeySizes(128, 256, 64) }; - } -} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NotSupported.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NotSupported.cs new file mode 100644 index 00000000000000..86483ca199806c --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NotSupported.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 Internal.Cryptography; + +namespace System.Security.Cryptography +{ + internal sealed partial class AesImplementation : Aes + { + private static UniversalCryptoTransform CreateTransformCore( + CipherMode cipherMode, + PaddingMode paddingMode, + byte[] key, + byte[]? iv, + int blockSize, + int paddingSize, + int feedback, + bool encrypting) + { + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(Aes))); + } + + private static ILiteSymmetricCipher CreateLiteCipher( + CipherMode cipherMode, + ReadOnlySpan key, + ReadOnlySpan iv, + int blockSize, + int paddingSize, + int feedback, + bool encrypting) + { + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(Aes))); + } + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManaged.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManaged.cs index 3791b5a87208e8..dc623710517336 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManaged.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManaged.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; +using System.Runtime.Versioning; namespace System.Security.Cryptography { [Obsolete(Obsoletions.DerivedCryptographicTypesMessage, DiagnosticId = Obsoletions.DerivedCryptographicTypesDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] [EditorBrowsable(EditorBrowsableState.Never)] + [UnsupportedOSPlatform("browser")] public sealed class AesManaged : Aes { private readonly Aes _impl; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManagedTransform.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManagedTransform.Browser.cs deleted file mode 100644 index 0ae66acc18e571..00000000000000 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManagedTransform.Browser.cs +++ /dev/null @@ -1,1015 +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; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -namespace System.Security.Cryptography -{ - internal sealed class AesManagedTransform : BasicSymmetricCipher, ILiteSymmetricCipher - { - private const int BlockSizeBytes = AesImplementation.BlockSizeBytes; - private const int BlockSizeInts = BlockSizeBytes / 4; - - private readonly bool _encrypting; - - private int[] _encryptKeyExpansion; - private int[] _decryptKeyExpansion; - - private readonly int _Nr; - private readonly int _Nk; - - private int[] _IV; - private int[] _lastBlockBuffer; - - public AesManagedTransform(ReadOnlySpan key, - ReadOnlySpan iv, - bool encrypting) - // AesManagedTransform doesn't use the base IV property, so just pass 'null'. - : base(iv: null, BlockSizeBytes, BlockSizeBytes) - { - Debug.Assert(BitConverter.IsLittleEndian, "The logic of casting Span to Span below assumes little endian"); - Debug.Assert(iv.Length == BlockSizeBytes); - - _encrypting = encrypting; - _Nr = GetNumberOfRounds(key); - _Nk = key.Length / 4; - - _IV = new int[BlockSizeInts]; - iv.CopyTo(MemoryMarshal.AsBytes(_IV.AsSpan())); - - GenerateKeyExpansion(key); - - _lastBlockBuffer = _IV.AsSpan().ToArray(); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - // We need to always zeroize the following fields because they contain sensitive data. - // Note: Can't use CryptographicOperations.ZeroMemory since these are int[] and not byte[]. - if (_IV != null) - { - Array.Clear(_IV); - _IV = null!; - } - if (_lastBlockBuffer != null) - { - Array.Clear(_lastBlockBuffer); - _lastBlockBuffer = null!; - } - if (_encryptKeyExpansion != null) - { - Array.Clear(_encryptKeyExpansion); - _encryptKeyExpansion = null!; - } - if (_decryptKeyExpansion != null) - { - Array.Clear(_decryptKeyExpansion); - _decryptKeyExpansion = null!; - } - } - - base.Dispose(disposing); - } - - public override int Transform(ReadOnlySpan input, Span output) - { - Debug.Assert(input.Length % BlockSizeBytes == 0); - Debug.Assert(output.Length >= input.Length); - - // the below algorithm doesn't allow overlap, so rent a buffer to transform into - if (input.Overlaps(output, out int offset) && offset != 0) - { - byte[] rented = CryptoPool.Rent(input.Length); - int bytesWritten = 0; - - try - { - bytesWritten = _encrypting ? - EncryptData(input, rented) : - DecryptData(input, rented); - rented.AsSpan(0, bytesWritten).CopyTo(output); - return bytesWritten; - } - finally - { - CryptoPool.Return(rented, clearSize: bytesWritten); - } - } - else - { - // with no overlap, we can just write directly to the output - return _encrypting ? - EncryptData(input, output) : - DecryptData(input, output); - } - } - - public override int TransformFinal(ReadOnlySpan input, Span output) - { - int bytesWritten = Transform(input, output); - Reset(); - return bytesWritten; - } - - // - // resets the state of the transform - // - - void ILiteSymmetricCipher.Reset(ReadOnlySpan iv) => throw new NotImplementedException(); // never invoked - - private void Reset() - { - _IV.AsSpan().CopyTo(_lastBlockBuffer); - } - - // - // Encrypts input into output using the AES encryption routine. - // This method writes the encrypted data into the output buffer. - // - private int EncryptData(ReadOnlySpan input, Span output) - { - int inputCount = input.Length; - - Span work = stackalloc int[BlockSizeInts]; - Span temp = stackalloc int[BlockSizeInts]; - - int workBaseIndex = 0; - int iNumBlocks = inputCount / BlockSizeBytes; - int transformCount = 0; - for (int blockNum = 0; blockNum < iNumBlocks; ++blockNum) - { - input.Slice(workBaseIndex, BlockSizeBytes).CopyTo(MemoryMarshal.AsBytes(work)); - - for (int i = 0; i < BlockSizeInts; ++i) - { - // XOR with the last encrypted block - work[i] ^= _lastBlockBuffer[i]; - } - - Enc(work, temp); - - for (int i = 0; i < BlockSizeInts; ++i) - { - output[transformCount++] = (byte)(temp[i] & 0xFF); - output[transformCount++] = (byte)(temp[i] >> 8 & 0xFF); - output[transformCount++] = (byte)(temp[i] >> 16 & 0xFF); - output[transformCount++] = (byte)(temp[i] >> 24 & 0xFF); - } - - Debug.Assert(_lastBlockBuffer.Length == BlockSizeInts); - temp.CopyTo(_lastBlockBuffer); - - workBaseIndex += BlockSizeBytes; - } - - return inputCount; - } - - // - // Decrypts intput into output using the AES encryption routine. - // This method writes the decrypted data into the output buffer. - // - private int DecryptData(ReadOnlySpan input, Span output) - { - int inputCount = input.Length; - - Span work = stackalloc int[BlockSizeInts]; - Span temp = stackalloc int[BlockSizeInts]; - - int iNumBlocks = inputCount / BlockSizeBytes; - int workBaseIndex = 0, index = 0, transformCount = 0; - for (int blockNum = 0; blockNum < iNumBlocks; ++blockNum) - { - index = workBaseIndex; - for (int i = 0; i < BlockSizeInts; ++i) - { - int i0 = input[index++]; - int i1 = input[index++]; - int i2 = input[index++]; - int i3 = input[index++]; - work[i] = i3 << 24 | i2 << 16 | i1 << 8 | i0; - } - - Dec(work, temp); - - index = workBaseIndex; - for (int i = 0; i < BlockSizeInts; ++i) - { - temp[i] ^= _lastBlockBuffer[i]; - // save the input buffer - int i0 = input[index++]; - int i1 = input[index++]; - int i2 = input[index++]; - int i3 = input[index++]; - _lastBlockBuffer[i] = i3 << 24 | i2 << 16 | i1 << 8 | i0; - } - - for (int i = 0; i < BlockSizeInts; ++i) - { - output[transformCount++] = (byte)(temp[i] & 0xFF); - output[transformCount++] = (byte)(temp[i] >> 8 & 0xFF); - output[transformCount++] = (byte)(temp[i] >> 16 & 0xFF); - output[transformCount++] = (byte)(temp[i] >> 24 & 0xFF); - } - - workBaseIndex += BlockSizeBytes; - } - - return inputCount; - } - - // - // AES encryption function. - // - private void Enc(Span work, Span temp) - { - for (int i = 0; i < BlockSizeInts; ++i) - { - work[i] ^= _encryptKeyExpansion[i]; - } - - ReadOnlySpan T = s_T; - ReadOnlySpan encryptindex = s_encryptindex; - int encryptindexIndex; - int encryptKeyExpansionIndex = BlockSizeInts; - for (int r = 1; r < _Nr; ++r) - { - encryptindexIndex = 0; - for (int i = 0; i < BlockSizeInts; ++i) - { - temp[i] = T[0 + (work[i] & 0xFF)] ^ - T[256 + ((work[encryptindex[encryptindexIndex]] >> 8) & 0xFF)] ^ - T[512 + ((work[encryptindex[encryptindexIndex + BlockSizeInts]] >> 16) & 0xFF)] ^ - T[768 + ((work[encryptindex[encryptindexIndex + (BlockSizeInts * 2)]] >> 24) & 0xFF)] ^ - _encryptKeyExpansion[encryptKeyExpansionIndex]; - encryptindexIndex++; - encryptKeyExpansionIndex++; - } - - temp.CopyTo(work); - } - - ReadOnlySpan TF = s_TF; - encryptindexIndex = 0; - for (int i = 0; i < BlockSizeInts; ++i) - { - temp[i] = TF[0 + (work[i] & 0xFF)] ^ - TF[256 + ((work[encryptindex[encryptindexIndex]] >> 8) & 0xFF)] ^ - TF[512 + ((work[encryptindex[encryptindexIndex + BlockSizeInts]] >> 16) & 0xFF)] ^ - TF[768 + ((work[encryptindex[encryptindexIndex + (BlockSizeInts * 2)]] >> 24) & 0xFF)] ^ - _encryptKeyExpansion[encryptKeyExpansionIndex]; - encryptindexIndex++; - encryptKeyExpansionIndex++; - } - } - - // - // AES decryption function. - // - - private void Dec(Span work, Span temp) - { - int keyIndex = BlockSizeInts * _Nr; - for (int i = 0; i < BlockSizeInts; ++i) - { - work[i] ^= _decryptKeyExpansion[keyIndex]; - keyIndex++; - } - - ReadOnlySpan iT = s_iT; - ReadOnlySpan decryptindex = s_decryptindex; - int decryptindexIndex; - int decryptKeyExpansionIndex; - for (int r = 1; r < _Nr; ++r) - { - keyIndex -= 2 * BlockSizeInts; - decryptindexIndex = 0; - decryptKeyExpansionIndex = keyIndex; - for (int i = 0; i < BlockSizeInts; ++i) - { - temp[i] = iT[0 + ((work[i]) & 0xFF)] ^ - iT[256 + ((work[decryptindex[decryptindexIndex]] >> 8) & 0xFF)] ^ - iT[512 + ((work[decryptindex[decryptindexIndex + BlockSizeInts]] >> 16) & 0xFF)] ^ - iT[768 + ((work[decryptindex[decryptindexIndex + (BlockSizeInts * 2)]] >> 24) & 0xFF)] ^ - _decryptKeyExpansion[decryptKeyExpansionIndex]; - keyIndex++; - decryptindexIndex++; - decryptKeyExpansionIndex++; - } - - temp.CopyTo(work); - } - - ReadOnlySpan iTF = s_iTF; - keyIndex = 0; - decryptindexIndex = 0; - decryptKeyExpansionIndex = keyIndex; - for (int i = 0; i < BlockSizeInts; ++i) - { - temp[i] = iTF[0 + ((work[i]) & 0xFF)] ^ - iTF[256 + ((work[decryptindex[decryptindexIndex]] >> 8) & 0xFF)] ^ - iTF[512 + ((work[decryptindex[decryptindexIndex + BlockSizeInts]] >> 16) & 0xFF)] ^ - iTF[768 + ((work[decryptindex[decryptindexIndex + (BlockSizeInts * 2)]] >> 24) & 0xFF)] ^ - _decryptKeyExpansion[decryptKeyExpansionIndex]; - decryptindexIndex++; - decryptKeyExpansionIndex++; - } - } - - private static int GetNumberOfRounds(ReadOnlySpan key) - { - return (BlockSizeBytes > key.Length ? BlockSizeBytes : key.Length) switch - { - 16 => 10, // 128 bits - // 24 => 12, // 192 bits is not supported by SubtleCrypto, so the managed implementation doesn't support it either - 32 => 14, // 256 bits - _ => throw new CryptographicException(SR.Cryptography_InvalidKeySize) - }; - } - - // - // Key expansion routine. - // - - [MemberNotNull(nameof(_encryptKeyExpansion))] - [MemberNotNull(nameof(_decryptKeyExpansion))] - private void GenerateKeyExpansion(ReadOnlySpan key) - { - _encryptKeyExpansion = new int[BlockSizeInts * (_Nr + 1)]; - _decryptKeyExpansion = new int[BlockSizeInts * (_Nr + 1)]; - int iTemp; - - int index = 0; - for (int i = 0; i < _Nk; ++i) - { - int i0 = key[index++]; - int i1 = key[index++]; - int i2 = key[index++]; - int i3 = key[index++]; - _encryptKeyExpansion[i] = i3 << 24 | i2 << 16 | i1 << 8 | i0; - } - - if (_Nk <= 6) - { - for (int i = _Nk; i < BlockSizeInts * (_Nr + 1); ++i) - { - iTemp = _encryptKeyExpansion[i - 1]; - - if (i % _Nk == 0) - { - iTemp = SubWord(rot3(iTemp)); - iTemp ^= s_Rcon[(i / _Nk) - 1]; - } - - _encryptKeyExpansion[i] = _encryptKeyExpansion[i - _Nk] ^ iTemp; - } - } - else - { - for (int i = _Nk; i < BlockSizeInts * (_Nr + 1); ++i) - { - iTemp = _encryptKeyExpansion[i - 1]; - - if (i % _Nk == 0) - { - iTemp = SubWord(rot3(iTemp)); - iTemp ^= s_Rcon[(i / _Nk) - 1]; - } - else if (i % _Nk == 4) - { - iTemp = SubWord(iTemp); - } - - _encryptKeyExpansion[i] = _encryptKeyExpansion[i - _Nk] ^ iTemp; - } - } - - for (int i = 0; i < BlockSizeInts; ++i) - { - _decryptKeyExpansion[i] = _encryptKeyExpansion[i]; - _decryptKeyExpansion[BlockSizeInts * _Nr + i] = _encryptKeyExpansion[BlockSizeInts * _Nr + i]; - } - - for (int i = BlockSizeInts; i < BlockSizeInts * _Nr; ++i) - { - int keyVal = _encryptKeyExpansion[i]; - int mul02 = MulX(keyVal); - int mul04 = MulX(mul02); - int mul08 = MulX(mul04); - int mul09 = keyVal ^ mul08; - _decryptKeyExpansion[i] = mul02 ^ mul04 ^ mul08 ^ rot3(mul02 ^ mul09) ^ rot2(mul04 ^ mul09) ^ rot1(mul09); - } - } - - private static int rot1(int val) => int.RotateLeft(val, 8); - private static int rot2(int val) => int.RotateLeft(val, 16); - private static int rot3(int val) => int.RotateLeft(val, 24); - - private static int SubWord(int a) - { - ReadOnlySpan sbox = Sbox; - return sbox[a & 0xFF] | - sbox[a >> 8 & 0xFF] << 8 | - sbox[a >> 16 & 0xFF] << 16 | - sbox[a >> 24 & 0xFF] << 24; - } - - private static int MulX(int x) - { - int u = x & unchecked((int)0x80808080); - return ((x & unchecked((int)0x7f7f7f7f)) << 1) ^ ((u - (u >> 7 & 0x01FFFFFF)) & 0x1b1b1b1b); - } - - private static ReadOnlySpan Sbox => new byte[] { - 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, - 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, - 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21, - 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, - 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, - 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, - 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, - 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, - 205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, - 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, - 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, - 231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, - 186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, - 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, - 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, - 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22 }; - - // Precompute the modulus operations: these are performance killers when called frequently - private static readonly int[] s_encryptindex = new int[BlockSizeInts * 3] { - 1, 2, 3, 0, - 2, 3, 0, 1, - 3, 0, 1, 2, - }; - - private static readonly int[] s_decryptindex = new int[BlockSizeInts * 3] { - 3, 0, 1, 2, - 2, 3, 0, 1, - 1, 2, 3, 0, - }; - - private static readonly int[] s_Rcon = new int[] { - 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, - 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, - 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 }; - - private static readonly int[] s_T = new int[4 * 256] - { - // s_T1 - -1520213050, -2072216328, -1720223762, -1921287178, 234025727, -1117033514, -1318096930, 1422247313, - 1345335392, 50397442, -1452841010, 2099981142, 436141799, 1658312629, -424957107, -1703512340, - 1170918031, -1652391393, 1086966153, -2021818886, 368769775, -346465870, -918075506, 200339707, - -324162239, 1742001331, -39673249, -357585083, -1080255453, -140204973, -1770884380, 1539358875, - -1028147339, 486407649, -1366060227, 1780885068, 1513502316, 1094664062, 49805301, 1338821763, - 1546925160, -190470831, 887481809, 150073849, -1821281822, 1943591083, 1395732834, 1058346282, - 201589768, 1388824469, 1696801606, 1589887901, 672667696, -1583966665, 251987210, -1248159185, - 151455502, 907153956, -1686077413, 1038279391, 652995533, 1764173646, -843926913, -1619692054, - 453576978, -1635548387, 1949051992, 773462580, 756751158, -1301385508, -296068428, -73359269, - -162377052, 1295727478, 1641469623, -827083907, 2066295122, 1055122397, 1898917726, -1752923117, - -179088474, 1758581177, 0, 753790401, 1612718144, 536673507, -927878791, -312779850, - -1100322092, 1187761037, -641810841, 1262041458, -565556588, -733197160, -396863312, 1255133061, - 1808847035, 720367557, -441800113, 385612781, -985447546, -682799718, 1429418854, -1803188975, - -817543798, 284817897, 100794884, -2122350594, -263171936, 1144798328, -1163944155, -475486133, - -212774494, -22830243, -1069531008, -1970303227, -1382903233, -1130521311, 1211644016, 83228145, - -541279133, -1044990345, 1977277103, 1663115586, 806359072, 452984805, 250868733, 1842533055, - 1288555905, 336333848, 890442534, 804056259, -513843266, -1567123659, -867941240, 957814574, - 1472513171, -223893675, -2105639172, 1195195770, -1402706744, -413311558, 723065138, -1787595802, - -1604296512, -1736343271, -783331426, 2145180835, 1713513028, 2116692564, -1416589253, -2088204277, - -901364084, 703524551, -742868885, 1007948840, 2044649127, -497131844, 487262998, 1994120109, - 1004593371, 1446130276, 1312438900, 503974420, -615954030, 168166924, 1814307912, -463709000, - 1573044895, 1859376061, -273896381, -1503501628, -1466855111, -1533700815, 937747667, -1954973198, - 854058965, 1137232011, 1496790894, -1217565222, -1936880383, 1691735473, -766620004, -525751991, - -1267962664, -95005012, 133494003, 636152527, -1352309302, -1904575756, -374428089, 403179536, - -709182865, -2005370640, 1864705354, 1915629148, 605822008, -240736681, -944458637, 1371981463, - 602466507, 2094914977, -1670089496, 555687742, -582268010, -591544991, -2037675251, -2054518257, - -1871679264, 1111375484, -994724495, -1436129588, -666351472, 84083462, 32962295, 302911004, - -1553899070, 1597322602, -111716434, -793134743, -1853454825, 1489093017, 656219450, -1180787161, - 954327513, 335083755, -1281845205, 856756514, -1150719534, 1893325225, -1987146233, -1483434957, - -1231316179, 572399164, -1836611819, 552200649, 1238290055, -11184726, 2015897680, 2061492133, - -1886614525, -123625127, -2138470135, 386731290, -624967835, 837215959, -968736124, -1201116976, - -1019133566, -1332111063, 1999449434, 286199582, -877612933, -61582168, -692339859, 974525996, - - // s_T2 - 1667483301, 2088564868, 2004348569, 2071721613, -218956019, 1802229437, 1869602481, -976907948, - 808476752, 16843267, 1734856361, 724260477, -16849127, -673729182, -1414836762, 1987505306, - -892694715, -2105401443, -909539008, 2105408135, -84218091, 1499050731, 1195871945, -252642549, - -1381154324, -724257945, -1566416899, -1347467798, -1667488833, -1532734473, 1920132246, -1061119141, - -1212713534, -33693412, -1819066962, 640044138, 909536346, 1061125697, -134744830, -859012273, - 875849820, -1515892236, -437923532, -235800312, 1903288979, -656888973, 825320019, 353708607, - 67373068, -943221422, 589514341, -1010590370, 404238376, -1768540255, 84216335, -1701171275, - 117902857, 303178806, -2139087973, -488448195, -336868058, 656887401, -1296924723, 1970662047, - 151589403, -2088559202, 741103732, 437924910, 454768173, 1852759218, 1515893998, -1600103429, - 1381147894, 993752653, -690571423, -1280082482, 690573947, -471605954, 791633521, -2071719017, - 1397991157, -774784664, 0, -303185620, 538984544, -50535649, -1313769016, 1532737261, - 1785386174, -875852474, -1094817831, 960066123, 1246401758, 1280088276, 1482207464, -808483510, - -791626901, -269499094, -1431679003, -67375850, 1128498885, 1296931543, 859006549, -2054876780, - 1162185423, -101062384, 33686534, 2139094657, 1347461360, 1010595908, -1616960070, -1465365533, - 1364304627, -1549574658, 1077969088, -1886452342, -1835909203, -1650646596, 943222856, -168431356, - -1128504353, -1229555775, -623202443, 555827811, 269492272, -6886, -202113778, -757940371, - -842170036, 202119188, 320022069, -320027857, 1600110305, -1751698014, 1145342156, 387395129, - -993750185, -1482205710, 2122251394, 1027439175, 1684326572, 1566423783, 421081643, 1936975509, - 1616953504, -2122245736, 1330618065, -589520001, 572671078, 707417214, -1869595733, -2004350077, - 1179028682, -286341335, -1195873325, 336865340, -555833479, 1583267042, 185275933, -606360202, - -522134725, 842163286, 976909390, 168432670, 1229558491, 101059594, 606357612, 1549580516, - -1027432611, -741098130, -1397996561, 1650640038, -1852753496, -1785384540, -454765769, 2038035083, - -404237006, -926381245, 926379609, 1835915959, -1920138868, -707415708, 1313774802, -1448523296, - 1819072692, 1448520954, -185273593, -353710299, 1701169839, 2054878350, -1364310039, 134746136, - -1162186795, 2021191816, 623200879, 774790258, 471611428, -1499047951, -1263242297, -960063663, - -387396829, -572677764, 1953818780, 522141217, 1263245021, -1111662116, -1953821306, -1970663547, - 1886445712, 1044282434, -1246400060, 1718013098, 1212715224, 50529797, -151587071, 235805714, - 1633796771, 892693087, 1465364217, -1179031088, -2038032495, -1044276904, 488454695, -1633802311, - -505292488, -117904621, -1734857805, 286335539, 1768542907, -640046736, -1903294583, -1802226777, - -1684329034, 505297954, -2021190254, -370554592, -825325751, 1431677695, 673730680, -538991238, - -1936981105, -1583261192, -1987507840, 218962455, -1077975590, -421079247, 1111655622, 1751699640, - 1094812355, -1718015568, 757946999, 252648977, -1330611253, 1414834428, -1145344554, 370551866, - - // s_T3 - 1673962851, 2096661628, 2012125559, 2079755643, -218165774, 1809235307, 1876865391, -980331323, - 811618352, 16909057, 1741597031, 727088427, -18408962, -675978537, -1420958037, 1995217526, - -896580150, -2111857278, -913751863, 2113570685, -84994566, 1504897881, 1200539975, -251982864, - -1388188499, -726439980, -1570767454, -1354372433, -1675378788, -1538000988, 1927583346, -1063560256, - -1217019209, -35578627, -1824674157, 642542118, 913070646, 1065238847, -134937865, -863809588, - 879254580, -1521355611, -439274267, -235337487, 1910674289, -659852328, 828527409, 355090197, - 67636228, -946515257, 591815971, -1013096765, 405809176, -1774739050, 84545285, -1708149350, - 118360327, 304363026, -2145674368, -488686110, -338876693, 659450151, -1300247118, 1978310517, - 152181513, -2095210877, 743994412, 439627290, 456535323, 1859957358, 1521806938, -1604584544, - 1386542674, 997608763, -692624938, -1283600717, 693271337, -472039709, 794718511, -2079090812, - 1403450707, -776378159, 0, -306107155, 541089824, -52224004, -1317418831, 1538714971, - 1792327274, -879933749, -1100490306, 963791673, 1251270218, 1285084236, 1487988824, -813348145, - -793023536, -272291089, -1437604438, -68348165, 1132905795, 1301993293, 862344499, -2062445435, - 1166724933, -102166279, 33818114, 2147385727, 1352724560, 1014514748, -1624917345, -1471421528, - 1369633617, -1554121053, 1082179648, -1895462257, -1841320558, -1658733411, 946882616, -168753931, - -1134305348, -1233665610, -626035238, 557998881, 270544912, -1762561, -201519373, -759206446, - -847164211, 202904588, 321271059, -322752532, 1606345055, -1758092649, 1149815876, 388905239, - -996976700, -1487539545, 2130477694, 1031423805, 1690872932, 1572530013, 422718233, 1944491379, - 1623236704, -2129028991, 1335808335, -593264676, 574907938, 710180394, -1875137648, -2012511352, - 1183631942, -288937490, -1200893000, 338181140, -559449634, 1589437022, 185998603, -609388837, - -522503200, 845436466, 980700730, 169090570, 1234361161, 101452294, 608726052, 1555620956, - -1029743166, -742560045, -1404833876, 1657054818, -1858492271, -1791908715, -455919644, 2045938553, - -405458201, -930397240, 929978679, 1843050349, -1929278323, -709794603, 1318900302, -1454776151, - 1826141292, 1454176854, -185399308, -355523094, 1707781989, 2062847610, -1371018834, 135272456, - -1167075910, 2029029496, 625635109, 777810478, 473441308, -1504185946, -1267480652, -963161658, - -389340184, -576619299, 1961401460, 524165407, 1268178251, -1117659971, -1962047861, -1978694262, - 1893765232, 1048330814, -1250835275, 1724688998, 1217452104, 50726147, -151584266, 236720654, - 1640145761, 896163637, 1471084887, -1184247623, -2045275770, -1046914879, 490350365, -1641563746, - -505857823, -118811656, -1741966440, 287453969, 1775418217, -643206951, -1912108658, -1808554092, - -1691502949, 507257374, -2028629369, -372694807, -829994546, 1437269845, 676362280, -542803233, - -1945923700, -1587939167, -1995865975, 219813645, -1083843905, -422104602, 1115997762, 1758509160, - 1099088705, -1725321063, 760903469, 253628687, -1334064208, 1420360788, -1150429509, 371997206, - - // s_T4 - -962239645, -125535108, -291932297, -158499973, -15863054, -692229269, -558796945, -1856715323, - 1615867952, 33751297, -827758745, 1451043627, -417726722, -1251813417, 1306962859, -325421450, - -1891251510, 530416258, -1992242743, -91783811, -283772166, -1293199015, -1899411641, -83103504, - 1106029997, -1285040940, 1610457762, 1173008303, 599760028, 1408738468, -459902350, -1688485696, - 1975695287, -518193667, 1034851219, 1282024998, 1817851446, 2118205247, -184354825, -2091922228, - 1750873140, 1374987685, -785062427, -116854287, -493653647, -1418471208, 1649619249, 708777237, - 135005188, -1789737017, 1181033251, -1654733885, 807933976, 933336726, 168756485, 800430746, - 235472647, 607523346, 463175808, -549592350, -853087253, 1315514151, 2144187058, -358648459, - 303761673, 496927619, 1484008492, 875436570, 908925723, -592286098, -1259447718, 1543217312, - -1527360942, 1984772923, -1218324778, 2110698419, 1383803177, -583080989, 1584475951, 328696964, - -1493871789, -1184312879, 0, -1054020115, 1080041504, -484442884, 2043195825, -1225958565, - -725718422, -1924740149, 1742323390, 1917532473, -1797371318, -1730917300, -1326950312, -2058694705, - -1150562096, -987041809, 1340451498, -317260805, -2033892541, -1697166003, 1716859699, 294946181, - -1966127803, -384763399, 67502594, -25067649, -1594863536, 2017737788, 632987551, 1273211048, - -1561112239, 1576969123, -2134884288, 92966799, 1068339858, 566009245, 1883781176, -251333131, - 1675607228, 2009183926, -1351230758, 1113792801, 540020752, -451215361, -49351693, -1083321646, - -2125673011, 403966988, 641012499, -1020269332, -1092526241, 899848087, -1999879100, 775493399, - -1822964540, 1441965991, -58556802, 2051489085, -928226204, -1159242403, 841685273, -426413197, - -1063231392, 429425025, -1630449841, -1551901476, 1147544098, 1417554474, 1001099408, 193169544, - -1932900794, -953553170, 1809037496, 675025940, -1485185314, -1126015394, 371002123, -1384719397, - -616832800, 1683370546, 1951283770, 337512970, -1831122615, 201983494, 1215046692, -1192993700, - -1621245246, -1116810285, 1139780780, -995728798, 967348625, 832869781, -751311644, -225740423, - -718084121, -1958491960, 1851340599, -625513107, 25988493, -1318791723, -1663938994, 1239460265, - -659264404, -1392880042, -217582348, -819598614, -894474907, -191989126, 1206496942, 270010376, - 1876277946, -259491720, 1248797989, 1550986798, 941890588, 1475454630, 1942467764, -1756248378, - -886839064, -1585652259, -392399756, 1042358047, -1763882165, 1641856445, 226921355, 260409994, - -527404944, 2084716094, 1908716981, -861247898, -1864873912, 100991747, -150866186, 470945294, - -1029480095, 1784624437, -1359390889, 1775286713, 395413126, -1722236479, 975641885, 666476190, - -650583583, -351012616, 733190296, 573772049, -759469719, -1452221991, 126455438, 866620564, - 766942107, 1008868894, 361924487, -920589847, -2025206066, -1426107051, 1350051880, -1518673953, - 59739276, 1509466529, 159418761, 437718285, 1708834751, -684595482, -2067381694, -793221016, - -2101132991, 699439513, 1517759789, 504434447, 2076946608, -1459858348, 1842789307, 742004246 }; - - private static readonly int[] s_TF = new int[4 * 256] - { - // s_TF1 - 99, 124, 119, 123, 242, 107, 111, 197, - 48, 1, 103, 43, 254, 215, 171, 118, - 202, 130, 201, 125, 250, 89, 71, 240, - 173, 212, 162, 175, 156, 164, 114, 192, - 183, 253, 147, 38, 54, 63, 247, 204, - 52, 165, 229, 241, 113, 216, 49, 21, - 4, 199, 35, 195, 24, 150, 5, 154, - 7, 18, 128, 226, 235, 39, 178, 117, - 9, 131, 44, 26, 27, 110, 90, 160, - 82, 59, 214, 179, 41, 227, 47, 132, - 83, 209, 0, 237, 32, 252, 177, 91, - 106, 203, 190, 57, 74, 76, 88, 207, - 208, 239, 170, 251, 67, 77, 51, 133, - 69, 249, 2, 127, 80, 60, 159, 168, - 81, 163, 64, 143, 146, 157, 56, 245, - 188, 182, 218, 33, 16, 255, 243, 210, - 205, 12, 19, 236, 95, 151, 68, 23, - 196, 167, 126, 61, 100, 93, 25, 115, - 96, 129, 79, 220, 34, 42, 144, 136, - 70, 238, 184, 20, 222, 94, 11, 219, - 224, 50, 58, 10, 73, 6, 36, 92, - 194, 211, 172, 98, 145, 149, 228, 121, - 231, 200, 55, 109, 141, 213, 78, 169, - 108, 86, 244, 234, 101, 122, 174, 8, - 186, 120, 37, 46, 28, 166, 180, 198, - 232, 221, 116, 31, 75, 189, 139, 138, - 112, 62, 181, 102, 72, 3, 246, 14, - 97, 53, 87, 185, 134, 193, 29, 158, - 225, 248, 152, 17, 105, 217, 142, 148, - 155, 30, 135, 233, 206, 85, 40, 223, - 140, 161, 137, 13, 191, 230, 66, 104, - 65, 153, 45, 15, 176, 84, 187, 22, - - // s_TF2 - 25344, 31744, 30464, 31488, 61952, 27392, 28416, 50432, - 12288, 256, 26368, 11008, 65024, 55040, 43776, 30208, - 51712, 33280, 51456, 32000, 64000, 22784, 18176, 61440, - 44288, 54272, 41472, 44800, 39936, 41984, 29184, 49152, - 46848, 64768, 37632, 9728, 13824, 16128, 63232, 52224, - 13312, 42240, 58624, 61696, 28928, 55296, 12544, 5376, - 1024, 50944, 8960, 49920, 6144, 38400, 1280, 39424, - 1792, 4608, 32768, 57856, 60160, 9984, 45568, 29952, - 2304, 33536, 11264, 6656, 6912, 28160, 23040, 40960, - 20992, 15104, 54784, 45824, 10496, 58112, 12032, 33792, - 21248, 53504, 0, 60672, 8192, 64512, 45312, 23296, - 27136, 51968, 48640, 14592, 18944, 19456, 22528, 52992, - 53248, 61184, 43520, 64256, 17152, 19712, 13056, 34048, - 17664, 63744, 512, 32512, 20480, 15360, 40704, 43008, - 20736, 41728, 16384, 36608, 37376, 40192, 14336, 62720, - 48128, 46592, 55808, 8448, 4096, 65280, 62208, 53760, - 52480, 3072, 4864, 60416, 24320, 38656, 17408, 5888, - 50176, 42752, 32256, 15616, 25600, 23808, 6400, 29440, - 24576, 33024, 20224, 56320, 8704, 10752, 36864, 34816, - 17920, 60928, 47104, 5120, 56832, 24064, 2816, 56064, - 57344, 12800, 14848, 2560, 18688, 1536, 9216, 23552, - 49664, 54016, 44032, 25088, 37120, 38144, 58368, 30976, - 59136, 51200, 14080, 27904, 36096, 54528, 19968, 43264, - 27648, 22016, 62464, 59904, 25856, 31232, 44544, 2048, - 47616, 30720, 9472, 11776, 7168, 42496, 46080, 50688, - 59392, 56576, 29696, 7936, 19200, 48384, 35584, 35328, - 28672, 15872, 46336, 26112, 18432, 768, 62976, 3584, - 24832, 13568, 22272, 47360, 34304, 49408, 7424, 40448, - 57600, 63488, 38912, 4352, 26880, 55552, 36352, 37888, - 39680, 7680, 34560, 59648, 52736, 21760, 10240, 57088, - 35840, 41216, 35072, 3328, 48896, 58880, 16896, 26624, - 16640, 39168, 11520, 3840, 45056, 21504, 47872, 5632, - - // s_TF3 - 6488064, 8126464, 7798784, 8060928, 15859712, 7012352, 7274496, 12910592, - 3145728, 65536, 6750208, 2818048, 16646144, 14090240, 11206656, 7733248, - 13238272, 8519680, 13172736, 8192000, 16384000, 5832704, 4653056, 15728640, - 11337728, 13893632, 10616832, 11468800, 10223616, 10747904, 7471104, 12582912, - 11993088, 16580608, 9633792, 2490368, 3538944, 4128768, 16187392, 13369344, - 3407872, 10813440, 15007744, 15794176, 7405568, 14155776, 3211264, 1376256, - 262144, 13041664, 2293760, 12779520, 1572864, 9830400, 327680, 10092544, - 458752, 1179648, 8388608, 14811136, 15400960, 2555904, 11665408, 7667712, - 589824, 8585216, 2883584, 1703936, 1769472, 7208960, 5898240, 10485760, - 5373952, 3866624, 14024704, 11730944, 2686976, 14876672, 3080192, 8650752, - 5439488, 13697024, 0, 15532032, 2097152, 16515072, 11599872, 5963776, - 6946816, 13303808, 12451840, 3735552, 4849664, 4980736, 5767168, 13565952, - 13631488, 15663104, 11141120, 16449536, 4390912, 5046272, 3342336, 8716288, - 4521984, 16318464, 131072, 8323072, 5242880, 3932160, 10420224, 11010048, - 5308416, 10682368, 4194304, 9371648, 9568256, 10289152, 3670016, 16056320, - 12320768, 11927552, 14286848, 2162688, 1048576, 16711680, 15925248, 13762560, - 13434880, 786432, 1245184, 15466496, 6225920, 9895936, 4456448, 1507328, - 12845056, 10944512, 8257536, 3997696, 6553600, 6094848, 1638400, 7536640, - 6291456, 8454144, 5177344, 14417920, 2228224, 2752512, 9437184, 8912896, - 4587520, 15597568, 12058624, 1310720, 14548992, 6160384, 720896, 14352384, - 14680064, 3276800, 3801088, 655360, 4784128, 393216, 2359296, 6029312, - 12713984, 13828096, 11272192, 6422528, 9502720, 9764864, 14942208, 7929856, - 15138816, 13107200, 3604480, 7143424, 9240576, 13959168, 5111808, 11075584, - 7077888, 5636096, 15990784, 15335424, 6619136, 7995392, 11403264, 524288, - 12189696, 7864320, 2424832, 3014656, 1835008, 10878976, 11796480, 12976128, - 15204352, 14483456, 7602176, 2031616, 4915200, 12386304, 9109504, 9043968, - 7340032, 4063232, 11862016, 6684672, 4718592, 196608, 16121856, 917504, - 6356992, 3473408, 5701632, 12124160, 8781824, 12648448, 1900544, 10354688, - 14745600, 16252928, 9961472, 1114112, 6881280, 14221312, 9306112, 9699328, - 10158080, 1966080, 8847360, 15269888, 13500416, 5570560, 2621440, 14614528, - 9175040, 10551296, 8978432, 851968, 12517376, 15073280, 4325376, 6815744, - 4259840, 10027008, 2949120, 983040, 11534336, 5505024, 12255232, 1441792, - - // s_TF4 - 1660944384, 2080374784, 1996488704, 2063597568, -234881024, 1795162112, 1862270976, -989855744, - 805306368, 16777216, 1728053248, 721420288, -33554432, -687865856, -1426063360, 1979711488, - -905969664, -2113929216, -922746880, 2097152000, -100663296, 1493172224, 1191182336, -268435456, - -1392508928, -738197504, -1577058304, -1358954496, -1677721600, -1543503872, 1912602624, -1073741824, - -1224736768, -50331648, -1828716544, 637534208, 905969664, 1056964608, -150994944, -872415232, - 872415232, -1526726656, -452984832, -251658240, 1895825408, -671088640, 822083584, 352321536, - 67108864, -956301312, 587202560, -1023410176, 402653184, -1778384896, 83886080, -1711276032, - 117440512, 301989888, -2147483648, -503316480, -352321536, 654311424, -1308622848, 1962934272, - 150994944, -2097152000, 738197504, 436207616, 452984832, 1845493760, 1509949440, -1610612736, - 1375731712, 989855744, -704643072, -1291845632, 687865856, -486539264, 788529152, -2080374784, - 1392508928, -788529152, 0, -318767104, 536870912, -67108864, -1325400064, 1526726656, - 1778384896, -889192448, -1107296256, 956301312, 1241513984, 1275068416, 1476395008, -822083584, - -805306368, -285212672, -1442840576, -83886080, 1124073472, 1291845632, 855638016, -2063597568, - 1157627904, -117440512, 33554432, 2130706432, 1342177280, 1006632960, -1627389952, -1476395008, - 1358954496, -1560281088, 1073741824, -1895825408, -1845493760, -1660944384, 939524096, -184549376, - -1140850688, -1241513984, -637534208, 553648128, 268435456, -16777216, -218103808, -771751936, - -855638016, 201326592, 318767104, -335544320, 1593835520, -1761607680, 1140850688, 385875968, - -1006632960, -1493172224, 2113929216, 1023410176, 1677721600, 1560281088, 419430400, 1929379840, - 1610612736, -2130706432, 1325400064, -603979776, 570425344, 704643072, -1879048192, -2013265920, - 1174405120, -301989888, -1207959552, 335544320, -570425344, 1577058304, 184549376, -620756992, - -536870912, 838860800, 973078528, 167772160, 1224736768, 100663296, 603979776, 1543503872, - -1040187392, -754974720, -1409286144, 1644167168, -1862270976, -1795162112, -469762048, 2030043136, - -419430400, -939524096, 922746880, 1828716544, -1929379840, -721420288, 1308622848, -1459617792, - 1811939328, 1442840576, -201326592, -369098752, 1694498816, 2046820352, -1375731712, 134217728, - -1174405120, 2013265920, 620756992, 771751936, 469762048, -1509949440, -1275068416, -973078528, - -402653184, -587202560, 1946157056, 520093696, 1258291200, -1124073472, -1962934272, -1979711488, - 1879048192, 1040187392, -1258291200, 1711276032, 1207959552, 50331648, -167772160, 234881024, - 1627389952, 889192448, 1459617792, -1191182336, -2046820352, -1056964608, 486539264, -1644167168, - -520093696, -134217728, -1744830464, 285212672, 1761607680, -654311424, -1912602624, -1811939328, - -1694498816, 503316480, -2030043136, -385875968, -838860800, 1426063360, 671088640, -553648128, - -1946157056, -1593835520, -1996488704, 218103808, -1090519040, -436207616, 1107296256, 1744830464, - 1090519040, -1728053248, 754974720, 251658240, -1342177280, 1409286144, -1157627904, 369098752 }; - - private static readonly int[] s_iT = new int[4 * 256] - { - // s_iT1 - 1353184337, 1399144830, -1012656358, -1772214470, -882136261, -247096033, -1420232020, -1828461749, - 1442459680, -160598355, -1854485368, 625738485, -52959921, -674551099, -2143013594, -1885117771, - 1230680542, 1729870373, -1743852987, -507445667, 41234371, 317738113, -1550367091, -956705941, - -413167869, -1784901099, -344298049, -631680363, 763608788, -752782248, 694804553, 1154009486, - 1787413109, 2021232372, 1799248025, -579749593, -1236278850, 397248752, 1722556617, -1271214467, - 407560035, -2110711067, 1613975959, 1165972322, -529046351, -2068943941, 480281086, -1809118983, - 1483229296, 436028815, -2022908268, -1208452270, 601060267, -503166094, 1468997603, 715871590, - 120122290, 63092015, -1703164538, -1526188077, -226023376, -1297760477, -1167457534, 1552029421, - 723308426, -1833666137, -252573709, -1578997426, -839591323, -708967162, 526529745, -1963022652, - -1655493068, -1604979806, 853641733, 1978398372, 971801355, -1427152832, 111112542, 1360031421, - -108388034, 1023860118, -1375387939, 1186850381, -1249028975, 90031217, 1876166148, -15380384, - 620468249, -1746289194, -868007799, 2006899047, -1119688528, -2004121337, 945494503, -605108103, - 1191869601, -384875908, -920746760, 0, -2088337399, 1223502642, -1401941730, 1316117100, - -67170563, 1446544655, 517320253, 658058550, 1691946762, 564550760, -783000677, 976107044, - -1318647284, 266819475, -761860428, -1634624741, 1338359936, -1574904735, 1766553434, 370807324, - 179999714, -450191168, 1138762300, 488053522, 185403662, -1379431438, -1180125651, -928440812, - -2061897385, 1275557295, -1143105042, -44007517, -1624899081, -1124765092, -985962940, 880737115, - 1982415755, -590994485, 1761406390, 1676797112, -891538985, 277177154, 1076008723, 538035844, - 2099530373, -130171950, 288553390, 1839278535, 1261411869, -214912292, -330136051, -790380169, - 1813426987, -1715900247, -95906799, 577038663, -997393240, 440397984, -668172970, -275762398, - -951170681, -1043253031, -22885748, 906744984, -813566554, 685669029, 646887386, -1530942145, - -459458004, 227702864, -1681105046, 1648787028, -1038905866, -390539120, 1593260334, -173030526, - -1098883681, 2090061929, -1456614033, -1290656305, 999926984, -1484974064, 1852021992, 2075868123, - 158869197, -199730834, 28809964, -1466282109, 1701746150, 2129067946, 147831841, -420997649, - -644094022, -835293366, -737566742, -696471511, -1347247055, 824393514, 815048134, -1067015627, - 935087732, -1496677636, -1328508704, 366520115, 1251476721, -136647615, 240176511, 804688151, - -1915335306, 1303441219, 1414376140, -553347356, -474623586, 461924940, -1205916479, 2136040774, - 82468509, 1563790337, 1937016826, 776014843, 1511876531, 1389550482, 861278441, 323475053, - -1939744870, 2047648055, -1911228327, -1992551445, -299390514, 902390199, -303751967, 1018251130, - 1507840668, 1064563285, 2043548696, -1086863501, -355600557, 1537932639, 342834655, -2032450440, - -2114736182, 1053059257, 741614648, 1598071746, 1925389590, 203809468, -1958134744, 1100287487, - 1895934009, -558691320, -1662733096, -1866377628, 1636092795, 1890988757, 1952214088, 1113045200, - - // s_iT2 - -1477160624, 1698790995, -1541989693, 1579629206, 1806384075, 1167925233, 1492823211, 65227667, - -97509291, 1836494326, 1993115793, 1275262245, -672837636, -886389289, 1144333952, -1553812081, - 1521606217, 465184103, 250234264, -1057071647, 1966064386, -263421678, -1756983901, -103584826, - 1603208167, -1668147819, 2054012907, 1498584538, -2084645843, 561273043, 1776306473, -926314940, - -1983744662, 2039411832, 1045993835, 1907959773, 1340194486, -1383534569, -1407137434, 986611124, - 1256153880, 823846274, 860985184, 2136171077, 2003087840, -1368671356, -1602093540, 722008468, - 1749577816, -45773031, 1826526343, -126135625, -747394269, 38499042, -1893735593, -1420466646, - 686535175, -1028313341, 2076542618, 137876389, -2027409166, -1514200142, 1778582202, -2112426660, - 483363371, -1267095662, -234359824, -496415071, -187013683, -1106966827, 1647628575, -22625142, - 1395537053, 1442030240, -511048398, -336157579, -326956231, -278904662, -1619960314, 275692881, - -1977532679, 115185213, 88006062, -1108980410, -1923837515, 1573155077, -737803153, 357589247, - -73918172, -373434729, 1128303052, -1629919369, 1122545853, -1953953912, 1528424248, -288851493, - 175939911, 256015593, 512030921, 0, -2038429309, -315936184, 1880170156, 1918528590, - -15794693, 948244310, -710001378, 959264295, -653325724, -1503893471, 1415289809, 775300154, - 1728711857, -413691121, -1762741038, -1852105826, -977239985, 551313826, 1266113129, 437394454, - -1164713462, 715178213, -534627261, 387650077, 218697227, -947129683, -1464455751, -1457646392, - 435246981, 125153100, -577114437, 1618977789, 637663135, -177054532, 996558021, 2130402100, - 692292470, -970732580, -51530136, -236668829, -600713270, -2057092592, 580326208, 298222624, - 608863613, 1035719416, 855223825, -1591097491, 798891339, 817028339, 1384517100, -473860144, - 380840812, -1183798887, 1217663482, 1693009698, -1929598780, 1072734234, 746411736, -1875696913, - 1313441735, -784803391, -1563783938, 198481974, -2114607409, -562387672, -1900553690, -1079165020, - -1657131804, -1837608947, -866162021, 1182684258, 328070850, -1193766680, -147247522, -1346141451, - -2141347906, -1815058052, 768962473, 304467891, -1716729797, 2098729127, 1671227502, -1153705093, - 2015808777, 408514292, -1214583807, -1706064984, 1855317605, -419452290, -809754360, -401215514, - -1679312167, 913263310, 161475284, 2091919830, -1297862225, 591342129, -1801075152, 1721906624, - -1135709129, -897385306, -795811664, -660131051, -1744506550, -622050825, 1355644686, -158263505, - -699566451, -1326496947, 1303039060, 76997855, -1244553501, -2006299621, 523026872, 1365591679, - -362898172, 898367837, 1955068531, 1091304238, 493335386, -757362094, 1443948851, 1205234963, - 1641519756, 211892090, 351820174, 1007938441, 665439982, -916342987, -451091987, -1320715716, - -539845543, 1945261375, -837543815, 935818175, -839429142, -1426235557, 1866325780, -616269690, - -206583167, -999769794, 874788908, 1084473951, -1021503886, 635616268, 1228679307, -1794244799, - 27801969, -1291056930, -457910116, -1051302768, -2067039391, -1238182544, 1550600308, 1471729730, - - // s_iT3 - -195997529, 1098797925, 387629988, 658151006, -1422144661, -1658851003, -89347240, -481586429, - 807425530, 1991112301, -863465098, 49620300, -447742761, 717608907, 891715652, 1656065955, - -1310832294, -1171953893, -364537842, -27401792, 801309301, 1283527408, 1183687575, -747911431, - -1895569569, -1844079204, 1841294202, 1385552473, -1093390973, 1951978273, -532076183, -913423160, - -1032492407, -1896580999, 1486449470, -1188569743, -507595185, -1997531219, 550069932, -830622662, - -547153846, 451248689, 1368875059, 1398949247, 1689378935, 1807451310, -2114052960, 150574123, - 1215322216, 1167006205, -560691348, 2069018616, 1940595667, 1265820162, 534992783, 1432758955, - -340654296, -1255210046, -981034373, 936617224, 674296455, -1088179547, 50510442, 384654466, - -813028580, 2041025204, 133427442, 1766760930, -630862348, 84334014, 886120290, -1497068802, - 775200083, -207445931, -1979370783, -156994069, -2096416276, 1614850799, 1901987487, 1857900816, - 557775242, -577356538, 1054715397, -431143235, 1418835341, -999226019, 100954068, 1348534037, - -1743182597, -1110009879, 1082772547, -647530594, -391070398, -1995994997, 434583643, -931537938, - 2090944266, 1115482383, -2064070370, 0, -2146860154, 724715757, 287222896, 1517047410, - 251526143, -2062592456, -1371726123, 758523705, 252339417, 1550328230, 1536938324, 908343854, - 168604007, 1469255655, -290139498, -1692688751, -1065332795, -597581280, 2002413899, 303830554, - -1813902662, -1597971158, 574374880, 454171927, 151915277, -1947030073, -1238517336, 504678569, - -245922535, 1974422535, -1712407587, 2141453664, 33005350, 1918680309, 1715782971, -77908866, - 1133213225, 600562886, -306812676, -457677839, 836225756, 1665273989, -1760346078, -964419567, - 1250262308, -1143801795, -106032846, 700935585, -1642247377, -1294142672, -2045907886, -1049112349, - -1288999914, 1890163129, -1810761144, -381214108, -56048500, -257942977, 2102843436, 857927568, - 1233635150, 953795025, -896729438, -728222197, -173617279, 2057644254, -1210440050, -1388337985, - 976020637, 2018512274, 1600822220, 2119459398, -1913208301, -661591880, 959340279, -1014827601, - 1570750080, -798393197, -714102483, 634368786, -1396163687, 403744637, -1662488989, 1004239803, - 650971512, 1500443672, -1695809097, 1334028442, -1780062866, -5603610, -1138685745, 368043752, - -407184997, 1867173430, -1612000247, -1339435396, -1540247630, 1059729699, -1513738092, -1573535642, - 1316239292, -2097371446, -1864322864, -1489824296, 82922136, -331221030, -847311280, -1860751370, - 1299615190, -280801872, -1429449651, -1763385596, -778116171, 1783372680, 750893087, 1699118929, - 1587348714, -1946067659, -2013629580, 201010753, 1739807261, -611167534, 283718486, -697494713, - -677737375, -1590199796, -128348652, 334203196, -1446056409, 1639396809, 484568549, 1199193265, - -761505313, -229294221, 337148366, -948715721, -145495347, -44082262, 1038029935, 1148749531, - -1345682957, 1756970692, 607661108, -1547542720, 488010435, -490992603, 1009290057, 234832277, - -1472630527, 201907891, -1260872476, 1449431233, -881106556, 852848822, 1816687708, -1194311081, - - // s_iT4 - 1364240372, 2119394625, 449029143, 982933031, 1003187115, 535905693, -1398056710, 1267925987, - 542505520, -1376359050, -2003732788, -182105086, 1341970405, -975713494, 645940277, -1248877726, - -565617999, 627514298, 1167593194, 1575076094, -1023249105, -2129465268, -1918658746, 1808202195, - 65494927, 362126482, -1075086739, -1780852398, -735214658, 1490231668, 1227450848, -1908094775, - 1969916354, -193431154, -1721024936, 668823993, -1095348255, -266883704, -916018144, 2108963534, - 1662536415, -444452582, -1755303087, 1648721747, -1310689436, -1148932501, -31678335, -107730168, - 1884842056, -1894122171, -1803064098, 1387788411, -1423715469, 1927414347, -480800993, 1714072405, - -1308153621, 788775605, -2036696123, -744159177, 821200680, 598910399, 45771267, -312704490, - -1976886065, -1483557767, -202313209, 1319232105, 1707996378, 114671109, -786472396, -997523802, - 882725678, -1566550541, 87220618, -1535775754, 188345475, 1084944224, 1577492337, -1118760850, - 1056541217, -1774385443, -575797954, 1296481766, -1850372780, 1896177092, 74437638, 1627329872, - 421854104, -694687299, -1983102144, 1735892697, -1329773848, 126389129, -415737063, 2044456648, - -1589179780, 2095648578, -121037180, 0, 159614592, 843640107, 514617361, 1817080410, - -33816818, 257308805, 1025430958, 908540205, 174381327, 1747035740, -1680780197, 607792694, - 212952842, -1827674281, -1261267218, 463376795, -2142255680, 1638015196, 1516850039, 471210514, - -502613357, -1058723168, 1011081250, 303896347, 235605257, -223492213, 767142070, 348694814, - 1468340721, -1353971851, -289677927, -1543675777, -140564991, 1555887474, 1153776486, 1530167035, - -1955190461, -874723805, -1234633491, -1201409564, -674571215, 1108378979, 322970263, -2078273082, - -2055396278, -755483205, -1374604551, -949116631, 491466654, -588042062, 233591430, 2010178497, - 728503987, -1449543312, 301615252, 1193436393, -1463513860, -1608892432, 1457007741, 586125363, - -2016981431, -641609416, -1929469238, -1741288492, -1496350219, -1524048262, -635007305, 1067761581, - 753179962, 1343066744, 1788595295, 1415726718, -155053171, -1863796520, 777975609, -2097827901, - -1614905251, 1769771984, 1873358293, -810347995, -935618132, 279411992, -395418724, -612648133, - -855017434, 1861490777, -335431782, -2086102449, -429560171, -1434523905, 554225596, -270079979, - -1160143897, 1255028335, -355202657, 701922480, 833598116, 707863359, -969894747, 901801634, - 1949809742, -56178046, -525283184, 857069735, -246769660, 1106762476, 2131644621, 389019281, - 1989006925, 1129165039, -866890326, -455146346, -1629243951, 1276872810, -1044898004, 1182749029, - -1660622242, 22885772, -93096825, -80854773, -1285939865, -1840065829, -382511600, 1829980118, - -1702075945, 930745505, 1502483704, -343327725, -823253079, -1221211807, -504503012, 2050797895, - -1671831598, 1430221810, 410635796, 1941911495, 1407897079, 1599843069, -552308931, 2022103876, - -897453137, -1187068824, 942421028, -1033944925, 376619805, -1140054558, 680216892, -12479219, - 963707304, 148812556, -660806476, 1687208278, 2069988555, -714033614, 1215585388, -800958536 }; - - private static readonly int[] s_iTF = new int[4 * 256] - { - // s_iTF1 - 82, 9, 106, 213, 48, 54, 165, 56, - 191, 64, 163, 158, 129, 243, 215, 251, - 124, 227, 57, 130, 155, 47, 255, 135, - 52, 142, 67, 68, 196, 222, 233, 203, - 84, 123, 148, 50, 166, 194, 35, 61, - 238, 76, 149, 11, 66, 250, 195, 78, - 8, 46, 161, 102, 40, 217, 36, 178, - 118, 91, 162, 73, 109, 139, 209, 37, - 114, 248, 246, 100, 134, 104, 152, 22, - 212, 164, 92, 204, 93, 101, 182, 146, - 108, 112, 72, 80, 253, 237, 185, 218, - 94, 21, 70, 87, 167, 141, 157, 132, - 144, 216, 171, 0, 140, 188, 211, 10, - 247, 228, 88, 5, 184, 179, 69, 6, - 208, 44, 30, 143, 202, 63, 15, 2, - 193, 175, 189, 3, 1, 19, 138, 107, - 58, 145, 17, 65, 79, 103, 220, 234, - 151, 242, 207, 206, 240, 180, 230, 115, - 150, 172, 116, 34, 231, 173, 53, 133, - 226, 249, 55, 232, 28, 117, 223, 110, - 71, 241, 26, 113, 29, 41, 197, 137, - 111, 183, 98, 14, 170, 24, 190, 27, - 252, 86, 62, 75, 198, 210, 121, 32, - 154, 219, 192, 254, 120, 205, 90, 244, - 31, 221, 168, 51, 136, 7, 199, 49, - 177, 18, 16, 89, 39, 128, 236, 95, - 96, 81, 127, 169, 25, 181, 74, 13, - 45, 229, 122, 159, 147, 201, 156, 239, - 160, 224, 59, 77, 174, 42, 245, 176, - 200, 235, 187, 60, 131, 83, 153, 97, - 23, 43, 4, 126, 186, 119, 214, 38, - 225, 105, 20, 99, 85, 33, 12, 125, - - // s_iTF2 - 20992, 2304, 27136, 54528, 12288, 13824, 42240, 14336, - 48896, 16384, 41728, 40448, 33024, 62208, 55040, 64256, - 31744, 58112, 14592, 33280, 39680, 12032, 65280, 34560, - 13312, 36352, 17152, 17408, 50176, 56832, 59648, 51968, - 21504, 31488, 37888, 12800, 42496, 49664, 8960, 15616, - 60928, 19456, 38144, 2816, 16896, 64000, 49920, 19968, - 2048, 11776, 41216, 26112, 10240, 55552, 9216, 45568, - 30208, 23296, 41472, 18688, 27904, 35584, 53504, 9472, - 29184, 63488, 62976, 25600, 34304, 26624, 38912, 5632, - 54272, 41984, 23552, 52224, 23808, 25856, 46592, 37376, - 27648, 28672, 18432, 20480, 64768, 60672, 47360, 55808, - 24064, 5376, 17920, 22272, 42752, 36096, 40192, 33792, - 36864, 55296, 43776, 0, 35840, 48128, 54016, 2560, - 63232, 58368, 22528, 1280, 47104, 45824, 17664, 1536, - 53248, 11264, 7680, 36608, 51712, 16128, 3840, 512, - 49408, 44800, 48384, 768, 256, 4864, 35328, 27392, - 14848, 37120, 4352, 16640, 20224, 26368, 56320, 59904, - 38656, 61952, 52992, 52736, 61440, 46080, 58880, 29440, - 38400, 44032, 29696, 8704, 59136, 44288, 13568, 34048, - 57856, 63744, 14080, 59392, 7168, 29952, 57088, 28160, - 18176, 61696, 6656, 28928, 7424, 10496, 50432, 35072, - 28416, 46848, 25088, 3584, 43520, 6144, 48640, 6912, - 64512, 22016, 15872, 19200, 50688, 53760, 30976, 8192, - 39424, 56064, 49152, 65024, 30720, 52480, 23040, 62464, - 7936, 56576, 43008, 13056, 34816, 1792, 50944, 12544, - 45312, 4608, 4096, 22784, 9984, 32768, 60416, 24320, - 24576, 20736, 32512, 43264, 6400, 46336, 18944, 3328, - 11520, 58624, 31232, 40704, 37632, 51456, 39936, 61184, - 40960, 57344, 15104, 19712, 44544, 10752, 62720, 45056, - 51200, 60160, 47872, 15360, 33536, 21248, 39168, 24832, - 5888, 11008, 1024, 32256, 47616, 30464, 54784, 9728, - 57600, 26880, 5120, 25344, 21760, 8448, 3072, 32000, - - // s_iTF3 - 5373952, 589824, 6946816, 13959168, 3145728, 3538944, 10813440, 3670016, - 12517376, 4194304, 10682368, 10354688, 8454144, 15925248, 14090240, 16449536, - 8126464, 14876672, 3735552, 8519680, 10158080, 3080192, 16711680, 8847360, - 3407872, 9306112, 4390912, 4456448, 12845056, 14548992, 15269888, 13303808, - 5505024, 8060928, 9699328, 3276800, 10878976, 12713984, 2293760, 3997696, - 15597568, 4980736, 9764864, 720896, 4325376, 16384000, 12779520, 5111808, - 524288, 3014656, 10551296, 6684672, 2621440, 14221312, 2359296, 11665408, - 7733248, 5963776, 10616832, 4784128, 7143424, 9109504, 13697024, 2424832, - 7471104, 16252928, 16121856, 6553600, 8781824, 6815744, 9961472, 1441792, - 13893632, 10747904, 6029312, 13369344, 6094848, 6619136, 11927552, 9568256, - 7077888, 7340032, 4718592, 5242880, 16580608, 15532032, 12124160, 14286848, - 6160384, 1376256, 4587520, 5701632, 10944512, 9240576, 10289152, 8650752, - 9437184, 14155776, 11206656, 0, 9175040, 12320768, 13828096, 655360, - 16187392, 14942208, 5767168, 327680, 12058624, 11730944, 4521984, 393216, - 13631488, 2883584, 1966080, 9371648, 13238272, 4128768, 983040, 131072, - 12648448, 11468800, 12386304, 196608, 65536, 1245184, 9043968, 7012352, - 3801088, 9502720, 1114112, 4259840, 5177344, 6750208, 14417920, 15335424, - 9895936, 15859712, 13565952, 13500416, 15728640, 11796480, 15073280, 7536640, - 9830400, 11272192, 7602176, 2228224, 15138816, 11337728, 3473408, 8716288, - 14811136, 16318464, 3604480, 15204352, 1835008, 7667712, 14614528, 7208960, - 4653056, 15794176, 1703936, 7405568, 1900544, 2686976, 12910592, 8978432, - 7274496, 11993088, 6422528, 917504, 11141120, 1572864, 12451840, 1769472, - 16515072, 5636096, 4063232, 4915200, 12976128, 13762560, 7929856, 2097152, - 10092544, 14352384, 12582912, 16646144, 7864320, 13434880, 5898240, 15990784, - 2031616, 14483456, 11010048, 3342336, 8912896, 458752, 13041664, 3211264, - 11599872, 1179648, 1048576, 5832704, 2555904, 8388608, 15466496, 6225920, - 6291456, 5308416, 8323072, 11075584, 1638400, 11862016, 4849664, 851968, - 2949120, 15007744, 7995392, 10420224, 9633792, 13172736, 10223616, 15663104, - 10485760, 14680064, 3866624, 5046272, 11403264, 2752512, 16056320, 11534336, - 13107200, 15400960, 12255232, 3932160, 8585216, 5439488, 10027008, 6356992, - 1507328, 2818048, 262144, 8257536, 12189696, 7798784, 14024704, 2490368, - 14745600, 6881280, 1310720, 6488064, 5570560, 2162688, 786432, 8192000, - - // s_iTF4 - 1375731712, 150994944, 1778384896, -721420288, 805306368, 905969664, -1526726656, 939524096, - -1090519040, 1073741824, -1560281088, -1644167168, -2130706432, -218103808, -687865856, -83886080, - 2080374784, -486539264, 956301312, -2113929216, -1694498816, 788529152, -16777216, -2030043136, - 872415232, -1912602624, 1124073472, 1140850688, -1006632960, -570425344, -385875968, -889192448, - 1409286144, 2063597568, -1811939328, 838860800, -1509949440, -1040187392, 587202560, 1023410176, - -301989888, 1275068416, -1795162112, 184549376, 1107296256, -100663296, -1023410176, 1308622848, - 134217728, 771751936, -1593835520, 1711276032, 671088640, -654311424, 603979776, -1308622848, - 1979711488, 1526726656, -1577058304, 1224736768, 1828716544, -1962934272, -788529152, 620756992, - 1912602624, -134217728, -167772160, 1677721600, -2046820352, 1744830464, -1744830464, 369098752, - -738197504, -1543503872, 1543503872, -872415232, 1560281088, 1694498816, -1241513984, -1845493760, - 1811939328, 1879048192, 1207959552, 1342177280, -50331648, -318767104, -1191182336, -637534208, - 1577058304, 352321536, 1174405120, 1459617792, -1493172224, -1929379840, -1660944384, -2080374784, - -1879048192, -671088640, -1426063360, 0, -1946157056, -1140850688, -754974720, 167772160, - -150994944, -469762048, 1476395008, 83886080, -1207959552, -1291845632, 1157627904, 100663296, - -805306368, 738197504, 503316480, -1895825408, -905969664, 1056964608, 251658240, 33554432, - -1056964608, -1358954496, -1124073472, 50331648, 16777216, 318767104, -1979711488, 1795162112, - 973078528, -1862270976, 285212672, 1090519040, 1325400064, 1728053248, -603979776, -369098752, - -1761607680, -234881024, -822083584, -838860800, -268435456, -1275068416, -436207616, 1929379840, - -1778384896, -1409286144, 1946157056, 570425344, -419430400, -1392508928, 889192448, -2063597568, - -503316480, -117440512, 922746880, -402653184, 469762048, 1962934272, -553648128, 1845493760, - 1191182336, -251658240, 436207616, 1895825408, 486539264, 687865856, -989855744, -1996488704, - 1862270976, -1224736768, 1644167168, 234881024, -1442840576, 402653184, -1107296256, 452984832, - -67108864, 1442840576, 1040187392, 1258291200, -973078528, -771751936, 2030043136, 536870912, - -1711276032, -620756992, -1073741824, -33554432, 2013265920, -855638016, 1509949440, -201326592, - 520093696, -587202560, -1476395008, 855638016, -2013265920, 117440512, -956301312, 822083584, - -1325400064, 301989888, 268435456, 1493172224, 654311424, -2147483648, -335544320, 1593835520, - 1610612736, 1358954496, 2130706432, -1459617792, 419430400, -1258291200, 1241513984, 218103808, - 754974720, -452984832, 2046820352, -1627389952, -1828716544, -922746880, -1677721600, -285212672, - -1610612736, -536870912, 989855744, 1291845632, -1375731712, 704643072, -184549376, -1342177280, - -939524096, -352321536, -1157627904, 1006632960, -2097152000, 1392508928, -1728053248, 1627389952, - 385875968, 721420288, 67108864, 2113929216, -1174405120, 1996488704, -704643072, 637534208, - -520093696, 1761607680, 335544320, 1660944384, 1426063360, 553648128, 201326592, 2097152000 }; - } -} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesSubtleCryptoTransform.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesSubtleCryptoTransform.Browser.cs deleted file mode 100644 index 08f46f5dad12a0..00000000000000 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesSubtleCryptoTransform.Browser.cs +++ /dev/null @@ -1,171 +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; -using Internal.Cryptography; - -namespace System.Security.Cryptography -{ - internal sealed class AesSubtleCryptoTransform : BasicSymmetricCipher, ILiteSymmetricCipher - { - private const int BlockSizeBytes = AesImplementation.BlockSizeBytes; - - private readonly bool _encrypting; - - private readonly byte[] _key; - private byte[]? _lastBlockBuffer; - - public AesSubtleCryptoTransform(byte[] key, - byte[] iv, - bool encrypting) - : base(iv, BlockSizeBytes, BlockSizeBytes) - { - _encrypting = encrypting; - - // iv is guaranteed to be cloned before this method, but not key - _key = key.CloneByteArray(); - } - - public AesSubtleCryptoTransform(ReadOnlySpan key, - ReadOnlySpan iv, - bool encrypting) - : base(iv.ToArray(), BlockSizeBytes, BlockSizeBytes) - { - _encrypting = encrypting; - - _key = key.ToArray(); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - // We need to always zeroize the following fields because they contain sensitive data - CryptographicOperations.ZeroMemory(_key); - CryptographicOperations.ZeroMemory(_lastBlockBuffer); - } - - base.Dispose(disposing); - } - - public override int Transform(ReadOnlySpan input, Span output) => - Transform(input, output, isFinal: false); - - public override int TransformFinal(ReadOnlySpan input, Span output) - { - int bytesWritten = Transform(input, output, isFinal: true); - Reset(); - return bytesWritten; - } - - private int Transform(ReadOnlySpan input, Span output, bool isFinal) - { - Debug.Assert(output.Length >= input.Length); - Debug.Assert(input.Length % BlockSizeInBytes == 0); - - if (input.IsEmpty) - { - return 0; - } - - // Note: SubtleCrypto always uses PKCS7 padding. - - // In order to implement streaming on top of SubtleCrypto's "one shot" API, we have to do the following: - // 1. Remember the last block of cipher text to pass as the "IV" of the next block. - // 2. When encrypting a complete block, PKCS7 padding will always add one block of '0x10' padding bytes. We - // need to strip this padding block off in between Transform calls. This is done by Interop.BrowserCrypto.EncryptDecrypt. - // 3. When decrypting, we need to do the inverse: append an encrypted block of '0x10' padding bytes, so - // SubtleCrypto will decrypt input as a complete message. This is done by Interop.BrowserCrypto.EncryptDecrypt. - - return _encrypting ? - EncryptBlock(input, output, isFinal) : - DecryptBlock(input, output, isFinal); - } - - private int EncryptBlock(ReadOnlySpan input, Span output, bool isFinal) - { - int bytesWritten = EncryptDecrypt(input, output); - - if (!isFinal) - { - SaveLastBlock(output.Slice(0, bytesWritten)); - } - - return bytesWritten; - } - - private int DecryptBlock(ReadOnlySpan input, Span output, bool isFinal) - { - Span lastInputBlockCopy = stackalloc byte[BlockSizeBytes]; - if (!isFinal) - { - // Save the lastInputBlock in a temp buffer first, in case input and output are overlapped - // and decrypting to the output overwrites the input. - ReadOnlySpan lastInputBlock = input.Slice(input.Length - BlockSizeBytes); - lastInputBlock.CopyTo(lastInputBlockCopy); - } - - int numBytesWritten = EncryptDecrypt(input, output); - - if (!isFinal) - { - SaveLastBlock(lastInputBlockCopy); - } - - return numBytesWritten; - } - - private void SaveLastBlock(ReadOnlySpan buffer) - { - Debug.Assert(buffer.Length > 0 && buffer.Length % BlockSizeBytes == 0); - - ReadOnlySpan lastBlock = buffer.Slice(buffer.Length - BlockSizeBytes); - if (_lastBlockBuffer is null) - { - _lastBlockBuffer = lastBlock.ToArray(); - } - else - { - Debug.Assert(_lastBlockBuffer.Length == BlockSizeBytes); - lastBlock.CopyTo(_lastBlockBuffer); - } - } - - private unsafe int EncryptDecrypt(ReadOnlySpan input, Span output) - { - byte[] iv = _lastBlockBuffer ?? IV!; - - fixed (byte* pKey = _key) - fixed (byte* pIV = iv) - fixed (byte* pInput = input) - fixed (byte* pOutput = output) - { - int bytesWritten = Interop.BrowserCrypto.EncryptDecrypt( - _encrypting ? 1 : 0, - pKey, _key.Length, - pIV, iv.Length, - pInput, input.Length, - pOutput, output.Length); - - if (bytesWritten < 0) - { - throw new CryptographicException(SR.Format(SR.Unknown_SubtleCrypto_Error, bytesWritten)); - } - - return bytesWritten; - } - } - - // - // resets the state of the transform - // - - void ILiteSymmetricCipher.Reset(ReadOnlySpan iv) => throw new NotImplementedException(); // never invoked - - private void Reset() - { - CryptographicOperations.ZeroMemory(_lastBlockBuffer); - _lastBlockBuffer = null; - } - } -} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptoConfig.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptoConfig.cs index 6b52faa7a6d79f..d7397d28223b93 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptoConfig.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptoConfig.cs @@ -373,20 +373,6 @@ public static void AddAlgorithm(Type algorithm, params string[] names) case "HMACSHA512": case "System.Security.Cryptography.HMACSHA512": return new HMACSHA512(); - -#pragma warning disable SYSLIB0021 // Obsolete: derived cryptographic types - case "AES": - case "System.Security.Cryptography.AesCryptoServiceProvider": - return new AesCryptoServiceProvider(); - case "AesManaged": - case "System.Security.Cryptography.AesManaged": - return new AesManaged(); - case "Rijndael": - case "System.Security.Cryptography.Rijndael": -#pragma warning disable SYSLIB0022 // Rijndael types are obsolete - return new RijndaelManaged(); -#pragma warning restore SYSLIB0022 -#pragma warning restore SYSLIB0021 } return null; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACHashProvider.Browser.Native.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACHashProvider.Browser.Native.cs deleted file mode 100644 index 0ca7ccfc212c10..00000000000000 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACHashProvider.Browser.Native.cs +++ /dev/null @@ -1,94 +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.IO; -using System.Diagnostics; -using System.Security.Cryptography; - -using SimpleDigest = Interop.BrowserCrypto.SimpleDigest; - -namespace System.Security.Cryptography -{ - internal sealed class HMACNativeHashProvider : HashProvider - { - private readonly int _hashSizeInBytes; - private readonly SimpleDigest _hashAlgorithm; - private readonly byte[] _key; - private MemoryStream? _buffer; - - public HMACNativeHashProvider(string hashAlgorithmId, ReadOnlySpan key) - { - Debug.Assert(Interop.BrowserCrypto.CanUseSubtleCrypto); - - (_hashAlgorithm, _hashSizeInBytes) = SHANativeHashProvider.HashAlgorithmToPal(hashAlgorithmId); - _key = key.ToArray(); - } - - public override void AppendHashData(ReadOnlySpan data) - { - _buffer ??= new MemoryStream(1000); - _buffer.Write(data); - } - - public override int FinalizeHashAndReset(Span destination) - { - int written = GetCurrentHash(destination); - _buffer = null; - - return written; - } - - public override int GetCurrentHash(Span destination) - { - Debug.Assert(destination.Length >= _hashSizeInBytes); - - ReadOnlySpan source = _buffer != null ? - new ReadOnlySpan(_buffer.GetBuffer(), 0, (int)_buffer.Length) : - default; - - Sign(_hashAlgorithm, _key, source, destination); - - return _hashSizeInBytes; - } - - public static int MacDataOneShot(string hashAlgorithmId, ReadOnlySpan key, ReadOnlySpan data, Span destination) - { - (SimpleDigest hashName, int hashSizeInBytes) = SHANativeHashProvider.HashAlgorithmToPal(hashAlgorithmId); - Debug.Assert(destination.Length >= hashSizeInBytes); - - Sign(hashName, key, data, destination); - - return hashSizeInBytes; - } - - private static unsafe void Sign(SimpleDigest hashName, ReadOnlySpan key, ReadOnlySpan data, Span destination) - { - fixed (byte* k = key) - fixed (byte* src = data) - fixed (byte* dest = destination) - { - int res = Interop.BrowserCrypto.Sign(hashName, k, key.Length, src, data.Length, dest, destination.Length); - if (res != 0) - { - throw new CryptographicException(SR.Format(SR.Unknown_SubtleCrypto_Error, res)); - } - } - } - - public override int HashSizeInBytes => _hashSizeInBytes; - - public override void Dispose(bool disposing) - { - if (disposing) - { - CryptographicOperations.ZeroMemory(_key); - } - } - - public override void Reset() - { - _buffer = null; - } - } -} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs index 8c604bb32b122f..af0169bebd9d92 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs @@ -13,9 +13,7 @@ public static HashProvider CreateHashProvider(string hashAlgorithmId) case HashAlgorithmNames.SHA256: case HashAlgorithmNames.SHA384: case HashAlgorithmNames.SHA512: - return Interop.BrowserCrypto.CanUseSubtleCrypto - ? new SHANativeHashProvider(hashAlgorithmId) - : new SHAManagedHashProvider(hashAlgorithmId); + return new SHAManagedHashProvider(hashAlgorithmId); } throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId)); } @@ -28,30 +26,16 @@ public static unsafe int MacData( ReadOnlySpan source, Span destination) { - if (Interop.BrowserCrypto.CanUseSubtleCrypto) - { - return HMACNativeHashProvider.MacDataOneShot(hashAlgorithmId, key, source, destination); - } - else - { - using HashProvider provider = CreateMacProvider(hashAlgorithmId, key); - provider.AppendHashData(source); - return provider.FinalizeHashAndReset(destination); - } + using HashProvider provider = CreateMacProvider(hashAlgorithmId, key); + provider.AppendHashData(source); + return provider.FinalizeHashAndReset(destination); } public static int HashData(string hashAlgorithmId, ReadOnlySpan source, Span destination) { - if (Interop.BrowserCrypto.CanUseSubtleCrypto) - { - return SHANativeHashProvider.HashOneShot(hashAlgorithmId, source, destination); - } - else - { - HashProvider provider = CreateHashProvider(hashAlgorithmId); - provider.AppendHashData(source); - return provider.FinalizeHashAndReset(destination); - } + HashProvider provider = CreateHashProvider(hashAlgorithmId); + provider.AppendHashData(source); + return provider.FinalizeHashAndReset(destination); } } @@ -63,9 +47,7 @@ public static unsafe HashProvider CreateMacProvider(string hashAlgorithmId, Read case HashAlgorithmNames.SHA256: case HashAlgorithmNames.SHA384: case HashAlgorithmNames.SHA512: - return Interop.BrowserCrypto.CanUseSubtleCrypto - ? new HMACNativeHashProvider(hashAlgorithmId, key) - : new HMACManagedHashProvider(hashAlgorithmId, key); + return new HMACManagedHashProvider(hashAlgorithmId, key); } throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId)); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Pbkdf2Implementation.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Pbkdf2Implementation.Browser.cs index dc087adb84736c..b5a59ab8d81c16 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Pbkdf2Implementation.Browser.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Pbkdf2Implementation.Browser.cs @@ -4,8 +4,6 @@ using System.Diagnostics; using Internal.Cryptography; -using SimpleDigest = Interop.BrowserCrypto.SimpleDigest; - namespace System.Security.Cryptography { internal static partial class Pbkdf2Implementation @@ -20,41 +18,7 @@ public static void Fill( Debug.Assert(!destination.IsEmpty); Debug.Assert(hashAlgorithmName.Name is not null); - if (Interop.BrowserCrypto.CanUseSubtleCrypto) - { - FillSubtleCrypto(password, salt, iterations, hashAlgorithmName, destination); - } - else - { - FillManaged(password, salt, iterations, hashAlgorithmName, destination); - } - } - - private static unsafe void FillSubtleCrypto( - ReadOnlySpan password, - ReadOnlySpan salt, - int iterations, - HashAlgorithmName hashAlgorithmName, - Span destination) - { - (SimpleDigest hashName, _) = SHANativeHashProvider.HashAlgorithmToPal(hashAlgorithmName.Name!); - - fixed (byte* pPassword = password) - fixed (byte* pSalt = salt) - fixed (byte* pDestination = destination) - { - int result = Interop.BrowserCrypto.DeriveBits( - pPassword, password.Length, - pSalt, salt.Length, - iterations, - hashName, - pDestination, destination.Length); - - if (result != 0) - { - throw new CryptographicException(SR.Format(SR.Unknown_SubtleCrypto_Error, result)); - } - } + FillManaged(password, salt, iterations, hashAlgorithmName, destination); } private static void FillManaged( diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Rijndael.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Rijndael.cs index 1e21e1f00e156f..4c9de50ee6e2ca 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Rijndael.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Rijndael.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Runtime.Versioning; using Internal.Cryptography; namespace System.Security.Cryptography @@ -11,6 +12,7 @@ namespace System.Security.Cryptography [EditorBrowsable(EditorBrowsableState.Never)] public abstract class Rijndael : SymmetricAlgorithm { + [UnsupportedOSPlatform("browser")] public static new Rijndael Create() { return new RijndaelImplementation(); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelImplementation.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelImplementation.cs index ed33c6554508ca..698926032dfdbb 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelImplementation.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelImplementation.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.Versioning; namespace System.Security.Cryptography { @@ -16,6 +17,7 @@ internal sealed class RijndaelImplementation : Rijndael { private readonly Aes _impl; + [UnsupportedOSPlatform("browser")] internal RijndaelImplementation() { LegalBlockSizesValue = new KeySizes[] { new KeySizes(minSize: 128, maxSize: 128, skipSize: 0) }; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelManaged.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelManaged.cs index 6727bdc71bcada..2c866a2459f692 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelManaged.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelManaged.cs @@ -3,11 +3,13 @@ using System.ComponentModel; using System.Diagnostics; +using System.Runtime.Versioning; namespace System.Security.Cryptography { [Obsolete(Obsoletions.RijndaelMessage, DiagnosticId = Obsoletions.RijndaelDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] [EditorBrowsable(EditorBrowsableState.Never)] + [UnsupportedOSPlatform("browser")] public sealed class RijndaelManaged : Rijndael { private readonly Aes _impl; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs deleted file mode 100644 index d5d53c05e1a0f7..00000000000000 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs +++ /dev/null @@ -1,98 +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.IO; -using System.Diagnostics; -using System.Security.Cryptography; - -using SimpleDigest = Interop.BrowserCrypto.SimpleDigest; - -namespace System.Security.Cryptography -{ - internal sealed class SHANativeHashProvider : HashProvider - { - private readonly int _hashSizeInBytes; - private readonly SimpleDigest _impl; - private MemoryStream? _buffer; - - public SHANativeHashProvider(string hashAlgorithmId) - { - Debug.Assert(Interop.BrowserCrypto.CanUseSubtleCrypto); - (_impl, _hashSizeInBytes) = HashAlgorithmToPal(hashAlgorithmId); - } - - public override void AppendHashData(ReadOnlySpan data) - { - _buffer ??= new MemoryStream(1000); - _buffer.Write(data); - } - - public override int FinalizeHashAndReset(Span destination) - { - GetCurrentHash(destination); - _buffer = null; - - return _hashSizeInBytes; - } - - public override int GetCurrentHash(Span destination) - { - Debug.Assert(destination.Length >= _hashSizeInBytes); - - ReadOnlySpan source = _buffer != null ? - new ReadOnlySpan(_buffer.GetBuffer(), 0, (int)_buffer.Length) : - default; - - SimpleDigestHash(_impl, source, destination); - - return _hashSizeInBytes; - } - - public static int HashOneShot(string hashAlgorithmId, ReadOnlySpan data, Span destination) - { - (SimpleDigest impl, int hashSizeInBytes) = HashAlgorithmToPal(hashAlgorithmId); - Debug.Assert(destination.Length >= hashSizeInBytes); - - SimpleDigestHash(impl, data, destination); - - return hashSizeInBytes; - } - - private static unsafe void SimpleDigestHash(SimpleDigest hashName, ReadOnlySpan data, Span destination) - { - fixed (byte* src = data) - fixed (byte* dest = destination) - { - int res = Interop.BrowserCrypto.SimpleDigestHash(hashName, src, data.Length, dest, destination.Length); - if (res != 0) - { - throw new CryptographicException(SR.Format(SR.Unknown_SubtleCrypto_Error, res)); - } - } - } - - public override int HashSizeInBytes => _hashSizeInBytes; - - public override void Dispose(bool disposing) - { - } - - public override void Reset() - { - _buffer = null; - } - - internal static (SimpleDigest HashName, int HashSizeInBytes) HashAlgorithmToPal(string hashAlgorithmId) - { - return hashAlgorithmId switch - { - HashAlgorithmNames.SHA256 => (SimpleDigest.Sha256, SHA256.HashSizeInBytes), - HashAlgorithmNames.SHA1 => (SimpleDigest.Sha1, SHA1.HashSizeInBytes), - HashAlgorithmNames.SHA384 => (SimpleDigest.Sha384, SHA384.HashSizeInBytes), - HashAlgorithmNames.SHA512 => (SimpleDigest.Sha512, SHA512.HashSizeInBytes), - _ => throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId)), - }; - } - } -} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SymmetricPadding.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SymmetricPadding.cs index 5adab7b88abd37..c293c12374ff26 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SymmetricPadding.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SymmetricPadding.cs @@ -33,7 +33,7 @@ public static int GetCiphertextLength(int plaintextLength, int paddingSizeInByte } } - public static int PadBlock(ReadOnlySpan block, Span destination, int paddingSizeInBytes, PaddingMode paddingMode) + public static int PadBlock(ReadOnlySpan block, Span destination, int paddingSizeInBytes, PaddingMode paddingMode) { int count = block.Length; int paddingRemainder = count % paddingSizeInBytes; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs index 8a06450fac91c3..0f0f8538fa9b68 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs @@ -47,7 +47,7 @@ public static ICertificatePal FromOtherCert(X509Certificate cert) return new AndroidCertificatePal(handle); } - public static ICertificatePal FromBlob(ReadOnlySpan rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) + private static ICertificatePal FromBlob(ReadOnlySpan rawData, SafePasswordHandle password, bool readingFromFile, X509KeyStorageFlags keyStorageFlags) { Debug.Assert(password != null); @@ -67,6 +67,8 @@ public static ICertificatePal FromBlob(ReadOnlySpan rawData, SafePasswordH throw new PlatformNotSupportedException(SR.Cryptography_X509_PKCS12_PersistKeySetNotSupported); } + X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile, password.PasswordProvided); + return ReadPkcs12(rawData, password, ephemeralSpecified); case X509ContentType.Cert: default: @@ -85,10 +87,15 @@ public static ICertificatePal FromBlob(ReadOnlySpan rawData, SafePasswordH throw new CryptographicException(); } + public static ICertificatePal FromBlob(ReadOnlySpan rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) + { + return FromBlob(rawData, password, readingFromFile: false, keyStorageFlags); + } + public static ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) { byte[] fileBytes = System.IO.File.ReadAllBytes(fileName); - return FromBlob(fileBytes, password, keyStorageFlags); + return FromBlob(fileBytes, password, readingFromFile: true, keyStorageFlags); } // Handles both DER and PEM diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.ImportExport.iOS.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.ImportExport.iOS.cs index d042ade14b0be1..daab1181c0e620 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.ImportExport.iOS.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.ImportExport.iOS.cs @@ -91,6 +91,7 @@ internal static ICertificatePal FromDerBlob( ReadOnlySpan rawData, X509ContentType contentType, SafePasswordHandle password, + bool readingFromFile, X509KeyStorageFlags keyStorageFlags) { Debug.Assert(password != null); @@ -116,6 +117,7 @@ internal static ICertificatePal FromDerBlob( throw new PlatformNotSupportedException(SR.Cryptography_X509_PKCS12_PersistKeySetNotSupported); } + X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile, password.PasswordProvided); return ImportPkcs12(rawData, password, ephemeralSpecified); } @@ -143,6 +145,15 @@ public static ICertificatePal FromBlob( ReadOnlySpan rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) + { + return FromBlob(rawData, password, readingFromFile: false, keyStorageFlags); + } + + private static ICertificatePal FromBlob( + ReadOnlySpan rawData, + SafePasswordHandle password, + bool readingFromFile, + X509KeyStorageFlags keyStorageFlags) { Debug.Assert(password != null); @@ -151,11 +162,11 @@ public static ICertificatePal FromBlob( rawData, (derData, contentType) => { - result = FromDerBlob(derData, contentType, password, keyStorageFlags); + result = FromDerBlob(derData, contentType, password, readingFromFile, keyStorageFlags); return false; }); - return result ?? FromDerBlob(rawData, GetDerCertContentType(rawData), password, keyStorageFlags); + return result ?? FromDerBlob(rawData, GetDerCertContentType(rawData), password, readingFromFile, keyStorageFlags); } // No temporary keychain on iOS diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.ImportExport.macOS.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.ImportExport.macOS.cs index 4e9d87d798c3f1..ed045143f94d70 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.ImportExport.macOS.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.ImportExport.macOS.cs @@ -18,6 +18,15 @@ public static ICertificatePal FromBlob( ReadOnlySpan rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) + { + return FromBlob(rawData, password, readingFromFile: false, keyStorageFlags); + } + + private static ICertificatePal FromBlob( + ReadOnlySpan rawData, + SafePasswordHandle password, + bool readingFromFile, + X509KeyStorageFlags keyStorageFlags) { Debug.Assert(password != null); @@ -42,6 +51,7 @@ public static ICertificatePal FromBlob( throw new PlatformNotSupportedException(SR.Cryptography_X509_NoEphemeralPfx); } + X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile, password.PasswordProvided); bool exportable = (keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable; bool persist = diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.Pkcs12.iOS.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.Pkcs12.iOS.cs index 01a60958416186..26a1f569abe17b 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.Pkcs12.iOS.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.Pkcs12.iOS.cs @@ -7,7 +7,7 @@ namespace System.Security.Cryptography.X509Certificates { internal sealed partial class AppleCertificatePal : ICertificatePal { - private static SafePasswordHandle s_passwordExportHandle = new SafePasswordHandle("DotnetExportPassphrase"); + private static SafePasswordHandle s_passwordExportHandle = new SafePasswordHandle("DotnetExportPassphrase", passwordProvided: true); private static AppleCertificatePal ImportPkcs12( ReadOnlySpan rawData, diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.cs index fb8c18acdcb39e..594bc8f5e8c1a7 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.cs @@ -70,12 +70,12 @@ public static ICertificatePal FromOtherCert(X509Certificate cert) return pal; } - public static ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) + internal static ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) { Debug.Assert(password != null); byte[] fileBytes = System.IO.File.ReadAllBytes(fileName); - return FromBlob(fileBytes, password, keyStorageFlags); + return FromBlob(fileBytes, password, readingFromFile: true, keyStorageFlags); } internal AppleCertificatePal(SafeSecCertificateHandle certHandle) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificatePal.Windows.Import.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificatePal.Windows.Import.cs index 904d84b8c6c63b..793725130ca4f8 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificatePal.Windows.Import.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificatePal.Windows.Import.cs @@ -85,6 +85,7 @@ out pCertContext } pCertContext?.Dispose(); + X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile: loadFromFile, password.PasswordProvided); pCertContext = FilterPFXStore(rawData, password, pfxCertStoreFlags); // If PersistKeySet is set we don't delete the key, so that it persists. diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/LocalAppContextSwitches.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/LocalAppContextSwitches.cs new file mode 100644 index 00000000000000..0b7eb187e76b0a --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/LocalAppContextSwitches.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System +{ + internal static partial class LocalAppContextSwitches + { + internal const long DefaultPkcs12UnspecifiedPasswordIterationLimit = 600_000; + + internal static long Pkcs12UnspecifiedPasswordIterationLimit { get; } = InitializePkcs12UnspecifiedPasswordIterationLimit(); + + private static long InitializePkcs12UnspecifiedPasswordIterationLimit() + { + object? data = AppContext.GetData("System.Security.Cryptography.Pkcs12UnspecifiedPasswordIterationLimit"); + + if (data is null) + { + return DefaultPkcs12UnspecifiedPasswordIterationLimit; + } + + try + { + return Convert.ToInt64(data); + } + catch + { + return DefaultPkcs12UnspecifiedPasswordIterationLimit; + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslCertificateAssetDownloader.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslCertificateAssetDownloader.cs index 2abdf8a791c864..309df74ed074a2 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslCertificateAssetDownloader.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslCertificateAssetDownloader.cs @@ -24,6 +24,16 @@ internal static class OpenSslCertificateAssetDownloader try { + X509ContentType contentType = X509Certificate2.GetCertContentType(data); + switch (contentType) + { + case X509ContentType.Cert: + case X509ContentType.Pkcs7: + break; + default: + return null; + } + X509Certificate2 certificate = new X509Certificate2(data); certificate.ThrowIfInvalid(); return certificate; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslPkcsFormatReader.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslPkcsFormatReader.cs index 4e07816023dae5..441aadebda8428 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslPkcsFormatReader.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslPkcsFormatReader.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using Microsoft.Win32.SafeHandles; namespace System.Security.Cryptography.X509Certificates @@ -241,6 +242,7 @@ internal static bool TryReadPkcs12( ReadOnlySpan rawData, SafePasswordHandle password, bool ephemeralSpecified, + bool readingFromFile, [NotNullWhen(true)] out ICertificatePal? certPal, out Exception? openSslException) { @@ -249,6 +251,7 @@ internal static bool TryReadPkcs12( password, single: true, ephemeralSpecified, + readingFromFile, out certPal!, out _, out openSslException); @@ -258,6 +261,7 @@ internal static bool TryReadPkcs12( ReadOnlySpan rawData, SafePasswordHandle password, bool ephemeralSpecified, + bool readingFromFile, [NotNullWhen(true)] out List? certPals, out Exception? openSslException) { @@ -266,6 +270,7 @@ internal static bool TryReadPkcs12( password, single: false, ephemeralSpecified, + readingFromFile, out _, out certPals!, out openSslException); @@ -276,6 +281,7 @@ private static bool TryReadPkcs12( SafePasswordHandle password, bool single, bool ephemeralSpecified, + bool readingFromFile, out ICertificatePal? readPal, out List? readCerts, out Exception? openSslException) @@ -292,18 +298,21 @@ private static bool TryReadPkcs12( using (pfx) { - return TryReadPkcs12(pfx, password, single, ephemeralSpecified, out readPal, out readCerts); + return TryReadPkcs12(rawData, pfx, password, single, ephemeralSpecified, readingFromFile, out readPal, out readCerts); } } private static bool TryReadPkcs12( + ReadOnlySpan rawData, OpenSslPkcs12Reader pfx, SafePasswordHandle password, bool single, bool ephemeralSpecified, + bool readingFromFile, out ICertificatePal? readPal, out List? readCerts) { + X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile, password.PasswordProvided); pfx.Decrypt(password, ephemeralSpecified); if (single) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509CertificateReader.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509CertificateReader.cs index fe1fa8f95dd1e6..64ae8fb9a365a4 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509CertificateReader.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509CertificateReader.cs @@ -52,7 +52,7 @@ public static ICertificatePal FromBlob(ReadOnlySpan rawData, SafePasswordH TryReadX509Pem(rawData, out cert) || OpenSslPkcsFormatReader.TryReadPkcs7Der(rawData, out cert) || OpenSslPkcsFormatReader.TryReadPkcs7Pem(rawData, out cert) || - OpenSslPkcsFormatReader.TryReadPkcs12(rawData, password, ephemeralSpecified, out cert, out openSslException)) + OpenSslPkcsFormatReader.TryReadPkcs12(rawData, password, ephemeralSpecified, readingFromFile: false, out cert, out openSslException)) { if (cert == null) { @@ -87,6 +87,7 @@ public static ICertificatePal FromFile(string fileName, SafePasswordHandle passw File.ReadAllBytes(fileName), password, ephemeralSpecified, + readingFromFile: true, out pal, out Exception? exception); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.Android.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.Android.cs index 7c31e1f3734354..1c6d27c1f94fda 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.Android.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.Android.cs @@ -15,7 +15,7 @@ internal static partial IStorePal FromHandle(IntPtr storeHandle) throw new NotImplementedException($"{nameof(StorePal)}.{nameof(FromHandle)}"); } - internal static partial ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) + private static ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle password, bool readingFromFile, X509KeyStorageFlags keyStorageFlags) { Debug.Assert(password != null); @@ -24,6 +24,7 @@ internal static partial ILoaderPal FromBlob(ReadOnlySpan rawData, SafePass if (contentType == X509ContentType.Pkcs12) { + X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile, password.PasswordProvided); ICertificatePal[] certPals = ReadPkcs12Collection(rawData, password, ephemeralSpecified); return new AndroidCertLoader(certPals); } @@ -34,10 +35,15 @@ internal static partial ILoaderPal FromBlob(ReadOnlySpan rawData, SafePass } } + internal static partial ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) + { + return FromBlob(rawData, password, readingFromFile: false, keyStorageFlags: keyStorageFlags); + } + internal static partial ILoaderPal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) { byte[] fileBytes = File.ReadAllBytes(fileName); - return FromBlob(fileBytes, password, keyStorageFlags); + return FromBlob(fileBytes, password, readingFromFile: true, keyStorageFlags: keyStorageFlags); } internal static partial IExportPal FromCertificate(ICertificatePalCore cert) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.OpenSsl.cs index e1c4a49284961c..4d31433360c0b1 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.OpenSsl.cs @@ -37,7 +37,7 @@ internal static partial ILoaderPal FromBlob(ReadOnlySpan rawData, SafePass if (OpenSslPkcsFormatReader.TryReadPkcs7Der(rawData, out certPals) || OpenSslPkcsFormatReader.TryReadPkcs7Pem(rawData, out certPals) || - OpenSslPkcsFormatReader.TryReadPkcs12(rawData, password, ephemeralSpecified, out certPals, out openSslException)) + OpenSslPkcsFormatReader.TryReadPkcs12(rawData, password, ephemeralSpecified, readingFromFile: false, out certPals, out openSslException)) { Debug.Assert(certPals != null); @@ -108,7 +108,7 @@ private static ILoaderPal FromBio( // Capture the exception so in case of failure, the call to BioSeek does not override it. Exception? openSslException; byte[] data = File.ReadAllBytes(fileName); - if (OpenSslPkcsFormatReader.TryReadPkcs12(data, password, ephemeralSpecified, out certPals, out openSslException)) + if (OpenSslPkcsFormatReader.TryReadPkcs12(data, password, ephemeralSpecified, readingFromFile: true, out certPals, out openSslException)) { return ListToLoaderPal(certPals); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.Windows.Import.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.Windows.Import.cs index b4d796eb4357fb..b76d6ce25f87ca 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.Windows.Import.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.Windows.Import.cs @@ -68,6 +68,11 @@ private static StorePal FromBlobOrFile(ReadOnlySpan rawData, string? fileN { rawData = File.ReadAllBytes(fileName!); } + else + { + X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile: false, password.PasswordProvided); + } + fixed (byte* pRawData2 = rawData) { Interop.Crypt32.DATA_BLOB blob2 = new Interop.Crypt32.DATA_BLOB(new IntPtr(pRawData2), (uint)rawData!.Length); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.iOS.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.iOS.cs index 02c3af95fa66f3..41ce25dcb00d11 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.iOS.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.iOS.cs @@ -15,7 +15,7 @@ internal static partial IStorePal FromHandle(IntPtr storeHandle) throw new PlatformNotSupportedException($"{nameof(StorePal)}.{nameof(FromHandle)}"); } - internal static partial ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) + private static ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle password, bool readingFromFile, X509KeyStorageFlags keyStorageFlags) { List? certificateList = null; @@ -23,8 +23,8 @@ internal static partial ILoaderPal FromBlob(ReadOnlySpan rawData, SafePass rawData, (derData, contentType) => { - certificateList ??= new List(); - certificateList.Add(AppleCertificatePal.FromDerBlob(derData, contentType, password, keyStorageFlags)); + certificateList = certificateList ?? new List(); + certificateList.Add(AppleCertificatePal.FromDerBlob(derData, contentType, password, readingFromFile, keyStorageFlags)); return true; }); @@ -45,6 +45,7 @@ internal static partial ILoaderPal FromBlob(ReadOnlySpan rawData, SafePass if (contentType == X509ContentType.Pkcs12) { + X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile, password.PasswordProvided); ApplePkcs12Reader reader = new ApplePkcs12Reader(rawData); try @@ -94,12 +95,17 @@ internal static partial ILoaderPal FromBlob(ReadOnlySpan rawData, SafePass return new CertCollectionLoader(certificateList); } + internal static partial ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) + { + return FromBlob(rawData, password, readingFromFile: false, keyStorageFlags); + } + internal static partial ILoaderPal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) { Debug.Assert(password != null); byte[] fileBytes = File.ReadAllBytes(fileName); - return FromBlob(fileBytes, password, keyStorageFlags); + return FromBlob(fileBytes, password, readingFromFile: true, keyStorageFlags); } internal static partial IExportPal FromCertificate(ICertificatePalCore cert) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.macOS.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.macOS.cs index 5587d6330e5cdc..69e48fb4868780 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.macOS.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.macOS.cs @@ -25,6 +25,11 @@ internal static partial IStorePal FromHandle(IntPtr storeHandle) } internal static partial ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) + { + return FromBlob(rawData, password, readingFromFile: false, keyStorageFlags); + } + + private static ILoaderPal FromBlob(ReadOnlySpan rawData, SafePasswordHandle password, bool readingFromFile, X509KeyStorageFlags keyStorageFlags) { Debug.Assert(password != null); @@ -37,6 +42,7 @@ internal static partial ILoaderPal FromBlob(ReadOnlySpan rawData, SafePass throw new PlatformNotSupportedException(SR.Cryptography_X509_NoEphemeralPfx); } + X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile, password.PasswordProvided); bool exportable = (keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable; bool persist = @@ -86,7 +92,7 @@ internal static partial ILoaderPal FromFile(string fileName, SafePasswordHandle Debug.Assert(password != null); byte[] fileBytes = File.ReadAllBytes(fileName); - return FromBlob(fileBytes, password, keyStorageFlags); + return FromBlob(fileBytes, password, readingFromFile: true, keyStorageFlags); } internal static partial IExportPal FromCertificate(ICertificatePalCore cert) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs index 59a82d473dc575..4abf6efbbe0f37 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs @@ -3,11 +3,14 @@ using Internal.Cryptography; using Microsoft.Win32.SafeHandles; +using System.Buffers; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Formats.Asn1; using System.Globalization; using System.Runtime.Serialization; using System.Runtime.Versioning; +using System.Security.Cryptography.Asn1.Pkcs12; using System.Text; namespace System.Security.Cryptography.X509Certificates @@ -62,7 +65,7 @@ private protected X509Certificate(ReadOnlySpan data) if (!data.IsEmpty) { // For compat reasons, this constructor treats passing a null or empty data set as the same as calling the nullary constructor. - using (var safePasswordHandle = new SafePasswordHandle((string?)null)) + using (var safePasswordHandle = new SafePasswordHandle((string?)null, passwordProvided: false)) { Pal = CertificatePal.FromBlob(data, safePasswordHandle, X509KeyStorageFlags.DefaultKeySet); } @@ -90,7 +93,7 @@ public X509Certificate(byte[] rawData, string? password, X509KeyStorageFlags key ValidateKeyStorageFlags(keyStorageFlags); - using (var safePasswordHandle = new SafePasswordHandle(password)) + using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true)) { Pal = CertificatePal.FromBlob(rawData, safePasswordHandle, keyStorageFlags); } @@ -105,7 +108,7 @@ public X509Certificate(byte[] rawData, SecureString? password, X509KeyStorageFla ValidateKeyStorageFlags(keyStorageFlags); - using (var safePasswordHandle = new SafePasswordHandle(password)) + using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true)) { Pal = CertificatePal.FromBlob(rawData, safePasswordHandle, keyStorageFlags); } @@ -118,7 +121,7 @@ private protected X509Certificate(ReadOnlySpan rawData, ReadOnlySpan ValidateKeyStorageFlags(keyStorageFlags); - using (var safePasswordHandle = new SafePasswordHandle(password)) + using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true)) { Pal = CertificatePal.FromBlob(rawData, safePasswordHandle, keyStorageFlags); } @@ -162,7 +165,7 @@ public X509Certificate(string fileName, string? password, X509KeyStorageFlags ke ValidateKeyStorageFlags(keyStorageFlags); - using (var safePasswordHandle = new SafePasswordHandle(password)) + using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true)) { Pal = CertificatePal.FromFile(fileName, safePasswordHandle, keyStorageFlags); } @@ -174,7 +177,7 @@ private protected X509Certificate(string fileName, ReadOnlySpan password, ValidateKeyStorageFlags(keyStorageFlags); - using (var safePasswordHandle = new SafePasswordHandle(password)) + using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true)) { Pal = CertificatePal.FromFile(fileName, safePasswordHandle, keyStorageFlags); } @@ -190,7 +193,7 @@ public X509Certificate(string fileName, SecureString? password, X509KeyStorageFl ValidateKeyStorageFlags(keyStorageFlags); - using (var safePasswordHandle = new SafePasswordHandle(password)) + using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true)) { Pal = CertificatePal.FromFile(fileName, safePasswordHandle, keyStorageFlags); } @@ -302,7 +305,7 @@ public virtual byte[] Export(X509ContentType contentType, string? password) if (Pal == null) throw new CryptographicException(ErrorCode.E_POINTER); // Not the greatest error, but needed for backward compat. - using (var safePasswordHandle = new SafePasswordHandle(password)) + using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true)) { return Pal.Export(contentType, safePasswordHandle); } @@ -316,7 +319,7 @@ public virtual byte[] Export(X509ContentType contentType, SecureString? password if (Pal == null) throw new CryptographicException(ErrorCode.E_POINTER); // Not the greatest error, but needed for backward compat. - using (var safePasswordHandle = new SafePasswordHandle(password)) + using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true)) { return Pal.Export(contentType, safePasswordHandle); } @@ -680,6 +683,88 @@ private static void VerifyContentType(X509ContentType contentType) throw new CryptographicException(SR.Cryptography_X509_InvalidContentType); } + internal static void EnforceIterationCountLimit(ReadOnlySpan pkcs12, bool readingFromFile, bool passwordProvided) + { + if (readingFromFile || passwordProvided) + { + return; + } + + long pkcs12UnspecifiedPasswordIterationLimit = LocalAppContextSwitches.Pkcs12UnspecifiedPasswordIterationLimit; + + // -1 = no limit + if (LocalAppContextSwitches.Pkcs12UnspecifiedPasswordIterationLimit == -1) + { + return; + } + + // any other negative number means use default limits + if (pkcs12UnspecifiedPasswordIterationLimit < 0) + { + pkcs12UnspecifiedPasswordIterationLimit = LocalAppContextSwitches.DefaultPkcs12UnspecifiedPasswordIterationLimit; + } + + try + { + try + { + checked + { + KdfWorkLimiter.SetIterationLimit((ulong)pkcs12UnspecifiedPasswordIterationLimit); + ulong observedIterationCount = GetIterationCount(pkcs12); + + // Check both conditions: we want a KDF-exceeded failure anywhere in the system to produce a failure here. + // There are some places within the GetIterationCount method where we optimistically try processing the + // PFX in one manner, and if we see failures we'll swallow any exceptions and try a different manner + // instead. The problem with this is that when we swallow failures, we don't have the ability to add the + // so-far-observed iteration count back to the running total returned by GetIterationCount. This + // potentially allows a clever adversary a window through which to squeeze in work beyond our configured + // limits. To mitigate this risk, we'll fail now if we observed *any* KDF-exceeded failure while processing + // this PFX. + if (observedIterationCount > (ulong)pkcs12UnspecifiedPasswordIterationLimit || KdfWorkLimiter.WasWorkLimitExceeded()) + { + throw new CryptographicException(); // iteration count exceeded + } + } + } + finally + { + KdfWorkLimiter.ResetIterationLimit(); + } + } + catch (Exception ex) + { + // It's important for this catch-all block to be *outside* the inner try/finally + // so that we can prevent exception filters from running before we've had a chance + // to clean up the threadstatic. + throw new CryptographicException(SR.Cryptography_X509_PfxWithoutPassword, ex); + } + } + + internal static ulong GetIterationCount(ReadOnlySpan pkcs12) + { + ulong iterations; + + unsafe + { + fixed (byte* pin = pkcs12) + { + using (var manager = new PointerMemoryManager(pin, pkcs12.Length)) + { + AsnValueReader reader = new AsnValueReader(pkcs12, AsnEncodingRules.BER); + PfxAsn.Decode(ref reader, manager.Memory, out PfxAsn pfx); + + // Don't throw when trailing data is present. + // Windows doesn't have such enforcement as well. + + iterations = pfx.CountTotalIterations(); + } + } + } + + return iterations; + } + internal const X509KeyStorageFlags KeyStorageFlagsAll = X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.MachineKeySet | diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2Collection.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2Collection.cs index bf62d21ac5ae98..4908ce8b94ddbb 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2Collection.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2Collection.cs @@ -106,12 +106,16 @@ public bool Contains(X509Certificate2 certificate) public byte[]? Export(X509ContentType contentType) { - return Export(contentType, password: null); + using (var safePasswordHandle = new SafePasswordHandle((string?)null, passwordProvided: false)) + using (IExportPal storePal = StorePal.LinkFromCertificateCollection(this)) + { + return storePal.Export(contentType, safePasswordHandle); + } } public byte[]? Export(X509ContentType contentType, string? password) { - using (var safePasswordHandle = new SafePasswordHandle(password)) + using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true)) using (IExportPal storePal = StorePal.LinkFromCertificateCollection(this)) { return storePal.Export(contentType, safePasswordHandle); @@ -147,7 +151,11 @@ public void Import(byte[] rawData) /// public void Import(ReadOnlySpan rawData) { - Import(rawData, password: null, keyStorageFlags: X509KeyStorageFlags.DefaultKeySet); + using (var safePasswordHandle = new SafePasswordHandle((string?)null, passwordProvided: false)) + using (ILoaderPal storePal = StorePal.FromBlob(rawData, safePasswordHandle, X509KeyStorageFlags.DefaultKeySet)) + { + storePal.MoveTo(this); + } } public void Import(byte[] rawData, string? password, X509KeyStorageFlags keyStorageFlags = 0) @@ -190,7 +198,7 @@ public void Import(ReadOnlySpan rawData, ReadOnlySpan password, X509 { X509Certificate.ValidateKeyStorageFlags(keyStorageFlags); - using (var safePasswordHandle = new SafePasswordHandle(password)) + using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true)) using (ILoaderPal storePal = StorePal.FromBlob(rawData, safePasswordHandle, keyStorageFlags)) { storePal.MoveTo(this); @@ -199,7 +207,14 @@ public void Import(ReadOnlySpan rawData, ReadOnlySpan password, X509 public void Import(string fileName) { - Import(fileName, password: null, keyStorageFlags: X509KeyStorageFlags.DefaultKeySet); + if (fileName == null) + throw new ArgumentNullException(nameof(fileName)); + + using (var safePasswordHandle = new SafePasswordHandle((string?)null, passwordProvided: false)) + using (ILoaderPal storePal = StorePal.FromFile(fileName, safePasswordHandle, X509KeyStorageFlags.DefaultKeySet)) + { + storePal.MoveTo(this); + } } public void Import(string fileName, string? password, X509KeyStorageFlags keyStorageFlags = 0) @@ -208,7 +223,7 @@ public void Import(string fileName, string? password, X509KeyStorageFlags keySto X509Certificate.ValidateKeyStorageFlags(keyStorageFlags); - using (var safePasswordHandle = new SafePasswordHandle(password)) + using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true)) using (ILoaderPal storePal = StorePal.FromFile(fileName, safePasswordHandle, keyStorageFlags)) { storePal.MoveTo(this); @@ -233,7 +248,7 @@ public void Import(string fileName, ReadOnlySpan password, X509KeyStorageF X509Certificate.ValidateKeyStorageFlags(keyStorageFlags); - using (var safePasswordHandle = new SafePasswordHandle(password)) + using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true)) using (ILoaderPal storePal = StorePal.FromFile(fileName, safePasswordHandle, keyStorageFlags)) { storePal.MoveTo(this); diff --git a/src/libraries/System.Security.Cryptography/tests/AesManagedTests.cs b/src/libraries/System.Security.Cryptography/tests/AesManagedTests.cs index e38c92d655416b..2db62616df8a5b 100644 --- a/src/libraries/System.Security.Cryptography/tests/AesManagedTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/AesManagedTests.cs @@ -11,6 +11,7 @@ namespace System.Security.Cryptography.Tests /// /// Since AesManaged wraps Aes, we only test minimally here. /// + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public class AesManagedTests { [Fact] @@ -26,7 +27,6 @@ public static void VerifyDefaults() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void EncryptDecryptKnownECB192() { byte[] plainTextBytes = diff --git a/src/libraries/System.Security.Cryptography/tests/AesTests.Browser.cs b/src/libraries/System.Security.Cryptography/tests/AesTests.Browser.cs deleted file mode 100644 index 6e2d7322f53504..00000000000000 --- a/src/libraries/System.Security.Cryptography/tests/AesTests.Browser.cs +++ /dev/null @@ -1,75 +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.Reflection; -using Xunit; - -namespace System.Security.Cryptography.Tests -{ - public partial class AesTests - { - private static byte[] s_plainText = new byte[] { 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59 }; - private static byte[] s_iv = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - private static byte[] s_destination = new byte[s_plainText.Length]; - - [Fact] - public static void AesThrows_PlatformNotSupported_CipherMode_Browser() - { - using (Aes aes = Aes.Create()) - { - Assert.Throws(() => aes.EncryptEcb(s_plainText, PaddingMode.PKCS7)); - Assert.Throws(() => aes.EncryptEcb(s_plainText.AsSpan(), PaddingMode.PKCS7)); - Assert.Throws(() => aes.EncryptEcb(s_plainText.AsSpan(), s_destination, PaddingMode.PKCS7)); - Assert.Throws(() => aes.DecryptEcb(s_plainText, PaddingMode.PKCS7)); - Assert.Throws(() => aes.DecryptEcb(s_plainText.AsSpan(), PaddingMode.PKCS7)); - Assert.Throws(() => aes.DecryptEcb(s_plainText.AsSpan(), s_destination, PaddingMode.PKCS7)); - - Assert.Throws(() => aes.EncryptCfb(s_plainText, s_iv)); - Assert.Throws(() => aes.EncryptCfb(s_plainText.AsSpan(), s_iv.AsSpan())); - Assert.Throws(() => aes.EncryptCfb(s_plainText.AsSpan(), s_iv, s_destination)); - Assert.Throws(() => aes.DecryptCfb(s_plainText, s_iv)); - Assert.Throws(() => aes.DecryptCfb(s_plainText.AsSpan(), s_iv.AsSpan())); - Assert.Throws(() => aes.DecryptCfb(s_plainText.AsSpan(), s_iv, s_destination)); - - aes.Mode = CipherMode.ECB; - Assert.Throws(() => aes.CreateEncryptor()); - Assert.Throws(() => aes.CreateEncryptor(s_iv, s_iv)); - Assert.Throws(() => aes.CreateDecryptor()); - Assert.Throws(() => aes.CreateDecryptor(s_iv, s_iv)); - - aes.Mode = CipherMode.CFB; - Assert.Throws(() => aes.CreateEncryptor()); - Assert.Throws(() => aes.CreateEncryptor(s_iv, s_iv)); - Assert.Throws(() => aes.CreateDecryptor()); - Assert.Throws(() => aes.CreateDecryptor(s_iv, s_iv)); - } - } - - // Browser's SubtleCrypto doesn't support AES-192 - [Fact] - public static void Aes_InvalidKeySize_192_Browser() - { - byte[] key192 = new byte[192 / 8]; - using (Aes aes = Aes.Create()) - { - Assert.False(aes.ValidKeySize(192)); - Assert.Throws(() => aes.Key = key192); - Assert.Throws(() => aes.KeySize = 192); - Assert.Throws(() => aes.CreateEncryptor(key192, s_iv)); - Assert.Throws(() => aes.CreateDecryptor(key192, s_iv)); - } - } - - [Fact] - public static void EnsureSubtleCryptoIsUsed() - { - bool canUseSubtleCrypto = (bool)Type.GetType("Interop+BrowserCrypto, System.Security.Cryptography") - .GetField("CanUseSubtleCrypto", BindingFlags.NonPublic | BindingFlags.Static) - .GetValue(null); - - bool expectedCanUseSubtleCrypto = Environment.GetEnvironmentVariable("TEST_EXPECT_SUBTLE_CRYPTO") == "true"; - - Assert.Equal(expectedCanUseSubtleCrypto, canUseSubtleCrypto); - } - } -} diff --git a/src/libraries/System.Security.Cryptography/tests/AesTests.cs b/src/libraries/System.Security.Cryptography/tests/AesTests.cs index e9ee4b435818be..815014cc908b76 100644 --- a/src/libraries/System.Security.Cryptography/tests/AesTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/AesTests.cs @@ -5,6 +5,7 @@ namespace System.Security.Cryptography.Tests { + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public partial class AesTests { [Fact] diff --git a/src/libraries/System.Security.Cryptography/tests/AsymmetricAlgorithmTests.cs b/src/libraries/System.Security.Cryptography/tests/AsymmetricAlgorithmTests.cs index ca40479e6c2583..0a0011d1646c5e 100644 --- a/src/libraries/System.Security.Cryptography/tests/AsymmetricAlgorithmTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/AsymmetricAlgorithmTests.cs @@ -370,7 +370,7 @@ public static void ExportPem_ExportEncryptedPkcs8PrivateKeyPem() PbeParameters expectedPbeParameters = new PbeParameters( PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA384, - RandomNumberGenerator.GetInt32(0, 100_000)); + RandomNumberGenerator.GetInt32(1, 100_000)); byte[] ExportEncryptedPkcs8PrivateKey(ReadOnlySpan password, PbeParameters pbeParameters) { @@ -407,7 +407,7 @@ public static void ExportPem_TryExportEncryptedPkcs8PrivateKeyPem() PbeParameters expectedPbeParameters = new PbeParameters( PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA384, - RandomNumberGenerator.GetInt32(0, 100_000)); + RandomNumberGenerator.GetInt32(1, 100_000)); bool TryExportEncryptedPkcs8PrivateKey( ReadOnlySpan password, diff --git a/src/libraries/System.Security.Cryptography/tests/CryptoConfigTests.cs b/src/libraries/System.Security.Cryptography/tests/CryptoConfigTests.cs index babea22f6cdaf0..964b4668a61a90 100644 --- a/src/libraries/System.Security.Cryptography/tests/CryptoConfigTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/CryptoConfigTests.cs @@ -106,36 +106,23 @@ public static void NamedKeyedHashAlgorithmCreate(string identifier, Type actualT } } - public static IEnumerable NamedSymmetricAlgorithmCreateData - { - get - { - yield return new object[] { "AES", typeof(Aes) }; -#pragma warning disable SYSLIB0022 // Rijndael types are obsolete - yield return new object[] { "Rijndael", typeof(Rijndael) }; - yield return new object[] { "System.Security.Cryptography.Rijndael", typeof(Rijndael) }; -#pragma warning restore SYSLIB0022 - - if (PlatformDetection.IsNotBrowser) - { + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBuiltWithAggressiveTrimming))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/37669", TestPlatforms.Browser)] + [InlineData("AES", typeof(Aes))] #pragma warning disable SYSLIB0022 // Rijndael types are obsolete - yield return new object[] { "http://www.w3.org/2001/04/xmlenc#aes128-cbc", typeof(Rijndael) }; - yield return new object[] { "http://www.w3.org/2001/04/xmlenc#aes192-cbc", typeof(Rijndael) }; - yield return new object[] { "http://www.w3.org/2001/04/xmlenc#aes256-cbc", typeof(Rijndael) }; + [InlineData("Rijndael", typeof(Rijndael))] + [InlineData("System.Security.Cryptography.Rijndael", typeof(Rijndael))] + [InlineData("http://www.w3.org/2001/04/xmlenc#aes128-cbc", typeof(Rijndael))] + [InlineData("http://www.w3.org/2001/04/xmlenc#aes192-cbc", typeof(Rijndael))] + [InlineData("http://www.w3.org/2001/04/xmlenc#aes256-cbc", typeof(Rijndael))] #pragma warning restore SYSLIB0022 - yield return new object[] { "3DES", typeof(TripleDES) }; - yield return new object[] { "TripleDES", typeof(TripleDES) }; - yield return new object[] { "System.Security.Cryptography.TripleDES", typeof(TripleDES) }; - yield return new object[] { "http://www.w3.org/2001/04/xmlenc#tripledes-cbc", typeof(TripleDES) }; - yield return new object[] { "DES", typeof(DES) }; - yield return new object[] { "System.Security.Cryptography.DES", typeof(DES) }; - yield return new object[] { "http://www.w3.org/2001/04/xmlenc#des-cbc", typeof(DES) }; - } - } - } - - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBuiltWithAggressiveTrimming))] - [MemberData(nameof(NamedSymmetricAlgorithmCreateData))] + [InlineData("3DES", typeof(TripleDES))] + [InlineData("TripleDES", typeof(TripleDES))] + [InlineData("System.Security.Cryptography.TripleDES", typeof(TripleDES))] + [InlineData("http://www.w3.org/2001/04/xmlenc#tripledes-cbc", typeof(TripleDES))] + [InlineData("DES", typeof(DES))] + [InlineData("System.Security.Cryptography.DES", typeof(DES))] + [InlineData("http://www.w3.org/2001/04/xmlenc#des-cbc", typeof(DES))] public static void NamedSymmetricAlgorithmCreate(string identifier, Type baseType) { using (SymmetricAlgorithm created = SymmetricAlgorithm.Create(identifier)) @@ -371,15 +358,6 @@ public static IEnumerable AllValidNames yield return new object[] { "HMACSHA512", "System.Security.Cryptography.HMACSHA512", true }; yield return new object[] { "System.Security.Cryptography.HMACSHA512", null, true }; - yield return new object[] { "AES", "System.Security.Cryptography.AesCryptoServiceProvider", true }; - yield return new object[] { "System.Security.Cryptography.AesCryptoServiceProvider", "System.Security.Cryptography.AesCryptoServiceProvider", true }; - yield return new object[] { "AesManaged", typeof(AesManaged).FullName, true }; - yield return new object[] { "System.Security.Cryptography.AesManaged", typeof(AesManaged).FullName, true }; -#pragma warning disable SYSLIB0022 // Rijndael types are obsolete - yield return new object[] { "Rijndael", typeof(RijndaelManaged).FullName, true }; - yield return new object[] { "System.Security.Cryptography.Rijndael", typeof(RijndaelManaged).FullName, true }; -#pragma warning restore SYSLIB0022 // Rijndael types are obsolete - if (PlatformDetection.IsBrowser) { // Hash functions @@ -444,9 +422,15 @@ public static IEnumerable AllValidNames yield return new object[] { "RC2", "System.Security.Cryptography.RC2CryptoServiceProvider", true }; yield return new object[] { "System.Security.Cryptography.RC2", "System.Security.Cryptography.RC2CryptoServiceProvider", true }; #pragma warning disable SYSLIB0022 // Rijndael types are obsolete + yield return new object[] { "Rijndael", typeof(RijndaelManaged).FullName, true }; + yield return new object[] { "System.Security.Cryptography.Rijndael", typeof(RijndaelManaged).FullName, true }; yield return new object[] { "System.Security.Cryptography.SymmetricAlgorithm", typeof(RijndaelManaged).FullName, true }; #pragma warning restore SYSLIB0022 // Rijndael types are obsolete + yield return new object[] { "AES", "System.Security.Cryptography.AesCryptoServiceProvider", true }; yield return new object[] { "AesCryptoServiceProvider", "System.Security.Cryptography.AesCryptoServiceProvider", true }; + yield return new object[] { "System.Security.Cryptography.AesCryptoServiceProvider", "System.Security.Cryptography.AesCryptoServiceProvider", true }; + yield return new object[] { "AesManaged", typeof(AesManaged).FullName, true }; + yield return new object[] { "System.Security.Cryptography.AesManaged", typeof(AesManaged).FullName, true }; // Xml Dsig/ Enc Hash algorithms yield return new object[] { "http://www.w3.org/2000/09/xmldsig#sha1", "System.Security.Cryptography.SHA1CryptoServiceProvider", true }; diff --git a/src/libraries/System.Security.Cryptography/tests/HashAlgorithmTest.cs b/src/libraries/System.Security.Cryptography/tests/HashAlgorithmTest.cs index 015f36c8a979f7..acd7df3a9f8077 100644 --- a/src/libraries/System.Security.Cryptography/tests/HashAlgorithmTest.cs +++ b/src/libraries/System.Security.Cryptography/tests/HashAlgorithmTest.cs @@ -62,19 +62,27 @@ public async Task VerifyComputeHashAsync(int size) [ActiveIssue("https://github.com/dotnet/runtime/issues/37669", TestPlatforms.Browser)] public async Task ComputeHashAsync_SupportsCancellation() { - using (CancellationTokenSource cancellationSource = new CancellationTokenSource(100)) - using (PositionValueStream stream = new SlowPositionValueStream(10000)) + using (CancellationTokenSource cancellationSource = new CancellationTokenSource()) + using (PositionValueStream stream = new SelfCancelingStream(10000, cancellationSource)) using (HashAlgorithm hash = new SummingTestHashAlgorithm()) { + // The stream has a length longer than ComputeHashAsync's read buffer, + // so ReadAsync will get called multiple times. + // The first call succeeds, but moves the cancellation source to canceled, + // and the second call then fails with an OperationCanceledException, canceling the + // whole operation. await Assert.ThrowsAnyAsync( () => hash.ComputeHashAsync(stream, cancellationSource.Token)); + + Assert.True(cancellationSource.IsCancellationRequested); } } [Fact] public void ComputeHashAsync_Disposed() { - using (PositionValueStream stream = new SlowPositionValueStream(10000)) + using (CancellationTokenSource cancellationSource = new CancellationTokenSource()) + using (PositionValueStream stream = new SelfCancelingStream(10000, cancellationSource)) using (HashAlgorithm hash = new SummingTestHashAlgorithm()) { hash.Dispose(); @@ -85,6 +93,10 @@ public void ComputeHashAsync_Disposed() // Not returning or awaiting the Task, it never got created. hash.ComputeHashAsync(stream); }); + + // If SelfCancelingStream.Read (or ReadAsync) was called it will trip cancellation, + // so use that as a signal for whether or not the stream was ever read from. + Assert.False(cancellationSource.IsCancellationRequested, "Stream.Read was invoked"); } } @@ -123,15 +135,19 @@ protected override void HashCore(byte[] array, int ibStart, int cbSize) // implementations by verifying the right value is produced. } - private class SlowPositionValueStream : PositionValueStream + private class SelfCancelingStream : PositionValueStream { - public SlowPositionValueStream(int totalCount) : base(totalCount) + private readonly CancellationTokenSource _cancellationSource; + + public SelfCancelingStream(int totalCount, CancellationTokenSource cancellationSource) + : base(totalCount) { + _cancellationSource = cancellationSource; } public override int Read(byte[] buffer, int offset, int count) { - System.Threading.Thread.Sleep(1000); + _cancellationSource.Cancel(throwOnFirstException: true); return base.Read(buffer, offset, count); } } diff --git a/src/libraries/System.Security.Cryptography/tests/PaddingModeTests.cs b/src/libraries/System.Security.Cryptography/tests/PaddingModeTests.cs index 21541682781985..dbde519a416493 100644 --- a/src/libraries/System.Security.Cryptography/tests/PaddingModeTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/PaddingModeTests.cs @@ -10,6 +10,7 @@ namespace System.Security.Cryptography.Tests { + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public static class PaddingModeTests { [Theory] diff --git a/src/libraries/System.Security.Cryptography/tests/RijndaelTests.cs b/src/libraries/System.Security.Cryptography/tests/RijndaelTests.cs index b35973aa377601..d8e9f975cae498 100644 --- a/src/libraries/System.Security.Cryptography/tests/RijndaelTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/RijndaelTests.cs @@ -13,6 +13,7 @@ namespace System.Security.Cryptography.Tests /// Since RijndaelImplementation (from Rijndael.Create()) and RijndaelManaged classes wrap Aes, /// we only test minimally here. /// + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public class RijndaelTests { [Fact] @@ -89,7 +90,6 @@ public static void VerifyBlocksizeIVNulling() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void EncryptDecryptKnownECB192() { static void test(Rijndael alg) @@ -300,7 +300,6 @@ public static void MultipleBlockDecryptTransform(bool blockAlignedOutput) [InlineData(128)] [InlineData(8)] [InlineData(null)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void CfbFeedbackSizeIsRespected(int? feedbackSize) { // Windows 7 CFB only supports CFB8. diff --git a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj index 9b95e193b873a5..50e110c0445dba 100644 --- a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj +++ b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj @@ -13,10 +13,6 @@ $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) - - $(WasmXHarnessMonoArgs) --setenv=TEST_EXPECT_SUBTLE_CRYPTO=true - $(WasmXHarnessArgs) --web-server-use-cop - true true @@ -313,9 +309,6 @@ - - - diff --git a/src/libraries/System.Security.Permissions/src/System.Security.Permissions.csproj b/src/libraries/System.Security.Permissions/src/System.Security.Permissions.csproj index 1994ef7007b9b3..402336b8beb1da 100644 --- a/src/libraries/System.Security.Permissions/src/System.Security.Permissions.csproj +++ b/src/libraries/System.Security.Permissions/src/System.Security.Permissions.csproj @@ -5,6 +5,9 @@ disable $(NoWarn);nullable true + + false + 0 Provides types supporting Code Access Security (CAS). diff --git a/src/libraries/System.ServiceModel.Syndication/src/System.ServiceModel.Syndication.csproj b/src/libraries/System.ServiceModel.Syndication/src/System.ServiceModel.Syndication.csproj index 3d1b9b9035a0aa..831b3eda41a36c 100644 --- a/src/libraries/System.ServiceModel.Syndication/src/System.ServiceModel.Syndication.csproj +++ b/src/libraries/System.ServiceModel.Syndication/src/System.ServiceModel.Syndication.csproj @@ -5,6 +5,9 @@ $(NoWarn);nullable true true + + false + 0 Provides classes related to service model syndication. diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj b/src/libraries/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj index 518c800ae01135..e4d83be68d0bb2 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj +++ b/src/libraries/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj @@ -4,6 +4,9 @@ true $(NoWarn);CA2249 true + + false + 1 Provides the System.ServiceProcess.ServiceContainer class, which allows you to connect to a running or stopped service, manipulate it, or get information about it. Commonly Used Types: diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs index ddc3e2ea601c5f..59123a336ec3a2 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs +++ b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs @@ -31,6 +31,7 @@ public class ServiceBase : Component private bool _commandPropsFrozen; // set to true once we've use the Can... properties. private bool _disposed; private bool _initialized; + private object _stopLock = new object(); private EventLog? _eventLog; /// @@ -501,27 +502,34 @@ private void DeferredSessionChange(int eventType, int sessionId) // This is a problem when multiple services are hosted in a single process. private unsafe void DeferredStop() { - fixed (SERVICE_STATUS* pStatus = &_status) + lock (_stopLock) { - int previousState = _status.currentState; - - _status.checkPoint = 0; - _status.waitHint = 0; - _status.currentState = ServiceControlStatus.STATE_STOP_PENDING; - SetServiceStatus(_statusHandle, pStatus); - try + // never call SetServiceStatus again after STATE_STOPPED is set. + if (_status.currentState != ServiceControlStatus.STATE_STOPPED) { - OnStop(); - WriteLogEntry(SR.StopSuccessful); - _status.currentState = ServiceControlStatus.STATE_STOPPED; - SetServiceStatus(_statusHandle, pStatus); - } - catch (Exception e) - { - _status.currentState = previousState; - SetServiceStatus(_statusHandle, pStatus); - WriteLogEntry(SR.Format(SR.StopFailed, e), EventLogEntryType.Error); - throw; + fixed (SERVICE_STATUS* pStatus = &_status) + { + int previousState = _status.currentState; + + _status.checkPoint = 0; + _status.waitHint = 0; + _status.currentState = ServiceControlStatus.STATE_STOP_PENDING; + SetServiceStatus(_statusHandle, pStatus); + try + { + OnStop(); + WriteLogEntry(SR.StopSuccessful); + _status.currentState = ServiceControlStatus.STATE_STOPPED; + SetServiceStatus(_statusHandle, pStatus); + } + catch (Exception e) + { + _status.currentState = previousState; + SetServiceStatus(_statusHandle, pStatus); + WriteLogEntry(SR.Format(SR.StopFailed, e), EventLogEntryType.Error); + throw; + } + } } } } @@ -533,14 +541,17 @@ private unsafe void DeferredShutdown() OnShutdown(); WriteLogEntry(SR.ShutdownOK); - if (_status.currentState == ServiceControlStatus.STATE_PAUSED || _status.currentState == ServiceControlStatus.STATE_RUNNING) + lock (_stopLock) { - fixed (SERVICE_STATUS* pStatus = &_status) + if (_status.currentState == ServiceControlStatus.STATE_PAUSED || _status.currentState == ServiceControlStatus.STATE_RUNNING) { - _status.checkPoint = 0; - _status.waitHint = 0; - _status.currentState = ServiceControlStatus.STATE_STOPPED; - SetServiceStatus(_statusHandle, pStatus); + fixed (SERVICE_STATUS* pStatus = &_status) + { + _status.checkPoint = 0; + _status.waitHint = 0; + _status.currentState = ServiceControlStatus.STATE_STOPPED; + SetServiceStatus(_statusHandle, pStatus); + } } } } @@ -654,7 +665,7 @@ private void Initialize(bool multipleServices) { if (!_initialized) { - //Cannot register the service with NT service manatger if the object has been disposed, since finalization has been suppressed. + //Cannot register the service with NT service manager if the object has been disposed, since finalization has been suppressed. if (_disposed) throw new ObjectDisposedException(GetType().Name); @@ -923,8 +934,14 @@ public unsafe void ServiceMainCallback(int argCount, IntPtr argPointer) { string errorMessage = new Win32Exception().Message; WriteLogEntry(SR.Format(SR.StartFailed, errorMessage), EventLogEntryType.Error); - _status.currentState = ServiceControlStatus.STATE_STOPPED; - SetServiceStatus(_statusHandle, pStatus); + lock (_stopLock) + { + if (_status.currentState != ServiceControlStatus.STATE_STOPPED) + { + _status.currentState = ServiceControlStatus.STATE_STOPPED; + SetServiceStatus(_statusHandle, pStatus); + } + } } } } diff --git a/src/libraries/System.Speech/Directory.Build.props b/src/libraries/System.Speech/Directory.Build.props index db2f816d5af405..d43c87c09cb684 100644 --- a/src/libraries/System.Speech/Directory.Build.props +++ b/src/libraries/System.Speech/Directory.Build.props @@ -1,10 +1,6 @@  - - 4.0.0.0 MicrosoftShared windows diff --git a/src/libraries/System.Speech/Directory.Build.targets b/src/libraries/System.Speech/Directory.Build.targets new file mode 100644 index 00000000000000..e8aeeb47a8da9c --- /dev/null +++ b/src/libraries/System.Speech/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + 4.0.0.0 + + diff --git a/src/libraries/System.Speech/src/System.Speech.csproj b/src/libraries/System.Speech/src/System.Speech.csproj index 22506f7bf966e0..0b2fe9a22975e1 100644 --- a/src/libraries/System.Speech/src/System.Speech.csproj +++ b/src/libraries/System.Speech/src/System.Speech.csproj @@ -9,6 +9,9 @@ annotations false true + + false + 0 true true Provides types to perform speech synthesis and speech recognition. diff --git a/src/libraries/System.Text.Encoding.CodePages/src/System.Text.Encoding.CodePages.csproj b/src/libraries/System.Text.Encoding.CodePages/src/System.Text.Encoding.CodePages.csproj index 8883abba36b613..dc0552b555db65 100644 --- a/src/libraries/System.Text.Encoding.CodePages/src/System.Text.Encoding.CodePages.csproj +++ b/src/libraries/System.Text.Encoding.CodePages/src/System.Text.Encoding.CodePages.csproj @@ -4,6 +4,9 @@ true true true + + false + 0 true Provides support for code-page based encodings, including Windows-1252, Shift-JIS, and GB2312. diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index d9698c71ce3f00..d9eef23cad5d51 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -3,11 +3,8 @@ using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; -using System.Reflection.Metadata; -using System.Text.Json; using System.Text.Json.Reflection; using System.Text.Json.Serialization; using Microsoft.CodeAnalysis; @@ -28,6 +25,7 @@ private sealed partial class Emitter private const string DefaultOptionsStaticVarName = "s_defaultOptions"; private const string DefaultContextBackingStaticVarName = "s_defaultContext"; internal const string GetConverterFromFactoryMethodName = "GetConverterFromFactory"; + private const string MakeReadOnlyMethodName = "MakeReadOnly"; private const string InfoVarName = "info"; private const string PropertyInfoVarName = "propertyInfo"; internal const string JsonContextVarName = "jsonContext"; @@ -152,7 +150,9 @@ private void AddSource(string fileName, string source, bool isRootContextDef = f bool isInGlobalNamespace = @namespace == JsonConstants.GlobalNamespaceValue; StringBuilder sb = new(@"// -#nullable enable + +#nullable enable annotations +#nullable disable warnings // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0618"); @@ -1013,6 +1013,8 @@ private static string GenerateFastPathFuncForType(TypeGenerationSpec typeGenSpec return $@" +// Intentionally not a static method because we create a delegate to it. Invoking delegates to instance +// methods is almost as fast as virtual calls. Static methods need to go through a shuffle thunk. private void {serializeMethodName}({Utf8JsonWriterTypeRef} {WriterVarName}, {valueTypeRef} {ValueVarName}) {{ {GetEarlyNullCheckSource(emitNullCheck)} @@ -1088,16 +1090,19 @@ private static string GenerateForType(TypeGenerationSpec typeMetadata, string me /// public {typeInfoPropertyTypeRef} {typeFriendlyName} {{ - get => _{typeFriendlyName} ??= {typeMetadata.CreateTypeInfoMethodName}({OptionsInstanceVariableName}); + get => _{typeFriendlyName} ??= {typeMetadata.CreateTypeInfoMethodName}({OptionsInstanceVariableName}, makeReadOnly: true); }} -// Intentionally not a static method because we create a delegate to it. Invoking delegates to instance -// methods is almost as fast as virtual calls. Static methods need to go through a shuffle thunk. -private {typeInfoPropertyTypeRef} {typeMetadata.CreateTypeInfoMethodName}({JsonSerializerOptionsTypeRef} {OptionsLocalVariableName}) +private {typeInfoPropertyTypeRef} {typeMetadata.CreateTypeInfoMethodName}({JsonSerializerOptionsTypeRef} {OptionsLocalVariableName}, bool makeReadOnly) {{ {typeInfoPropertyTypeRef}? {JsonTypeInfoReturnValueLocalVariableName} = null; {WrapWithCheckForCustomConverter(metadataInitSource, typeCompilableName)} + if (makeReadOnly) + {{ + {JsonTypeInfoReturnValueLocalVariableName}.{MakeReadOnlyMethodName}(); + }} + return {JsonTypeInfoReturnValueLocalVariableName}; }} {additionalSource}"; @@ -1274,30 +1279,23 @@ private string GetGetTypeInfoImplementation() // Explicit IJsonTypeInfoResolver implementation sb.AppendLine(); sb.Append(@$"{JsonTypeInfoTypeRef}? {JsonTypeInfoResolverTypeRef}.GetTypeInfo({TypeTypeRef} type, {JsonSerializerOptionsTypeRef} {OptionsLocalVariableName}) -{{ - if ({OptionsInstanceVariableName} == {OptionsLocalVariableName}) - {{ - return this.GetTypeInfo(type); - }} - else - {{"); +{{"); // TODO (https://github.com/dotnet/runtime/issues/52218): Make this Dictionary-lookup-based if root-serializable type count > 64. foreach (TypeGenerationSpec metadata in types) { if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen) { sb.Append($@" - if (type == typeof({metadata.TypeRef})) - {{ - return {metadata.CreateTypeInfoMethodName}({OptionsLocalVariableName}); - }} + if (type == typeof({metadata.TypeRef})) + {{ + return {metadata.CreateTypeInfoMethodName}({OptionsLocalVariableName}, makeReadOnly: false); + }} "); } } sb.Append($@" - return null; - }} + return null; }} "); diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs index 8f4cd15e2dfb9d..1a7df0e821e605 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs @@ -13,7 +13,9 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; +#if !ROSLYN4_4_OR_GREATER using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; +#endif namespace System.Text.Json.SourceGeneration { @@ -27,7 +29,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { IncrementalValuesProvider classDeclarations = context.SyntaxProvider .ForAttributeWithMetadataName( +#if !ROSLYN4_4_OR_GREATER context, +#endif Parser.JsonSerializableAttributeFullName, (node, _) => node is ClassDeclarationSyntax, (context, _) => (ClassDeclarationSyntax)context.TargetNode); diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.Roslyn4.4.csproj b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.Roslyn4.4.csproj new file mode 100644 index 00000000000000..9e538deef6e31b --- /dev/null +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.Roslyn4.4.csproj @@ -0,0 +1,20 @@ + + + + 4.4 + $(MicrosoftCodeAnalysisVersion_4_4) + $(DefineConstants);ROSLYN4_0_OR_GREATER;ROSLYN4_4_OR_GREATER + + + + + + + + + + + + + + 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 0cb93468eb15c6..b31df9113c6035 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -1190,7 +1190,9 @@ public abstract partial class JsonTypeInfo internal JsonTypeInfo() { } public System.Text.Json.Serialization.JsonConverter Converter { get { throw null; } } public System.Func? CreateObject { get { throw null; } set { } } + public bool IsReadOnly { get { throw null; } } public System.Text.Json.Serialization.Metadata.JsonTypeInfoKind Kind { get { throw null; } } + public void MakeReadOnly() { throw null; } public System.Text.Json.Serialization.JsonNumberHandling? NumberHandling { get { throw null; } set { } } public System.Action? OnDeserialized { get { throw null; } set { } } public System.Action? OnDeserializing { get { throw null; } set { } } diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 838a56c7e10cc2..3d8d5ece69ebc3 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -247,10 +247,10 @@ Cannot add callbacks to the 'Modifiers' property after the resolver has been used for the first time. - JsonTypeInfo cannot be changed after first usage. + This JsonTypeInfo instance is marked read-only or has already been used in serialization or deserialization. - JsonPropertyInfo cannot be changed after first usage. + This JsonTypeInfo instance is marked read-only or has already been used in serialization or deserialization. Max depth must be positive. @@ -337,7 +337,7 @@ The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. - Serializer options cannot be changed once serialization or deserialization has occurred. + This JsonSerializerOptions instance is read-only or has already been used in serialization or deserialization. Stream is not writable. 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 e17ce01c770357..4f20eb177dcc3c 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -8,6 +8,8 @@ CS8969 true true + false + 3 true Provides high-performance and low-allocating types that serialize objects to JavaScript Object Notation (JSON) text and deserialize JSON text to objects, with UTF-8 support built-in. Also provides types to read and write JSON text encoded as UTF-8, and to create an in-memory document object model (DOM), that is read-only, for random access of the JSON elements within a structured view of the data. @@ -36,6 +38,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET + @@ -384,5 +387,6 @@ The System.Text.Json library is built-in as part of the shared framework in .NET + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/AppContextSwitchHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/AppContextSwitchHelper.cs new file mode 100644 index 00000000000000..9c028f02165171 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/AppContextSwitchHelper.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.Text.Json +{ + internal static class AppContextSwitchHelper + { + public static bool IsSourceGenReflectionFallbackEnabled => s_isSourceGenReflectionFallbackEnabled; + + private static readonly bool s_isSourceGenReflectionFallbackEnabled = + AppContext.TryGetSwitch( + switchName: "System.Text.Json.Serialization.EnableSourceGenReflectionFallback", + isEnabled: out bool value) + ? value : false; + } +} 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 9e9adcd0a52ea5..4706b64753c971 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 @@ -15,7 +15,7 @@ internal static partial class JsonHelpers /// Returns the span for the given reader. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan GetSpan(this ref Utf8JsonReader reader) + public static ReadOnlySpan GetSpan(this scoped ref Utf8JsonReader reader) { return reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs index ed716927856a87..c087b5f4d44ce6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs @@ -6,7 +6,7 @@ namespace System.Text.Json.Nodes public abstract partial class JsonNode { // linker-safe default JsonSerializerOptions instance used by JsonNode methods. - private protected readonly JsonSerializerOptions s_defaultOptions = new(); + private protected static readonly JsonSerializerOptions s_defaultOptions = new(); /// /// Converts the current instance to string in JSON format. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.ReadCore.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.ReadCore.cs index df4963e3b6ef23..975df8ae20227e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.ReadCore.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.ReadCore.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.Text.Json.Serialization.Metadata; - namespace System.Text.Json.Serialization { public partial class JsonConverter @@ -58,7 +56,7 @@ public partial class JsonConverter } } - bool success = TryRead(ref reader, TypeToConvert, options, ref state, out T? value); + bool success = TryRead(ref reader, state.Current.JsonTypeInfo.Type, options, ref state, out T? value); if (success) { // Read any trailing whitespace. This will throw if JsonCommentHandling=Disallow. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs index f0a7e71dbc965f..497eb3e36dfff5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs @@ -141,18 +141,23 @@ internal void ClearCaches() internal sealed class CachingContext { private readonly ConcurrentDictionary _jsonTypeInfoCache = new(); + private readonly Func _jsonTypeInfoFactory; - public CachingContext(JsonSerializerOptions options) + public CachingContext(JsonSerializerOptions options, int hashCode) { Options = options; + HashCode = hashCode; + _jsonTypeInfoFactory = Options.GetTypeInfoNoCaching; } public JsonSerializerOptions Options { get; } + public int HashCode { get; } // Property only accessed by reflection in testing -- do not remove. // If changing please ensure that src/ILLink.Descriptors.LibraryBuild.xml is up-to-date. public int Count => _jsonTypeInfoCache.Count; - public JsonTypeInfo? GetOrAddJsonTypeInfo(Type type) => _jsonTypeInfoCache.GetOrAdd(type, Options.GetTypeInfoNoCaching); + public JsonTypeInfo? GetOrAddJsonTypeInfo(Type type) => _jsonTypeInfoCache.GetOrAdd(type, _jsonTypeInfoFactory); + public bool TryGetJsonTypeInfo(Type type, [NotNullWhen(true)] out JsonTypeInfo? typeInfo) => _jsonTypeInfoCache.TryGetValue(type, out typeInfo); public void Clear() @@ -163,146 +168,90 @@ public void Clear() /// /// Defines a cache of CachingContexts; instead of using a ConditionalWeakTable which can be slow to traverse - /// this approach uses a concurrent dictionary pointing to weak references of . - /// Relevant caching contexts are looked up using the equality comparison defined by . + /// this approach uses a fixed-size array of weak references of that can be looked up lock-free. + /// Relevant caching contexts are looked up by linear traversal using the equality comparison defined by . /// internal static class TrackedCachingContexts { private const int MaxTrackedContexts = 64; - private static readonly ConcurrentDictionary> s_cache = - new(concurrencyLevel: 1, capacity: MaxTrackedContexts, new EqualityComparer()); - - private const int EvictionCountHistory = 16; - private static readonly Queue s_recentEvictionCounts = new(EvictionCountHistory); - private static int s_evictionRunsToSkip; + private static readonly WeakReference?[] s_trackedContexts = new WeakReference[MaxTrackedContexts]; + private static readonly EqualityComparer s_optionsComparer = new(); public static CachingContext GetOrCreate(JsonSerializerOptions options) { Debug.Assert(options.IsImmutable, "Cannot create caching contexts for mutable JsonSerializerOptions instances"); Debug.Assert(options._typeInfoResolver != null); - ConcurrentDictionary> cache = s_cache; + int hashCode = s_optionsComparer.GetHashCode(options); - if (cache.TryGetValue(options, out WeakReference? wr) && wr.TryGetTarget(out CachingContext? ctx)) + if (TryGetContext(options, hashCode, out int firstUnpopulatedIndex, out CachingContext? result)) { - return ctx; + return result; + } + else if (firstUnpopulatedIndex < 0) + { + // Cache is full; return a fresh instance. + return new CachingContext(options, hashCode); } - lock (cache) + lock (s_trackedContexts) { - if (cache.TryGetValue(options, out wr)) + if (TryGetContext(options, hashCode, out firstUnpopulatedIndex, out result)) { - if (!wr.TryGetTarget(out ctx)) - { - // Found a dangling weak reference; replenish with a fresh instance. - ctx = new CachingContext(options); - wr.SetTarget(ctx); - } - - return ctx; + return result; } - if (cache.Count == MaxTrackedContexts) + var ctx = new CachingContext(options, hashCode); + + if (firstUnpopulatedIndex >= 0) { - if (!TryEvictDanglingEntries()) + // Cache has capacity -- store the context in the first available index. + ref WeakReference? weakRef = ref s_trackedContexts[firstUnpopulatedIndex]; + + if (weakRef is null) { - // Cache is full; return a fresh instance. - return new CachingContext(options); + weakRef = new(ctx); + } + else + { + Debug.Assert(weakRef.TryGetTarget(out _) is false); + weakRef.SetTarget(ctx); } } - Debug.Assert(cache.Count < MaxTrackedContexts); - - // Use a defensive copy of the options instance as key to - // avoid capturing references to any caching contexts. - var key = new JsonSerializerOptions(options); - Debug.Assert(key._cachingContext == null); - - ctx = new CachingContext(options); - bool success = cache.TryAdd(key, new WeakReference(ctx)); - Debug.Assert(success); - return ctx; } } - public static void Clear() - { - lock (s_cache) - { - s_cache.Clear(); - s_recentEvictionCounts.Clear(); - s_evictionRunsToSkip = 0; - } - } - - private static bool TryEvictDanglingEntries() + private static bool TryGetContext( + JsonSerializerOptions options, + int hashCode, + out int firstUnpopulatedIndex, + [NotNullWhen(true)] out CachingContext? result) { - // Worst case scenario, the cache has been filled with permanent entries. - // Evictions are synchronized and each run is in the order of microseconds, - // so we want to avoid triggering runs every time an instance is initialized, - // For this reason we use a backoff strategy to average out the cost of eviction - // across multiple initializations. The backoff count is determined by the eviction - // rates of the most recent runs. - - Debug.Assert(Monitor.IsEntered(s_cache)); - - if (s_evictionRunsToSkip > 0) - { - --s_evictionRunsToSkip; - return false; - } - - int currentEvictions = 0; - foreach (KeyValuePair> kvp in s_cache) - { - if (!kvp.Value.TryGetTarget(out _)) - { - bool result = s_cache.TryRemove(kvp.Key, out _); - Debug.Assert(result); - currentEvictions++; - } - } - - s_evictionRunsToSkip = EstimateEvictionRunsToSkip(currentEvictions); - return currentEvictions > 0; + WeakReference?[] trackedContexts = s_trackedContexts; - // Estimate the number of eviction runs to skip based on recent eviction rates. - static int EstimateEvictionRunsToSkip(int latestEvictionCount) + firstUnpopulatedIndex = -1; + for (int i = 0; i < trackedContexts.Length; i++) { - Queue recentEvictionCounts = s_recentEvictionCounts; + WeakReference? weakRef = trackedContexts[i]; - if (recentEvictionCounts.Count < EvictionCountHistory - 1) + if (weakRef is null || !weakRef.TryGetTarget(out CachingContext? ctx)) { - // Insufficient data points to determine a skip count. - recentEvictionCounts.Enqueue(latestEvictionCount); - return 0; - } - else if (recentEvictionCounts.Count == EvictionCountHistory) - { - recentEvictionCounts.Dequeue(); + if (firstUnpopulatedIndex < 0) + { + firstUnpopulatedIndex = i; + } } - - recentEvictionCounts.Enqueue(latestEvictionCount); - - // Calculate the total number of eviction in the latest runs - // - If we have at least one eviction per run, on average, - // do not skip any future eviction runs. - // - Otherwise, skip ~the number of runs needed per one eviction. - - int totalEvictions = 0; - foreach (int evictionCount in recentEvictionCounts) + else if (hashCode == ctx.HashCode && s_optionsComparer.Equals(options, ctx.Options)) { - totalEvictions += evictionCount; + result = ctx; + return true; } - - int evictionRunsToSkip = - totalEvictions >= EvictionCountHistory ? 0 : - (int)Math.Round((double)EvictionCountHistory / Math.Max(totalEvictions, 1)); - - Debug.Assert(0 <= evictionRunsToSkip && evictionRunsToSkip <= EvictionCountHistory); - return evictionRunsToSkip; } + + result = null; + return false; } } @@ -339,6 +288,7 @@ public bool Equals(JsonSerializerOptions? left, JsonSerializerOptions? right) CompareLists(left._converters, right._converters); static bool CompareLists(ConfigurationList left, ConfigurationList right) + where TValue : class? { int n; if ((n = left.Count) != right.Count) @@ -348,7 +298,7 @@ static bool CompareLists(ConfigurationList left, ConfigurationLi for (int i = 0; i < n; i++) { - if (!left[i]!.Equals(right[i])) + if (left[i] != right[i]) { return false; } @@ -362,35 +312,49 @@ public int GetHashCode(JsonSerializerOptions options) { HashCode hc = default; - hc.Add(options._dictionaryKeyPolicy); - hc.Add(options._jsonPropertyNamingPolicy); - hc.Add(options._readCommentHandling); - hc.Add(options._referenceHandler); - hc.Add(options._encoder); - hc.Add(options._defaultIgnoreCondition); - hc.Add(options._numberHandling); - hc.Add(options._unknownTypeHandling); - hc.Add(options._defaultBufferSize); - hc.Add(options._maxDepth); - hc.Add(options._allowTrailingCommas); - hc.Add(options._ignoreNullValues); - hc.Add(options._ignoreReadOnlyProperties); - hc.Add(options._ignoreReadonlyFields); - hc.Add(options._includeFields); - hc.Add(options._propertyNameCaseInsensitive); - hc.Add(options._writeIndented); - hc.Add(options._typeInfoResolver); - GetHashCode(ref hc, options._converters); - - static void GetHashCode(ref HashCode hc, ConfigurationList list) + AddHashCode(ref hc, options._dictionaryKeyPolicy); + AddHashCode(ref hc, options._jsonPropertyNamingPolicy); + AddHashCode(ref hc, options._readCommentHandling); + AddHashCode(ref hc, options._referenceHandler); + AddHashCode(ref hc, options._encoder); + AddHashCode(ref hc, options._defaultIgnoreCondition); + AddHashCode(ref hc, options._numberHandling); + AddHashCode(ref hc, options._unknownTypeHandling); + AddHashCode(ref hc, options._defaultBufferSize); + AddHashCode(ref hc, options._maxDepth); + AddHashCode(ref hc, options._allowTrailingCommas); + AddHashCode(ref hc, options._ignoreNullValues); + AddHashCode(ref hc, options._ignoreReadOnlyProperties); + AddHashCode(ref hc, options._ignoreReadonlyFields); + AddHashCode(ref hc, options._includeFields); + AddHashCode(ref hc, options._propertyNameCaseInsensitive); + AddHashCode(ref hc, options._writeIndented); + AddHashCode(ref hc, options._typeInfoResolver); + AddListHashCode(ref hc, options._converters); + + return hc.ToHashCode(); + + static void AddListHashCode(ref HashCode hc, ConfigurationList list) { - for (int i = 0; i < list.Count; i++) + int n = list.Count; + for (int i = 0; i < n; i++) { - hc.Add(list[i]); + AddHashCode(ref hc, list[i]); } } - return hc.ToHashCode(); + static void AddHashCode(ref HashCode hc, TValue? value) + { + if (typeof(TValue).IsValueType) + { + hc.Add(value); + } + else + { + Debug.Assert(!typeof(TValue).IsSealed, "Sealed reference types like string should not use this method."); + hc.Add(RuntimeHelpers.GetHashCode(value)); + } + } } #if !NETCOREAPP diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs index 596a9c37bae488..fa543671792fb0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs @@ -628,7 +628,20 @@ internal void InitializeForReflectionSerializer() // Even if a resolver has already been specified, we need to root // the default resolver to gain access to the default converters. DefaultJsonTypeInfoResolver defaultResolver = DefaultJsonTypeInfoResolver.RootDefaultInstance(); - _typeInfoResolver ??= defaultResolver; + + switch (_typeInfoResolver) + { + case null: + // Use the default reflection-based resolver if no resolver has been specified. + _typeInfoResolver = defaultResolver; + break; + + case JsonSerializerContext ctx when AppContextSwitchHelper.IsSourceGenReflectionFallbackEnabled: + // .NET 6 compatibility mode: enable fallback to reflection metadata for JsonSerializerContext + _effectiveJsonTypeInfoResolver = JsonTypeInfoResolver.Combine(ctx, defaultResolver); + break; + } + IsImmutable = true; _isInitializedForReflectionSerializer = true; } @@ -636,6 +649,9 @@ internal void InitializeForReflectionSerializer() internal bool IsInitializedForReflectionSerializer => _isInitializedForReflectionSerializer; private volatile bool _isInitializedForReflectionSerializer; + // Only populated in .NET 6 compatibility mode encoding reflection fallback in source gen + private IJsonTypeInfoResolver? _effectiveJsonTypeInfoResolver; + internal void InitializeForMetadataGeneration() { if (_typeInfoResolver is null) @@ -648,7 +664,7 @@ internal void InitializeForMetadataGeneration() private JsonTypeInfo? GetTypeInfoNoCaching(Type type) { - JsonTypeInfo? info = _typeInfoResolver?.GetTypeInfo(type, this); + JsonTypeInfo? info = (_effectiveJsonTypeInfoResolver ?? _typeInfoResolver)?.GetTypeInfo(type, this); if (info != null) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptionsUpdateHandler.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptionsUpdateHandler.cs index 8f7f94140f0c48..53031f1a3c34eb 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptionsUpdateHandler.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptionsUpdateHandler.cs @@ -23,9 +23,6 @@ public static void ClearCache(Type[]? types) options.Key.ClearCaches(); } - // Flush the shared caching contexts - JsonSerializerOptions.TrackedCachingContexts.Clear(); - // Flush the dynamic method cache ReflectionEmitCachingMemberAccessor.Clear(); } 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 dee80c211cbc92..52a40a61567ccd 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 @@ -268,7 +268,7 @@ internal static JsonPropertyInfo GetPropertyPlaceholder() private protected void VerifyMutable() { - if (_isConfigured) + if (ParentTypeInfo?.IsReadOnly == true) { ThrowHelper.ThrowInvalidOperationException_PropertyInfoImmutable(); } 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 ef345aa231d6bd..c2c57d5b0c9ec7 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 @@ -256,6 +256,23 @@ public JsonPolymorphismOptions? PolymorphismOptions } } + /// + /// Specifies whether the current instance has been locked for modification. + /// + /// + /// A instance can be locked either if + /// it has been passed to one of the methods, + /// has been associated with a instance, + /// or a user explicitly called the method on the instance. + /// + public bool IsReadOnly { get; private set; } + + /// + /// Locks the current instance for further modification. + /// + /// This method is idempotent. + public void MakeReadOnly() => IsReadOnly = true; + private protected JsonPolymorphismOptions? _polymorphismOptions; internal object? CreateObjectWithArgs { get; set; } @@ -482,7 +499,7 @@ internal JsonTypeInfo(Type type, JsonConverter converter, JsonSerializerOptions internal void VerifyMutable() { - if (_isConfigured) + if (IsReadOnly) { ThrowHelper.ThrowInvalidOperationException_TypeInfoImmutable(); } @@ -516,6 +533,7 @@ void ConfigureLocked() { Configure(); + IsReadOnly = true; _isConfigured = true; } catch (Exception e) @@ -699,6 +717,7 @@ public static JsonTypeInfo CreateJsonTypeInfo(Type type, JsonSerializerOptions o /// A blank instance. /// or is null. /// cannot be used for serialization. + /// The instance has been locked for further modification. [RequiresUnreferencedCode(MetadataFactoryRequiresUnreferencedCode)] [RequiresDynamicCode(MetadataFactoryRequiresUnreferencedCode)] public JsonPropertyInfo CreateJsonPropertyInfo(Type propertyType, string name) @@ -718,6 +737,7 @@ public JsonPropertyInfo CreateJsonPropertyInfo(Type propertyType, string name) ThrowHelper.ThrowArgumentException_CannotSerializeInvalidType(nameof(propertyType), propertyType, Type, name); } + VerifyMutable(); JsonPropertyInfo propertyInfo = CreatePropertyUsingReflection(propertyType); propertyInfo.Name = name; @@ -737,7 +757,6 @@ private void PopulatePropertyList() { if (!_isConfigured) { - // Ensure SourceGen had a chance to add properties LateAddProperties(); _properties = new(this); return; @@ -755,6 +774,8 @@ internal void CacheMember(JsonPropertyInfo jsonPropertyInfo, JsonPropertyDiction Debug.Assert(jsonPropertyInfo.MemberName != null, "MemberName can be null in custom JsonPropertyInfo instances and should never be passed in this method"); string memberName = jsonPropertyInfo.MemberName; + jsonPropertyInfo.EnsureChildOf(this); + if (jsonPropertyInfo.IsExtensionData) { if (ExtensionDataProperty != null) @@ -882,12 +903,7 @@ internal void InitializePropertyCache() } else { - // Resolver didn't modify properties - - // Source gen currently when initializes properties - // also assigns JsonPropertyInfo's JsonTypeInfo which causes SO if there are any - // cycles in the object graph. For that reason properties cannot be added immediately. - // This is a no-op for ReflectionJsonTypeInfo + // Resolver didn't modify any properties, create the property cache from scratch. LateAddProperties(); PropertyCache ??= CreatePropertyCache(capacity: 0); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs index 72fb5a48ac0ab3..e56e1fae3a20e0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs @@ -23,11 +23,6 @@ internal ReflectionJsonTypeInfo(JsonConverter converter, JsonSerializerOptions o PopulatePolymorphismMetadata(); MapInterfaceTypesToCallbacks(); - if (PropertyInfoForTypeInfo.ConverterStrategy == ConverterStrategy.Object) - { - AddPropertiesAndParametersUsingReflection(); - } - Func? createObject = Options.MemberAccessorStrategy.CreateConstructor(typeof(T)); SetCreateObjectIfCompatible(createObject); CreateObjectForExtensionDataProperty = createObject; @@ -37,11 +32,17 @@ internal ReflectionJsonTypeInfo(JsonConverter converter, JsonSerializerOptions o converter.ConfigureJsonTypeInfoUsingReflection(this, options); } - [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] - [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] - private void AddPropertiesAndParametersUsingReflection() + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:RequiresUnreferencedCode", + Justification = "The ctor is marked RequiresUnreferencedCode.")] + internal override void LateAddProperties() { - Debug.Assert(PropertyInfoForTypeInfo.ConverterStrategy == ConverterStrategy.Object); + Debug.Assert(!IsConfigured); + Debug.Assert(PropertyCache is null); + + if (Kind != JsonTypeInfoKind.Object) + { + return; + } const BindingFlags BindingFlags = BindingFlags.Instance | diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadBufferState.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadBufferState.cs index efbc842b3687f4..c84c00a35247af 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadBufferState.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadBufferState.cs @@ -122,7 +122,6 @@ public void AdvanceBuffer(int bytesConsumed) // Copy the unprocessed data to the new buffer while shifting the processed bytes. Buffer.BlockCopy(oldBuffer, _offset + bytesConsumed, newBuffer, 0, _count); _buffer = newBuffer; - _offset = 0; _maxCount = _count; // Clear and return the old buffer @@ -133,9 +132,10 @@ public void AdvanceBuffer(int bytesConsumed) { // Shift the processed bytes to the beginning of buffer to make more room. Buffer.BlockCopy(_buffer, _offset + bytesConsumed, _buffer, 0, _count); - _offset = 0; } } + + _offset = 0; } private void ProcessReadBytes() diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs index 65c6fd303c13ca..7cd0e3b45a4a19 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs @@ -112,12 +112,12 @@ private void WriteStringEscapeProperty(scoped ReadOnlySpan propertyName, i Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length); char[]? propertyArray = null; + scoped Span escapedPropertyName; if (firstEscapeIndexProp != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - scoped Span escapedPropertyName; if (length > JsonConstants.StackallocCharThreshold) { propertyArray = ArrayPool.Shared.Rent(length); @@ -269,12 +269,12 @@ private void WriteStringEscapeProperty(scoped ReadOnlySpan utf8PropertyNam Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length); byte[]? propertyArray = null; + scoped Span escapedPropertyName; if (firstEscapeIndexProp != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - scoped Span escapedPropertyName; if (length > JsonConstants.StackallocByteThreshold) { propertyArray = ArrayPool.Shared.Rent(length); @@ -1076,12 +1076,12 @@ private void WriteStringEscapePropertyOrValue(scoped ReadOnlySpan property char[]? valueArray = null; char[]? propertyArray = null; + scoped Span escapedValue; if (firstEscapeIndexVal != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal); - scoped Span escapedValue; if (length > JsonConstants.StackallocCharThreshold) { valueArray = ArrayPool.Shared.Rent(length); @@ -1096,11 +1096,12 @@ private void WriteStringEscapePropertyOrValue(scoped ReadOnlySpan property value = escapedValue.Slice(0, written); } + scoped Span escapedPropertyName; + if (firstEscapeIndexProp != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - scoped Span escapedPropertyName; if (length > JsonConstants.StackallocCharThreshold) { propertyArray = ArrayPool.Shared.Rent(length); @@ -1135,12 +1136,12 @@ private void WriteStringEscapePropertyOrValue(scoped ReadOnlySpan utf8Prop byte[]? valueArray = null; byte[]? propertyArray = null; + scoped Span escapedValue; if (firstEscapeIndexVal != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal); - scoped Span escapedValue; if (length > JsonConstants.StackallocByteThreshold) { valueArray = ArrayPool.Shared.Rent(length); @@ -1155,11 +1156,12 @@ private void WriteStringEscapePropertyOrValue(scoped ReadOnlySpan utf8Prop utf8Value = escapedValue.Slice(0, written); } + scoped Span escapedPropertyName; + if (firstEscapeIndexProp != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - scoped Span escapedPropertyName; if (length > JsonConstants.StackallocByteThreshold) { propertyArray = ArrayPool.Shared.Rent(length); @@ -1194,12 +1196,12 @@ private void WriteStringEscapePropertyOrValue(scoped ReadOnlySpan property byte[]? valueArray = null; char[]? propertyArray = null; + scoped Span escapedValue; if (firstEscapeIndexVal != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal); - scoped Span escapedValue; if (length > JsonConstants.StackallocByteThreshold) { valueArray = ArrayPool.Shared.Rent(length); @@ -1214,11 +1216,12 @@ private void WriteStringEscapePropertyOrValue(scoped ReadOnlySpan property utf8Value = escapedValue.Slice(0, written); } + scoped Span escapedPropertyName; + if (firstEscapeIndexProp != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - scoped Span escapedPropertyName; if (length > JsonConstants.StackallocCharThreshold) { propertyArray = ArrayPool.Shared.Rent(length); @@ -1253,12 +1256,12 @@ private void WriteStringEscapePropertyOrValue(scoped ReadOnlySpan utf8Prop char[]? valueArray = null; byte[]? propertyArray = null; + scoped Span escapedValue; if (firstEscapeIndexVal != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal); - scoped Span escapedValue; if (length > JsonConstants.StackallocCharThreshold) { valueArray = ArrayPool.Shared.Rent(length); @@ -1273,11 +1276,12 @@ private void WriteStringEscapePropertyOrValue(scoped ReadOnlySpan utf8Prop value = escapedValue.Slice(0, written); } + scoped Span escapedPropertyName; + if (firstEscapeIndexProp != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - scoped Span escapedPropertyName; if (length > JsonConstants.StackallocByteThreshold) { propertyArray = ArrayPool.Shared.Rent(length); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs index 7e97443adfabc4..edce3f1655eef7 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs @@ -21,6 +21,70 @@ public static void VariousNestingAndVisibilityLevelsAreSupported() Assert.NotNull(NestedPublicContext.NestedProtectedInternalClass.Default); } + [Fact] + public static void PropertyMetadataIsImmutable() + { + JsonTypeInfo typeInfo = PersonJsonContext.Default.Person; + + Assert.True(typeInfo.IsReadOnly); + Assert.Throws(() => typeInfo.CreateObject = null); + Assert.Throws(() => typeInfo.OnDeserializing = obj => { }); + Assert.Throws(() => typeInfo.Properties.Clear()); + + JsonPropertyInfo propertyInfo = typeInfo.Properties[0]; + Assert.Throws(() => propertyInfo.Name = "differentName"); + Assert.Throws(() => propertyInfo.NumberHandling = JsonNumberHandling.AllowReadingFromString); + Assert.Throws(() => propertyInfo.IsRequired = true); + Assert.Throws(() => propertyInfo.Order = -1); + } + + [Fact] + public static void JsonSerializerContext_GetTypeInfo_MetadataIsImmutable() + { + JsonTypeInfo typeInfo = (JsonTypeInfo)PersonJsonContext.Default.GetTypeInfo(typeof(Person)); + + Assert.True(typeInfo.IsReadOnly); + Assert.Throws(() => typeInfo.CreateObject = null); + Assert.Throws(() => typeInfo.OnDeserializing = obj => { }); + Assert.Throws(() => typeInfo.Properties.Clear()); + + JsonPropertyInfo propertyInfo = typeInfo.Properties[0]; + Assert.Throws(() => propertyInfo.Name = "differentName"); + Assert.Throws(() => propertyInfo.NumberHandling = JsonNumberHandling.AllowReadingFromString); + Assert.Throws(() => propertyInfo.IsRequired = true); + Assert.Throws(() => propertyInfo.Order = -1); + } + + [Fact] + public static void IJsonTypeInfoResolver_GetTypeInfo_MetadataIsMutable() + { + IJsonTypeInfoResolver resolver = PersonJsonContext.Default; + JsonTypeInfo typeInfo = (JsonTypeInfo)resolver.GetTypeInfo(typeof(Person), PersonJsonContext.Default.Options); + + Assert.NotSame(typeInfo, PersonJsonContext.Default.Person); + Assert.False(typeInfo.IsReadOnly); + + JsonTypeInfo typeInfo2 = (JsonTypeInfo)resolver.GetTypeInfo(typeof(Person), PersonJsonContext.Default.Options); + Assert.NotSame(typeInfo, typeInfo2); + Assert.False(typeInfo.IsReadOnly); + + typeInfo.CreateObject = null; + typeInfo.OnDeserializing = obj => { }; + + JsonPropertyInfo propertyInfo = typeInfo.Properties[0]; + propertyInfo.Name = "differentName"; + propertyInfo.NumberHandling = JsonNumberHandling.AllowReadingFromString; + propertyInfo.IsRequired = true; + propertyInfo.Order = -1; + + typeInfo.Properties.Clear(); + Assert.Equal(0, typeInfo.Properties.Count); + + // Changes should not impact other metadata instances + Assert.Equal(2, typeInfo2.Properties.Count); + Assert.Equal(2, PersonJsonContext.Default.Person.Properties.Count); + } + [Fact] public static void VariousGenericsAreSupported() { @@ -380,6 +444,37 @@ public static void SupportsGenericParameterWithCustomConverterFactory() Assert.Equal(@"[""Cee""]", json); } + // Regression test for https://github.com/dotnet/runtime/issues/74652 + [Fact] + public static void ClassWithStringValuesRoundtrips() + { + JsonSerializerOptions options = ClassWithStringValuesContext.Default.Options; + + ClassWithStringValues obj = new() + { + StringValuesProperty = new(new[] { "abc", "def" }) + }; + + string json = JsonSerializer.Serialize(obj, options); + Assert.Equal("""{"StringValuesProperty":["abc","def"]}""", json); + } + + // Regression test for https://github.com/dotnet/runtime/issues/61734 + [Fact] + public static void ClassWithDictionaryPropertyRoundtrips() + { + JsonSerializerOptions options = ClassWithDictionaryPropertyContext.Default.Options; + + ClassWithDictionaryProperty obj = new(new Dictionary() + { + ["foo"] = "bar", + ["test"] = "baz", + }); + + string json = JsonSerializer.Serialize(obj, options); + Assert.Equal("""{"DictionaryProperty":{"foo":"bar","test":"baz"}}""", json); + } + [JsonConverter(typeof(JsonStringEnumConverter))] public enum TestEnum { @@ -394,7 +489,16 @@ internal partial class GenericParameterWithCustomConverterFactoryContext : JsonS [JsonSerializable(typeof(ClassWithPocoListDictionaryAndNullable))] internal partial class ClassWithPocoListDictionaryAndNullablePropertyContext : JsonSerializerContext { + } + [JsonSerializable(typeof(ClassWithStringValues))] + internal partial class ClassWithStringValuesContext : JsonSerializerContext + { + } + + [JsonSerializable(typeof(ClassWithDictionaryProperty))] + internal partial class ClassWithDictionaryPropertyContext : JsonSerializerContext + { } internal class ClassWithPocoListDictionaryAndNullable diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets index a759efb7739842..1b9e0d17b199e8 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets @@ -112,6 +112,10 @@ + + + + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs index 4a52c8ce8179b0..eb4da1df79f20e 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Text.Json.Serialization; +using Microsoft.Extensions.Primitives; namespace System.Text.Json.SourceGeneration.Tests.RepeatedTypes { @@ -275,4 +276,15 @@ public class PublicTestClass { internal class InternalNestedClass { } } + + public sealed class ClassWithStringValues + { + public StringValues StringValuesProperty { get; set; } + } + + public class ClassWithDictionaryProperty + { + public ClassWithDictionaryProperty(Dictionary property) => DictionaryProperty = property; + public Dictionary DictionaryProperty { get; } + } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ContinuationTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ContinuationTests.cs index de4bcaa9191230..f1c73024c5a140 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ContinuationTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ContinuationTests.cs @@ -235,6 +235,31 @@ public static void InvalidJsonShouldFailAtAnyPosition_Sequence( Assert.Equal(expectedFailure.Column, ex.BytePositionInLine); } + [Fact] + public static async Task BomHandlingRegressionTest() + { + byte[] utf8Bom = Encoding.UTF8.GetPreamble(); + byte[] json = """{ "Value" : "Hello" }"""u8.ToArray(); + + using var stream = new MemoryStream(); + stream.Write(utf8Bom, 0, utf8Bom.Length); + stream.Write(json, 0, json.Length); + stream.Position = 0; + + var options = new JsonSerializerOptions + { + DefaultBufferSize = 32 + }; + + Test result = await JsonSerializer.DeserializeAsync(stream, options); + Assert.Equal("Hello", result.Value); + } + + private class Test + { + public string Value { get; set; } + } + private class Chunk : ReadOnlySequenceSegment { public Chunk(string json, int firstSegmentLength) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Polymorphic.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Polymorphic.cs index e61319e55caa14..cbf48b487946ee 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Polymorphic.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Polymorphic.cs @@ -278,5 +278,64 @@ public override void Write(Utf8JsonWriter writer, IRepro value, JsonSeri } } } + + [Fact] + public static void PolymorphicBaseClassConverter_IsPassedCorrectTypeToConvertParameter() + { + // Regression test for https://github.com/dotnet/runtime/issues/77173 + var options = new JsonSerializerOptions { Converters = { new PolymorphicBaseClassConverter() } }; + + // Sanity check -- returns converter for the base class. + JsonConverter converter = options.GetConverter(typeof(DerivedClass)); + Assert.IsAssignableFrom(converter); + + // Validate that the correct typeToConvert parameter is passed in all serialization contexts: + // 1. Typed root value. + // 2. Untyped root value (where the reported regression occured). + // 3. Nested values in POCOs, collections & dictionaries. + + DerivedClass result = JsonSerializer.Deserialize("{}", options); + Assert.IsType(result); + + object objResult = JsonSerializer.Deserialize("{}", typeof(DerivedClass), options); + Assert.IsType(objResult); + + PocoWithDerivedClassProperty pocoResult = JsonSerializer.Deserialize("""{"Value":{}}""", options); + Assert.IsType(pocoResult.Value); + + DerivedClass[] arrayResult = JsonSerializer.Deserialize("[{}]", options); + Assert.IsType(arrayResult[0]); + + Dictionary dictResult = JsonSerializer.Deserialize>("""{"Value":{}}""", options); + Assert.IsType(dictResult["Value"]); + } + + public class BaseClass + { } + + public class DerivedClass : BaseClass + { } + + public class PocoWithDerivedClassProperty + { + public DerivedClass Value { get; set; } + } + + public class PolymorphicBaseClassConverter : JsonConverter + { + public override bool CanConvert(Type typeToConvert) => typeof(BaseClass).IsAssignableFrom(typeToConvert); + + public override BaseClass? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + Assert.Equal(typeof(DerivedClass), typeToConvert); + reader.Skip(); + return (BaseClass)Activator.CreateInstance(typeToConvert); + } + + public override void Write(Utf8JsonWriter writer, BaseClass value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapper.Reflection.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapper.Reflection.cs index 24636171cfc7d1..6296522dd91d3e 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapper.Reflection.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapper.Reflection.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.IO; using System.Runtime.CompilerServices; using System.Text.Json.Nodes; @@ -15,9 +16,9 @@ public abstract partial class JsonSerializerWrapper public static JsonSerializerWrapper SpanSerializer { get; } = new SpanSerializerWrapper(); public static JsonSerializerWrapper StringSerializer { get; } = new StringSerializerWrapper(); public static StreamingJsonSerializerWrapper AsyncStreamSerializer { get; } = new AsyncStreamSerializerWrapper(); - public static StreamingJsonSerializerWrapper AsyncStreamSerializerWithSmallBuffer { get; } = new AsyncStreamSerializerWrapper(forceSmallBufferInOptions: true); + public static StreamingJsonSerializerWrapper AsyncStreamSerializerWithSmallBuffer { get; } = new AsyncStreamSerializerWrapper(forceSmallBufferInOptions: true, forceBomInsertions: true); public static StreamingJsonSerializerWrapper SyncStreamSerializer { get; } = new SyncStreamSerializerWrapper(); - public static StreamingJsonSerializerWrapper SyncStreamSerializerWithSmallBuffer { get; } = new SyncStreamSerializerWrapper(forceSmallBufferInOptions: true); + public static StreamingJsonSerializerWrapper SyncStreamSerializerWithSmallBuffer { get; } = new SyncStreamSerializerWrapper(forceSmallBufferInOptions: true, forceBomInsertions: true); public static JsonSerializerWrapper ReaderWriterSerializer { get; } = new ReaderWriterSerializerWrapper(); public static JsonSerializerWrapper DocumentSerializer { get; } = new DocumentSerializerWrapper(); public static JsonSerializerWrapper ElementSerializer { get; } = new ElementSerializerWrapper(); @@ -120,17 +121,22 @@ public override Task DeserializeWrapper(string json, Type type, JsonSeri private class AsyncStreamSerializerWrapper : StreamingJsonSerializerWrapper { private readonly bool _forceSmallBufferInOptions; + private readonly bool _forceBomInsertions; public override bool IsAsyncSerializer => true; - public AsyncStreamSerializerWrapper(bool forceSmallBufferInOptions = false) + public AsyncStreamSerializerWrapper(bool forceSmallBufferInOptions = false, bool forceBomInsertions = false) { _forceSmallBufferInOptions = forceSmallBufferInOptions; + _forceBomInsertions = forceBomInsertions; } private JsonSerializerOptions? ResolveOptionsInstance(JsonSerializerOptions? options) => _forceSmallBufferInOptions ? JsonSerializerOptionsSmallBufferMapper.ResolveOptionsInstanceWithSmallBuffer(options) : options; + private Stream ResolveReadStream(Stream stream) + => stream is not null && _forceBomInsertions ? new Utf8BomInsertingStream(stream) : stream; + public override Task SerializeWrapper(Stream utf8Json, T value, JsonSerializerOptions options = null) { return JsonSerializer.SerializeAsync(utf8Json, value, ResolveOptionsInstance(options)); @@ -153,38 +159,43 @@ public override Task SerializeWrapper(Stream stream, object value, Type inputTyp public override async Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions options = null) { - return await JsonSerializer.DeserializeAsync(utf8Json, ResolveOptionsInstance(options)); + return await JsonSerializer.DeserializeAsync(ResolveReadStream(utf8Json), ResolveOptionsInstance(options)); } public override async Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions options = null) { - return await JsonSerializer.DeserializeAsync(utf8Json, returnType, ResolveOptionsInstance(options)); + return await JsonSerializer.DeserializeAsync(ResolveReadStream(utf8Json), returnType, ResolveOptionsInstance(options)); } public override async Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo) { - return await JsonSerializer.DeserializeAsync(utf8Json, jsonTypeInfo); + return await JsonSerializer.DeserializeAsync(ResolveReadStream(utf8Json), jsonTypeInfo); } public override async Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerContext context) { - return await JsonSerializer.DeserializeAsync(utf8Json, returnType, context); + return await JsonSerializer.DeserializeAsync(ResolveReadStream(utf8Json), returnType, context); } } private class SyncStreamSerializerWrapper : StreamingJsonSerializerWrapper { private readonly bool _forceSmallBufferInOptions; + private readonly bool _forceBomInsertions; + + public override bool IsAsyncSerializer => false; - public SyncStreamSerializerWrapper(bool forceSmallBufferInOptions = false) + public SyncStreamSerializerWrapper(bool forceSmallBufferInOptions = false, bool forceBomInsertions = false) { _forceSmallBufferInOptions = forceSmallBufferInOptions; + _forceBomInsertions = forceBomInsertions; } private JsonSerializerOptions? ResolveOptionsInstance(JsonSerializerOptions? options) => _forceSmallBufferInOptions ? JsonSerializerOptionsSmallBufferMapper.ResolveOptionsInstanceWithSmallBuffer(options) : options; - public override bool IsAsyncSerializer => false; + private Stream ResolveReadStream(Stream stream) + => stream is not null && _forceBomInsertions ? new Utf8BomInsertingStream(stream) : stream; public override Task SerializeWrapper(Stream utf8Json, T value, JsonSerializerOptions options = null) { @@ -212,25 +223,25 @@ public override Task SerializeWrapper(Stream stream, object value, Type inputTyp public override Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions options = null) { - T result = JsonSerializer.Deserialize(utf8Json, ResolveOptionsInstance(options)); + T result = JsonSerializer.Deserialize(ResolveReadStream(utf8Json), ResolveOptionsInstance(options)); return Task.FromResult(result); } public override Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions options = null) { - object result = JsonSerializer.Deserialize(utf8Json, returnType, ResolveOptionsInstance(options)); + object result = JsonSerializer.Deserialize(ResolveReadStream(utf8Json), returnType, ResolveOptionsInstance(options)); return Task.FromResult(result); } public override Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo) { - T result = JsonSerializer.Deserialize(utf8Json, jsonTypeInfo); + T result = JsonSerializer.Deserialize(ResolveReadStream(utf8Json), jsonTypeInfo); return Task.FromResult(result); } public override Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerContext context) { - object result = JsonSerializer.Deserialize(utf8Json, returnType, context); + object result = JsonSerializer.Deserialize(ResolveReadStream(utf8Json), returnType, context); return Task.FromResult(result); } } @@ -653,5 +664,88 @@ public static JsonSerializerOptions ResolveOptionsInstanceWithSmallBuffer(JsonSe return smallBufferCopy; } } + + private sealed class Utf8BomInsertingStream : Stream + { + private const int Utf8BomLength = 3; + private readonly static byte[] s_utf8Bom = Encoding.UTF8.GetPreamble(); + + private readonly Stream _source; + private byte[]? _prefixBytes; + private int _prefixBytesOffset = 0; + private int _prefixBytesCount = 0; + + public Utf8BomInsertingStream(Stream source) + { + Debug.Assert(source.CanRead); + _source = source; + } + + public override bool CanRead => _source.CanRead; + public override bool CanSeek => false; + public override bool CanWrite => false; + + public override int Read(byte[] buffer, int offset, int count) + { + if (_prefixBytes is null) + { + // This is the first read operation; read the first 3 bytes + // from the source to determine if it already includes a BOM. + // Only insert a BOM if it's missing from the source stream. + + _prefixBytes = new byte[2 * Utf8BomLength]; + int bytesRead = ReadExactlyFromSource(_prefixBytes, Utf8BomLength, Utf8BomLength); + + if (_prefixBytes.AsSpan(Utf8BomLength).SequenceEqual(s_utf8Bom)) + { + _prefixBytesOffset = Utf8BomLength; + _prefixBytesCount = Utf8BomLength; + } + else + { + s_utf8Bom.CopyTo(_prefixBytes, 0); + _prefixBytesOffset = 0; + _prefixBytesCount = Utf8BomLength + bytesRead; + } + } + + int prefixBytesToWrite = Math.Min(_prefixBytesCount, count); + if (prefixBytesToWrite > 0) + { + _prefixBytes.AsSpan(_prefixBytesOffset, prefixBytesToWrite).CopyTo(buffer.AsSpan(offset, count)); + _prefixBytesOffset += prefixBytesToWrite; + _prefixBytesCount -= prefixBytesToWrite; + offset += prefixBytesToWrite; + count -= prefixBytesToWrite; + } + + return prefixBytesToWrite + _source.Read(buffer, offset, count); + } + + private int ReadExactlyFromSource(byte[] buffer, int offset, int count) + { + int totalRead = 0; + + while (totalRead < count) + { + int read = _source.Read(buffer, offset + totalRead, count - totalRead); + if (read == 0) + { + break; + } + + totalRead += read; + } + + return totalRead; + } + + public override long Length => throw new NotSupportedException(); + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + public override void Flush() => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonPropertyInfo.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonPropertyInfo.cs index cb8c53ae646240..84939076aa9bc8 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonPropertyInfo.cs @@ -1700,5 +1700,69 @@ public string? Value set => _value = value ?? "NULL"; } } + + [Fact] + public static void TypeWithIgnoredUnsupportedType_ShouldBeSupported() + { + // Regression test for https://github.com/dotnet/runtime/issues/76807 + + // Sanity check -- metadata resolution for the unsupported type is failing + Assert.Throws(() => JsonSerializerOptions.Default.GetTypeInfo(typeof(UnsupportedType))); + + // Serialization works as expected + string json = JsonSerializer.Serialize(new PocoWithIgnoredUnsupportedType()); + JsonTestHelper.AssertJsonEqual("{}", json); + + // Metadata is reported as expected + JsonTypeInfo jti = JsonSerializerOptions.Default.GetTypeInfo(typeof(PocoWithIgnoredUnsupportedType)); + Assert.Equal(1, jti.Properties.Count); + JsonPropertyInfo propertyInfo = jti.Properties[0]; + Assert.Null(propertyInfo.Get); + Assert.Null(propertyInfo.Set); + } + + public class PocoWithIgnoredUnsupportedType + { + [JsonIgnore] + public UnsupportedType UnsuportedProperty { get; set; } + } + + [Fact] + public static void TypeWithUnIgnoredUnsupportedType_CanModifyUnsupportedType() + { + var options = new JsonSerializerOptions + { + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = + { + static jti => + { + if (jti.Type == typeof(PocoWithUnIgnoredUnsupportedType)) + { + Assert.Equal(1, jti.Properties.Count); + JsonPropertyInfo propertyInfo = jti.Properties[0]; + Assert.Equal(typeof(UnsupportedType), propertyInfo.PropertyType); + + jti.Properties.Clear(); + } + } + } + } + }; + + string json = JsonSerializer.Serialize(new PocoWithUnIgnoredUnsupportedType(), options); + JsonTestHelper.AssertJsonEqual("{}", json); + } + + public class PocoWithUnIgnoredUnsupportedType + { + public UnsupportedType UnsuportedProperty { get; set; } + } + + public class UnsupportedType + { + public ReadOnlySpan Span => Array.Empty(); + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonTypeInfo.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonTypeInfo.cs index a4a08fc290d629..03992b2767cc35 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonTypeInfo.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonTypeInfo.cs @@ -36,6 +36,7 @@ public static void TypeInfoPropertiesDefaults(Type type) JsonTypeInfo ti = r.GetTypeInfo(type, o); + Assert.False(ti.IsReadOnly); Assert.Same(o, ti.Options); Assert.NotNull(ti.Properties); @@ -400,17 +401,25 @@ private static void TestTypeInfoImmutability(JsonTypeInfo typeInfo) Assert.Equal(typeof(T), typeInfo.Type); Assert.True(typeInfo.Converter.CanConvert(typeof(T))); - JsonPropertyInfo prop = typeInfo.CreateJsonPropertyInfo(typeof(string), "foo"); + Assert.True(typeInfo.IsReadOnly); Assert.True(typeInfo.Properties.IsReadOnly); + Assert.Throws(() => typeInfo.CreateJsonPropertyInfo(typeof(string), "foo")); Assert.Throws(() => untyped.CreateObject = untyped.CreateObject); Assert.Throws(() => typeInfo.CreateObject = typeInfo.CreateObject); Assert.Throws(() => typeInfo.NumberHandling = typeInfo.NumberHandling); + Assert.Throws(() => typeInfo.CreateJsonPropertyInfo(typeof(string), "foo")); Assert.Throws(() => typeInfo.Properties.Clear()); - Assert.Throws(() => typeInfo.Properties.Add(prop)); - Assert.Throws(() => typeInfo.Properties.Insert(0, prop)); Assert.Throws(() => typeInfo.PolymorphismOptions = null); Assert.Throws(() => typeInfo.PolymorphismOptions = new()); + if (typeInfo.Properties.Count > 0) + { + JsonPropertyInfo prop = typeInfo.Properties[0]; + Assert.Throws(() => typeInfo.Properties.Add(prop)); + Assert.Throws(() => typeInfo.Properties.Insert(0, prop)); + Assert.Throws(() => typeInfo.Properties.RemoveAt(0)); + } + if (typeInfo.PolymorphismOptions is JsonPolymorphismOptions jpo) { Assert.True(jpo.DerivedTypes.IsReadOnly); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs index a91467a9943a3e..4cde8efc24dac2 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs @@ -437,9 +437,18 @@ public static void Options_JsonSerializerContext_DoesNotFallbackToReflection() } [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] - [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - public static void Options_JsonSerializerContext_GetConverter_DoesNotFallBackToReflectionConverter() + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [InlineData(false)] + [InlineData(true)] + public static void Options_JsonSerializerContext_GetConverter_DoesNotFallBackToReflectionConverter(bool isCompatibilitySwitchExplicitlyDisabled) { + var options = new RemoteInvokeOptions(); + + if (isCompatibilitySwitchExplicitlyDisabled) + { + options.RuntimeConfigurationOptions.Add("System.Text.Json.Serialization.EnableSourceGenReflectionFallback", false); + } + RemoteExecutor.Invoke(static () => { JsonContext context = JsonContext.Default; @@ -460,7 +469,40 @@ public static void Options_JsonSerializerContext_GetConverter_DoesNotFallBackToR Assert.Throws(() => context.Options.GetConverter(typeof(MyClass))); Assert.Throws(() => JsonSerializer.Serialize(unsupportedValue, context.Options)); - }).Dispose(); + }, options).Dispose(); + } + + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public static void Options_JsonSerializerContext_Net6CompatibilitySwitch_FallsBackToReflectionResolver() + { + var options = new RemoteInvokeOptions + { + RuntimeConfigurationOptions = + { + ["System.Text.Json.Serialization.EnableSourceGenReflectionFallback"] = true + } + }; + + RemoteExecutor.Invoke(static () => + { + var unsupportedValue = new MyClass { Value = "value" }; + + // JsonSerializerContext does not return metadata for the type + Assert.Null(JsonContext.Default.GetTypeInfo(typeof(MyClass))); + + // Serialization fails using the JsonSerializerContext overload + Assert.Throws(() => JsonSerializer.Serialize(unsupportedValue, unsupportedValue.GetType(), JsonContext.Default)); + + // Serialization uses reflection fallback using the JsonSerializerOptions overload + string json = JsonSerializer.Serialize(unsupportedValue, JsonContext.Default.Options); + JsonTestHelper.AssertJsonEqual("""{"Value":"value", "Thing":null}""", json); + + // A converter can be resolved when looking up JsonSerializerOptions + JsonConverter converter = JsonContext.Default.Options.GetConverter(typeof(MyClass)); + Assert.IsAssignableFrom>(converter); + + }, options).Dispose(); } [Fact] @@ -952,9 +994,11 @@ public static void GetTypeInfo_MutableOptionsInstance(Type type) options.TypeInfoResolver = new DefaultJsonTypeInfoResolver(); JsonTypeInfo typeInfo = options.GetTypeInfo(type); Assert.Equal(type, typeInfo.Type); + Assert.False(typeInfo.IsReadOnly); JsonTypeInfo typeInfo2 = options.GetTypeInfo(type); Assert.Equal(type, typeInfo2.Type); + Assert.False(typeInfo2.IsReadOnly); Assert.NotSame(typeInfo, typeInfo2); @@ -973,6 +1017,7 @@ public static void GetTypeInfo_ImmutableOptionsInstance(Type type) JsonTypeInfo typeInfo = options.GetTypeInfo(type); Assert.Equal(type, typeInfo.Type); + Assert.True(typeInfo.IsReadOnly); JsonTypeInfo typeInfo2 = options.GetTypeInfo(type); Assert.Same(typeInfo, typeInfo2); @@ -984,6 +1029,7 @@ public static void GetTypeInfo_MutableOptions_CanModifyMetadata() var options = new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; JsonTypeInfo jti = (JsonTypeInfo)options.GetTypeInfo(typeof(TestClassForEncoding)); + Assert.False(jti.IsReadOnly); Assert.Equal(1, jti.Properties.Count); jti.Properties.Clear(); @@ -992,11 +1038,13 @@ public static void GetTypeInfo_MutableOptions_CanModifyMetadata() Assert.Equal("{}", json); // Using JsonTypeInfo will lock JsonSerializerOptions + Assert.True(jti.IsReadOnly); Assert.Throws(() => options.IncludeFields = false); // Getting JsonTypeInfo now should return a fresh immutable instance JsonTypeInfo jti2 = (JsonTypeInfo)options.GetTypeInfo(typeof(TestClassForEncoding)); Assert.NotSame(jti, jti2); + Assert.True(jti2.IsReadOnly); Assert.Equal(1, jti2.Properties.Count); Assert.Throws(() => jti2.Properties.Clear()); diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs index 62fb094cf0f569..539dc095f73df8 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs @@ -126,13 +126,13 @@ private static void EmitRegexDerivedImplementation( if (rm.Tree.CaptureNumberSparseMapping is not null) { writer.Write(" base.Caps = new Hashtable {"); - AppendHashtableContents(writer, rm.Tree.CaptureNumberSparseMapping); + AppendHashtableContents(writer, rm.Tree.CaptureNumberSparseMapping.Cast().OrderBy(de => de.Key as int?)); writer.WriteLine($" }};"); } if (rm.Tree.CaptureNameToNumberMapping is not null) { writer.Write(" base.CapNames = new Hashtable {"); - AppendHashtableContents(writer, rm.Tree.CaptureNameToNumberMapping); + AppendHashtableContents(writer, rm.Tree.CaptureNameToNumberMapping.Cast().OrderBy(de => de.Key as string, StringComparer.Ordinal)); writer.WriteLine($" }};"); } if (rm.Tree.CaptureNames is not null) @@ -152,11 +152,10 @@ private static void EmitRegexDerivedImplementation( writer.WriteLine(runnerFactoryImplementation); writer.WriteLine($"}}"); - static void AppendHashtableContents(IndentedTextWriter writer, Hashtable ht) + static void AppendHashtableContents(IndentedTextWriter writer, IEnumerable contents) { - IDictionaryEnumerator en = ht.GetEnumerator(); string separator = ""; - while (en.MoveNext()) + foreach (DictionaryEntry en in contents) { writer.Write(separator); separator = ", "; @@ -176,8 +175,27 @@ static void AppendHashtableContents(IndentedTextWriter writer, Hashtable ht) } /// Emits the code for the RunnerFactory. This is the actual logic for the regular expression. - private static void EmitRegexDerivedTypeRunnerFactory(IndentedTextWriter writer, RegexMethod rm, Dictionary requiredHelpers) + private static void EmitRegexDerivedTypeRunnerFactory(IndentedTextWriter writer, RegexMethod rm, Dictionary requiredHelpers, bool checkOverflow) { + void EnterCheckOverflow() + { + if (checkOverflow) + { + writer.WriteLine($"unchecked"); + writer.WriteLine($"{{"); + writer.Indent++; + } + } + + void ExitCheckOverflow() + { + if (checkOverflow) + { + writer.Indent--; + writer.WriteLine($"}}"); + } + } + writer.WriteLine($"/// Provides a factory for creating instances to be used by methods on ."); writer.WriteLine($"private sealed class RunnerFactory : RegexRunnerFactory"); writer.WriteLine($"{{"); @@ -212,7 +230,9 @@ private static void EmitRegexDerivedTypeRunnerFactory(IndentedTextWriter writer, writer.WriteLine($" protected override void Scan(ReadOnlySpan inputSpan)"); writer.WriteLine($" {{"); writer.Indent += 3; + EnterCheckOverflow(); (bool needsTryFind, bool needsTryMatch) = EmitScan(writer, rm); + ExitCheckOverflow(); writer.Indent -= 3; writer.WriteLine($" }}"); if (needsTryFind) @@ -224,7 +244,9 @@ private static void EmitRegexDerivedTypeRunnerFactory(IndentedTextWriter writer, writer.WriteLine($" private bool TryFindNextPossibleStartingPosition(ReadOnlySpan inputSpan)"); writer.WriteLine($" {{"); writer.Indent += 3; + EnterCheckOverflow(); EmitTryFindNextPossibleStartingPosition(writer, rm, requiredHelpers); + ExitCheckOverflow(); writer.Indent -= 3; writer.WriteLine($" }}"); } @@ -237,7 +259,9 @@ private static void EmitRegexDerivedTypeRunnerFactory(IndentedTextWriter writer, writer.WriteLine($" private bool TryMatchAtCurrentPosition(ReadOnlySpan inputSpan)"); writer.WriteLine($" {{"); writer.Indent += 3; - EmitTryMatchAtCurrentPosition(writer, rm, requiredHelpers); + EnterCheckOverflow(); + EmitTryMatchAtCurrentPosition(writer, rm, requiredHelpers, checkOverflow); + ExitCheckOverflow(); writer.Indent -= 3; writer.WriteLine($" }}"); } @@ -292,23 +316,24 @@ private static void AddIsWordCharHelper(Dictionary requiredHel } /// Adds the IsBoundary helper to the required helpers collection. - private static void AddIsBoundaryHelper(Dictionary requiredHelpers) + private static void AddIsBoundaryHelper(Dictionary requiredHelpers, bool checkOverflow) { const string IsBoundary = nameof(IsBoundary); if (!requiredHelpers.ContainsKey(IsBoundary)) { + string uncheckedKeyword = checkOverflow ? "unchecked" : ""; requiredHelpers.Add(IsBoundary, new string[] { - "/// Determines whether the specified index is a boundary.", - "[MethodImpl(MethodImplOptions.AggressiveInlining)]", - "internal static bool IsBoundary(ReadOnlySpan inputSpan, int index)", - "{", - " int indexMinus1 = index - 1;", - " return ((uint)indexMinus1 < (uint)inputSpan.Length && IsBoundaryWordChar(inputSpan[indexMinus1])) !=", - " ((uint)index < (uint)inputSpan.Length && IsBoundaryWordChar(inputSpan[index]));", - "", - " static bool IsBoundaryWordChar(char ch) => IsWordChar(ch) || (ch == '\\u200C' | ch == '\\u200D');", - "}", + $"/// Determines whether the specified index is a boundary.", + $"[MethodImpl(MethodImplOptions.AggressiveInlining)]", + $"internal static bool IsBoundary(ReadOnlySpan inputSpan, int index)", + $"{{", + $" int indexMinus1 = index - 1;", + $" return {uncheckedKeyword}((uint)indexMinus1 < (uint)inputSpan.Length && IsBoundaryWordChar(inputSpan[indexMinus1])) !=", + $" {uncheckedKeyword}((uint)index < (uint)inputSpan.Length && IsBoundaryWordChar(inputSpan[index]));", + $"", + $" static bool IsBoundaryWordChar(char ch) => IsWordChar(ch) || (ch == '\\u200C' | ch == '\\u200D');", + $"}}", }); AddIsWordCharHelper(requiredHelpers); @@ -316,27 +341,27 @@ private static void AddIsBoundaryHelper(Dictionary requiredHel } /// Adds the IsECMABoundary helper to the required helpers collection. - private static void AddIsECMABoundaryHelper(Dictionary requiredHelpers) + private static void AddIsECMABoundaryHelper(Dictionary requiredHelpers, bool checkOverflow) { const string IsECMABoundary = nameof(IsECMABoundary); if (!requiredHelpers.ContainsKey(IsECMABoundary)) { + string uncheckedKeyword = checkOverflow ? "unchecked" : ""; requiredHelpers.Add(IsECMABoundary, new string[] { - "/// Determines whether the specified index is a boundary (ECMAScript).", - "[MethodImpl(MethodImplOptions.AggressiveInlining)]", - "internal static bool IsECMABoundary(ReadOnlySpan inputSpan, int index)", - "{", - " int indexMinus1 = index - 1;", - " return ((uint)indexMinus1 < (uint)inputSpan.Length && IsECMAWordChar(inputSpan[indexMinus1])) !=", - " ((uint)index < (uint)inputSpan.Length && IsECMAWordChar(inputSpan[index]));", - "", - " static bool IsECMAWordChar(char ch) =>", - " ((((uint)ch - 'A') & ~0x20) < 26) || // ASCII letter", - " (((uint)ch - '0') < 10) || // digit", - " ch == '_' || // underscore", - " ch == '\\u0130'; // latin capital letter I with dot above", - "}", + $"/// Determines whether the specified index is a boundary (ECMAScript).", + $"[MethodImpl(MethodImplOptions.AggressiveInlining)]", + $"internal static bool IsECMABoundary(ReadOnlySpan inputSpan, int index)", + $"{{", + $" int indexMinus1 = index - 1;", + $" return {uncheckedKeyword}((uint)indexMinus1 < (uint)inputSpan.Length && IsECMAWordChar(inputSpan[indexMinus1])) !=", + $" {uncheckedKeyword}((uint)index < (uint)inputSpan.Length && IsECMAWordChar(inputSpan[index]));", + $"", + $" static bool IsECMAWordChar(char ch) =>", + $" char.IsAsciiLetterOrDigit(ch) ||", + $" ch == '_' ||", + $" ch == '\\u0130'; // latin capital letter I with dot above", + $"}}", }); } } @@ -430,7 +455,7 @@ FindNextStartingPositionMode.LeadingAnchor_RightToLeft_Start or /// Emits the body of the TryFindNextPossibleStartingPosition. private static void EmitTryFindNextPossibleStartingPosition(IndentedTextWriter writer, RegexMethod rm, Dictionary requiredHelpers) { - RegexOptions options = (RegexOptions)rm.Options; + RegexOptions options = rm.Options; RegexTree regexTree = rm.Tree; bool rtl = (options & RegexOptions.RightToLeft) != 0; @@ -1026,7 +1051,7 @@ void EmitLiteralAfterAtomicLoop() } /// Emits the body of the TryMatchAtCurrentPosition. - private static void EmitTryMatchAtCurrentPosition(IndentedTextWriter writer, RegexMethod rm, Dictionary requiredHelpers) + private static void EmitTryMatchAtCurrentPosition(IndentedTextWriter writer, RegexMethod rm, Dictionary requiredHelpers, bool checkOverflow) { // In .NET Framework and up through .NET Core 3.1, the code generated for RegexOptions.Compiled was effectively an unrolled // version of what RegexInterpreter would process. The RegexNode tree would be turned into a series of opcodes via @@ -2669,14 +2694,14 @@ void EmitBoundary(RegexNode node) call = node.Kind is RegexNodeKind.Boundary ? $"!{HelpersTypeName}.IsBoundary" : $"{HelpersTypeName}.IsBoundary"; - AddIsBoundaryHelper(requiredHelpers); + AddIsBoundaryHelper(requiredHelpers, checkOverflow); } else { call = node.Kind is RegexNodeKind.ECMABoundary ? $"!{HelpersTypeName}.IsECMABoundary" : $"{HelpersTypeName}.IsECMABoundary"; - AddIsECMABoundaryHelper(requiredHelpers); + AddIsECMABoundaryHelper(requiredHelpers, checkOverflow); } using (EmitBlock(writer, $"if ({call}(inputSpan, pos{(sliceStaticPos > 0 ? $" + {sliceStaticPos}" : "")}))")) @@ -3848,10 +3873,16 @@ void EmitLoop(RegexNode node) bool isAtomic = rm.Analysis.IsAtomicByAncestor(node); string? startingStackpos = null; - if (isAtomic) + if (isAtomic || minIterations > 1) { + // If the loop is atomic, constructs will need to backtrack around it, and as such any backtracking + // state pushed by the loop should be removed prior to exiting the loop. Similarly, if the loop has + // a minimum iteration count greater than 1, we might end up with at least one successful iteration + // only to find we can't iterate further, and will need to clear any pushed state from the backtracking + // stack. For both cases, we need to store the starting stack index so it can be reset to that position. startingStackpos = ReserveName("startingStackpos"); - writer.WriteLine($"int {startingStackpos} = stackpos;"); + additionalDeclarations.Add($"int {startingStackpos} = 0;"); + writer.WriteLine($"{startingStackpos} = stackpos;"); } string originalDoneLabel = doneLabel; @@ -4044,6 +4075,22 @@ void EmitLoop(RegexNode node) using (EmitBlock(writer, $"if ({CountIsLessThan(iterationCount, minIterations)})")) { writer.WriteLine($"// All possible iterations have matched, but it's below the required minimum of {minIterations}. Fail the loop."); + + // If the minimum iterations is 1, then since we're only here if there are fewer, there must be 0 + // iterations, in which case there's nothing to reset. If, however, the minimum iteration count is + // greater than 1, we need to check if there was at least one successful iteration, in which case + // any backtracking state still set needs to be reset; otherwise, constructs earlier in the sequence + // trying to pop their own state will erroneously pop this state instead. + if (minIterations > 1) + { + Debug.Assert(startingStackpos is not null); + using (EmitBlock(writer, $"if ({iterationCount} != 0)")) + { + writer.WriteLine($"// Ensure any stale backtracking state is removed."); + writer.WriteLine($"stackpos = {startingStackpos};"); + } + } + Goto(originalDoneLabel); } writer.WriteLine(); @@ -4099,8 +4146,10 @@ void EmitLoop(RegexNode node) writer.WriteLine(); // Store the loop's state - EmitStackPush(iterationMayBeEmpty ? - new[] { startingPos!, iterationCount } : + EmitStackPush( + startingPos is not null && startingStackpos is not null ? new[] { startingPos, startingStackpos, iterationCount } : + startingPos is not null ? new[] { startingPos, iterationCount } : + startingStackpos is not null ? new[] { startingStackpos, iterationCount } : new[] { iterationCount }); // Skip past the backtracking section @@ -4111,8 +4160,10 @@ void EmitLoop(RegexNode node) // Emit a backtracking section that restores the loop's state and then jumps to the previous done label string backtrack = ReserveName("LoopBacktrack"); MarkLabel(backtrack, emitSemicolon: false); - EmitStackPop(iterationMayBeEmpty ? - new[] { iterationCount, startingPos! } : + EmitStackPop( + startingPos is not null && startingStackpos is not null ? new[] { iterationCount, startingStackpos, startingPos } : + startingPos is not null ? new[] { iterationCount, startingPos } : + startingStackpos is not null ? new[] { iterationCount, startingStackpos } : new[] { iterationCount }); // We're backtracking. Check the timeout. diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs index ed237b82379f11..da7d936bed5e79 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; [assembly: System.Resources.NeutralResourcesLanguage("en-us")] @@ -37,26 +36,40 @@ public partial class RegexGenerator : IIncrementalGenerator "#pragma warning disable CS0219 // Variable assigned but never used", }; + private record struct CompilationData(bool AllowUnsafe, bool CheckOverflow); + public void Initialize(IncrementalGeneratorInitializationContext context) { + // To avoid invalidating every regex's output when anything from the compilation changes, + // we extract from it the only things we care about. + IncrementalValueProvider compilationDataProvider = + context.CompilationProvider + .Select((x, _) => + x.Options is CSharpCompilationOptions options ? + new CompilationData(options.AllowUnsafe, options.CheckOverflow) : + default); + // Produces one entry per generated regex. This may be: // - Diagnostic in the case of a failure that should end the compilation // - (RegexMethod regexMethod, string runnerFactoryImplementation, Dictionary requiredHelpers) in the case of valid regex // - (RegexMethod regexMethod, string reason, Diagnostic diagnostic) in the case of a limited-support regex - IncrementalValueProvider> codeOrDiagnostics = + IncrementalValueProvider> results = context.SyntaxProvider // Find all MethodDeclarationSyntax nodes attributed with GeneratedRegex and gather the required information. .ForAttributeWithMetadataName( - context, GeneratedRegexAttributeName, (node, _) => node is MethodDeclarationSyntax, GetSemanticTargetForGeneration) .Where(static m => m is not null) + // Incorporate the compilation data, as it impacts code generation. + .Combine(compilationDataProvider) + // Generate the RunnerFactory for each regex, if possible. This is where the bulk of the implementation occurs. - .Select((state, _) => + .Select((methodOrDiagnosticAndCompilationData, _) => { + object? state = methodOrDiagnosticAndCompilationData.Left; if (state is not RegexMethod regexMethod) { Debug.Assert(state is Diagnostic); @@ -76,26 +89,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var writer = new IndentedTextWriter(sw); writer.Indent += 2; writer.WriteLine(); - EmitRegexDerivedTypeRunnerFactory(writer, regexMethod, requiredHelpers); + EmitRegexDerivedTypeRunnerFactory(writer, regexMethod, requiredHelpers, methodOrDiagnosticAndCompilationData.Right.CheckOverflow); writer.Indent -= 2; - return (regexMethod, sw.ToString(), requiredHelpers); + return (regexMethod, sw.ToString(), requiredHelpers, methodOrDiagnosticAndCompilationData.Right); }) .Collect(); - // To avoid invalidating every regex's output when anything from the compilation changes, - // we extract from it the only things we care about: whether unsafe code is allowed, - // and a name based on the assembly's name, and only that information is then fed into - // RegisterSourceOutput along with all of the cached generated data from each regex. - IncrementalValueProvider<(bool AllowUnsafe, string? AssemblyName)> compilationDataProvider = - context.CompilationProvider - .Select((x, _) => (x.Options is CSharpCompilationOptions { AllowUnsafe: true }, x.AssemblyName)); - // When there something to output, take all the generated strings and concatenate them to output, // and raise all of the created diagnostics. - context.RegisterSourceOutput(codeOrDiagnostics.Combine(compilationDataProvider), static (context, compilationDataAndResults) => + context.RegisterSourceOutput(results, static (context, results) => { - ImmutableArray results = compilationDataAndResults.Left; - // Report any top-level diagnostics. bool allFailures = true; foreach (object result in results) @@ -152,7 +155,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.ReportDiagnostic(limitedSupportResult.Item3); regexMethod = limitedSupportResult.Item1; } - else if (result is ValueTuple> regexImpl) + else if (result is ValueTuple, CompilationData> regexImpl) { foreach (KeyValuePair helper in regexImpl.Item3) { @@ -216,11 +219,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) writer.WriteLine(); } } - else if (result is ValueTuple> regexImpl) + else if (result is ValueTuple, CompilationData> regexImpl) { if (!regexImpl.Item1.IsDuplicate) { - EmitRegexDerivedImplementation(writer, regexImpl.Item1, regexImpl.Item2, compilationDataAndResults.Right.AllowUnsafe); + EmitRegexDerivedImplementation(writer, regexImpl.Item1, regexImpl.Item2, regexImpl.Item4.AllowUnsafe); writer.WriteLine(); } } diff --git a/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj b/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj index bf14a855c9014e..4714c7cfb2b306 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj +++ b/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj @@ -13,21 +13,13 @@ - - + + - - - - - - - - diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.cs index 1598c7e380194d..6516af531e2a6f 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.cs @@ -322,6 +322,7 @@ public int[] GetGroupNumbers() { result[(int)de.Value!] = (int)de.Key; } + Array.Sort(result); } return result; diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCaseEquivalences.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCaseEquivalences.cs index 7c65c63e90dfb6..72a61998b8ccf0 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCaseEquivalences.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCaseEquivalences.cs @@ -33,7 +33,7 @@ internal static partial class RegexCaseEquivalences /// If is involved in case conversion, then equivalences will contain the /// span of character which should be considered equal to in a case-insensitive comparison. /// if is involved in case conversion; otherwise, - public static bool TryFindCaseEquivalencesForCharWithIBehavior(char c, CultureInfo culture, ref RegexCaseBehavior mappingBehavior, out ReadOnlySpan equivalences) + public static bool TryFindCaseEquivalencesForCharWithIBehavior(char c, CultureInfo culture, scoped ref RegexCaseBehavior mappingBehavior, out ReadOnlySpan equivalences) { if ((c | 0x20) == 'i' || (c | 0x01) == '\u0131') { 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 642ce1c0b1d260..ffa33732125385 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 @@ -4613,8 +4613,13 @@ void EmitLoop(RegexNode node) bool isAtomic = analysis.IsAtomicByAncestor(node); LocalBuilder? startingStackpos = null; - if (isAtomic) + if (isAtomic || minIterations > 1) { + // If the loop is atomic, constructs will need to backtrack around it, and as such any backtracking + // state pushed by the loop should be removed prior to exiting the loop. Similarly, if the loop has + // a minimum iteration count greater than 1, we might end up with at least one successful iteration + // only to find we can't iterate further, and will need to clear any pushed state from the backtracking + // stack. For both cases, we need to store the starting stack index so it can be reset to that position. startingStackpos = DeclareInt32(); Ldloc(stackpos); Stloc(startingStackpos); @@ -4802,7 +4807,6 @@ void EmitLoop(RegexNode node) } EmitUncaptureUntilPopped(); - // If there's a required minimum iteration count, validate now that we've processed enough iterations. if (minIterations > 0) { @@ -4821,7 +4825,7 @@ void EmitLoop(RegexNode node) // since the only value that wouldn't meet that is 0. if (minIterations > 1) { - // if (iterationCount < minIterations) goto doneLabel/originalDoneLabel; + // if (iterationCount < minIterations) goto doneLabel; Ldloc(iterationCount); Ldc(minIterations); BltFar(doneLabel); @@ -4831,10 +4835,36 @@ void EmitLoop(RegexNode node) { // The child doesn't backtrack, which means there's no other way the matched iterations could // match differently, so if we haven't already greedily processed enough iterations, fail the loop. - // if (iterationCount < minIterations) goto doneLabel/originalDoneLabel; + // if (iterationCount < minIterations) + // { + // if (iterationCount != 0) stackpos = startingStackpos; + // goto originalDoneLabel; + // } + + Label enoughIterations = DefineLabel(); Ldloc(iterationCount); Ldc(minIterations); - BltFar(originalDoneLabel); + Bge(enoughIterations); + + // If the minimum iterations is 1, then since we're only here if there are fewer, there must be 0 + // iterations, in which case there's nothing to reset. If, however, the minimum iteration count is + // greater than 1, we need to check if there was at least one successful iteration, in which case + // any backtracking state still set needs to be reset; otherwise, constructs earlier in the sequence + // trying to pop their own state will erroneously pop this state instead. + if (minIterations > 1) + { + Debug.Assert(startingStackpos is not null); + + Ldloc(iterationCount); + Ldc(0); + BeqFar(originalDoneLabel); + + Ldloc(startingStackpos); + Stloc(stackpos); + } + BrFar(originalDoneLabel); + + MarkLabel(enoughIterations); } } @@ -4888,11 +4918,15 @@ void EmitLoop(RegexNode node) if (analysis.IsInLoop(node)) { // Store the loop's state - EmitStackResizeIfNeeded(1 + (startingPos is not null ? 1 : 0)); + EmitStackResizeIfNeeded(1 + (startingPos is not null ? 1 : 0) + (startingStackpos is not null ? 1 : 0)); if (startingPos is not null) { EmitStackPush(() => Ldloc(startingPos)); } + if (startingStackpos is not null) + { + EmitStackPush(() => Ldloc(startingStackpos)); + } EmitStackPush(() => Ldloc(iterationCount)); // Skip past the backtracking section @@ -4911,6 +4945,11 @@ void EmitLoop(RegexNode node) // startingPos = base.runstack[--runstack]; EmitStackPop(); Stloc(iterationCount); + if (startingStackpos is not null) + { + EmitStackPop(); + Stloc(startingStackpos); + } if (startingPos is not null) { EmitStackPop(); diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexNode.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexNode.cs index ee8e487dd96ad9..3ed8102ed49d1f 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexNode.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexNode.cs @@ -1606,22 +1606,33 @@ static bool CanCombineCounts(int nodeMin, int nodeMax, int nextMin, int nextMax) // Coalescing a loop with its same type case RegexNodeKind.Oneloop or RegexNodeKind.Oneloopatomic or RegexNodeKind.Onelazy or RegexNodeKind.Notoneloop or RegexNodeKind.Notoneloopatomic or RegexNodeKind.Notonelazy when nextNode.Kind == currentNode.Kind && currentNode.Ch == nextNode.Ch: case RegexNodeKind.Setloop or RegexNodeKind.Setloopatomic or RegexNodeKind.Setlazy when nextNode.Kind == currentNode.Kind && currentNode.Str == nextNode.Str: - if (CanCombineCounts(currentNode.M, currentNode.N, nextNode.M, nextNode.N)) + if (nextNode.M > 0 && + currentNode.Kind is RegexNodeKind.Oneloopatomic or RegexNodeKind.Notoneloopatomic or RegexNodeKind.Setloopatomic) { - currentNode.M += nextNode.M; - if (currentNode.N != int.MaxValue) - { - currentNode.N = nextNode.N == int.MaxValue ? int.MaxValue : currentNode.N + nextNode.N; - } - next++; - continue; + // Atomic loops can only be combined if the second loop has no lower bound, as if it has a lower bound, + // combining them changes behavior. Uncombined, the first loop can consume all matching items; + // the second loop might then not be able to meet its minimum and fail. But if they're combined, the combined + // minimum of the sole loop could now be met, introducing matches where there shouldn't have been any. + break; } - break; + + if (!CanCombineCounts(currentNode.M, currentNode.N, nextNode.M, nextNode.N)) + { + break; + } + + currentNode.M += nextNode.M; + if (currentNode.N != int.MaxValue) + { + currentNode.N = nextNode.N == int.MaxValue ? int.MaxValue : currentNode.N + nextNode.N; + } + next++; + continue; // Coalescing a loop with an additional item of the same type - case RegexNodeKind.Oneloop or RegexNodeKind.Oneloopatomic or RegexNodeKind.Onelazy when nextNode.Kind == RegexNodeKind.One && currentNode.Ch == nextNode.Ch: - case RegexNodeKind.Notoneloop or RegexNodeKind.Notoneloopatomic or RegexNodeKind.Notonelazy when nextNode.Kind == RegexNodeKind.Notone && currentNode.Ch == nextNode.Ch: - case RegexNodeKind.Setloop or RegexNodeKind.Setloopatomic or RegexNodeKind.Setlazy when nextNode.Kind == RegexNodeKind.Set && currentNode.Str == nextNode.Str: + case RegexNodeKind.Oneloop or RegexNodeKind.Onelazy when nextNode.Kind == RegexNodeKind.One && currentNode.Ch == nextNode.Ch: + case RegexNodeKind.Notoneloop or RegexNodeKind.Notonelazy when nextNode.Kind == RegexNodeKind.Notone && currentNode.Ch == nextNode.Ch: + case RegexNodeKind.Setloop or RegexNodeKind.Setlazy when nextNode.Kind == RegexNodeKind.Set && currentNode.Str == nextNode.Str: if (CanCombineCounts(currentNode.M, currentNode.N, 1, 1)) { currentNode.M++; @@ -1635,7 +1646,7 @@ static bool CanCombineCounts(int nodeMin, int nodeMax, int nextMin, int nextMax) break; // Coalescing a loop with a subsequent string - case RegexNodeKind.Oneloop or RegexNodeKind.Oneloopatomic or RegexNodeKind.Onelazy when nextNode.Kind == RegexNodeKind.Multi && currentNode.Ch == nextNode.Str![0]: + case RegexNodeKind.Oneloop or RegexNodeKind.Onelazy when nextNode.Kind == RegexNodeKind.Multi && currentNode.Ch == nextNode.Str![0]: { // Determine how many of the multi's characters can be combined. // We already checked for the first, so we know it's at least one. @@ -2056,15 +2067,15 @@ private static bool CanBeMadeAtomic(RegexNode node, RegexNode subsequent, bool i case RegexNodeKind.Multi when node.Ch != subsequent.Str![0]: case RegexNodeKind.End: case RegexNodeKind.EndZ or RegexNodeKind.Eol when node.Ch != '\n': - case RegexNodeKind.Boundary when RegexCharClass.IsBoundaryWordChar(node.Ch): - case RegexNodeKind.NonBoundary when !RegexCharClass.IsBoundaryWordChar(node.Ch): - case RegexNodeKind.ECMABoundary when RegexCharClass.IsECMAWordChar(node.Ch): - case RegexNodeKind.NonECMABoundary when !RegexCharClass.IsECMAWordChar(node.Ch): return true; case RegexNodeKind.Onelazy or RegexNodeKind.Oneloop or RegexNodeKind.Oneloopatomic when subsequent.M == 0 && node.Ch != subsequent.Ch: case RegexNodeKind.Notonelazy or RegexNodeKind.Notoneloop or RegexNodeKind.Notoneloopatomic when subsequent.M == 0 && node.Ch == subsequent.Ch: case RegexNodeKind.Setlazy or RegexNodeKind.Setloop or RegexNodeKind.Setloopatomic when subsequent.M == 0 && !RegexCharClass.CharInClass(node.Ch, subsequent.Str!): + case RegexNodeKind.Boundary when RegexCharClass.IsBoundaryWordChar(node.Ch): + case RegexNodeKind.NonBoundary when !RegexCharClass.IsBoundaryWordChar(node.Ch): + case RegexNodeKind.ECMABoundary when RegexCharClass.IsECMAWordChar(node.Ch): + case RegexNodeKind.NonECMABoundary when !RegexCharClass.IsECMAWordChar(node.Ch): // The loop can be made atomic based on this subsequent node, but we'll need to evaluate the next one as well. break; @@ -2103,14 +2114,14 @@ private static bool CanBeMadeAtomic(RegexNode node, RegexNode subsequent, bool i case RegexNodeKind.Multi when !RegexCharClass.CharInClass(subsequent.Str![0], node.Str!): case RegexNodeKind.End: case RegexNodeKind.EndZ or RegexNodeKind.Eol when !RegexCharClass.CharInClass('\n', node.Str!): - case RegexNodeKind.Boundary when node.Str is RegexCharClass.WordClass or RegexCharClass.DigitClass: - case RegexNodeKind.NonBoundary when node.Str is RegexCharClass.NotWordClass or RegexCharClass.NotDigitClass: - case RegexNodeKind.ECMABoundary when node.Str is RegexCharClass.ECMAWordClass or RegexCharClass.ECMADigitClass: - case RegexNodeKind.NonECMABoundary when node.Str is RegexCharClass.NotECMAWordClass or RegexCharClass.NotDigitClass: return true; case RegexNodeKind.Onelazy or RegexNodeKind.Oneloop or RegexNodeKind.Oneloopatomic when subsequent.M == 0 && !RegexCharClass.CharInClass(subsequent.Ch, node.Str!): case RegexNodeKind.Setlazy or RegexNodeKind.Setloop or RegexNodeKind.Setloopatomic when subsequent.M == 0 && !RegexCharClass.MayOverlap(node.Str!, subsequent.Str!): + case RegexNodeKind.Boundary when node.Str is RegexCharClass.WordClass or RegexCharClass.DigitClass: + case RegexNodeKind.NonBoundary when node.Str is RegexCharClass.NotWordClass or RegexCharClass.NotDigitClass: + case RegexNodeKind.ECMABoundary when node.Str is RegexCharClass.ECMAWordClass or RegexCharClass.ECMADigitClass: + case RegexNodeKind.NonECMABoundary when node.Str is RegexCharClass.NotECMAWordClass or RegexCharClass.NotDigitClass: // The loop can be made atomic based on this subsequent node, but we'll need to evaluate the next one as well. break; @@ -2125,8 +2136,6 @@ private static bool CanBeMadeAtomic(RegexNode node, RegexNode subsequent, bool i // We only get here if the node could be made atomic based on subsequent but subsequent has a lower bound of zero // and thus we need to move subsequent to be the next node in sequence and loop around to try again. - Debug.Assert(subsequent.Kind is RegexNodeKind.Oneloop or RegexNodeKind.Oneloopatomic or RegexNodeKind.Onelazy or RegexNodeKind.Notoneloop or RegexNodeKind.Notoneloopatomic or RegexNodeKind.Notonelazy or RegexNodeKind.Setloop or RegexNodeKind.Setloopatomic or RegexNodeKind.Setlazy); - Debug.Assert(subsequent.M == 0); if (!iterateNullableSubsequent) { return false; diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs index 70390343c3405c..89650f3e27a69a 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs @@ -448,13 +448,13 @@ private int FindEndPosition inputForInnerLoop = _checkTimeout && input.Length - pos > CharsPerTimeoutCheck ? - input.Slice(0, pos + CharsPerTimeoutCheck) : - input; + int innerLoopLength = _checkTimeout && input.Length - pos > CharsPerTimeoutCheck ? + pos + CharsPerTimeoutCheck : + input.Length; bool done = currentState.NfaState is not null ? - FindEndPositionDeltas(inputForInnerLoop, mode, ref pos, ref currentState, ref endPos, ref endStateId, ref initialStatePos, ref initialStatePosCandidate) : - FindEndPositionDeltas(inputForInnerLoop, mode, ref pos, ref currentState, ref endPos, ref endStateId, ref initialStatePos, ref initialStatePosCandidate); + FindEndPositionDeltas(input, innerLoopLength, mode, ref pos, ref currentState, ref endPos, ref endStateId, ref initialStatePos, ref initialStatePosCandidate) : + FindEndPositionDeltas(input, innerLoopLength, mode, ref pos, ref currentState, ref endPos, ref endStateId, ref initialStatePos, ref initialStatePosCandidate); // If the inner loop indicates that the search finished (for example due to reaching a deadend state) or // there is no more input available, then the whole search is done. @@ -466,7 +466,7 @@ private int FindEndPosition - private bool FindEndPositionDeltas(ReadOnlySpan input, RegexRunnerMode mode, + private bool FindEndPositionDeltas(ReadOnlySpan input, int length, RegexRunnerMode mode, ref int posRef, ref CurrentState state, ref int endPosRef, ref int endStateIdRef, ref int initialStatePosRef, ref int initialStatePosCandidateRef) where TStateHandler : struct, IStateHandler where TInputReader : struct, IInputReader @@ -561,7 +561,7 @@ private bool FindEndPositionDeltas= length || !TStateHandler.TryTakeTransition(this, ref state, positionId)) { return false; } diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.GetGroupNames.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.GetGroupNames.Tests.cs index b270ea509ec884..8b1d21f250494e 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.GetGroupNames.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.GetGroupNames.Tests.cs @@ -149,6 +149,10 @@ public void GroupNamesAndNumbers(string pattern, string input, string[] expected int[] numbers = regex.GetGroupNumbers(); Assert.Equal(expectedNumbers.Length, numbers.Length); + for (int i = 0; i < numbers.Length - 1; i++) + { + Assert.True(numbers[i] <= numbers[i + 1]); + } string[] names = regex.GetGroupNames(); Assert.Equal(expectedNames.Length, names.Length); diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs index 02182bd87ede6e..c9e1dd282c2a58 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs @@ -149,6 +149,15 @@ public static IEnumerable Match_MemberData() yield return (Case("(?>[^z]+)z"), "zzzzxyxyxyz123", options, 4, 9, true, "xyxyxyz"); yield return (Case("(?>(?>[^z]+))z"), "zzzzxyxyxyz123", options, 4, 9, true, "xyxyxyz"); yield return (Case("(?>[^z]*)z123"), "zzzzxyxyxyz123", options, 4, 10, true, "xyxyxyz123"); + yield return (Case("(?>a*)a"), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>a*)a+"), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>a+)a+"), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>.*)."), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>.*).+"), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>.+).+"), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>\\w*)\\w"), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>\\w*)\\w+"), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>\\w+)\\w+"), "aaa", options, 0, 3, false, ""); yield return (Case("(?>[^12]+)1"), "121231", options, 0, 6, true, "31"); yield return (Case("(?>[^123]+)1"), "12312341", options, 0, 8, true, "41"); @@ -354,6 +363,15 @@ public static IEnumerable Match_MemberData() yield return ("a*(?:a[ab]*)*", "aaaababbbbbbabababababaaabbb", RegexOptions.None, 0, 28, true, "aaaa"); yield return ("a*(?:a[ab]*?)*?", "aaaababbbbbbabababababaaabbb", RegexOptions.None, 0, 28, true, "aaaa"); + // Sequences of loops + yield return (@"(ver\.? |[_ ]+)?\d+(\.\d+){2,3}$", " Ver 2.0", RegexOptions.IgnoreCase, 0, 8, false, ""); + yield return (@"(?:|a)?(?:\b\d){2,}", " a 0", RegexOptions.None, 0, 4, false, ""); + yield return (@"(?:|a)?(\d){2,}", " a00a", RegexOptions.None, 0, 5, true, "a00"); + yield return (@"^( | )?((\w\d){3,}){3,}", " 12345678901234567", RegexOptions.None, 0, 18, false, ""); + yield return (@"^( | )?((\w\d){3,}){3,}", " 123456789012345678", RegexOptions.None, 0, 19, true, " 123456789012345678"); + yield return (@"^( | )?((\w\d){3,}){3,}", " 123456789012345678", RegexOptions.None, 0, 20, true, " 123456789012345678"); + yield return (@"^( | )?((\w\d){3,}){3,}", " 123456789012345678", RegexOptions.None, 0, 21, false, ""); + // Using beginning/end of string chars \A, \Z: Actual - "\\Aaaa\\w+zzz\\Z" yield return (@"\Aaaa\w+zzz\Z", "aaaasdfajsdlfjzzz", RegexOptions.IgnoreCase, 0, 17, true, "aaaasdfajsdlfjzzz"); yield return (@"\Aaaaaa\w+zzz\Z", "aaaa", RegexOptions.IgnoreCase, 0, 4, false, string.Empty); @@ -1269,6 +1287,22 @@ public void Match_CachedPattern_NewTimeoutApplies(RegexOptions options) Assert.InRange(sw.Elapsed.TotalSeconds, 0, 10); // arbitrary upper bound that should be well above what's needed with a 1ms timeout } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))] + public void NonBacktracking_NoEndAnchorMatchAtTimeoutCheck() + { + // This constant must be at least as large as the one in the implementation that sets the maximum number + // of innermost loop iterations between timeout checks. + const int CharsToTriggerTimeoutCheck = 10000; + // Check that it is indeed large enough to trigger timeouts. If this fails the constant above needs to be larger. + Assert.Throws(() => new Regex("a*", RegexHelpers.RegexOptionNonBacktracking, TimeSpan.FromTicks(1)) + .Match(new string('a', CharsToTriggerTimeoutCheck))); + + // The actual test: ^a*$ shouldn't match in a string ending in 'b' + Regex testPattern = new Regex("^a*$", RegexHelpers.RegexOptionNonBacktracking, TimeSpan.FromHours(1)); + string input = string.Concat(new string('a', CharsToTriggerTimeoutCheck), 'b'); + Assert.False(testPattern.IsMatch(input)); + } + public static IEnumerable Match_Advanced_TestData() { foreach (RegexEngine engine in RegexHelpers.AvailableEngines) diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.MultipleMatches.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.MultipleMatches.Tests.cs index 744fd174351171..bd0b1eab45ce37 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.MultipleMatches.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.MultipleMatches.Tests.cs @@ -276,6 +276,22 @@ public static IEnumerable Matches_TestData() } }; + yield return new object[] + { + engine, + @"\w*\b\w+", "abc def ghij kl m nop qrstuv", RegexOptions.None, + new[] + { + new CaptureData("abc", 0, 3), + new CaptureData("def", 4, 3), + new CaptureData("ghij", 8, 4), + new CaptureData("kl", 13, 2), + new CaptureData("m", 16, 1), + new CaptureData("nop", 18, 3), + new CaptureData("qrstuv", 22, 6), + } + }; + if (!PlatformDetection.IsNetFramework) { // .NET Framework missing fix in https://github.com/dotnet/runtime/pull/1075 @@ -294,6 +310,20 @@ public static IEnumerable Matches_TestData() if (!RegexHelpers.IsNonBacktracking(engine)) { + yield return new object[] + { + engine, + @"(\b(?!ab|nop)\w*\b)\w+", "abc def ghij kl m nop qrstuv", RegexOptions.None, + new[] + { + new CaptureData("def", 4, 3), + new CaptureData("ghij", 8, 4), + new CaptureData("kl", 13, 2), + new CaptureData("m", 16, 1), + new CaptureData("qrstuv", 22, 6), + } + }; + yield return new object[] { engine, diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs index d3a54c331f3d3e..340ed7fbea5dfe 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs @@ -67,14 +67,14 @@ internal static byte[] CreateAssemblyImage(string source, string assemblyName) throw new InvalidOperationException(); } - internal static async Task> RunGenerator( - string code, bool compile = false, LanguageVersion langVersion = LanguageVersion.Preview, MetadataReference[]? additionalRefs = null, bool allowUnsafe = false, CancellationToken cancellationToken = default) + private static async Task<(Compilation, GeneratorDriverRunResult)> RunGeneratorCore( + string code, LanguageVersion langVersion = LanguageVersion.Preview, MetadataReference[]? additionalRefs = null, bool allowUnsafe = false, bool checkOverflow = true, CancellationToken cancellationToken = default) { var proj = new AdhocWorkspace() .AddSolution(SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create())) .AddProject("RegexGeneratorTest", "RegexGeneratorTest.dll", "C#") .WithMetadataReferences(additionalRefs is not null ? References.Concat(additionalRefs) : References) - .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: allowUnsafe) + .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: allowUnsafe, checkOverflow: checkOverflow) .WithNullableContextOptions(NullableContextOptions.Enable)) .WithParseOptions(new CSharpParseOptions(langVersion)) .AddDocument("RegexGenerator.g.cs", SourceText.From(code, Encoding.UTF8)).Project; @@ -87,7 +87,13 @@ internal static async Task> RunGenerator( var generator = new RegexGenerator(); CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { generator.AsSourceGenerator() }, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(langVersion)); GeneratorDriver gd = cgd.RunGenerators(comp!, cancellationToken); - GeneratorDriverRunResult generatorResults = gd.GetRunResult(); + return (comp, gd.GetRunResult()); + } + + internal static async Task> RunGenerator( + string code, bool compile = false, LanguageVersion langVersion = LanguageVersion.Preview, MetadataReference[]? additionalRefs = null, bool allowUnsafe = false, bool checkOverflow = true, CancellationToken cancellationToken = default) + { + (Compilation comp, GeneratorDriverRunResult generatorResults) = await RunGeneratorCore(code, langVersion, additionalRefs, allowUnsafe, checkOverflow, cancellationToken); if (!compile) { return generatorResults.Diagnostics; @@ -107,6 +113,20 @@ internal static async Task> RunGenerator( return generatorResults.Diagnostics.Concat(results.Diagnostics).Where(d => d.Severity != DiagnosticSeverity.Hidden).ToArray(); } + internal static async Task GenerateSourceText( + string code, LanguageVersion langVersion = LanguageVersion.Preview, MetadataReference[]? additionalRefs = null, bool allowUnsafe = false, bool checkOverflow = true, CancellationToken cancellationToken = default) + { + (Compilation comp, GeneratorDriverRunResult generatorResults) = await RunGeneratorCore(code, langVersion, additionalRefs, allowUnsafe, checkOverflow, cancellationToken); + string generatedSource = string.Concat(generatorResults.GeneratedTrees.Select(t => t.ToString())); + + if (generatorResults.Diagnostics.Length != 0) + { + throw new ArgumentException(string.Join(Environment.NewLine, generatorResults.Diagnostics) + Environment.NewLine + generatedSource); + } + + return generatedSource; + } + internal static async Task SourceGenRegexAsync( string pattern, CultureInfo? culture, RegexOptions? options = null, TimeSpan? matchTimeout = null, CancellationToken cancellationToken = default) { @@ -177,6 +197,7 @@ internal static async Task SourceGenRegexAsync( .WithCompilationOptions( new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary, + checkOverflow: true, warningLevel: 9999, // docs recommend using "9999" to catch all warnings now and in the future specificDiagnosticOptions: ImmutableDictionary.Empty.Add("SYSLIB1044", ReportDiagnostic.Hidden)) // regex with limited support .WithNullableContextOptions(NullableContextOptions.Enable)) diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs index f9ccaaf568a382..fa86ca59e60999 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs @@ -3,7 +3,10 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.DotNet.RemoteExecutor; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Globalization; using System.Threading.Tasks; using Xunit; @@ -436,22 +439,28 @@ partial class C ", compile: true)); } + public static IEnumerable Valid_ClassWithNamespace_ConfigurationOptions_MemberData() => + from pattern in new[] { "", "ab", "ab*c|de*?f|(ghi){1,3}", "\\\\w\\\\W\\\\b\\\\B\\\\d\\\\D\\\\s\\\\S" } + from options in new[] { RegexOptions.None, RegexOptions.IgnoreCase, RegexOptions.ECMAScript, RegexOptions.RightToLeft } + from allowUnsafe in new[] { false, true } + from checkOverflow in new[] { false, true } + select new object[] { pattern, options, allowUnsafe, checkOverflow }; + [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task Valid_ClassWithNamespace(bool allowUnsafe) + [MemberData(nameof(Valid_ClassWithNamespace_ConfigurationOptions_MemberData))] + public async Task Valid_ClassWithNamespace_ConfigurationOptions(string pattern, RegexOptions options, bool allowUnsafe, bool checkOverflow) { - Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator($@" using System.Text.RegularExpressions; namespace A - { + {{ partial class C - { - [GeneratedRegex(""ab"")] + {{ + [GeneratedRegex(""{pattern}"", RegexOptions.{options})] private static partial Regex Valid(); - } - } - ", compile: true, allowUnsafe: allowUnsafe)); + }} + }} + ", compile: true, allowUnsafe: allowUnsafe, checkOverflow: checkOverflow)); } [Fact] @@ -839,5 +848,36 @@ partial class C public static partial Regex Valid(); }", compile: true)); } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [OuterLoop("Takes several seconds")] + public void Deterministic_SameRegexProducesSameSource() + { + string first = Generate(); + for (int trials = 0; trials < 3; trials++) + { + Assert.Equal(first, Generate()); + } + + static string Generate() + { + const string Code = + @"using System.Text.RegularExpressions; + partial class C + { + [GeneratedRegex(""(?\w+) (?\w+), (?\w+) (?[A-Z]{2}) (?[0-9]{5})"")] + public static partial Regex Valid(); + }"; + + // Generate the source in a new process so that any process-specific randomization is different between runs, + // e.g. hash code randomization for strings. + + using RemoteInvokeHandle handle = RemoteExecutor.Invoke( + async () => Console.WriteLine(await RegexGeneratorHelper.GenerateSourceText(Code)), + new RemoteInvokeOptions { StartInfo = new ProcessStartInfo { RedirectStandardOutput = true } }); + + return handle.Process.StandardOutput.ReadToEnd(); + } + } } } diff --git a/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexReductionTests.cs b/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexReductionTests.cs index b21b26071bc2e6..dbb22cf5026485 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexReductionTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexReductionTests.cs @@ -50,26 +50,14 @@ public class RegexReductionTests // Two atomic one loops [InlineData("(?>a*)(?>a*)", "(?>a*)")] [InlineData("(?>a*)(?>(?:a*))", "(?>a*)")] - [InlineData("(?>a*)(?>a+)", "(?>a+)")] [InlineData("(?>a*)(?>a?)", "(?>a*)")] - [InlineData("(?>a*)(?>a{1,3})", "(?>a+)")] [InlineData("(?>a+)(?>a*)", "(?>a+)")] - [InlineData("(?>a+)(?>a+)", "(?>a{2,})")] [InlineData("(?>a+)(?>a?)", "(?>a+)")] - [InlineData("(?>a+)(?>a{1,3})", "(?>a{2,})")] [InlineData("(?>a?)(?>a*)", "(?>a*)")] - [InlineData("(?>a?)(?>a+)", "(?>a+)")] [InlineData("(?>a?)(?>a?)", "(?>a{0,2})")] - [InlineData("(?>a?)(?>a{1,3})", "(?>a{1,4})")] [InlineData("(?>a{1,3})(?>a*)", "(?>a+)")] - [InlineData("(?>a{1,3})(?>a+)", "(?>a{2,})")] [InlineData("(?>a{1,3})(?>a?)", "(?>a{1,4})")] - [InlineData("(?>a{1,3})(?>a{1,3})", "(?>a{2,6})")] - // Atomic one loop and one - [InlineData("(?>a*)a", "(?>a+)")] - [InlineData("(?>a+)a", "(?>a{2,})")] - [InlineData("(?>a?)a", "(?>a{1,2})")] - [InlineData("(?>a{1,3})a", "(?>a{2,4})")] + // One and atomic one loop [InlineData("a(?>a*)", "(?>a+)")] [InlineData("a(?>a+)", "(?>a{2,})")] [InlineData("a(?>a?)", "(?>a{1,2})")] @@ -136,21 +124,13 @@ public class RegexReductionTests [InlineData("[^a]{1,3}?[^a]{1,3}?", "[^a]{2,6}?")] // Two atomic notone loops [InlineData("(?>[^a]*)(?>[^a]*)", "(?>[^a]*)")] - [InlineData("(?>[^a]*)(?>[^a]+)", "(?>[^a]+)")] [InlineData("(?>[^a]*)(?>[^a]?)", "(?>[^a]*)")] - [InlineData("(?>[^a]*)(?>[^a]{1,3})", "(?>[^a]+)")] [InlineData("(?>[^a]+)(?>[^a]*)", "(?>[^a]+)")] - [InlineData("(?>[^a]+)(?>[^a]+)", "(?>[^a]{2,})")] [InlineData("(?>[^a]+)(?>[^a]?)", "(?>[^a]+)")] - [InlineData("(?>[^a]+)(?>[^a]{1,3})", "(?>[^a]{2,})")] [InlineData("(?>[^a]?)(?>[^a]*)", "(?>[^a]*)")] - [InlineData("(?>[^a]?)(?>[^a]+)", "(?>[^a]+)")] [InlineData("(?>[^a]?)(?>[^a]?)", "(?>[^a]{0,2})")] - [InlineData("(?>[^a]?)(?>[^a]{1,3})", "(?>[^a]{1,4})")] [InlineData("(?>[^a]{1,3})(?>[^a]*)", "(?>[^a]+)")] - [InlineData("(?>[^a]{1,3})(?>[^a]+)", "(?>[^a]{2,})")] [InlineData("(?>[^a]{1,3})(?>[^a]?)", "(?>[^a]{1,4})")] - [InlineData("(?>[^a]{1,3})(?>[^a]{1,3})", "(?>[^a]{2,6})")] // Greedy notone loop and notone [InlineData("[^a]*[^a]", "[^a]+")] [InlineData("[^a]+[^a]", "[^a]{2,}")] @@ -169,11 +149,7 @@ public class RegexReductionTests [InlineData("[^a][^a]+?", "[^a]{2,}?")] [InlineData("[^a][^a]??", "[^a]{1,2}?")] [InlineData("[^a][^a]{1,3}?", "[^a]{2,4}?")] - // Atomic notone loop and notone - [InlineData("(?>[^a]*)[^a]", "(?>[^a]+)")] - [InlineData("(?>[^a]+)[^a]", "(?>[^a]{2,})")] - [InlineData("(?>[^a]?)[^a]", "(?>[^a]{1,2})")] - [InlineData("(?>[^a]{1,3})[^a]", "(?>[^a]{2,4})")] + // Notone and atomic notone loop [InlineData("[^a](?>[^a]*)", "(?>[^a]+)")] [InlineData("[^a](?>[^a]+)", "(?>[^a]{2,})")] [InlineData("[^a](?>[^a]?)", "(?>[^a]{1,2})")] @@ -206,11 +182,7 @@ public class RegexReductionTests [InlineData("[0-9][0-9]+", "[0-9]{2,}")] [InlineData("[0-9][0-9]?", "[0-9]{1,2}")] [InlineData("[0-9][0-9]{1,3}", "[0-9]{2,4}")] - // Atomic set loop and set - [InlineData("(?>[0-9]*)[0-9]", "(?>[0-9]+)")] - [InlineData("(?>[0-9]+)[0-9]", "(?>[0-9]{2,})")] - [InlineData("(?>[0-9]?)[0-9]", "(?>[0-9]{1,2})")] - [InlineData("(?>[0-9]{1,3})[0-9]", "(?>[0-9]{2,4})")] + // Set and atomic set loop [InlineData("[0-9](?>[0-9]*)", "(?>[0-9]+)")] [InlineData("[0-9](?>[0-9]+)", "(?>[0-9]{2,})")] [InlineData("[0-9](?>[0-9]?)", "(?>[0-9]{1,2})")] @@ -234,21 +206,13 @@ public class RegexReductionTests [InlineData("[0-9]{1,3}?[0-9]{1,3}?", "[0-9]{2,6}?")] // Two atomic set loops [InlineData("(?>[0-9]*)(?>[0-9]*)", "(?>[0-9]*)")] - [InlineData("(?>[0-9]*)(?>[0-9]+)", "(?>[0-9]+)")] [InlineData("(?>[0-9]*)(?>[0-9]?)", "(?>[0-9]*)")] - [InlineData("(?>[0-9]*)(?>[0-9]{1,3})", "(?>[0-9]+)")] [InlineData("(?>[0-9]+)(?>[0-9]*)", "(?>[0-9]+)")] - [InlineData("(?>[0-9]+)(?>[0-9]+)", "(?>[0-9]{2,})")] [InlineData("(?>[0-9]+)(?>[0-9]?)", "(?>[0-9]+)")] - [InlineData("(?>[0-9]+)(?>[0-9]{1,3})", "(?>[0-9]{2,})")] [InlineData("(?>[0-9]?)(?>[0-9]*)", "(?>[0-9]*)")] - [InlineData("(?>[0-9]?)(?>[0-9]+)", "(?>[0-9]+)")] [InlineData("(?>[0-9]?)(?>[0-9]?)", "(?>[0-9]{0,2})")] - [InlineData("(?>[0-9]?)(?>[0-9]{1,3})", "(?>[0-9]{1,4})")] [InlineData("(?>[0-9]{1,3})(?>[0-9]*)", "(?>[0-9]+)")] - [InlineData("(?>[0-9]{1,3})(?>[0-9]+)", "(?>[0-9]{2,})")] [InlineData("(?>[0-9]{1,3})(?>[0-9]?)", "(?>[0-9]{1,4})")] - [InlineData("(?>[0-9]{1,3})(?>[0-9]{1,3})", "(?>[0-9]{2,6})")] // Lazy set loop and set [InlineData("[0-9]*?[0-9]", "[0-9]+?")] [InlineData("[0-9]+?[0-9]", "[0-9]{2,}?")] @@ -381,6 +345,8 @@ public class RegexReductionTests [InlineData("(?:w*)+\\.", "(?>w*)+\\.")] [InlineData("(a[bcd]e*)*fg", "(a[bcd](?>e*))*fg")] [InlineData("(\\w[bcd]\\s*)*fg", "(\\w[bcd](?>\\s*))*fg")] + [InlineData(@"\b(\w+)\b", @"\b((?>\w+))\b")] + [InlineData(@"\b(?:\w+)\b ", @"\b(?>\w+)\b ")] // Nothing handling [InlineData(@"\wabc(?!)def", "(?!)")] [InlineData(@"\wabc(?!)def|ghi(?!)", "(?!)")] @@ -423,6 +389,42 @@ public void PatternsReduceIdentically(string actual, string expected) [InlineData("a*?a*", "a*")] [InlineData("a*[^a]*", "a*")] [InlineData("[^a]*a*", "a*")] + [InlineData("(?>a*)(?>a+)", "(?>a+)")] + [InlineData("(?>a*)(?>a{1,3})", "(?>a+)")] + [InlineData("(?>a+)(?>a+)", "(?>a{2,})")] + [InlineData("(?>a+)(?>a{1,3})", "(?>a{2,})")] + [InlineData("(?>a?)(?>a+)", "(?>a+)")] + [InlineData("(?>a?)(?>a{1,3})", "(?>a{1,4})")] + [InlineData("(?>a{1,3})(?>a+)", "(?>a{2,})")] + [InlineData("(?>a{1,3})(?>a{1,3})", "(?>a{2,6})")] + [InlineData("(?>[^a]*)(?>[^a]+)", "(?>[^a]+)")] + [InlineData("(?>[^a]*)(?>[^a]{1,3})", "(?>[^a]+)")] + [InlineData("(?>[^a]+)(?>[^a]+)", "(?>[^a]{2,})")] + [InlineData("(?>[^a]+)(?>[^a]{1,3})", "(?>[^a]{2,})")] + [InlineData("(?>[^a]?)(?>[^a]+)", "(?>[^a]+)")] + [InlineData("(?>[^a]?)(?>[^a]{1,3})", "(?>[^a]{1,4})")] + [InlineData("(?>[^a]{1,3})(?>[^a]+)", "(?>[^a]{2,})")] + [InlineData("(?>[^a]{1,3})(?>[^a]{1,3})", "(?>[^a]{2,6})")] + [InlineData("(?>[0-9]*)(?>[0-9]+)", "(?>[0-9]+)")] + [InlineData("(?>[0-9]*)(?>[0-9]{1,3})", "(?>[0-9]+)")] + [InlineData("(?>[0-9]+)(?>[0-9]+)", "(?>[0-9]{2,})")] + [InlineData("(?>[0-9]+)(?>[0-9]{1,3})", "(?>[0-9]{2,})")] + [InlineData("(?>[0-9]?)(?>[0-9]+)", "(?>[0-9]+)")] + [InlineData("(?>[0-9]?)(?>[0-9]{1,3})", "(?>[0-9]{1,4})")] + [InlineData("(?>[0-9]{1,3})(?>[0-9]+)", "(?>[0-9]{2,})")] + [InlineData("(?>[0-9]{1,3})(?>[0-9]{1,3})", "(?>[0-9]{2,6})")] + [InlineData("(?>a*)a", "(?>a+)")] + [InlineData("(?>a+)a", "(?>a{2,})")] + [InlineData("(?>a?)a", "(?>a{1,2})")] + [InlineData("(?>a{1,3})a", "(?>a{2,4})")] + [InlineData("(?>[^a]*)[^a]", "(?>[^a]+)")] + [InlineData("(?>[^a]+)[^a]", "(?>[^a]{2,})")] + [InlineData("(?>[^a]?)[^a]", "(?>[^a]{1,2})")] + [InlineData("(?>[^a]{1,3})[^a]", "(?>[^a]{2,4})")] + [InlineData("(?>[0-9]*)[0-9]", "(?>[0-9]+)")] + [InlineData("(?>[0-9]+)[0-9]", "(?>[0-9]{2,})")] + [InlineData("(?>[0-9]?)[0-9]", "(?>[0-9]{1,2})")] + [InlineData("(?>[0-9]{1,3})[0-9]", "(?>[0-9]{2,4})")] [InlineData("a{2147483646}a", "a{2147483647}")] [InlineData("a{2147483647}a", "a{2147483647}")] [InlineData("a{0,2147483646}a", "a{0,2147483647}")] @@ -470,6 +472,10 @@ public void PatternsReduceIdentically(string actual, string expected) [InlineData("(?:ab??){2}", "(?:a(?>b??)){2}")] [InlineData("(?:ab??){2, 3}", "(?:a(?>b??)){2, 3}")] [InlineData("ab??(b)", "a(?>b??)(b)")] + [InlineData(@"\w+\b\w+", @"(?>\w+)\b\w")] + [InlineData(@"\w*\b\w+", @"(?>\w*)\b\w+")] + [InlineData(@"\W+\B\W+", @"(?>\W+)\B\W")] + [InlineData(@"\W*\B\W+", @"(?>\W*)\B\W")] // Loops inside alternation constructs [InlineData("(abc*|def)chi", "(ab(?>c*)|def)chi")] [InlineData("(abc|def*)fhi", "(abc|de(?>f*))fhi")] @@ -505,6 +511,9 @@ public void PatternsReduceDifferently(string actual, string expected) [InlineData(@"a??", RegexOptions.None, 0, 1)] [InlineData(@"a+", RegexOptions.None, 1, null)] [InlineData(@"a+?", RegexOptions.None, 1, null)] + [InlineData(@"(?>a*)a", RegexOptions.None, 1, null)] + [InlineData(@"(?>a*)a+", RegexOptions.None, 1, null)] + [InlineData(@"(?>a*)a*", RegexOptions.None, 0, null)] [InlineData(@"a{2}", RegexOptions.None, 2, 2)] [InlineData(@"a{2}?", RegexOptions.None, 2, 2)] [InlineData(@"a{3,17}", RegexOptions.None, 3, 17)] diff --git a/src/libraries/System.Text.RegularExpressions/tools/GenerateRegexCasingTable.csproj b/src/libraries/System.Text.RegularExpressions/tools/GenerateRegexCasingTable.csproj index 26b5733684c3f5..4df90752204029 100644 --- a/src/libraries/System.Text.RegularExpressions/tools/GenerateRegexCasingTable.csproj +++ b/src/libraries/System.Text.RegularExpressions/tools/GenerateRegexCasingTable.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + $(NetCoreAppToolCurrent) latest enable diff --git a/src/libraries/System.Threading.AccessControl/ref/System.Threading.AccessControl.csproj b/src/libraries/System.Threading.AccessControl/ref/System.Threading.AccessControl.csproj index 8da1d59d6024ec..d5ce621047c4d3 100644 --- a/src/libraries/System.Threading.AccessControl/ref/System.Threading.AccessControl.csproj +++ b/src/libraries/System.Threading.AccessControl/ref/System.Threading.AccessControl.csproj @@ -6,11 +6,11 @@ - + - \ No newline at end of file + diff --git a/src/libraries/System.Threading.AccessControl/src/System.Threading.AccessControl.csproj b/src/libraries/System.Threading.AccessControl/src/System.Threading.AccessControl.csproj index 54c8e0bad5fe76..a3177384242aae 100644 --- a/src/libraries/System.Threading.AccessControl/src/System.Threading.AccessControl.csproj +++ b/src/libraries/System.Threading.AccessControl/src/System.Threading.AccessControl.csproj @@ -3,6 +3,9 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);$(NetCoreAppMinimum)-windows;$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + + false + 1 Provides support for managing access and audit control lists for synchronization primitives. Commonly Used Types: diff --git a/src/libraries/System.Threading.RateLimiting/ref/System.Threading.RateLimiting.cs b/src/libraries/System.Threading.RateLimiting/ref/System.Threading.RateLimiting.cs index b4905c286ecd9f..70be12aed58a7e 100644 --- a/src/libraries/System.Threading.RateLimiting/ref/System.Threading.RateLimiting.cs +++ b/src/libraries/System.Threading.RateLimiting/ref/System.Threading.RateLimiting.cs @@ -29,8 +29,8 @@ public FixedWindowRateLimiter(System.Threading.RateLimiting.FixedWindowRateLimit public override System.TimeSpan? IdleDuration { get { throw null; } } public override bool IsAutoReplenishing { get { throw null; } } public override System.TimeSpan ReplenishmentPeriod { get { throw null; } } - protected override System.Threading.Tasks.ValueTask AcquireAsyncCore(int requestCount, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - protected override System.Threading.RateLimiting.RateLimitLease AttemptAcquireCore(int requestCount) { throw null; } + protected override System.Threading.Tasks.ValueTask AcquireAsyncCore(int permitCount, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + protected override System.Threading.RateLimiting.RateLimitLease AttemptAcquireCore(int permitCount) { throw null; } protected override void Dispose(bool disposing) { } protected override System.Threading.Tasks.ValueTask DisposeAsyncCore() { throw null; } public override System.Threading.RateLimiting.RateLimiterStatistics? GetStatistics() { throw null; } @@ -103,10 +103,10 @@ protected virtual void Dispose(bool disposing) { } public partial class RateLimiterStatistics { public RateLimiterStatistics() { } - public long CurrentAvailablePermits { get { throw null; } set { } } - public long CurrentQueuedCount { get { throw null; } set { } } - public long TotalFailedLeases { get { throw null; } set { } } - public long TotalSuccessfulLeases { get { throw null; } set { } } + public long CurrentAvailablePermits { get { throw null; } init { } } + public long CurrentQueuedCount { get { throw null; } init { } } + public long TotalFailedLeases { get { throw null; } init { } } + public long TotalSuccessfulLeases { get { throw null; } init { } } } public abstract partial class RateLimitLease : System.IDisposable { @@ -150,8 +150,8 @@ public SlidingWindowRateLimiter(System.Threading.RateLimiting.SlidingWindowRateL public override System.TimeSpan? IdleDuration { get { throw null; } } public override bool IsAutoReplenishing { get { throw null; } } public override System.TimeSpan ReplenishmentPeriod { get { throw null; } } - protected override System.Threading.Tasks.ValueTask AcquireAsyncCore(int requestCount, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - protected override System.Threading.RateLimiting.RateLimitLease AttemptAcquireCore(int requestCount) { throw null; } + protected override System.Threading.Tasks.ValueTask AcquireAsyncCore(int permitCount, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + protected override System.Threading.RateLimiting.RateLimitLease AttemptAcquireCore(int permitCount) { throw null; } protected override void Dispose(bool disposing) { } protected override System.Threading.Tasks.ValueTask DisposeAsyncCore() { throw null; } public override System.Threading.RateLimiting.RateLimiterStatistics? GetStatistics() { throw null; } diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiter.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiter.cs index fe4b0c29c3a627..381f7038fc60be 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiter.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiter.cs @@ -13,7 +13,7 @@ namespace System.Threading.RateLimiting /// public sealed class FixedWindowRateLimiter : ReplenishingRateLimiter { - private int _requestCount; + private int _permitCount; private int _queueCount; private long _lastReplenishmentTick; private long? _idleSince; @@ -59,9 +59,9 @@ public FixedWindowRateLimiter(FixedWindowRateLimiterOptions options) { throw new ArgumentException($"{nameof(options.QueueLimit)} must be set to a value greater than or equal to 0.", nameof(options)); } - if (options.Window < TimeSpan.Zero) + if (options.Window <= TimeSpan.Zero) { - throw new ArgumentException($"{nameof(options.Window)} must be set to a value greater than or equal to TimeSpan.Zero.", nameof(options)); + throw new ArgumentException($"{nameof(options.Window)} must be set to a value greater than TimeSpan.Zero.", nameof(options)); } _options = new FixedWindowRateLimiterOptions @@ -73,7 +73,7 @@ public FixedWindowRateLimiter(FixedWindowRateLimiterOptions options) AutoReplenishment = options.AutoReplenishment }; - _requestCount = options.PermitLimit; + _permitCount = options.PermitLimit; _idleSince = _lastReplenishmentTick = Stopwatch.GetTimestamp(); @@ -89,7 +89,7 @@ public FixedWindowRateLimiter(FixedWindowRateLimiterOptions options) ThrowIfDisposed(); return new RateLimiterStatistics() { - CurrentAvailablePermits = _requestCount, + CurrentAvailablePermits = _permitCount, CurrentQueuedCount = _queueCount, TotalFailedLeases = Interlocked.Read(ref _failedLeasesCount), TotalSuccessfulLeases = Interlocked.Read(ref _successfulLeasesCount), @@ -97,55 +97,55 @@ public FixedWindowRateLimiter(FixedWindowRateLimiterOptions options) } /// - protected override RateLimitLease AttemptAcquireCore(int requestCount) + protected override RateLimitLease AttemptAcquireCore(int permitCount) { // These amounts of resources can never be acquired // Raises a PermitLimitExceeded ArgumentOutOFRangeException - if (requestCount > _options.PermitLimit) + if (permitCount > _options.PermitLimit) { - throw new ArgumentOutOfRangeException(nameof(requestCount), requestCount, SR.Format(SR.PermitLimitExceeded, requestCount, _options.PermitLimit)); + throw new ArgumentOutOfRangeException(nameof(permitCount), permitCount, SR.Format(SR.PermitLimitExceeded, permitCount, _options.PermitLimit)); } // Return SuccessfulLease or FailedLease depending to indicate limiter state - if (requestCount == 0 && !_disposed) + if (permitCount == 0 && !_disposed) { // Check if the requests are permitted in a window // Requests will be allowed if the total served request is less than the max allowed requests (permit limit). - if (_requestCount > 0) + if (_permitCount > 0) { Interlocked.Increment(ref _successfulLeasesCount); return SuccessfulLease; } Interlocked.Increment(ref _failedLeasesCount); - return CreateFailedWindowLease(requestCount); + return CreateFailedWindowLease(permitCount); } lock (Lock) { - if (TryLeaseUnsynchronized(requestCount, out RateLimitLease? lease)) + if (TryLeaseUnsynchronized(permitCount, out RateLimitLease? lease)) { return lease; } Interlocked.Increment(ref _failedLeasesCount); - return CreateFailedWindowLease(requestCount); + return CreateFailedWindowLease(permitCount); } } /// - protected override ValueTask AcquireAsyncCore(int requestCount, CancellationToken cancellationToken = default) + protected override ValueTask AcquireAsyncCore(int permitCount, CancellationToken cancellationToken = default) { // These amounts of resources can never be acquired - if (requestCount > _options.PermitLimit) + if (permitCount > _options.PermitLimit) { - throw new ArgumentOutOfRangeException(nameof(requestCount), requestCount, SR.Format(SR.PermitLimitExceeded, requestCount, _options.PermitLimit)); + throw new ArgumentOutOfRangeException(nameof(permitCount), permitCount, SR.Format(SR.PermitLimitExceeded, permitCount, _options.PermitLimit)); } ThrowIfDisposed(); - // Return SuccessfulAcquisition if requestCount is 0 and resources are available - if (requestCount == 0 && _requestCount > 0) + // Return SuccessfulAcquisition if permitCount is 0 and resources are available + if (permitCount == 0 && _permitCount > 0) { Interlocked.Increment(ref _successfulLeasesCount); return new ValueTask(SuccessfulLease); @@ -153,16 +153,16 @@ protected override ValueTask AcquireAsyncCore(int requestCount, lock (Lock) { - if (TryLeaseUnsynchronized(requestCount, out RateLimitLease? lease)) + if (TryLeaseUnsynchronized(permitCount, out RateLimitLease? lease)) { return new ValueTask(lease); } // Avoid integer overflow by using subtraction instead of addition Debug.Assert(_options.QueueLimit >= _queueCount); - if (_options.QueueLimit - _queueCount < requestCount) + if (_options.QueueLimit - _queueCount < permitCount) { - if (_options.QueueProcessingOrder == QueueProcessingOrder.NewestFirst && requestCount <= _options.QueueLimit) + if (_options.QueueProcessingOrder == QueueProcessingOrder.NewestFirst && permitCount <= _options.QueueLimit) { // remove oldest items from queue until there is space for the newest acquisition request do @@ -179,17 +179,17 @@ protected override ValueTask AcquireAsyncCore(int requestCount, Interlocked.Increment(ref _failedLeasesCount); } } - while (_options.QueueLimit - _queueCount < requestCount); + while (_options.QueueLimit - _queueCount < permitCount); } else { Interlocked.Increment(ref _failedLeasesCount); // Don't queue if queue limit reached and QueueProcessingOrder is OldestFirst - return new ValueTask(CreateFailedWindowLease(requestCount)); + return new ValueTask(CreateFailedWindowLease(permitCount)); } } - CancelQueueState tcs = new CancelQueueState(requestCount, this, cancellationToken); + CancelQueueState tcs = new CancelQueueState(permitCount, this, cancellationToken); CancellationTokenRegistration ctr = default; if (cancellationToken.CanBeCanceled) { @@ -199,32 +199,32 @@ protected override ValueTask AcquireAsyncCore(int requestCount, }, tcs); } - RequestRegistration registration = new RequestRegistration(requestCount, tcs, ctr); + RequestRegistration registration = new RequestRegistration(permitCount, tcs, ctr); _queue.EnqueueTail(registration); - _queueCount += requestCount; + _queueCount += permitCount; Debug.Assert(_queueCount <= _options.QueueLimit); return new ValueTask(registration.Tcs.Task); } } - private RateLimitLease CreateFailedWindowLease(int requestCount) + private RateLimitLease CreateFailedWindowLease(int permitCount) { - int replenishAmount = requestCount - _requestCount + _queueCount; + int replenishAmount = permitCount - _permitCount + _queueCount; // can't have 0 replenish window, that would mean it should be a successful lease int replenishWindow = Math.Max(replenishAmount / _options.PermitLimit, 1); return new FixedWindowLease(false, TimeSpan.FromTicks(_options.Window.Ticks * replenishWindow)); } - private bool TryLeaseUnsynchronized(int requestCount, [NotNullWhen(true)] out RateLimitLease? lease) + private bool TryLeaseUnsynchronized(int permitCount, [NotNullWhen(true)] out RateLimitLease? lease) { ThrowIfDisposed(); // if permitCount is 0 we want to queue it if there are no available permits - if (_requestCount >= requestCount && _requestCount != 0) + if (_permitCount >= permitCount && _permitCount != 0) { - if (requestCount == 0) + if (permitCount == 0) { Interlocked.Increment(ref _successfulLeasesCount); // Edge case where the check before the lock showed 0 available permit counters but when we got the lock, some permits were now available @@ -237,8 +237,8 @@ private bool TryLeaseUnsynchronized(int requestCount, [NotNullWhen(true)] out Ra if (_queueCount == 0 || (_queueCount > 0 && _options.QueueProcessingOrder == QueueProcessingOrder.NewestFirst)) { _idleSince = null; - _requestCount -= requestCount; - Debug.Assert(_requestCount >= 0); + _permitCount -= permitCount; + Debug.Assert(_permitCount >= 0); Interlocked.Increment(ref _successfulLeasesCount); lease = SuccessfulLease; return true; @@ -287,29 +287,22 @@ private void ReplenishInternal(long nowTicks) return; } - if ((long)((nowTicks - _lastReplenishmentTick) * TickFrequency) < _options.Window.Ticks) + if (((nowTicks - _lastReplenishmentTick) * TickFrequency) < _options.Window.Ticks && !_options.AutoReplenishment) { return; } _lastReplenishmentTick = nowTicks; - int availableRequestCounters = _requestCount; - int maxPermits = _options.PermitLimit; - int resourcesToAdd; + int availablePermitCounters = _permitCount; - if (availableRequestCounters < maxPermits) - { - resourcesToAdd = maxPermits - availableRequestCounters; - } - else + if (availablePermitCounters >= _options.PermitLimit) { // All counters available, nothing to do return; } - _requestCount += resourcesToAdd; - Debug.Assert(_requestCount == _options.PermitLimit); + _permitCount = _options.PermitLimit; // Process queued requests while (_queue.Count > 0) @@ -319,7 +312,7 @@ private void ReplenishInternal(long nowTicks) ? _queue.PeekHead() : _queue.PeekTail(); - if (_requestCount >= nextPendingRequest.Count) + if (_permitCount >= nextPendingRequest.Count) { // Request can be fulfilled nextPendingRequest = @@ -328,13 +321,13 @@ private void ReplenishInternal(long nowTicks) : _queue.DequeueTail(); _queueCount -= nextPendingRequest.Count; - _requestCount -= nextPendingRequest.Count; - Debug.Assert(_requestCount >= 0); + _permitCount -= nextPendingRequest.Count; + Debug.Assert(_permitCount >= 0); if (!nextPendingRequest.Tcs.TrySetResult(SuccessfulLease)) { // Queued item was canceled so add count back - _requestCount += nextPendingRequest.Count; + _permitCount += nextPendingRequest.Count; // Updating queue count is handled by the cancellation code _queueCount += nextPendingRequest.Count; } @@ -352,7 +345,7 @@ private void ReplenishInternal(long nowTicks) } } - if (_requestCount == _options.PermitLimit) + if (_permitCount == _options.PermitLimit) { Debug.Assert(_idleSince is null); Debug.Assert(_queueCount == 0); @@ -433,9 +426,9 @@ public override bool TryGetMetadata(string metadataName, out object? metadata) private readonly struct RequestRegistration { - public RequestRegistration(int requestCount, TaskCompletionSource tcs, CancellationTokenRegistration cancellationTokenRegistration) + public RequestRegistration(int permitCount, TaskCompletionSource tcs, CancellationTokenRegistration cancellationTokenRegistration) { - Count = requestCount; + Count = permitCount; // Use VoidAsyncOperationWithData instead Tcs = tcs; CancellationTokenRegistration = cancellationTokenRegistration; @@ -450,14 +443,14 @@ public RequestRegistration(int requestCount, TaskCompletionSource { - private readonly int _requestCount; + private readonly int _permitCount; private readonly FixedWindowRateLimiter _limiter; private readonly CancellationToken _cancellationToken; - public CancelQueueState(int requestCount, FixedWindowRateLimiter limiter, CancellationToken cancellationToken) + public CancelQueueState(int permitCount, FixedWindowRateLimiter limiter, CancellationToken cancellationToken) : base(TaskCreationOptions.RunContinuationsAsynchronously) { - _requestCount = requestCount; + _permitCount = permitCount; _limiter = limiter; _cancellationToken = cancellationToken; } @@ -468,7 +461,7 @@ public CancelQueueState(int requestCount, FixedWindowRateLimiter limiter, Cancel { lock (_limiter.Lock) { - _limiter._queueCount -= _requestCount; + _limiter._queueCount -= _permitCount; } return true; } diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiterOptions.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiterOptions.cs index 92cac84012c064..8f7dbaa344beba 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiterOptions.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiterOptions.cs @@ -10,7 +10,7 @@ public sealed class FixedWindowRateLimiterOptions { /// /// Specifies the time window that takes in the requests. - /// Must be set to a value >= by the time these options are passed to the constructor of . + /// Must be set to a value greater than by the time these options are passed to the constructor of . /// public TimeSpan Window { get; set; } = TimeSpan.Zero; diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiter.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiter.cs index 1ccf40775e2d87..1db6c8e9cbb39b 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiter.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiter.cs @@ -13,7 +13,7 @@ namespace System.Threading.RateLimiting /// public sealed class SlidingWindowRateLimiter : ReplenishingRateLimiter { - private int _requestCount; + private int _permitCount; private int _queueCount; private int[] _requestsPerSegment; private int _currentSegmentIndex; @@ -26,6 +26,7 @@ public sealed class SlidingWindowRateLimiter : ReplenishingRateLimiter private readonly Timer? _renewTimer; private readonly SlidingWindowRateLimiterOptions _options; + private readonly TimeSpan _replenishmentPeriod; private readonly Deque _queue = new Deque(); // Use the queue as the lock field so we don't need to allocate another object for a lock and have another field in the object @@ -42,7 +43,7 @@ public sealed class SlidingWindowRateLimiter : ReplenishingRateLimiter public override bool IsAutoReplenishing => _options.AutoReplenishment; /// - public override TimeSpan ReplenishmentPeriod => new TimeSpan(_options.Window.Ticks / _options.SegmentsPerWindow); + public override TimeSpan ReplenishmentPeriod => _replenishmentPeriod; /// /// Initializes the . @@ -62,9 +63,9 @@ public SlidingWindowRateLimiter(SlidingWindowRateLimiterOptions options) { throw new ArgumentException($"{nameof(options.QueueLimit)} must be set to a value greater than or equal to 0.", nameof(options)); } - if (options.Window < TimeSpan.Zero) + if (options.Window <= TimeSpan.Zero) { - throw new ArgumentException($"{nameof(options.Window)} must be set to a value greater than or equal to TimeSpan.Zero.", nameof(options)); + throw new ArgumentException($"{nameof(options.Window)} must be set to a value greater than TimeSpan.Zero.", nameof(options)); } _options = new SlidingWindowRateLimiterOptions @@ -77,7 +78,8 @@ public SlidingWindowRateLimiter(SlidingWindowRateLimiterOptions options) AutoReplenishment = options.AutoReplenishment }; - _requestCount = options.PermitLimit; + _permitCount = options.PermitLimit; + _replenishmentPeriod = new TimeSpan(_options.Window.Ticks / _options.SegmentsPerWindow); // _requestsPerSegment holds the no. of acquired requests in each window segment _requestsPerSegment = new int[options.SegmentsPerWindow]; @@ -97,7 +99,7 @@ public SlidingWindowRateLimiter(SlidingWindowRateLimiterOptions options) ThrowIfDisposed(); return new RateLimiterStatistics() { - CurrentAvailablePermits = _requestCount, + CurrentAvailablePermits = _permitCount, CurrentQueuedCount = _queueCount, TotalFailedLeases = Interlocked.Read(ref _failedLeasesCount), TotalSuccessfulLeases = Interlocked.Read(ref _successfulLeasesCount), @@ -105,18 +107,18 @@ public SlidingWindowRateLimiter(SlidingWindowRateLimiterOptions options) } /// - protected override RateLimitLease AttemptAcquireCore(int requestCount) + protected override RateLimitLease AttemptAcquireCore(int permitCount) { // These amounts of resources can never be acquired - if (requestCount > _options.PermitLimit) + if (permitCount > _options.PermitLimit) { - throw new ArgumentOutOfRangeException(nameof(requestCount), requestCount, SR.Format(SR.PermitLimitExceeded, requestCount, _options.PermitLimit)); + throw new ArgumentOutOfRangeException(nameof(permitCount), permitCount, SR.Format(SR.PermitLimitExceeded, permitCount, _options.PermitLimit)); } // Return SuccessfulLease or FailedLease depending to indicate limiter state - if (requestCount == 0 && !_disposed) + if (permitCount == 0 && !_disposed) { - if (_requestCount > 0) + if (_permitCount > 0) { Interlocked.Increment(ref _successfulLeasesCount); return SuccessfulLease; @@ -128,7 +130,7 @@ protected override RateLimitLease AttemptAcquireCore(int requestCount) lock (Lock) { - if (TryLeaseUnsynchronized(requestCount, out RateLimitLease? lease)) + if (TryLeaseUnsynchronized(permitCount, out RateLimitLease? lease)) { return lease; } @@ -140,18 +142,18 @@ protected override RateLimitLease AttemptAcquireCore(int requestCount) } /// - protected override ValueTask AcquireAsyncCore(int requestCount, CancellationToken cancellationToken = default) + protected override ValueTask AcquireAsyncCore(int permitCount, CancellationToken cancellationToken = default) { // These amounts of resources can never be acquired - if (requestCount > _options.PermitLimit) + if (permitCount > _options.PermitLimit) { - throw new ArgumentOutOfRangeException(nameof(requestCount), requestCount, SR.Format(SR.PermitLimitExceeded, requestCount, _options.PermitLimit)); + throw new ArgumentOutOfRangeException(nameof(permitCount), permitCount, SR.Format(SR.PermitLimitExceeded, permitCount, _options.PermitLimit)); } ThrowIfDisposed(); // Return SuccessfulAcquisition if resources are available - if (requestCount == 0 && _requestCount > 0) + if (permitCount == 0 && _permitCount > 0) { Interlocked.Increment(ref _successfulLeasesCount); return new ValueTask(SuccessfulLease); @@ -159,16 +161,16 @@ protected override ValueTask AcquireAsyncCore(int requestCount, lock (Lock) { - if (TryLeaseUnsynchronized(requestCount, out RateLimitLease? lease)) + if (TryLeaseUnsynchronized(permitCount, out RateLimitLease? lease)) { return new ValueTask(lease); } // Avoid integer overflow by using subtraction instead of addition Debug.Assert(_options.QueueLimit >= _queueCount); - if (_options.QueueLimit - _queueCount < requestCount) + if (_options.QueueLimit - _queueCount < permitCount) { - if (_options.QueueProcessingOrder == QueueProcessingOrder.NewestFirst && requestCount <= _options.QueueLimit) + if (_options.QueueProcessingOrder == QueueProcessingOrder.NewestFirst && permitCount <= _options.QueueLimit) { // Remove oldest items from queue until there is space for the newest acquisition request do @@ -185,7 +187,7 @@ protected override ValueTask AcquireAsyncCore(int requestCount, Interlocked.Increment(ref _failedLeasesCount); } } - while (_options.QueueLimit - _queueCount < requestCount); + while (_options.QueueLimit - _queueCount < permitCount); } else { @@ -195,7 +197,7 @@ protected override ValueTask AcquireAsyncCore(int requestCount, } } - CancelQueueState tcs = new CancelQueueState(requestCount, this, cancellationToken); + CancelQueueState tcs = new CancelQueueState(permitCount, this, cancellationToken); CancellationTokenRegistration ctr = default; if (cancellationToken.CanBeCanceled) { @@ -205,23 +207,23 @@ protected override ValueTask AcquireAsyncCore(int requestCount, }, tcs); } - RequestRegistration registration = new RequestRegistration(requestCount, tcs, ctr); + RequestRegistration registration = new RequestRegistration(permitCount, tcs, ctr); _queue.EnqueueTail(registration); - _queueCount += requestCount; + _queueCount += permitCount; Debug.Assert(_queueCount <= _options.QueueLimit); return new ValueTask(registration.Tcs.Task); } } - private bool TryLeaseUnsynchronized(int requestCount, [NotNullWhen(true)] out RateLimitLease? lease) + private bool TryLeaseUnsynchronized(int permitCount, [NotNullWhen(true)] out RateLimitLease? lease) { ThrowIfDisposed(); - // if requestCount is 0 we want to queue it if there are no available permits - if (_requestCount >= requestCount && _requestCount != 0) + // if permitCount is 0 we want to queue it if there are no available permits + if (_permitCount >= permitCount && _permitCount != 0) { - if (requestCount == 0) + if (permitCount == 0) { Interlocked.Increment(ref _successfulLeasesCount); // Edge case where the check before the lock showed 0 available permits but when we got the lock some permits were now available @@ -234,9 +236,9 @@ private bool TryLeaseUnsynchronized(int requestCount, [NotNullWhen(true)] out Ra if (_queueCount == 0 || (_queueCount > 0 && _options.QueueProcessingOrder == QueueProcessingOrder.NewestFirst)) { _idleSince = null; - _requestsPerSegment[_currentSegmentIndex] += requestCount; - _requestCount -= requestCount; - Debug.Assert(_requestCount >= 0); + _requestsPerSegment[_currentSegmentIndex] += permitCount; + _permitCount -= permitCount; + Debug.Assert(_permitCount >= 0); Interlocked.Increment(ref _successfulLeasesCount); lease = SuccessfulLease; return true; @@ -287,7 +289,7 @@ private void ReplenishInternal(long nowTicks) return; } - if ((long)((nowTicks - _lastReplenishmentTick) * TickFrequency) < ReplenishmentPeriod.Ticks) + if (((nowTicks - _lastReplenishmentTick) * TickFrequency) < ReplenishmentPeriod.Ticks && !_options.AutoReplenishment) { return; } @@ -297,16 +299,16 @@ private void ReplenishInternal(long nowTicks) // Increment the current segment index while move the window // We need to know the no. of requests that were acquired in a segment previously to ensure that we don't acquire more than the permit limit. _currentSegmentIndex = (_currentSegmentIndex + 1) % _options.SegmentsPerWindow; - int oldSegmentRequestCount = _requestsPerSegment[_currentSegmentIndex]; + int oldSegmentPermitCount = _requestsPerSegment[_currentSegmentIndex]; _requestsPerSegment[_currentSegmentIndex] = 0; - if (oldSegmentRequestCount == 0) + if (oldSegmentPermitCount == 0) { return; } - _requestCount += oldSegmentRequestCount; - Debug.Assert(_requestCount <= _options.PermitLimit); + _permitCount += oldSegmentPermitCount; + Debug.Assert(_permitCount <= _options.PermitLimit); // Process queued requests while (_queue.Count > 0) @@ -317,7 +319,7 @@ private void ReplenishInternal(long nowTicks) : _queue.PeekTail(); // If we have enough permits after replenishing to serve the queued requests - if (_requestCount >= nextPendingRequest.Count) + if (_permitCount >= nextPendingRequest.Count) { // Request can be fulfilled nextPendingRequest = @@ -326,14 +328,14 @@ private void ReplenishInternal(long nowTicks) : _queue.DequeueTail(); _queueCount -= nextPendingRequest.Count; - _requestCount -= nextPendingRequest.Count; + _permitCount -= nextPendingRequest.Count; _requestsPerSegment[_currentSegmentIndex] += nextPendingRequest.Count; - Debug.Assert(_requestCount >= 0); + Debug.Assert(_permitCount >= 0); if (!nextPendingRequest.Tcs.TrySetResult(SuccessfulLease)) { // Queued item was canceled so add count back - _requestCount += nextPendingRequest.Count; + _permitCount += nextPendingRequest.Count; _requestsPerSegment[_currentSegmentIndex] -= nextPendingRequest.Count; // Updating queue count is handled by the cancellation code _queueCount += nextPendingRequest.Count; @@ -352,7 +354,7 @@ private void ReplenishInternal(long nowTicks) } } - if (_requestCount == _options.PermitLimit) + if (_permitCount == _options.PermitLimit) { Debug.Assert(_idleSince is null); Debug.Assert(_queueCount == 0); @@ -433,9 +435,9 @@ public override bool TryGetMetadata(string metadataName, out object? metadata) private readonly struct RequestRegistration { - public RequestRegistration(int requestCount, TaskCompletionSource tcs, CancellationTokenRegistration cancellationTokenRegistration) + public RequestRegistration(int permitCount, TaskCompletionSource tcs, CancellationTokenRegistration cancellationTokenRegistration) { - Count = requestCount; + Count = permitCount; // Use VoidAsyncOperationWithData instead Tcs = tcs; CancellationTokenRegistration = cancellationTokenRegistration; @@ -450,14 +452,14 @@ public RequestRegistration(int requestCount, TaskCompletionSource { - private readonly int _requestCount; + private readonly int _permitCount; private readonly SlidingWindowRateLimiter _limiter; private readonly CancellationToken _cancellationToken; - public CancelQueueState(int requestCount, SlidingWindowRateLimiter limiter, CancellationToken cancellationToken) + public CancelQueueState(int permitCount, SlidingWindowRateLimiter limiter, CancellationToken cancellationToken) : base(TaskCreationOptions.RunContinuationsAsynchronously) { - _requestCount = requestCount; + _permitCount = permitCount; _limiter = limiter; _cancellationToken = cancellationToken; } @@ -468,7 +470,7 @@ public CancelQueueState(int requestCount, SlidingWindowRateLimiter limiter, Canc { lock (_limiter.Lock) { - _limiter._queueCount -= _requestCount; + _limiter._queueCount -= _permitCount; } return true; } diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiterOptions.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiterOptions.cs index 8e1d397a57f11c..93f7ba933b464f 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiterOptions.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiterOptions.cs @@ -10,7 +10,7 @@ public sealed class SlidingWindowRateLimiterOptions { /// /// Specifies the minimum period between replenishments. - /// Must be set to a value >= by the time these options are passed to the constructor of . + /// Must be set to a value greater than by the time these options are passed to the constructor of . /// public TimeSpan Window { get; set; } = TimeSpan.Zero; diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiter.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiter.cs index 7baf91ea590804..f1fbcb4433c4d8 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiter.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiter.cs @@ -13,7 +13,7 @@ namespace System.Threading.RateLimiting /// public sealed class TokenBucketRateLimiter : ReplenishingRateLimiter { - private int _tokenCount; + private double _tokenCount; private int _queueCount; private long _lastReplenishmentTick; private long? _idleSince; @@ -22,6 +22,7 @@ public sealed class TokenBucketRateLimiter : ReplenishingRateLimiter private long _failedLeasesCount; private long _successfulLeasesCount; + private readonly double _fillRate; private readonly Timer? _renewTimer; private readonly TokenBucketRateLimiterOptions _options; private readonly Deque _queue = new Deque(); @@ -60,9 +61,9 @@ public TokenBucketRateLimiter(TokenBucketRateLimiterOptions options) { throw new ArgumentException($"{nameof(options.QueueLimit)} must be set to a value greater than or equal to 0.", nameof(options)); } - if (options.ReplenishmentPeriod < TimeSpan.Zero) + if (options.ReplenishmentPeriod <= TimeSpan.Zero) { - throw new ArgumentException($"{nameof(options.ReplenishmentPeriod)} must be set to a value greater than or equal to TimeSpan.Zero.", nameof(options)); + throw new ArgumentException($"{nameof(options.ReplenishmentPeriod)} must be set to a value greater than TimeSpan.Zero.", nameof(options)); } _options = new TokenBucketRateLimiterOptions @@ -76,6 +77,7 @@ public TokenBucketRateLimiter(TokenBucketRateLimiterOptions options) }; _tokenCount = options.TokenLimit; + _fillRate = (double)options.TokensPerPeriod / options.ReplenishmentPeriod.Ticks; _idleSince = _lastReplenishmentTick = Stopwatch.GetTimestamp(); @@ -91,7 +93,7 @@ public TokenBucketRateLimiter(TokenBucketRateLimiterOptions options) ThrowIfDisposed(); return new RateLimiterStatistics() { - CurrentAvailablePermits = _tokenCount, + CurrentAvailablePermits = (long)_tokenCount, CurrentQueuedCount = _queueCount, TotalFailedLeases = Interlocked.Read(ref _failedLeasesCount), TotalSuccessfulLeases = Interlocked.Read(ref _successfulLeasesCount), @@ -210,7 +212,7 @@ protected override ValueTask AcquireAsyncCore(int tokenCount, Ca private RateLimitLease CreateFailedTokenLease(int tokenCount) { - int replenishAmount = tokenCount - _tokenCount + _queueCount; + int replenishAmount = tokenCount - (int)_tokenCount + _queueCount; // can't have 0 replenish periods, that would mean it should be a successful lease // if TokensPerPeriod is larger than the replenishAmount needed then it would be 0 Debug.Assert(_options.TokensPerPeriod > 0); @@ -278,7 +280,7 @@ private static void Replenish(object? state) limiter!.ReplenishInternal(nowTicks); } - // Used in tests that test behavior with specific time intervals + // Used in tests to avoid dealing with real time private void ReplenishInternal(long nowTicks) { // method is re-entrant (from Timer), lock to avoid multiple simultaneous replenishes @@ -289,37 +291,35 @@ private void ReplenishInternal(long nowTicks) return; } - if ((long)((nowTicks - _lastReplenishmentTick) * TickFrequency) < _options.ReplenishmentPeriod.Ticks) + if (_tokenCount == _options.TokenLimit) { return; } - _lastReplenishmentTick = nowTicks; - - int availablePermits = _tokenCount; - TokenBucketRateLimiterOptions options = _options; - int maxPermits = options.TokenLimit; - int resourcesToAdd; + double add; - if (availablePermits < maxPermits) + // Trust the timer to be close enough to when we want to replenish, this avoids issues with Timer jitter where it might be .99 seconds instead of 1, and 1.1 seconds the next time etc. + if (_options.AutoReplenishment) { - resourcesToAdd = Math.Min(options.TokensPerPeriod, maxPermits - availablePermits); + add = _options.TokensPerPeriod; } else { - // All tokens available, nothing to do - return; + add = _fillRate * (nowTicks - _lastReplenishmentTick) * TickFrequency; } + _tokenCount = Math.Min(_options.TokenLimit, _tokenCount + add); + + _lastReplenishmentTick = nowTicks; + // Process queued requests Deque queue = _queue; - _tokenCount += resourcesToAdd; Debug.Assert(_tokenCount <= _options.TokenLimit); while (queue.Count > 0) { RequestRegistration nextPendingRequest = - options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst + _options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst ? queue.PeekHead() : queue.PeekTail(); @@ -327,7 +327,7 @@ private void ReplenishInternal(long nowTicks) { // Request can be fulfilled nextPendingRequest = - options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst + _options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst ? queue.DequeueHead() : queue.DequeueTail(); diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiterOptions.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiterOptions.cs index 55b63f65d36bc5..2c065d9432e67c 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiterOptions.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiterOptions.cs @@ -10,7 +10,7 @@ public sealed class TokenBucketRateLimiterOptions { /// /// Specifies the minimum period between replenishments. - /// Must be set to a value >= by the time these options are passed to the constructor of . + /// Must be set to a value greater than by the time these options are passed to the constructor of . /// public TimeSpan ReplenishmentPeriod { get; set; } = TimeSpan.Zero; diff --git a/src/libraries/System.Threading.RateLimiting/tests/FixedWindowRateLimiterTests.cs b/src/libraries/System.Threading.RateLimiting/tests/FixedWindowRateLimiterTests.cs index 6830a1ce742816..1f597748d67f35 100644 --- a/src/libraries/System.Threading.RateLimiting/tests/FixedWindowRateLimiterTests.cs +++ b/src/libraries/System.Threading.RateLimiting/tests/FixedWindowRateLimiterTests.cs @@ -17,7 +17,7 @@ public override void CanAcquireResource() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(); @@ -27,7 +27,7 @@ public override void CanAcquireResource() lease.Dispose(); Assert.False(limiter.AttemptAcquire().IsAcquired); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.True(limiter.AttemptAcquire().IsAcquired); } @@ -37,31 +37,49 @@ public override void InvalidOptionsThrows() { Assert.Throws( () => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions - { - PermitLimit = -1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - Window = TimeSpan.FromMinutes(2), - AutoReplenishment = false - })); + { + PermitLimit = -1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.FromMinutes(2), + AutoReplenishment = false + })); Assert.Throws( () => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions - { - PermitLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = -1, - Window = TimeSpan.FromMinutes(2), - AutoReplenishment = false - })); + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = -1, + Window = TimeSpan.FromMinutes(2), + AutoReplenishment = false + })); Assert.Throws( () => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions - { - PermitLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - Window = TimeSpan.MinValue, - AutoReplenishment = false - })); + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.MinValue, + AutoReplenishment = false + })); + Assert.Throws( + () => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.FromMinutes(-2), + AutoReplenishment = false, + })); + Assert.Throws( + () => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.Zero, + AutoReplenishment = false, + })); } [Fact] @@ -72,7 +90,7 @@ public override async Task CanAcquireResourceAsync() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -82,7 +100,7 @@ public override async Task CanAcquireResourceAsync() var wait = limiter.AcquireAsync(); Assert.False(wait.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.True((await wait).IsAcquired); } @@ -95,7 +113,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = await limiter.AcquireAsync(); @@ -107,7 +125,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); lease = await wait1; Assert.True(lease.IsAcquired); @@ -115,7 +133,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() lease.Dispose(); Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -129,7 +147,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 3, - Window = TimeSpan.FromMinutes(0), + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -142,7 +160,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); // second queued item completes first with NewestFirst lease = await wait2; @@ -151,7 +169,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() lease.Dispose(); Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); lease = await wait1; Assert.True(lease.IsAcquired); @@ -165,7 +183,7 @@ public override async Task FailsWhenQueuingMoreThanLimit_OldestFirst() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); using var lease = limiter.AttemptAcquire(1); @@ -174,7 +192,7 @@ public override async Task FailsWhenQueuingMoreThanLimit_OldestFirst() var failedLease = await limiter.AcquireAsync(1); Assert.False(failedLease.IsAcquired); Assert.True(failedLease.TryGetMetadata(MetadataName.RetryAfter, out var timeSpan)); - Assert.Equal(TimeSpan.Zero, timeSpan); + Assert.Equal(TimeSpan.FromMilliseconds(2), timeSpan); } [Fact] @@ -185,7 +203,7 @@ public override async Task DropsOldestWhenQueuingMoreThanLimit_NewestFirst() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -197,7 +215,7 @@ public override async Task DropsOldestWhenQueuingMoreThanLimit_NewestFirst() Assert.False(lease1.IsAcquired); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -211,7 +229,7 @@ public override async Task DropsMultipleOldestWhenQueuingMoreThanLimit_NewestFir PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(2); @@ -229,7 +247,7 @@ public override async Task DropsMultipleOldestWhenQueuingMoreThanLimit_NewestFir Assert.False(lease2.IsAcquired); Assert.False(wait3.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait3; Assert.True(lease.IsAcquired); @@ -243,7 +261,7 @@ public override async Task DropsRequestedLeaseIfPermitCountGreaterThanQueueLimit PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(2); @@ -256,7 +274,7 @@ public override async Task DropsRequestedLeaseIfPermitCountGreaterThanQueueLimit var lease1 = await limiter.AcquireAsync(2); Assert.False(lease1.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -270,7 +288,7 @@ public override async Task QueueAvailableAfterQueueLimitHitAndResources_BecomeAv PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -279,14 +297,14 @@ public override async Task QueueAvailableAfterQueueLimitHitAndResources_BecomeAv var failedLease = await limiter.AcquireAsync(1); Assert.False(failedLease.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); wait = limiter.AcquireAsync(1); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); } @@ -299,7 +317,7 @@ public override async Task LargeAcquiresAndQueuesDoNotIntegerOverflow() PermitLimit = int.MaxValue, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = int.MaxValue, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(int.MaxValue); @@ -315,7 +333,7 @@ public override async Task LargeAcquiresAndQueuesDoNotIntegerOverflow() var lease1 = await wait; Assert.False(lease1.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); var lease2 = await wait2; Assert.True(lease2.IsAcquired); } @@ -328,7 +346,7 @@ public override void ThrowsWhenAcquiringMoreThanLimit() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); Assert.Throws(() => limiter.AttemptAcquire(2)); @@ -342,7 +360,7 @@ public override async Task ThrowsWhenWaitingForMoreThanLimit() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); await Assert.ThrowsAsync(async () => await limiter.AcquireAsync(2)); @@ -356,7 +374,7 @@ public override void ThrowsWhenAcquiringLessThanZero() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); Assert.Throws(() => limiter.AttemptAcquire(-1)); @@ -370,7 +388,7 @@ public override async Task ThrowsWhenWaitingForLessThanZero() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); await Assert.ThrowsAsync(async () => await limiter.AcquireAsync(-1)); @@ -384,7 +402,7 @@ public override void AcquireZero_WithAvailability() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -400,7 +418,7 @@ public override void AcquireZero_WithoutAvailability() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); using var lease = limiter.AttemptAcquire(1); @@ -419,7 +437,7 @@ public override async Task AcquireAsyncZero_WithAvailability() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -435,7 +453,7 @@ public override async Task AcquireAsyncZero_WithoutAvailabilityWaitsForAvailabil PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = await limiter.AcquireAsync(1); @@ -445,7 +463,7 @@ public override async Task AcquireAsyncZero_WithoutAvailabilityWaitsForAvailabil Assert.False(wait.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); using var lease2 = await wait; Assert.True(lease2.IsAcquired); } @@ -458,7 +476,7 @@ public override async Task CanDequeueMultipleResourcesAtOnce() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); using var lease = await limiter.AcquireAsync(2); @@ -470,7 +488,7 @@ public override async Task CanDequeueMultipleResourcesAtOnce() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); var lease1 = await wait1; var lease2 = await wait2; @@ -486,7 +504,7 @@ public override async Task CanCancelAcquireAsyncAfterQueuing() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -500,7 +518,7 @@ public override async Task CanCancelAcquireAsyncAfterQueuing() Assert.Equal(cts.Token, ex.CancellationToken); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); } @@ -513,7 +531,7 @@ public override async Task CanCancelAcquireAsyncBeforeQueuing() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -526,7 +544,7 @@ public override async Task CanCancelAcquireAsyncBeforeQueuing() Assert.Equal(cts.Token, ex.CancellationToken); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); } @@ -539,7 +557,7 @@ public override async Task CancelUpdatesQueueLimit() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -555,7 +573,7 @@ public override async Task CancelUpdatesQueueLimit() wait = limiter.AcquireAsync(1); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); } @@ -568,7 +586,7 @@ public override void NoMetadataOnAcquiredLease() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); using var lease = limiter.AttemptAcquire(1); @@ -583,7 +601,7 @@ public override void MetadataNamesContainsAllMetadata() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); using var lease = limiter.AttemptAcquire(1); @@ -598,7 +616,7 @@ public override async Task DisposeReleasesQueuedAcquires() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -631,7 +649,7 @@ public override async Task DisposeAsyncReleasesQueuedAcquires() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -770,7 +788,7 @@ public override async Task CanAcquireResourcesWithAcquireAsyncWithQueuedItemsIfN PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -785,7 +803,7 @@ public override async Task CanAcquireResourcesWithAcquireAsyncWithQueuedItemsIfN Assert.True(lease.IsAcquired); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -799,7 +817,7 @@ public override async Task CannotAcquireResourcesWithAcquireAsyncWithQueuedItems PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -811,13 +829,13 @@ public override async Task CannotAcquireResourcesWithAcquireAsyncWithQueuedItems Assert.False(wait.IsCompleted); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -831,7 +849,7 @@ public override async Task CanAcquireResourcesWithAcquireWithQueuedItemsIfNewest PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -845,7 +863,7 @@ public override async Task CanAcquireResourcesWithAcquireWithQueuedItemsIfNewest Assert.True(lease.IsAcquired); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -859,7 +877,7 @@ public override async Task CannotAcquireResourcesWithAcquireWithQueuedItemsIfOld PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -872,7 +890,7 @@ public override async Task CannotAcquireResourcesWithAcquireWithQueuedItemsIfOld lease = limiter.AttemptAcquire(1); Assert.False(lease.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -918,11 +936,11 @@ public override void IdleDurationUpdatesWhenChangingFromActive() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); limiter.AttemptAcquire(1); - limiter.TryReplenish(); + Replenish(limiter, 1L); Assert.NotNull(limiter.IdleDuration); } @@ -962,7 +980,7 @@ public override async Task CanFillQueueWithNewestFirstAfterCancelingQueuedReques PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(2); @@ -988,7 +1006,7 @@ public override async Task CanFillQueueWithNewestFirstAfterCancelingQueuedReques lease = await wait2; Assert.False(lease.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait3; Assert.True(lease.IsAcquired); } @@ -1001,7 +1019,7 @@ public override async Task CanDisposeAfterCancelingQueuedRequest() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -1026,7 +1044,7 @@ public override void GetStatisticsReturnsNewInstances() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -1049,7 +1067,7 @@ public override async Task GetStatisticsHasCorrectValues() PermitLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -1093,7 +1111,7 @@ public override async Task GetStatisticsHasCorrectValues() Assert.Equal(2, stats.TotalFailedLeases); Assert.Equal(1, stats.TotalSuccessfulLeases); - limiter.TryReplenish(); + Replenish(limiter, 1); await lease2Task; // success from wait + available + queue @@ -1112,7 +1130,7 @@ public override async Task GetStatisticsWithZeroPermitCount() PermitLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(0); @@ -1145,11 +1163,45 @@ public override void GetStatisticsThrowsAfterDispose() PermitLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); limiter.Dispose(); Assert.Throws(limiter.GetStatistics); } + + [Fact] + public void AutoReplenishIgnoresTimerJitter() + { + var replenishmentPeriod = TimeSpan.FromMinutes(10); + using var limiter = new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions + { + PermitLimit = 10, + QueueProcessingOrder = QueueProcessingOrder.OldestFirst, + QueueLimit = 1, + Window = replenishmentPeriod, + AutoReplenishment = true, + }); + + var lease = limiter.AttemptAcquire(permitCount: 3); + Assert.True(lease.IsAcquired); + + Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); + + // Replenish 1 millisecond less than ReplenishmentPeriod while AutoReplenishment is enabled + Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds - 1); + + Assert.Equal(10, limiter.GetStatistics().CurrentAvailablePermits); + } + + private static readonly double TickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency; + + static internal void Replenish(FixedWindowRateLimiter limiter, long addMilliseconds) + { + var replenishInternalMethod = typeof(FixedWindowRateLimiter).GetMethod("ReplenishInternal", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; + var internalTick = typeof(FixedWindowRateLimiter).GetField("_lastReplenishmentTick", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; + var currentTick = (long)internalTick.GetValue(limiter); + replenishInternalMethod.Invoke(limiter, new object[] { currentTick + addMilliseconds * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) }); + } } } diff --git a/src/libraries/System.Threading.RateLimiting/tests/SlidingWindowRateLimiterTests.cs b/src/libraries/System.Threading.RateLimiting/tests/SlidingWindowRateLimiterTests.cs index 7241a39e0f1f4d..66e6cb2d5f228a 100644 --- a/src/libraries/System.Threading.RateLimiting/tests/SlidingWindowRateLimiterTests.cs +++ b/src/libraries/System.Threading.RateLimiting/tests/SlidingWindowRateLimiterTests.cs @@ -17,7 +17,7 @@ public override void CanAcquireResource() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -28,8 +28,8 @@ public override void CanAcquireResource() lease.Dispose(); Assert.False(limiter.AttemptAcquire().IsAcquired); - Assert.True(limiter.TryReplenish()); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Replenish(limiter, 1L); Assert.True(limiter.AttemptAcquire().IsAcquired); } @@ -39,44 +39,64 @@ public override void InvalidOptionsThrows() { Assert.Throws( () => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions - { - PermitLimit = -1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - Window = TimeSpan.FromMinutes(2), - SegmentsPerWindow = 1, - AutoReplenishment = false - })); + { + PermitLimit = -1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.FromMinutes(2), + SegmentsPerWindow = 1, + AutoReplenishment = false + })); Assert.Throws( () => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions - { - PermitLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = -1, - Window = TimeSpan.FromMinutes(2), - SegmentsPerWindow = 1, - AutoReplenishment = false - })); + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = -1, + Window = TimeSpan.FromMinutes(2), + SegmentsPerWindow = 1, + AutoReplenishment = false + })); Assert.Throws( () => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions - { - PermitLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - Window = TimeSpan.FromMinutes(2), - SegmentsPerWindow = -1, - AutoReplenishment = false - })); + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.FromMinutes(2), + SegmentsPerWindow = -1, + AutoReplenishment = false + })); Assert.Throws( () => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions - { - PermitLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - Window = TimeSpan.MinValue, - SegmentsPerWindow = 1, - AutoReplenishment = false - })); + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.MinValue, + SegmentsPerWindow = 1, + AutoReplenishment = false + })); + Assert.Throws( + () => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.FromMinutes(-2), + SegmentsPerWindow = 1, + AutoReplenishment = false + })); + Assert.Throws( + () => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.Zero, + SegmentsPerWindow = 1, + AutoReplenishment = false + })); } [Fact] @@ -87,7 +107,7 @@ public override async Task CanAcquireResourceAsync() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 4, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -98,14 +118,14 @@ public override async Task CanAcquireResourceAsync() var wait = limiter.AcquireAsync(2); Assert.False(wait.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.False(wait.IsCompleted); var wait2 = limiter.AcquireAsync(2); Assert.False(wait2.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.True((await wait2).IsAcquired); } @@ -121,7 +141,7 @@ public async Task CanAcquireMultipleRequestsAsync() PermitLimit = 4, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 4, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(3), SegmentsPerWindow = 3, AutoReplenishment = false }); @@ -132,19 +152,19 @@ public async Task CanAcquireMultipleRequestsAsync() var wait = limiter.AcquireAsync(3); Assert.False(wait.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.False(wait.IsCompleted); var wait2 = limiter.AcquireAsync(2); Assert.True(wait2.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); var wait3 = limiter.AcquireAsync(2); Assert.False(wait3.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.True((await wait3).IsAcquired); Assert.False((await wait).IsAcquired); @@ -159,7 +179,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.FromMinutes(0), + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -172,10 +192,10 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.False(wait1.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); lease = await wait1; Assert.True(lease.IsAcquired); @@ -183,8 +203,8 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() lease.Dispose(); Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -198,7 +218,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 3, - Window = TimeSpan.FromMinutes(0), + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -212,10 +232,10 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.False(wait2.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); // second queued item completes first with NewestFirst lease = await wait2; Assert.True(lease.IsAcquired); @@ -223,8 +243,8 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() lease.Dispose(); Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait1; Assert.True(lease.IsAcquired); @@ -238,7 +258,7 @@ public override async Task FailsWhenQueuingMoreThanLimit_OldestFirst() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -257,7 +277,7 @@ public override async Task DropsOldestWhenQueuingMoreThanLimit_NewestFirst() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -270,8 +290,8 @@ public override async Task DropsOldestWhenQueuingMoreThanLimit_NewestFirst() Assert.False(lease1.IsAcquired); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -285,7 +305,7 @@ public override async Task DropsMultipleOldestWhenQueuingMoreThanLimit_NewestFir PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -304,8 +324,8 @@ public override async Task DropsMultipleOldestWhenQueuingMoreThanLimit_NewestFir Assert.False(lease2.IsAcquired); Assert.False(wait3.IsCompleted); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait3; Assert.True(lease.IsAcquired); @@ -319,7 +339,7 @@ public override async Task DropsRequestedLeaseIfPermitCountGreaterThanQueueLimit PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -333,8 +353,8 @@ public override async Task DropsRequestedLeaseIfPermitCountGreaterThanQueueLimit var lease1 = await limiter.AcquireAsync(2); Assert.False(lease1.IsAcquired); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -348,7 +368,7 @@ public override async Task QueueAvailableAfterQueueLimitHitAndResources_BecomeAv PermitLimit = 3, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(3), SegmentsPerWindow = 3, AutoReplenishment = false }); @@ -358,20 +378,20 @@ public override async Task QueueAvailableAfterQueueLimitHitAndResources_BecomeAv var failedLease = await limiter.AcquireAsync(2); Assert.False(failedLease.IsAcquired); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); wait = limiter.AcquireAsync(2); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -385,7 +405,7 @@ public override async Task LargeAcquiresAndQueuesDoNotIntegerOverflow() PermitLimit = int.MaxValue, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = int.MaxValue, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -402,8 +422,8 @@ public override async Task LargeAcquiresAndQueuesDoNotIntegerOverflow() var lease1 = await wait; Assert.False(lease1.IsAcquired); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); var lease2 = await wait2; Assert.True(lease2.IsAcquired); } @@ -416,7 +436,7 @@ public override void ThrowsWhenAcquiringMoreThanLimit() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -431,7 +451,7 @@ public override async Task ThrowsWhenWaitingForMoreThanLimit() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -446,7 +466,7 @@ public override void ThrowsWhenAcquiringLessThanZero() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -461,7 +481,7 @@ public override async Task ThrowsWhenWaitingForLessThanZero() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -476,7 +496,7 @@ public override void AcquireZero_WithAvailability() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -493,7 +513,7 @@ public override void AcquireZero_WithoutAvailability() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -513,7 +533,7 @@ public override async Task AcquireAsyncZero_WithAvailability() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -530,7 +550,7 @@ public override async Task AcquireAsyncZero_WithoutAvailabilityWaitsForAvailabil PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -541,8 +561,8 @@ public override async Task AcquireAsyncZero_WithoutAvailabilityWaitsForAvailabil Assert.False(wait.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Replenish(limiter, 1L); using var lease2 = await wait; Assert.True(lease2.IsAcquired); } @@ -555,7 +575,7 @@ public override async Task CanDequeueMultipleResourcesAtOnce() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 4, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -568,8 +588,8 @@ public override async Task CanDequeueMultipleResourcesAtOnce() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Replenish(limiter, 1L); var lease1 = await wait1; var lease2 = await wait2; @@ -585,7 +605,7 @@ public override async Task CanCancelAcquireAsyncAfterQueuing() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -600,7 +620,7 @@ public override async Task CanCancelAcquireAsyncAfterQueuing() Assert.Equal(cts.Token, ex.CancellationToken); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits); } @@ -613,7 +633,7 @@ public override async Task CanCancelAcquireAsyncBeforeQueuing() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -627,7 +647,7 @@ public override async Task CanCancelAcquireAsyncBeforeQueuing() Assert.Equal(cts.Token, ex.CancellationToken); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits); } @@ -640,7 +660,7 @@ public override async Task CancelUpdatesQueueLimit() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -657,8 +677,8 @@ public override async Task CancelUpdatesQueueLimit() wait = limiter.AcquireAsync(1); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -673,7 +693,7 @@ public override void NoMetadataOnAcquiredLease() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -689,7 +709,7 @@ public override void MetadataNamesContainsAllMetadata() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -705,7 +725,7 @@ public override async Task DisposeReleasesQueuedAcquires() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -739,7 +759,7 @@ public override async Task DisposeAsyncReleasesQueuedAcquires() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -809,7 +829,7 @@ public override async Task CanAcquireResourcesWithAcquireAsyncWithQueuedItemsIfN PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(3), SegmentsPerWindow = 3, AutoReplenishment = false }); @@ -825,12 +845,12 @@ public override async Task CanAcquireResourcesWithAcquireAsyncWithQueuedItemsIfN Assert.True(lease.IsAcquired); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Replenish(limiter, 1L); Assert.False(wait.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); } @@ -843,7 +863,7 @@ public override async Task CannotAcquireResourcesWithAcquireAsyncWithQueuedItems PermitLimit = 3, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 5, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -856,18 +876,18 @@ public override async Task CannotAcquireResourcesWithAcquireAsyncWithQueuedItems Assert.False(wait.IsCompleted); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); Assert.False(wait.IsCompleted); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -881,7 +901,7 @@ public override async Task CanAcquireResourcesWithAcquireWithQueuedItemsIfNewest PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -896,8 +916,8 @@ public override async Task CanAcquireResourcesWithAcquireWithQueuedItemsIfNewest Assert.True(lease.IsAcquired); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -911,7 +931,7 @@ public override async Task CannotAcquireResourcesWithAcquireWithQueuedItemsIfOld PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -925,8 +945,8 @@ public override async Task CannotAcquireResourcesWithAcquireWithQueuedItemsIfOld lease = limiter.AttemptAcquire(1); Assert.False(lease.IsAcquired); - limiter.TryReplenish(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -974,13 +994,13 @@ public override void IdleDurationUpdatesWhenChangingFromActive() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); limiter.AttemptAcquire(1); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); Assert.NotNull(limiter.IdleDuration); } @@ -1022,7 +1042,7 @@ public override async Task CanFillQueueWithNewestFirstAfterCancelingQueuedReques PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -1041,7 +1061,7 @@ public override async Task CanFillQueueWithNewestFirstAfterCancelingQueuedReques Assert.Equal(cts.Token, ex.CancellationToken); lease.Dispose(); - limiter.TryReplenish(); + Replenish(limiter, 1L); var wait3 = limiter.AcquireAsync(2); Assert.False(wait3.IsCompleted); @@ -1050,7 +1070,7 @@ public override async Task CanFillQueueWithNewestFirstAfterCancelingQueuedReques lease = await wait2; Assert.False(lease.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait3; Assert.True(lease.IsAcquired); } @@ -1063,7 +1083,7 @@ public override async Task CanDisposeAfterCancelingQueuedRequest() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -1089,7 +1109,7 @@ public override void GetStatisticsReturnsNewInstances() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -1113,7 +1133,7 @@ public override async Task GetStatisticsHasCorrectValues() PermitLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -1138,7 +1158,7 @@ public override async Task GetStatisticsHasCorrectValues() Assert.Equal(0, stats.TotalFailedLeases); Assert.Equal(1, stats.TotalSuccessfulLeases); - limiter.TryReplenish(); + Replenish(limiter, 1); var lease3 = await limiter.AcquireAsync(1); Assert.False(lease3.IsAcquired); @@ -1156,7 +1176,7 @@ public override async Task GetStatisticsHasCorrectValues() Assert.Equal(2, stats.TotalFailedLeases); Assert.Equal(1, stats.TotalSuccessfulLeases); - limiter.TryReplenish(); + Replenish(limiter, 1); await lease2Task; stats = limiter.GetStatistics(); @@ -1174,7 +1194,7 @@ public override async Task GetStatisticsWithZeroPermitCount() PermitLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(3), SegmentsPerWindow = 3, AutoReplenishment = false }); @@ -1208,12 +1228,57 @@ public override void GetStatisticsThrowsAfterDispose() PermitLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(3), SegmentsPerWindow = 3, AutoReplenishment = false }); limiter.Dispose(); Assert.Throws(limiter.GetStatistics); } + + [Fact] + public void AutoReplenishIgnoresTimerJitter() + { + var replenishmentPeriod = TimeSpan.FromMinutes(10); + using var limiter = new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions + { + PermitLimit = 10, + QueueProcessingOrder = QueueProcessingOrder.OldestFirst, + QueueLimit = 1, + Window = replenishmentPeriod, + SegmentsPerWindow = 2, + AutoReplenishment = true, + }); + + var lease = limiter.AttemptAcquire(permitCount: 3); + Assert.True(lease.IsAcquired); + + Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); + + // Replenish 1 millisecond less than ReplenishmentPeriod while AutoReplenishment is enabled + Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds / 2 - 1); + + Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); + + lease = limiter.AttemptAcquire(permitCount: 3); + Assert.True(lease.IsAcquired); + + Assert.Equal(4, limiter.GetStatistics().CurrentAvailablePermits); + + // Replenish 1 millisecond longer than ReplenishmentPeriod while AutoReplenishment is enabled + Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds / 2 + 1); + + Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); + } + + private static readonly double TickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency; + + static internal void Replenish(SlidingWindowRateLimiter limiter, long addMilliseconds) + { + var replenishInternalMethod = typeof(SlidingWindowRateLimiter).GetMethod("ReplenishInternal", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; + var internalTick = typeof(SlidingWindowRateLimiter).GetField("_lastReplenishmentTick", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; + var currentTick = (long)internalTick.GetValue(limiter); + replenishInternalMethod.Invoke(limiter, new object[] { currentTick + addMilliseconds * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) }); + } } } diff --git a/src/libraries/System.Threading.RateLimiting/tests/TokenBucketRateLimiterTests.cs b/src/libraries/System.Threading.RateLimiting/tests/TokenBucketRateLimiterTests.cs index 272c294a09b345..79c368e2b6d34a 100644 --- a/src/libraries/System.Threading.RateLimiting/tests/TokenBucketRateLimiterTests.cs +++ b/src/libraries/System.Threading.RateLimiting/tests/TokenBucketRateLimiterTests.cs @@ -17,7 +17,7 @@ public override void CanAcquireResource() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -26,9 +26,10 @@ public override void CanAcquireResource() Assert.True(lease.IsAcquired); Assert.False(limiter.AttemptAcquire().IsAcquired); + // Dispose doesn't change token count lease.Dispose(); Assert.False(limiter.AttemptAcquire().IsAcquired); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.True(limiter.AttemptAcquire().IsAcquired); } @@ -38,44 +39,64 @@ public override void InvalidOptionsThrows() { Assert.Throws( () => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions - { - TokenLimit = -1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.FromMinutes(2), - TokensPerPeriod = 1, - AutoReplenishment = false - })); + { + TokenLimit = -1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + ReplenishmentPeriod = TimeSpan.FromMinutes(2), + TokensPerPeriod = 1, + AutoReplenishment = false + })); Assert.Throws( () => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions - { - TokenLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = -1, - ReplenishmentPeriod = TimeSpan.FromMinutes(2), - TokensPerPeriod = 1, - AutoReplenishment = false - })); + { + TokenLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = -1, + ReplenishmentPeriod = TimeSpan.FromMinutes(2), + TokensPerPeriod = 1, + AutoReplenishment = false + })); Assert.Throws( () => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions - { - TokenLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.FromMinutes(2), - TokensPerPeriod = -1, - AutoReplenishment = false - })); + { + TokenLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + ReplenishmentPeriod = TimeSpan.FromMinutes(2), + TokensPerPeriod = -1, + AutoReplenishment = false + })); Assert.Throws( () => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions - { - TokenLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.MinValue, - TokensPerPeriod = 1, - AutoReplenishment = false - })); + { + TokenLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + ReplenishmentPeriod = TimeSpan.MinValue, + TokensPerPeriod = 1, + AutoReplenishment = false + })); + Assert.Throws( + () => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions + { + TokenLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(-1), + TokensPerPeriod = 1, + AutoReplenishment = false + })); + Assert.Throws( + () => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions + { + TokenLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + ReplenishmentPeriod = TimeSpan.Zero, + TokensPerPeriod = 1, + AutoReplenishment = false + })); } [Fact] @@ -86,7 +107,7 @@ public override async Task CanAcquireResourceAsync() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -97,7 +118,7 @@ public override async Task CanAcquireResourceAsync() var wait = limiter.AcquireAsync(); Assert.False(wait.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.True((await wait).IsAcquired); } @@ -110,7 +131,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -123,7 +144,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); lease = await wait1; Assert.True(lease.IsAcquired); @@ -131,7 +152,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() lease.Dispose(); Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -145,7 +166,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 3, - ReplenishmentPeriod = TimeSpan.FromMinutes(0), + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -159,7 +180,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); // second queued item completes first with NewestFirst lease = await wait2; @@ -168,8 +189,9 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() lease.Dispose(); Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); + Replenish(limiter, 1L); lease = await wait1; Assert.True(lease.IsAcquired); @@ -183,7 +205,7 @@ public override async Task FailsWhenQueuingMoreThanLimit_OldestFirst() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -193,7 +215,7 @@ public override async Task FailsWhenQueuingMoreThanLimit_OldestFirst() var failedLease = await limiter.AcquireAsync(1); Assert.False(failedLease.IsAcquired); Assert.True(failedLease.TryGetMetadata(MetadataName.RetryAfter, out var timeSpan)); - Assert.Equal(TimeSpan.Zero, timeSpan); + Assert.Equal(TimeSpan.FromMilliseconds(2), timeSpan); } [Fact] @@ -204,7 +226,7 @@ public override async Task DropsOldestWhenQueuingMoreThanLimit_NewestFirst() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -217,7 +239,7 @@ public override async Task DropsOldestWhenQueuingMoreThanLimit_NewestFirst() Assert.False(lease1.IsAcquired); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -231,7 +253,7 @@ public override async Task DropsMultipleOldestWhenQueuingMoreThanLimit_NewestFir TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -250,8 +272,8 @@ public override async Task DropsMultipleOldestWhenQueuingMoreThanLimit_NewestFir Assert.False(lease2.IsAcquired); Assert.False(wait3.IsCompleted); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait3; Assert.True(lease.IsAcquired); @@ -265,7 +287,7 @@ public override async Task DropsRequestedLeaseIfPermitCountGreaterThanQueueLimit TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -279,7 +301,7 @@ public override async Task DropsRequestedLeaseIfPermitCountGreaterThanQueueLimit var lease1 = await limiter.AcquireAsync(2); Assert.False(lease1.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -293,7 +315,7 @@ public override async Task QueueAvailableAfterQueueLimitHitAndResources_BecomeAv TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -303,14 +325,14 @@ public override async Task QueueAvailableAfterQueueLimitHitAndResources_BecomeAv var failedLease = await limiter.AcquireAsync(1); Assert.False(failedLease.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); wait = limiter.AcquireAsync(1); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); } @@ -323,7 +345,7 @@ public override async Task LargeAcquiresAndQueuesDoNotIntegerOverflow() TokenLimit = int.MaxValue, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = int.MaxValue, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = int.MaxValue, AutoReplenishment = false }); @@ -340,7 +362,7 @@ public override async Task LargeAcquiresAndQueuesDoNotIntegerOverflow() var lease1 = await wait; Assert.False(lease1.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); var lease2 = await wait2; Assert.True(lease2.IsAcquired); } @@ -353,7 +375,7 @@ public override void ThrowsWhenAcquiringMoreThanLimit() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -368,7 +390,7 @@ public override async Task ThrowsWhenWaitingForMoreThanLimit() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -383,7 +405,7 @@ public override void ThrowsWhenAcquiringLessThanZero() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -398,7 +420,7 @@ public override async Task ThrowsWhenWaitingForLessThanZero() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -413,7 +435,7 @@ public override void AcquireZero_WithAvailability() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -430,7 +452,7 @@ public override void AcquireZero_WithoutAvailability() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -450,7 +472,7 @@ public override async Task AcquireAsyncZero_WithAvailability() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -467,7 +489,7 @@ public override async Task AcquireAsyncZero_WithoutAvailabilityWaitsForAvailabil TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -478,7 +500,7 @@ public override async Task AcquireAsyncZero_WithoutAvailabilityWaitsForAvailabil Assert.False(wait.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); using var lease2 = await wait; Assert.True(lease2.IsAcquired); } @@ -491,7 +513,7 @@ public override async Task CanDequeueMultipleResourcesAtOnce() TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 2, AutoReplenishment = false }); @@ -504,7 +526,7 @@ public override async Task CanDequeueMultipleResourcesAtOnce() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); var lease1 = await wait1; var lease2 = await wait2; @@ -520,7 +542,7 @@ public override async Task CanCancelAcquireAsyncAfterQueuing() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -535,7 +557,7 @@ public override async Task CanCancelAcquireAsyncAfterQueuing() Assert.Equal(cts.Token, ex.CancellationToken); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); } @@ -548,7 +570,7 @@ public override async Task CanFillQueueWithNewestFirstAfterCancelingQueuedReques TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 2, AutoReplenishment = false }); @@ -575,7 +597,7 @@ public override async Task CanFillQueueWithNewestFirstAfterCancelingQueuedReques lease = await wait2; Assert.False(lease.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait3; Assert.True(lease.IsAcquired); } @@ -588,7 +610,7 @@ public override async Task CanDisposeAfterCancelingQueuedRequest() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -614,7 +636,7 @@ public override async Task CanCancelAcquireAsyncBeforeQueuing() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -628,7 +650,7 @@ public override async Task CanCancelAcquireAsyncBeforeQueuing() Assert.Equal(cts.Token, ex.CancellationToken); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); } @@ -641,7 +663,7 @@ public override async Task CancelUpdatesQueueLimit() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -658,7 +680,7 @@ public override async Task CancelUpdatesQueueLimit() wait = limiter.AcquireAsync(1); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); } @@ -671,7 +693,7 @@ public override void NoMetadataOnAcquiredLease() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -687,7 +709,7 @@ public override void MetadataNamesContainsAllMetadata() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -703,7 +725,7 @@ public override async Task DisposeReleasesQueuedAcquires() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -737,7 +759,7 @@ public override async Task DisposeAsyncReleasesQueuedAcquires() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -888,14 +910,14 @@ public async Task CorrectRetryMetadataWithNonZeroAvailableItems() } [Fact] - public void TryReplenishHonorsTokensPerPeriod() + public void ReplenishHonorsTokensPerPeriod() { var limiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions { TokenLimit = 7, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 3, AutoReplenishment = false }); @@ -903,27 +925,28 @@ public void TryReplenishHonorsTokensPerPeriod() Assert.False(limiter.AttemptAcquire(3).IsAcquired); Assert.Equal(2, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(5, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); } [Fact] - public void TryReplenishWithAllTokensAvailable_Noops() + public async void TryReplenishWithAllTokensAvailable_Noops() { var limiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions { TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(30), TokensPerPeriod = 1, AutoReplenishment = false }); Assert.Equal(2, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); + await Task.Delay(100); + limiter.TryReplenish(); Assert.Equal(2, limiter.GetStatistics().CurrentAvailablePermits); } @@ -971,7 +994,7 @@ public override async Task CanAcquireResourcesWithAcquireAsyncWithQueuedItemsIfN TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 2, AutoReplenishment = false }); @@ -987,7 +1010,7 @@ public override async Task CanAcquireResourcesWithAcquireAsyncWithQueuedItemsIfN Assert.True(lease.IsAcquired); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -1001,7 +1024,7 @@ public override async Task CannotAcquireResourcesWithAcquireAsyncWithQueuedItems TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 2, AutoReplenishment = false }); @@ -1014,13 +1037,13 @@ public override async Task CannotAcquireResourcesWithAcquireAsyncWithQueuedItems Assert.False(wait.IsCompleted); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -1034,7 +1057,7 @@ public override async Task CanAcquireResourcesWithAcquireWithQueuedItemsIfNewest TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 3, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 2, AutoReplenishment = false }); @@ -1049,7 +1072,7 @@ public override async Task CanAcquireResourcesWithAcquireWithQueuedItemsIfNewest Assert.True(lease.IsAcquired); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -1063,7 +1086,7 @@ public override async Task CannotAcquireResourcesWithAcquireWithQueuedItemsIfOld TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 2, AutoReplenishment = false }); @@ -1077,14 +1100,12 @@ public override async Task CannotAcquireResourcesWithAcquireWithQueuedItemsIfOld lease = limiter.AttemptAcquire(1); Assert.False(lease.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); } - private static readonly double TickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency; - [Fact] public async Task ReplenishWorksWithTicksOverInt32Max() { @@ -1098,16 +1119,16 @@ public async Task ReplenishWorksWithTicksOverInt32Max() AutoReplenishment = false }); + // Ensure next tick is over uint.MaxValue + Replenish(limiter, uint.MaxValue); + var lease = limiter.AttemptAcquire(10); Assert.True(lease.IsAcquired); var wait = limiter.AcquireAsync(1); Assert.False(wait.IsCompleted); - var replenishInternalMethod = typeof(TokenBucketRateLimiter).GetMethod("ReplenishInternal", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; - // Ensure next tick is over uint.MaxValue - var tick = Stopwatch.GetTimestamp() + uint.MaxValue; - replenishInternalMethod.Invoke(limiter, new object[] { tick }); + Replenish(limiter, 2L); lease = await wait; Assert.True(lease.IsAcquired); @@ -1116,11 +1137,11 @@ public async Task ReplenishWorksWithTicksOverInt32Max() Assert.False(wait.IsCompleted); // Tick 1 millisecond too soon and verify that the queued item wasn't completed - replenishInternalMethod.Invoke(limiter, new object[] { tick + 1L * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) }); + Replenish(limiter, 1L); Assert.False(wait.IsCompleted); // ticks would wrap if using uint - replenishInternalMethod.Invoke(limiter, new object[] { tick + 2L * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) }); + Replenish(limiter, 2L); lease = await wait; Assert.True(lease.IsAcquired); } @@ -1167,12 +1188,12 @@ public override void IdleDurationUpdatesWhenChangingFromActive() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); limiter.AttemptAcquire(1); - limiter.TryReplenish(); + Replenish(limiter, 1L); Assert.NotNull(limiter.IdleDuration); } @@ -1214,7 +1235,7 @@ public override void GetStatisticsReturnsNewInstances() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 2, AutoReplenishment = false }); @@ -1238,7 +1259,7 @@ public override async Task GetStatisticsHasCorrectValues() TokenLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 30, AutoReplenishment = false }); @@ -1279,7 +1300,7 @@ public override async Task GetStatisticsHasCorrectValues() Assert.Equal(2, stats.TotalFailedLeases); Assert.Equal(1, stats.TotalSuccessfulLeases); - limiter.TryReplenish(); + Replenish(limiter, 1); await lease2Task; stats = limiter.GetStatistics(); @@ -1297,7 +1318,7 @@ public override async Task GetStatisticsWithZeroPermitCount() TokenLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 30, AutoReplenishment = false }); @@ -1331,12 +1352,82 @@ public override void GetStatisticsThrowsAfterDispose() TokenLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 30, AutoReplenishment = false }); limiter.Dispose(); Assert.Throws(limiter.GetStatistics); } + + [Fact] + public void AutoReplenishIgnoresTimerJitter() + { + var replenishmentPeriod = TimeSpan.FromMinutes(10); + using var limiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions + { + TokenLimit = 10, + QueueProcessingOrder = QueueProcessingOrder.OldestFirst, + QueueLimit = 1, + ReplenishmentPeriod = replenishmentPeriod, + AutoReplenishment = true, + TokensPerPeriod = 1, + }); + + var lease = limiter.AttemptAcquire(permitCount: 3); + Assert.True(lease.IsAcquired); + + Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); + + // Replenish 1 millisecond less than ReplenishmentPeriod while AutoReplenishment is enabled + Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds - 1); + + Assert.Equal(8, limiter.GetStatistics().CurrentAvailablePermits); + + // Replenish 1 millisecond longer than ReplenishmentPeriod while AutoReplenishment is enabled + Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds + 1); + + Assert.Equal(9, limiter.GetStatistics().CurrentAvailablePermits); + } + + [Fact] + public void ManualReplenishPreservesTimeWithTimerJitter() + { + var replenishmentPeriod = TimeSpan.FromMinutes(10); + using var limiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions + { + TokenLimit = 10, + QueueProcessingOrder = QueueProcessingOrder.OldestFirst, + QueueLimit = 1, + ReplenishmentPeriod = replenishmentPeriod, + AutoReplenishment = false, + TokensPerPeriod = 1, + }); + + var lease = limiter.AttemptAcquire(permitCount: 3); + Assert.True(lease.IsAcquired); + + Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); + + // Replenish 1 millisecond less than ReplenishmentPeriod while AutoReplenishment is enabled + Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds - 1); + + Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); + + // Replenish 1 millisecond longer than ReplenishmentPeriod while AutoReplenishment is enabled + Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds + 1); + + Assert.Equal(9, limiter.GetStatistics().CurrentAvailablePermits); + } + + private static readonly double TickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency; + + static internal void Replenish(TokenBucketRateLimiter limiter, long addMilliseconds) + { + var replenishInternalMethod = typeof(TokenBucketRateLimiter).GetMethod("ReplenishInternal", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; + var internalTick = typeof(TokenBucketRateLimiter).GetField("_lastReplenishmentTick", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; + var currentTick = (long)internalTick.GetValue(limiter); + replenishInternalMethod.Invoke(limiter, new object[] { currentTick + addMilliseconds * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) }); + } } } diff --git a/src/libraries/System.Threading.Thread/src/System.Threading.Thread.csproj b/src/libraries/System.Threading.Thread/src/System.Threading.Thread.csproj index 57a6f7f3c4ddb0..4d133621e598d9 100644 --- a/src/libraries/System.Threading.Thread/src/System.Threading.Thread.csproj +++ b/src/libraries/System.Threading.Thread/src/System.Threading.Thread.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/libraries/System.Threading.ThreadPool/src/System.Threading.ThreadPool.csproj b/src/libraries/System.Threading.ThreadPool/src/System.Threading.ThreadPool.csproj index bda556178b9e6c..051ec503dc2450 100644 --- a/src/libraries/System.Threading.ThreadPool/src/System.Threading.ThreadPool.csproj +++ b/src/libraries/System.Threading.ThreadPool/src/System.Threading.ThreadPool.csproj @@ -6,10 +6,10 @@ - + - \ No newline at end of file + diff --git a/src/libraries/System.Transactions.Local/ref/System.Transactions.Local.cs b/src/libraries/System.Transactions.Local/ref/System.Transactions.Local.cs index 2f08b176bc27bf..99e82f525ee689 100644 --- a/src/libraries/System.Transactions.Local/ref/System.Transactions.Local.cs +++ b/src/libraries/System.Transactions.Local/ref/System.Transactions.Local.cs @@ -4,6 +4,8 @@ // Changes to this file must follow the https://aka.ms/api-review process. // ------------------------------------------------------------------------------ +using System.Runtime.Versioning; + namespace System.Transactions { [System.Runtime.Versioning.UnsupportedOSPlatform("browser")] @@ -191,6 +193,7 @@ public static partial class TransactionManager [System.Diagnostics.CodeAnalysis.DisallowNullAttribute] public static System.Transactions.HostCurrentTransactionCallback? HostCurrentCallback { get { throw null; } set { } } public static System.TimeSpan MaximumTimeout { get { throw null; } set { } } + public static bool ImplicitDistributedTransactions { get; [System.Runtime.Versioning.SupportedOSPlatform("windows")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Distributed transactions support may not be compatible with trimming. If your program creates a distributed transaction via System.Transactions, the correctness of the application cannot be guaranteed after trimming.")] set; } public static event System.Transactions.TransactionStartedEventHandler? DistributedTransactionStarted { add { } remove { } } public static void RecoveryComplete(System.Guid resourceManagerIdentifier) { } public static System.Transactions.Enlistment Reenlist(System.Guid resourceManagerIdentifier, byte[] recoveryInformation, System.Transactions.IEnlistmentNotification enlistmentNotification) { throw null; } diff --git a/src/libraries/System.Transactions.Local/src/Resources/Strings.resx b/src/libraries/System.Transactions.Local/src/Resources/Strings.resx index 2acf86bfbbbe07..647187affad810 100644 --- a/src/libraries/System.Transactions.Local/src/Resources/Strings.resx +++ b/src/libraries/System.Transactions.Local/src/Resources/Strings.resx @@ -420,7 +420,13 @@ [Base] - + Distributed transactions are currently unsupported in 32-bit processes. + + Implicit distributed transactions have not been enabled. If you're intentionally starting a distributed transaction, set TransactionManager.ImplicitDistributedTransactions to true. + + + TransactionManager.ImplicitDistributedTransaction cannot be changed once set, or once System.Transactions distributed transactions have been initialized. Set this flag once at the start of your program. + \ No newline at end of file diff --git a/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj b/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj index 86a68905ab6ed6..ff37cf621bb917 100644 --- a/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj +++ b/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj @@ -5,7 +5,6 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent) CA1805;IDE0059;CS1591 $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) - false diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IPrepareInfo.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IPrepareInfo.cs index 0b6c1158f59b31..576f55f66d6a41 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IPrepareInfo.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IPrepareInfo.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.InteropServices; namespace System.Transactions.DtcProxyShim.DtcInterfaces; diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManagerFactory2.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManagerFactory2.cs index b788a17032a5de..dd4777c5445942 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManagerFactory2.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManagerFactory2.cs @@ -10,15 +10,15 @@ namespace System.Transactions.DtcProxyShim.DtcInterfaces; internal interface IResourceManagerFactory2 { internal void Create( - Guid pguidRM, + in Guid pguidRM, [MarshalAs(UnmanagedType.LPStr)] string pszRMName, [MarshalAs(UnmanagedType.Interface)] IResourceManagerSink pIResMgrSink, [MarshalAs(UnmanagedType.Interface)] out IResourceManager rm); internal void CreateEx( - Guid pguidRM, + in Guid pguidRM, [MarshalAs(UnmanagedType.LPStr)] string pszRMName, [MarshalAs(UnmanagedType.Interface)] IResourceManagerSink pIResMgrSink, - Guid riidRequested, + in Guid riidRequested, [MarshalAs(UnmanagedType.Interface)] out object rm); } diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransaction.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransaction.cs index e516d2c0d038fc..de54474609e95b 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransaction.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransaction.cs @@ -1,9 +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; using System.Runtime.InteropServices; -using System.Transactions.Oletx; namespace System.Transactions.DtcProxyShim.DtcInterfaces; @@ -11,9 +9,15 @@ namespace System.Transactions.DtcProxyShim.DtcInterfaces; [ComImport, Guid(Guids.IID_ITransaction), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface ITransaction { - void Commit([MarshalAs(UnmanagedType.Bool)] bool fRetainingt, [MarshalAs(UnmanagedType.U4)] OletxXacttc grfTC, uint grfRM); + void Commit( + [MarshalAs(UnmanagedType.Bool)] bool fRetaining, + [MarshalAs(UnmanagedType.U4)] OletxXacttc grfTC, + uint grfRM); - void Abort(IntPtr reason, [MarshalAs(UnmanagedType.Bool)] bool retaining, [MarshalAs(UnmanagedType.Bool)] bool async); + void Abort( + IntPtr reason, + [MarshalAs(UnmanagedType.Bool)] bool retaining, + [MarshalAs(UnmanagedType.Bool)] bool async); void GetTransactionInfo(out OletxXactTransInfo xactInfo); } diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionCloner.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionCloner.cs index 7ce0b361bdf5ff..3090333223c951 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionCloner.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionCloner.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.InteropServices; namespace System.Transactions.DtcProxyShim.DtcInterfaces; @@ -10,5 +9,17 @@ namespace System.Transactions.DtcProxyShim.DtcInterfaces; [ComImport, Guid("02656950-2152-11d0-944C-00A0C905416E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface ITransactionCloner { + void Commit( + [MarshalAs(UnmanagedType.Bool)] bool fRetainingt, + [MarshalAs(UnmanagedType.U4)] OletxXacttc grfTC, + uint grfRM); + + void Abort( + IntPtr reason, + [MarshalAs(UnmanagedType.Bool)] bool retaining, + [MarshalAs(UnmanagedType.Bool)] bool async); + + void GetTransactionInfo(out OletxXactTransInfo xactInfo); + void CloneWithCommitDisabled([MarshalAs(UnmanagedType.Interface)] out ITransaction ppITransaction); } diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionDispenser.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionDispenser.cs index c45ea2c5f11631..d5ca43615067fc 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionDispenser.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionDispenser.cs @@ -7,7 +7,7 @@ namespace System.Transactions.DtcProxyShim.DtcInterfaces; -// https://docs.microsoft.com/previous-versions/windows/desktop/ms679525(v=vs.85) +// https://docs.microsoft.com/previous-versions/windows/desktop/ms687604(v=vs.85) [ComImport, Guid(Guids.IID_ITransactionDispenser), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface ITransactionDispenser { diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionEnlistmentAsync.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionEnlistmentAsync.cs index da738568efd5ec..8fb42b4abf8b51 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionEnlistmentAsync.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionEnlistmentAsync.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.InteropServices; namespace System.Transactions.DtcProxyShim.DtcInterfaces; diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExport.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExport.cs index 02f64369697ae8..75b6e7ee935220 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExport.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExport.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.InteropServices; namespace System.Transactions.DtcProxyShim.DtcInterfaces; diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExportFactory.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExportFactory.cs index b82c8f5ebac37f..0369283f3eb181 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExportFactory.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExportFactory.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.InteropServices; namespace System.Transactions.DtcProxyShim.DtcInterfaces; diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionImport.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionImport.cs index 310f7d98a54eb7..a7095a6e0237dd 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionImport.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionImport.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.InteropServices; namespace System.Transactions.DtcProxyShim.DtcInterfaces; @@ -13,6 +12,6 @@ internal interface ITransactionImport void Import( uint cbTransactionCookie, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] byte[] rgbTransactionCookie, - Guid piid, + in Guid piid, [MarshalAs(UnmanagedType.Interface)] out object ppvTransaction); } diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOptions.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOptions.cs index 645b4cbcf98b33..66358613be5f5c 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOptions.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOptions.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.InteropServices; namespace System.Transactions.DtcProxyShim.DtcInterfaces; diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOutcomeEvents.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOutcomeEvents.cs index d0cabc4d708311..362773036f69ca 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOutcomeEvents.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOutcomeEvents.cs @@ -10,11 +10,21 @@ namespace System.Transactions.DtcProxyShim.DtcInterfaces; [ComImport, Guid("3A6AD9E2-23B9-11cf-AD60-00AA00A74CCD"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface ITransactionOutcomeEvents { - void Committed([MarshalAs(UnmanagedType.Bool)] bool fRetaining, IntPtr pNewUOW /* always null? */, int hresult); + void Committed( + [MarshalAs(UnmanagedType.Bool)] bool fRetaining, + IntPtr pNewUOW, + int hresult); - void Aborted(IntPtr pboidReason, [MarshalAs(UnmanagedType.Bool)] bool fRetaining, IntPtr pNewUOW, int hresult); + void Aborted( + IntPtr pboidReason, + [MarshalAs(UnmanagedType.Bool)] bool fRetaining, + IntPtr pNewUOW, + int hresult); - void HeuristicDecision([MarshalAs(UnmanagedType.U4)] OletxTransactionHeuristic dwDecision, IntPtr pboidReason, int hresult); + void HeuristicDecision( + [MarshalAs(UnmanagedType.U4)] OletxTransactionHeuristic dwDecision, + IntPtr pboidReason, + int hresult); void Indoubt(); } diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionReceiver.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionReceiver.cs index ec66d1cab704da..1b1f3886f349fe 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionReceiver.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionReceiver.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.InteropServices; namespace System.Transactions.DtcProxyShim.DtcInterfaces; diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionReceiverFactory.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionReceiverFactory.cs index 30f957cd7f92cf..8a29e9989c89c3 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionReceiverFactory.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionReceiverFactory.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.InteropServices; namespace System.Transactions.DtcProxyShim.DtcInterfaces; diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionResourceAsync.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionResourceAsync.cs index fec5d00462c2dd..79489c671d14b1 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionResourceAsync.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionResourceAsync.cs @@ -1,9 +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; using System.Runtime.InteropServices; -using System.Transactions.Oletx; namespace System.Transactions.DtcProxyShim.DtcInterfaces; @@ -19,7 +17,10 @@ void PrepareRequest( void CommitRequest(OletxXactRm grfRM, IntPtr pNewUOW); - void AbortRequest(IntPtr pboidReason, [MarshalAs(UnmanagedType.Bool)] bool fRetaining, IntPtr pNewUOW); + void AbortRequest( + IntPtr pboidReason, + [MarshalAs(UnmanagedType.Bool)] bool fRetaining, + IntPtr pNewUOW); void TMDown(); } diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionTransmitter.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionTransmitter.cs index c427b9a233023b..e00cba9cd6f404 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionTransmitter.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionTransmitter.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.InteropServices; namespace System.Transactions.DtcProxyShim.DtcInterfaces; diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionTransmitterFactory.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionTransmitterFactory.cs index 73c7fa701ebff8..91797781d679a3 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionTransmitterFactory.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionTransmitterFactory.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.InteropServices; namespace System.Transactions.DtcProxyShim.DtcInterfaces; diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionVoterNotifyAsync2.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionVoterNotifyAsync2.cs index 2eca4063932b72..9fb0abb54d9caa 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionVoterNotifyAsync2.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionVoterNotifyAsync2.cs @@ -10,11 +10,21 @@ namespace System.Transactions.DtcProxyShim.DtcInterfaces; [ComImport, Guid("5433376B-414D-11d3-B206-00C04FC2F3EF"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface ITransactionVoterNotifyAsync2 { - void Committed([MarshalAs(UnmanagedType.Bool)] bool fRetaining, IntPtr pNewUOW /* always null? */, uint hresult); + void Committed( + [MarshalAs(UnmanagedType.Bool)] bool fRetaining, + IntPtr pNewUOW, + uint hresult); - void Aborted(IntPtr pboidReason, [MarshalAs(UnmanagedType.Bool)] bool fRetaining, IntPtr pNewUOW, uint hresult); + void Aborted( + IntPtr pboidReason, + [MarshalAs(UnmanagedType.Bool)] bool fRetaining, + IntPtr pNewUOW, + uint hresult); - void HeuristicDecision([MarshalAs(UnmanagedType.U4)] OletxTransactionHeuristic dwDecision, IntPtr pboidReason, uint hresult); + void HeuristicDecision( + [MarshalAs(UnmanagedType.U4)] OletxTransactionHeuristic dwDecision, + IntPtr pboidReason, + uint hresult); void Indoubt(); diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcProxyShimFactory.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcProxyShimFactory.cs index e15983a52ab074..2ec9b30f49812a 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcProxyShimFactory.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcProxyShimFactory.cs @@ -21,6 +21,11 @@ internal sealed class DtcProxyShimFactory // at the same time. private static readonly object _proxyInitLock = new(); + // This object will perform the actual distributed transaction connection. + // It will be set only if TransactionManager.ImplicitDefaultTransactions + // is set to true, allowing the relevant code to be trimmed otherwise. + internal static ITransactionConnector? s_transactionConnector; + // Lock to protect access to listOfNotifications. private readonly object _notificationLock = new(); @@ -30,6 +35,8 @@ internal sealed class DtcProxyShimFactory private readonly ConcurrentQueue _cachedTransmitters = new(); private readonly ConcurrentQueue _cachedReceivers = new(); + private static readonly int s_maxCachedInterfaces = Environment.ProcessorCount * 2; + private readonly EventWaitHandle _eventHandle; private ITransactionDispenser _transactionDispenser = null!; // Late-initialized in ConnectToProxy @@ -39,6 +46,7 @@ internal DtcProxyShimFactory(EventWaitHandle notificationEventHandle) // https://docs.microsoft.com/previous-versions/windows/desktop/ms678898(v=vs.85) [DllImport(Interop.Libraries.Xolehlp, CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] + [RequiresUnreferencedCode(TransactionManager.DistributedTransactionTrimmingWarning)] private static extern void DtcGetTransactionManagerExW( [MarshalAs(UnmanagedType.LPWStr)] string? pszHost, [MarshalAs(UnmanagedType.LPWStr)] string? pszTmName, @@ -47,7 +55,10 @@ private static extern void DtcGetTransactionManagerExW( object? pvConfigPararms, [MarshalAs(UnmanagedType.Interface)] out ITransactionDispenser ppvObject); - [UnconditionalSuppressMessage("Trimming", "IL2050", Justification = "Leave me alone")] + [RequiresUnreferencedCode(TransactionManager.DistributedTransactionTrimmingWarning)] + private static void DtcGetTransactionManager(string? nodeName, out ITransactionDispenser localDispenser) => + DtcGetTransactionManagerExW(nodeName, null, Guids.IID_ITransactionDispenser_Guid, 0, null, out localDispenser); + public void ConnectToProxy( string? nodeName, Guid resourceManagerIdentifier, @@ -58,12 +69,36 @@ public void ConnectToProxy( { if (RuntimeInformation.ProcessArchitecture == Architecture.X86) { - throw new PlatformNotSupportedException(SR.DistributedNotSupportOn32Bits); + throw new PlatformNotSupportedException(SR.DistributedNotSupportedOn32Bits); + } + + lock (TransactionManager.s_implicitDistributedTransactionsLock) + { + if (s_transactionConnector is null) + { + // We set TransactionManager.ImplicitDistributedTransactionsInternal, so that any attempt to change it + // later will cause an exception. + TransactionManager.s_implicitDistributedTransactions = false; + + throw new NotSupportedException(SR.ImplicitDistributedTransactionsDisabled); + } } + s_transactionConnector.ConnectToProxyCore(this, nodeName, resourceManagerIdentifier, managedIdentifier, out nodeNameMatches, out whereabouts, out resourceManagerShim); + } + + [RequiresUnreferencedCode(TransactionManager.DistributedTransactionTrimmingWarning)] + private void ConnectToProxyCore( + string? nodeName, + Guid resourceManagerIdentifier, + object managedIdentifier, + out bool nodeNameMatches, + out byte[] whereabouts, + out ResourceManagerShim resourceManagerShim) + { lock (_proxyInitLock) { - DtcGetTransactionManagerExW(nodeName, null, Guids.IID_ITransactionDispenser_Guid, 0, null, out ITransactionDispenser? localDispenser); + DtcGetTransactionManager(nodeName, out ITransactionDispenser? localDispenser); // Check to make sure the node name matches. if (nodeName is not null) @@ -333,7 +368,15 @@ internal ITransactionTransmitter GetCachedTransmitter(ITransaction transaction) } internal void ReturnCachedTransmitter(ITransactionTransmitter transmitter) - => _cachedTransmitters.Enqueue(transmitter); + { + // Note that due to race conditions, we may end up enqueuing above s_maxCachedInterfaces. + // This is benign, as this is only a best-effort cache, and there are no negative consequences. + if (_cachedTransmitters.Count < s_maxCachedInterfaces) + { + transmitter.Reset(); + _cachedTransmitters.Enqueue(transmitter); + } + } internal ITransactionReceiver GetCachedReceiver() { @@ -349,5 +392,47 @@ internal ITransactionReceiver GetCachedReceiver() } internal void ReturnCachedReceiver(ITransactionReceiver receiver) - => _cachedReceivers.Enqueue(receiver); + { + // Note that due to race conditions, we may end up enqueuing above s_maxCachedInterfaces. + // This is benign, as this is only a best-effort cache, and there are no negative consequences. + if (_cachedReceivers.Count < s_maxCachedInterfaces) + { + receiver.Reset(); + _cachedReceivers.Enqueue(receiver); + } + } + + internal interface ITransactionConnector + { + void ConnectToProxyCore( + DtcProxyShimFactory proxyShimFactory, + string? nodeName, + Guid resourceManagerIdentifier, + object managedIdentifier, + out bool nodeNameMatches, + out byte[] whereabouts, + out ResourceManagerShim resourceManagerShim); + } + + [RequiresUnreferencedCode(TransactionManager.DistributedTransactionTrimmingWarning)] + internal sealed class DtcTransactionConnector : ITransactionConnector + { + public void ConnectToProxyCore( + DtcProxyShimFactory proxyShimFactory, + string? nodeName, + Guid resourceManagerIdentifier, + object managedIdentifier, + out bool nodeNameMatches, + out byte[] whereabouts, + out ResourceManagerShim resourceManagerShim) + { + proxyShimFactory.ConnectToProxyCore( + nodeName, + resourceManagerIdentifier, + managedIdentifier, + out nodeNameMatches, + out whereabouts, + out resourceManagerShim); + } + } } diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs index 7ce21c51bad238..93a6fefadb88b0 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs @@ -4,8 +4,12 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Runtime.Versioning; using System.Threading; using System.Transactions.Configuration; +#if WINDOWS +using System.Transactions.DtcProxyShim; +#endif using System.Transactions.Oletx; namespace System.Transactions @@ -29,6 +33,10 @@ public static class TransactionManager private static TransactionTable? s_transactionTable; private static TransactionStartedEventHandler? s_distributedTransactionStartedDelegate; + + internal const string DistributedTransactionTrimmingWarning = + "Distributed transactions support may not be compatible with trimming. If your program creates a distributed transaction via System.Transactions, the correctness of the application cannot be guaranteed after trimming."; + public static event TransactionStartedEventHandler? DistributedTransactionStarted { add @@ -391,6 +399,60 @@ public static TimeSpan MaximumTimeout } } + /// + /// Controls whether usage of System.Transactions APIs that require escalation to a distributed transaction will do so; + /// if your application requires distributed transaction, opt into using them by setting this to . + /// If set to (the default), escalation to a distributed transaction will throw a . + /// +#if WINDOWS + public static bool ImplicitDistributedTransactions + { + get => DtcProxyShimFactory.s_transactionConnector is not null; + + [SupportedOSPlatform("windows")] + [RequiresUnreferencedCode(DistributedTransactionTrimmingWarning)] + set + { + lock (s_implicitDistributedTransactionsLock) + { + // Make sure this flag can only be set once, and that once distributed transactions have been initialized, + // it's frozen. + if (s_implicitDistributedTransactions is null) + { + s_implicitDistributedTransactions = value; + + if (value) + { + DtcProxyShimFactory.s_transactionConnector ??= new DtcProxyShimFactory.DtcTransactionConnector(); + } + } + else if (value != s_implicitDistributedTransactions) + { + throw new InvalidOperationException(SR.ImplicitDistributedTransactionsCannotBeChanged); + } + } + } + } + + internal static bool? s_implicitDistributedTransactions; + internal static object s_implicitDistributedTransactionsLock = new(); +#else + public static bool ImplicitDistributedTransactions + { + get => false; + + [SupportedOSPlatform("windows")] + [RequiresUnreferencedCode(DistributedTransactionTrimmingWarning)] + set + { + if (value) + { + throw new PlatformNotSupportedException(SR.DistributedNotSupported); + } + } + } +#endif + // This routine writes the "header" for the recovery information, based on the // type of the calling object and its provided parameter collection. This information // we be read back by the static Reenlist method to create the necessary transaction diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionState.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionState.cs index 331e679db04ea1..1e0cdd68ff1634 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionState.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionState.cs @@ -1867,7 +1867,6 @@ Guid promoterType return false; } - internal override void CompleteBlockingClone(InternalTransaction tx) { // First try to complete one of the internal blocking clones @@ -1900,17 +1899,23 @@ internal override void CompleteBlockingClone(InternalTransaction tx) Monitor.Exit(tx); try { - dtx.Complete(); + try + { + dtx.Complete(); + } + finally + { + dtx.Dispose(); + } } finally { - dtx.Dispose(); + Monitor.Enter(tx); } } } } - internal override void CompleteAbortingClone(InternalTransaction tx) { // If we have a phase1Volatile.VolatileDemux, we have a phase1 volatile enlistment @@ -1937,11 +1942,18 @@ internal override void CompleteAbortingClone(InternalTransaction tx) Monitor.Exit(tx); try { - dtx.Complete(); + try + { + dtx.Complete(); + } + finally + { + dtx.Dispose(); + } } finally { - dtx.Dispose(); + Monitor.Enter(tx); } } } 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 ec230efce89728..f9d4e77006ff0b 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionsEtwProvider.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionsEtwProvider.cs @@ -573,7 +573,7 @@ internal void EnlistmentCreated(TraceSourceType traceSource, EnlistmentTraceIden } [Event(ENLISTMENT_CREATED_LTM_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Informational, Task = Tasks.Enlistment, Opcode = Opcodes.Create, Message = "Enlistment Created (LTM). ID is {0}, type is {1}, options is {2}")] - [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026", Justification = "Only string/int are passed")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Only string/int are passed")] private void EnlistmentCreatedLtm(int enlistmentIdentifier, string enlistmentType, string enlistmentOptions) { SetActivityId(string.Empty); @@ -581,7 +581,7 @@ private void EnlistmentCreatedLtm(int enlistmentIdentifier, string enlistmentTyp } [Event(ENLISTMENT_CREATED_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Informational, Task = Tasks.Enlistment, Opcode = Opcodes.Create, Message = "Enlistment Created (OLETX). ID is {0}, type is {1}, options is {2}")] - [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026", Justification = "Only string/int are passed")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Only string/int are passed")] private void EnlistmentCreatedOleTx(int enlistmentIdentifier, string enlistmentType, string enlistmentOptions) { SetActivityId(string.Empty); diff --git a/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs b/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs index dc2ba01f63bcbb..6313200e46ada0 100644 --- a/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs +++ b/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs @@ -21,7 +21,6 @@ public class NonMsdtcPromoterTests : IDisposable private static MethodInfo s_setDistributedTransactionIdentifierMethodInfo; private static MethodInfo s_getPromotedTokenMethodInfo; private static PropertyInfo s_promoterTypePropertyInfo; - private static FieldInfo s_promoterTypeDtcFieldInfo; public NonMsdtcPromoterTests() { @@ -51,17 +50,12 @@ private static void VerifySoftDependencies() // And the PropertyInfo objects for PromoterType s_promoterTypePropertyInfo = typeof(Transaction).GetTypeInfo().GetProperty("PromoterType", typeof(Guid)); - - // And the FieldInfo for TransactionInterop.PromoterTypeDtc - s_promoterTypeDtcFieldInfo = typeof(TransactionInterop).GetTypeInfo().GetField("PromoterTypeDtc", BindingFlags.Public | BindingFlags.Static); } bool allMethodsAreThere = ((s_enlistPromotableSinglePhaseMethodInfo != null) && (s_setDistributedTransactionIdentifierMethodInfo != null) && (s_getPromotedTokenMethodInfo != null) && - (s_promoterTypePropertyInfo != null) && - (s_promoterTypeDtcFieldInfo != null) - ); + (s_promoterTypePropertyInfo != null)); Assert.True(allMethodsAreThere, "At least one of the expected new methods or properties is not implemented by the available System.Transactions."); } @@ -339,14 +333,6 @@ private static byte[] TxPromotedToken(Transaction txToGet) return (byte[])s_getPromotedTokenMethodInfo.Invoke(txToGet, null); } - private static Guid PromoterTypeDtc - { - get - { - return (Guid)s_promoterTypeDtcFieldInfo.GetValue(null); - } - } - #endregion #region NonMSDTCPromoterEnlistment @@ -1706,45 +1692,6 @@ private static void TestCase_PromoterType() TestPassed(); } - private static void TestCase_PromoterTypeMSDTC() - { - string testCaseDescription = "TestCase_PromoterTypeMSDTC"; - - Trace("**** " + testCaseDescription + " ****"); - - AutoResetEvent volCompleted = new AutoResetEvent(false); - MyEnlistment vol = null; - - try - { - using (TransactionScope ts = new TransactionScope()) - { - Assert.Equal(Guid.Empty, TxPromoterType(Transaction.Current)); - - vol = CreateVolatileEnlistment(volCompleted); - - // Force MSDTC promotion. - TransactionInterop.GetDtcTransaction(Transaction.Current); - - // TransactionInterop.PromoterTypeDtc - Assert.Equal(PromoterTypeDtc, TxPromoterType(Transaction.Current)); - - ts.Complete(); - } - } - catch (Exception ex) - { - Trace(string.Format("Caught unexpected exception {0}:{1}", ex.GetType().ToString(), ex.ToString())); - return; - } - - Assert.True(volCompleted.WaitOne(TimeSpan.FromSeconds(5))); - - Assert.True(vol.CommittedOutcome); - - TestPassed(); - } - private static void TestCase_FailPromotableSinglePhaseNotificationCalls() { string testCaseDescription = "TestCase_FailPromotableSinglePhaseNotificationCalls"; @@ -2133,16 +2080,6 @@ public void PSPENonMsdtcGetPromoterType() TestCase_PromoterType(); } - /// - /// PSPE Non-MSDTC Get PromoterType. - /// - [Fact] - public void PSPENonMsdtcGetPromoterTypeMSDTC() - { - // get_PromoterType - TestCase_PromoterTypeMSDTC(); - } - /// /// PSPE Non-MSDTC Fail PromotableSinglePhaseNotification Calls. /// diff --git a/src/libraries/System.Transactions.Local/tests/OleTxNonWindowsUnsupportedTests.cs b/src/libraries/System.Transactions.Local/tests/OleTxNonWindowsUnsupportedTests.cs index fc753cef0e2d0a..bcf50d06c31738 100644 --- a/src/libraries/System.Transactions.Local/tests/OleTxNonWindowsUnsupportedTests.cs +++ b/src/libraries/System.Transactions.Local/tests/OleTxNonWindowsUnsupportedTests.cs @@ -55,10 +55,15 @@ public void TransmitterPropagationToken() [Fact] public void GetWhereabouts() - => Assert.Throws(() => TransactionInterop.GetWhereabouts()); + => Assert.Throws(TransactionInterop.GetWhereabouts); [Fact] public void GetExportCookie() - => Assert.Throws(() => TransactionInterop.GetExportCookie( - new CommittableTransaction(), new byte[200])); + => Assert.Throws(() => + TransactionInterop.GetExportCookie(new CommittableTransaction(), new byte[200])); + + [Fact] + public void GetDtcTransaction() + => Assert.Throws(() => + TransactionInterop.GetDtcTransaction(new CommittableTransaction())); } diff --git a/src/libraries/System.Transactions.Local/tests/OleTxTests.cs b/src/libraries/System.Transactions.Local/tests/OleTxTests.cs index 48b2792ddef05f..0d52469c696d86 100644 --- a/src/libraries/System.Transactions.Local/tests/OleTxTests.cs +++ b/src/libraries/System.Transactions.Local/tests/OleTxTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using Microsoft.DotNet.RemoteExecutor; @@ -13,6 +14,7 @@ namespace System.Transactions.Tests; #nullable enable [PlatformSpecific(TestPlatforms.Windows)] +[SkipOnMono("COM Interop not supported on Mono")] public class OleTxTests : IClassFixture { private static readonly TimeSpan Timeout = TimeSpan.FromMinutes(1); @@ -26,242 +28,245 @@ public OleTxTests(OleTxFixture fixture) [InlineData(Phase1Vote.Prepared, Phase1Vote.ForceRollback, EnlistmentOutcome.Aborted, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] [InlineData(Phase1Vote.ForceRollback, Phase1Vote.Prepared, EnlistmentOutcome.Aborted, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] public void Two_durable_enlistments_commit(Phase1Vote vote1, Phase1Vote vote2, EnlistmentOutcome expectedOutcome1, EnlistmentOutcome expectedOutcome2, TransactionStatus expectedTxStatus) - { - if (!Environment.Is64BitProcess) + => Test(() => { - return; // Temporarily skip on 32-bit where we have an issue - } - - using var tx = new CommittableTransaction(); + using var tx = new CommittableTransaction(); - try - { - var enlistment1 = new TestEnlistment(vote1, expectedOutcome1); - var enlistment2 = new TestEnlistment(vote2, expectedOutcome2); + try + { + var enlistment1 = new TestEnlistment(vote1, expectedOutcome1); + var enlistment2 = new TestEnlistment(vote2, expectedOutcome2); - tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None); - tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None); + tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None); + tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None); - Assert.Equal(TransactionStatus.Active, tx.TransactionInformation.Status); - tx.Commit(); - } - catch (TransactionInDoubtException) - { - Assert.Equal(TransactionStatus.InDoubt, expectedTxStatus); - } - catch (TransactionAbortedException) - { - Assert.Equal(TransactionStatus.Aborted, expectedTxStatus); - } + Assert.Equal(TransactionStatus.Active, tx.TransactionInformation.Status); + tx.Commit(); + } + catch (TransactionInDoubtException) + { + Assert.Equal(TransactionStatus.InDoubt, expectedTxStatus); + } + catch (TransactionAbortedException) + { + Assert.Equal(TransactionStatus.Aborted, expectedTxStatus); + } - Retry(() => Assert.Equal(expectedTxStatus, tx.TransactionInformation.Status)); - } + Retry(() => Assert.Equal(expectedTxStatus, tx.TransactionInformation.Status)); + }); [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] public void Two_durable_enlistments_rollback() - { - if (!Environment.Is64BitProcess) + => Test(() => { - return; // Temporarily skip on 32-bit where we have an issue - } - - using var tx = new CommittableTransaction(); + using var tx = new CommittableTransaction(); - var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Aborted); - var enlistment2 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Aborted); + var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Aborted); + var enlistment2 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Aborted); - tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None); - tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None); + tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None); + tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None); - tx.Rollback(); + tx.Rollback(); - Assert.False(enlistment1.WasPreparedCalled); - Assert.False(enlistment2.WasPreparedCalled); + Assert.False(enlistment1.WasPreparedCalled); + Assert.False(enlistment2.WasPreparedCalled); - // This matches the .NET Framework behavior - Retry(() => Assert.Equal(TransactionStatus.Aborted, tx.TransactionInformation.Status)); - } + // This matches the .NET Framework behavior + Retry(() => Assert.Equal(TransactionStatus.Aborted, tx.TransactionInformation.Status)); + }); [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] [InlineData(0)] [InlineData(1)] [InlineData(2)] public void Volatile_and_durable_enlistments(int volatileCount) - { - if (!Environment.Is64BitProcess) + => Test(() => { - return; // Temporarily skip on 32-bit where we have an issue - } - - using var tx = new CommittableTransaction(); + using var tx = new CommittableTransaction(); - if (volatileCount > 0) - { - TestEnlistment[] volatiles = new TestEnlistment[volatileCount]; - for (int i = 0; i < volatileCount; i++) + if (volatileCount > 0) { - // It doesn't matter what we specify for SinglePhaseVote. - volatiles[i] = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); - tx.EnlistVolatile(volatiles[i], EnlistmentOptions.None); + TestEnlistment[] volatiles = new TestEnlistment[volatileCount]; + for (int i = 0; i < volatileCount; i++) + { + // It doesn't matter what we specify for SinglePhaseVote. + volatiles[i] = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); + tx.EnlistVolatile(volatiles[i], EnlistmentOptions.None); + } } - } - var durable = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); + var durable = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); - // Creation of two phase durable enlistment attempts to promote to MSDTC - tx.EnlistDurable(Guid.NewGuid(), durable, EnlistmentOptions.None); + // Creation of two phase durable enlistment attempts to promote to MSDTC + tx.EnlistDurable(Guid.NewGuid(), durable, EnlistmentOptions.None); - tx.Commit(); + tx.Commit(); - Retry(() => Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status)); - } + Retry(() => Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status)); + }); protected static bool IsRemoteExecutorSupportedAndNotNano => RemoteExecutor.IsSupported && PlatformDetection.IsNotWindowsNanoServer; [ConditionalFact(nameof(IsRemoteExecutorSupportedAndNotNano))] public void Promotion() - { - if (!Environment.Is64BitProcess) - { - return; // Temporarily skip on 32-bit where we have an issue - } + => PromotionCore(); - // This simulates the full promotable flow, as implemented for SQL Server. - - // We are going to spin up two external processes. - // 1. The 1st external process will create the transaction and save its propagation token to disk. - // 2. The main process will read that, and propagate the transaction to the 2nd external process. - // 3. The main process will then notify the 1st external process to commit (as the main's transaction is delegated to it). - // 4. At that point the MSDTC Commit will be triggered; enlistments on both the 1st and 2nd processes will be notified - // to commit, and the transaction status will reflect the committed status in the main process. - using var tx = new CommittableTransaction(); + // #76010 + [ConditionalFact(nameof(IsRemoteExecutorSupportedAndNotNano))] + public void Promotion_twice() + { + PromotionCore(); + PromotionCore(); + } - string propagationTokenFilePath = Path.GetTempFileName(); - string exportCookieFilePath = Path.GetTempFileName(); - using var waitHandle1 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion1"); - using var waitHandle2 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion2"); - using var waitHandle3 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion3"); + private void PromotionCore() + { + Test(() => + { + // This simulates the full promotable flow, as implemented for SQL Server. + + // We are going to spin up two external processes. + // 1. The 1st external process will create the transaction and save its propagation token to disk. + // 2. The main process will read that, and propagate the transaction to the 2nd external process. + // 3. The main process will then notify the 1st external process to commit (as the main's transaction is delegated to it). + // 4. At that point the MSDTC Commit will be triggered; enlistments on both the 1st and 2nd processes will be notified + // to commit, and the transaction status will reflect the committed status in the main process. + using var tx = new CommittableTransaction(); - RemoteInvokeHandle? remote1 = null, remote2 = null; + string propagationTokenFilePath = Path.GetTempFileName(); + string exportCookieFilePath = Path.GetTempFileName(); + using var waitHandle1 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion1"); + using var waitHandle2 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion2"); + using var waitHandle3 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion3"); - try - { - remote1 = RemoteExecutor.Invoke(Remote1, propagationTokenFilePath, new RemoteInvokeOptions { ExpectedExitCode = 42 }); - - // Wait for the external process to start a transaction and save its propagation token - Assert.True(waitHandle1.WaitOne(Timeout)); - - // Enlist the first PSPE. No escalation happens yet, since its the only enlistment. - var pspe1 = new TestPromotableSinglePhaseNotification(propagationTokenFilePath); - Assert.True(tx.EnlistPromotableSinglePhase(pspe1)); - Assert.True(pspe1.WasInitializedCalled); - Assert.False(pspe1.WasPromoteCalled); - Assert.False(pspe1.WasRollbackCalled); - Assert.False(pspe1.WasSinglePhaseCommitCalled); - - // Enlist the second PSPE. This returns false and does nothing, since there's already an enlistment. - var pspe2 = new TestPromotableSinglePhaseNotification(propagationTokenFilePath); - Assert.False(tx.EnlistPromotableSinglePhase(pspe2)); - Assert.False(pspe2.WasInitializedCalled); - Assert.False(pspe2.WasPromoteCalled); - Assert.False(pspe2.WasRollbackCalled); - Assert.False(pspe2.WasSinglePhaseCommitCalled); - - // Now generate an export cookie for the 2nd external process. This causes escalation and promotion. - byte[] whereabouts = TransactionInterop.GetWhereabouts(); - byte[] exportCookie = TransactionInterop.GetExportCookie(tx, whereabouts); - - Assert.True(pspe1.WasPromoteCalled); - Assert.False(pspe1.WasRollbackCalled); - Assert.False(pspe1.WasSinglePhaseCommitCalled); - - // Write the export cookie and start the 2nd external process, which will read the cookie and enlist in the transaction. - // Wait for it to complete. - File.WriteAllBytes(exportCookieFilePath, exportCookie); - remote2 = RemoteExecutor.Invoke(Remote2, exportCookieFilePath, new RemoteInvokeOptions { ExpectedExitCode = 42 }); - Assert.True(waitHandle2.WaitOne(Timeout)); - - // We now have two external processes with enlistments to our distributed transaction. Commit. - // Since our transaction is delegated to the 1st PSPE enlistment, Sys.Tx will call SinglePhaseCommit on it. - // In SQL Server this contacts the 1st DB to actually commit the transaction with MSDTC. In this simulation we'll just use a wait handle to trigger this. - tx.Commit(); - Assert.True(pspe1.WasSinglePhaseCommitCalled); - waitHandle3.Set(); + RemoteInvokeHandle? remote1 = null, remote2 = null; - Retry(() => Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status)); - } - catch - { try { - remote1?.Process.Kill(); - remote2?.Process.Kill(); + remote1 = RemoteExecutor.Invoke(Remote1, propagationTokenFilePath, new RemoteInvokeOptions { ExpectedExitCode = 42 }); + + // Wait for the external process to start a transaction and save its propagation token + Assert.True(waitHandle1.WaitOne(Timeout)); + + // Enlist the first PSPE. No escalation happens yet, since its the only enlistment. + var pspe1 = new TestPromotableSinglePhaseNotification(propagationTokenFilePath); + Assert.True(tx.EnlistPromotableSinglePhase(pspe1)); + Assert.True(pspe1.WasInitializedCalled); + Assert.False(pspe1.WasPromoteCalled); + Assert.False(pspe1.WasRollbackCalled); + Assert.False(pspe1.WasSinglePhaseCommitCalled); + + // Enlist the second PSPE. This returns false and does nothing, since there's already an enlistment. + var pspe2 = new TestPromotableSinglePhaseNotification(propagationTokenFilePath); + Assert.False(tx.EnlistPromotableSinglePhase(pspe2)); + Assert.False(pspe2.WasInitializedCalled); + Assert.False(pspe2.WasPromoteCalled); + Assert.False(pspe2.WasRollbackCalled); + Assert.False(pspe2.WasSinglePhaseCommitCalled); + + // Now generate an export cookie for the 2nd external process. This causes escalation and promotion. + byte[] whereabouts = TransactionInterop.GetWhereabouts(); + byte[] exportCookie = TransactionInterop.GetExportCookie(tx, whereabouts); + + Assert.True(pspe1.WasPromoteCalled); + Assert.False(pspe1.WasRollbackCalled); + Assert.False(pspe1.WasSinglePhaseCommitCalled); + + // Write the export cookie and start the 2nd external process, which will read the cookie and enlist in the transaction. + // Wait for it to complete. + File.WriteAllBytes(exportCookieFilePath, exportCookie); + remote2 = RemoteExecutor.Invoke(Remote2, exportCookieFilePath, new RemoteInvokeOptions { ExpectedExitCode = 42 }); + Assert.True(waitHandle2.WaitOne(Timeout)); + + // We now have two external processes with enlistments to our distributed transaction. Commit. + // Since our transaction is delegated to the 1st PSPE enlistment, Sys.Tx will call SinglePhaseCommit on it. + // In SQL Server this contacts the 1st DB to actually commit the transaction with MSDTC. In this simulation we'll just use a wait handle to trigger this. + tx.Commit(); + Assert.True(pspe1.WasSinglePhaseCommitCalled); + waitHandle3.Set(); + + Retry(() => Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status)); } catch { - } + try + { + remote1?.Process.Kill(); + remote2?.Process.Kill(); + } + catch + { + } - throw; - } - finally - { - File.Delete(propagationTokenFilePath); - } + throw; + } + finally + { + File.Delete(propagationTokenFilePath); + } - // Disposal of the RemoteExecutor handles will wait for the external processes to exit with the right exit code, - // which will happen when their enlistments receive the commit. - remote1?.Dispose(); - remote2?.Dispose(); + // Disposal of the RemoteExecutor handles will wait for the external processes to exit with the right exit code, + // which will happen when their enlistments receive the commit. + remote1?.Dispose(); + remote2?.Dispose(); + }); static void Remote1(string propagationTokenFilePath) - { - using var tx = new CommittableTransaction(); + => Test(() => + { + var outcomeEvent = new AutoResetEvent(false); - var outcomeEvent = new AutoResetEvent(false); - var enlistment = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent); - tx.EnlistDurable(Guid.NewGuid(), enlistment, EnlistmentOptions.None); + using (var tx = new CommittableTransaction()) + { + var enlistment = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent); + tx.EnlistDurable(Guid.NewGuid(), enlistment, EnlistmentOptions.None); - // We now have an OleTx transaction. Save its propagation token to disk so that the main process can read it when promoting. - byte[] propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); - File.WriteAllBytes(propagationTokenFilePath, propagationToken); + // We now have an OleTx transaction. Save its propagation token to disk so that the main process can read it when promoting. + byte[] propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); + File.WriteAllBytes(propagationTokenFilePath, propagationToken); - // Signal to the main process that the propagation token is ready to be read - using var waitHandle1 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion1"); - waitHandle1.Set(); + // Signal to the main process that the propagation token is ready to be read + using var waitHandle1 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion1"); + waitHandle1.Set(); - // The main process will now import our transaction via the propagation token, and propagate it to a 2nd process. - // In the main process the transaction is delegated; we're the one who started it, and so we're the one who need to Commit. - // When Commit() is called in the main process, that will trigger a SinglePhaseCommit on the PSPE which represents us. In SQL Server this - // contacts the DB to actually commit the transaction with MSDTC. In this simulation we'll just use the wait handle again to trigger this. - using var waitHandle3 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion3"); - Assert.True(waitHandle3.WaitOne(Timeout)); + // The main process will now import our transaction via the propagation token, and propagate it to a 2nd process. + // In the main process the transaction is delegated; we're the one who started it, and so we're the one who need to Commit. + // When Commit() is called in the main process, that will trigger a SinglePhaseCommit on the PSPE which represents us. In SQL Server this + // contacts the DB to actually commit the transaction with MSDTC. In this simulation we'll just use the wait handle again to trigger this. + using var waitHandle3 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion3"); + Assert.True(waitHandle3.WaitOne(Timeout)); - tx.Commit(); + tx.Commit(); + } - // Wait for the commit to occur on our enlistment, then exit successfully. - Assert.True(outcomeEvent.WaitOne(Timeout)); - Environment.Exit(42); // 42 is error code expected by RemoteExecutor - } + // Wait for the commit to occur on our enlistment, then exit successfully. + Assert.True(outcomeEvent.WaitOne(Timeout)); + Environment.Exit(42); // 42 is error code expected by RemoteExecutor + }); static void Remote2(string exportCookieFilePath) - { - // Load the export cookie and enlist durably - byte[] exportCookie = File.ReadAllBytes(exportCookieFilePath); - using var tx = TransactionInterop.GetTransactionFromExportCookie(exportCookie); - - // Now enlist durably. This triggers promotion of the first PSPE, reading the propagation token. - var outcomeEvent = new AutoResetEvent(false); - var enlistment = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent); - tx.EnlistDurable(Guid.NewGuid(), enlistment, EnlistmentOptions.None); - - // Signal to the main process that we're enlisted and ready to commit - using var waitHandle = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion2"); - waitHandle.Set(); - - // Wait for the main process to commit the transaction - Assert.True(outcomeEvent.WaitOne(Timeout)); - Environment.Exit(42); // 42 is error code expected by RemoteExecutor - } + => Test(() => + { + var outcomeEvent = new AutoResetEvent(false); + + // Load the export cookie and enlist durably + byte[] exportCookie = File.ReadAllBytes(exportCookieFilePath); + using (var tx = TransactionInterop.GetTransactionFromExportCookie(exportCookie)) + { + // Now enlist durably. This triggers promotion of the first PSPE, reading the propagation token. + var enlistment = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent); + tx.EnlistDurable(Guid.NewGuid(), enlistment, EnlistmentOptions.None); + + // Signal to the main process that we're enlisted and ready to commit + using var waitHandle = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion2"); + waitHandle.Set(); + } + + // Wait for the main process to commit the transaction + Assert.True(outcomeEvent.WaitOne(Timeout)); + Environment.Exit(42); // 42 is error code expected by RemoteExecutor + }); } public class TestPromotableSinglePhaseNotification : IPromotableSinglePhaseNotification @@ -300,88 +305,87 @@ public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment) [ConditionalFact(nameof(IsRemoteExecutorSupportedAndNotNano))] public void Recovery() { - if (!Environment.Is64BitProcess) + Test(() => { - return; // Temporarily skip on 32-bit where we have an issue - } + // We are going to spin up an external process to also enlist in the transaction, and then to crash when it + // receives the commit notification. We will then initiate the recovery flow. - // We are going to spin up an external process to also enlist in the transaction, and then to crash when it - // receives the commit notification. We will then initiate the recovery flow. + using var tx = new CommittableTransaction(); - using var tx = new CommittableTransaction(); + var outcomeEvent1 = new AutoResetEvent(false); + var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent1); + var guid1 = Guid.NewGuid(); + tx.EnlistDurable(guid1, enlistment1, EnlistmentOptions.None); - var outcomeEvent1 = new AutoResetEvent(false); - var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent1); - var guid1 = Guid.NewGuid(); - tx.EnlistDurable(guid1, enlistment1, EnlistmentOptions.None); - - // The propagation token is used to propagate the transaction to that process so it can enlist to our - // transaction. We also provide the resource manager identifier GUID, and a path where the external process will - // write the recovery information it will receive from the MSDTC when preparing. - // We'll need these two elements later in order to Reenlist and trigger recovery. - byte[] propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); - string propagationTokenText = Convert.ToBase64String(propagationToken); - var guid2 = Guid.NewGuid(); - string secondEnlistmentRecoveryFilePath = Path.GetTempFileName(); - - using var waitHandle = new EventWaitHandle( - initialState: false, - EventResetMode.ManualReset, - "System.Transactions.Tests.OleTxTests.Recovery"); - - try - { - using (RemoteExecutor.Invoke( - EnlistAndCrash, - propagationTokenText, guid2.ToString(), secondEnlistmentRecoveryFilePath, - new RemoteInvokeOptions { ExpectedExitCode = 42 })) + // The propagation token is used to propagate the transaction to that process so it can enlist to our + // transaction. We also provide the resource manager identifier GUID, and a path where the external process will + // write the recovery information it will receive from the MSDTC when preparing. + // We'll need these two elements later in order to Reenlist and trigger recovery. + byte[] propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); + string propagationTokenText = Convert.ToBase64String(propagationToken); + var guid2 = Guid.NewGuid(); + string secondEnlistmentRecoveryFilePath = Path.GetTempFileName(); + + using var waitHandle = new EventWaitHandle( + initialState: false, + EventResetMode.ManualReset, + "System.Transactions.Tests.OleTxTests.Recovery"); + + try { - // Wait for the external process to enlist in the transaction, it will signal this EventWaitHandle. - Assert.True(waitHandle.WaitOne(Timeout)); + using (RemoteExecutor.Invoke( + EnlistAndCrash, + propagationTokenText, guid2.ToString(), secondEnlistmentRecoveryFilePath, + new RemoteInvokeOptions { ExpectedExitCode = 42 })) + { + // Wait for the external process to enlist in the transaction, it will signal this EventWaitHandle. + Assert.True(waitHandle.WaitOne(Timeout)); - tx.Commit(); - } + tx.Commit(); + } - // The other has crashed when the MSDTC notified it to commit. - // Load the recovery information the other process has written to disk for us and reenlist with - // the failed RM's Guid to commit. - var outcomeEvent3 = new AutoResetEvent(false); - var enlistment3 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent3); - byte[] secondRecoveryInformation = File.ReadAllBytes(secondEnlistmentRecoveryFilePath); - _ = TransactionManager.Reenlist(guid2, secondRecoveryInformation, enlistment3); - TransactionManager.RecoveryComplete(guid2); - - Assert.True(outcomeEvent1.WaitOne(Timeout)); - Assert.True(outcomeEvent3.WaitOne(Timeout)); - Assert.Equal(EnlistmentOutcome.Committed, enlistment1.Outcome); - Assert.Equal(EnlistmentOutcome.Committed, enlistment3.Outcome); - Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status); - - // Note: verify manually in the MSDTC console that the distributed transaction is gone - // (i.e. successfully committed), - // (Start -> Component Services -> Computers -> My Computer -> Distributed Transaction Coordinator -> - // Local DTC -> Transaction List) - } - finally - { - File.Delete(secondEnlistmentRecoveryFilePath); - } + // The other has crashed when the MSDTC notified it to commit. + // Load the recovery information the other process has written to disk for us and reenlist with + // the failed RM's Guid to commit. + var outcomeEvent3 = new AutoResetEvent(false); + var enlistment3 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent3); + byte[] secondRecoveryInformation = File.ReadAllBytes(secondEnlistmentRecoveryFilePath); + _ = TransactionManager.Reenlist(guid2, secondRecoveryInformation, enlistment3); + TransactionManager.RecoveryComplete(guid2); + + Assert.True(outcomeEvent1.WaitOne(Timeout)); + Assert.True(outcomeEvent3.WaitOne(Timeout)); + Assert.Equal(EnlistmentOutcome.Committed, enlistment1.Outcome); + Assert.Equal(EnlistmentOutcome.Committed, enlistment3.Outcome); + Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status); + + // Note: verify manually in the MSDTC console that the distributed transaction is gone + // (i.e. successfully committed), + // (Start -> Component Services -> Computers -> My Computer -> Distributed Transaction Coordinator -> + // Local DTC -> Transaction List) + } + finally + { + File.Delete(secondEnlistmentRecoveryFilePath); + } + }); static void EnlistAndCrash(string propagationTokenText, string resourceManagerIdentifierGuid, string recoveryInformationFilePath) - { - byte[] propagationToken = Convert.FromBase64String(propagationTokenText); - using var tx = TransactionInterop.GetTransactionFromTransmitterPropagationToken(propagationToken); + => Test(() => + { + byte[] propagationToken = Convert.FromBase64String(propagationTokenText); + using var tx = TransactionInterop.GetTransactionFromTransmitterPropagationToken(propagationToken); - var crashingEnlistment = new CrashingEnlistment(recoveryInformationFilePath); - tx.EnlistDurable(Guid.Parse(resourceManagerIdentifierGuid), crashingEnlistment, EnlistmentOptions.None); + var crashingEnlistment = new CrashingEnlistment(recoveryInformationFilePath); + tx.EnlistDurable(Guid.Parse(resourceManagerIdentifierGuid), crashingEnlistment, EnlistmentOptions.None); - // Signal to the main process that we've enlisted and are ready to accept prepare/commit. - using var waitHandle = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Recovery"); - waitHandle.Set(); + // Signal to the main process that we've enlisted and are ready to accept prepare/commit. + using var waitHandle = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Recovery"); + waitHandle.Set(); - // We've enlisted, and set it up so that when the MSDTC tells us to commit, the process will crash. - Thread.Sleep(Timeout); - } + // We've enlisted, and set it up so that when the MSDTC tells us to commit, the process will crash. + Thread.Sleep(Timeout); + }); } public class CrashingEnlistment : IEnlistmentNotification @@ -411,46 +415,190 @@ public void InDoubt(Enlistment enlistment) [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] public void TransmitterPropagationToken() + => Test(() => + { + using var tx = new CommittableTransaction(); + + Assert.Equal(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + + var propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); + + Assert.NotEqual(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + + var tx2 = TransactionInterop.GetTransactionFromTransmitterPropagationToken(propagationToken); + + Assert.Equal(tx.TransactionInformation.DistributedIdentifier, tx2.TransactionInformation.DistributedIdentifier); + }); + + // #76010 + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] + public void TransactionScope_with_DependentTransaction() + => Test(() => { + using var committableTransaction = new CommittableTransaction(); + var propagationToken = TransactionInterop.GetTransmitterPropagationToken(committableTransaction); + + var dependentTransaction = TransactionInterop.GetTransactionFromTransmitterPropagationToken(propagationToken); + + using (var scope = new TransactionScope(dependentTransaction)) + { + scope.Complete(); + } + }); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] + public void GetExportCookie() + => Test(() => + { + using var tx = new CommittableTransaction(); + + var whereabouts = TransactionInterop.GetWhereabouts(); + + Assert.Equal(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + + var exportCookie = TransactionInterop.GetExportCookie(tx, whereabouts); + + Assert.NotEqual(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + + var tx2 = TransactionInterop.GetTransactionFromExportCookie(exportCookie); + + Assert.Equal(tx.TransactionInformation.DistributedIdentifier, tx2.TransactionInformation.DistributedIdentifier); + }); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] + public void GetDtcTransaction() + => Test(() => + { + using var tx = new CommittableTransaction(); + + var outcomeReceived = new AutoResetEvent(false); + + var enlistment = new TestEnlistment( + Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeReceived); + + Assert.Equal(Guid.Empty, tx.PromoterType); + + tx.EnlistVolatile(enlistment, EnlistmentOptions.None); + + // Forces promotion to MSDTC, returns an ITransaction for use only with System.EnterpriseServices. + _ = TransactionInterop.GetDtcTransaction(tx); + + Assert.Equal(TransactionStatus.Active, tx.TransactionInformation.Status); + Assert.Equal(TransactionInterop.PromoterTypeDtc, tx.PromoterType); + + tx.Commit(); + + Assert.True(outcomeReceived.WaitOne(Timeout)); + Assert.Equal(EnlistmentOutcome.Committed, enlistment.Outcome); + Retry(() => Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status)); + }); + + [ConditionalFact(nameof(IsRemoteExecutorSupportedAndNotNano))] + public void Distributed_transactions_require_ImplicitDistributedTransactions_true() + { + // Temporarily skip on 32-bit where we have an issue. if (!Environment.Is64BitProcess) { - return; // Temporarily skip on 32-bit where we have an issue + return; } - using var tx = new CommittableTransaction(); + using var _ = RemoteExecutor.Invoke(() => + { + Assert.False(TransactionManager.ImplicitDistributedTransactions); - Assert.Equal(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + using var tx = new CommittableTransaction(); - var propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); + Assert.Throws(MinimalOleTxScenario); + }); + } - Assert.NotEqual(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + [ConditionalFact(nameof(IsRemoteExecutorSupportedAndNotNano))] + public void ImplicitDistributedTransactions_cannot_be_changed_after_being_set() + { + // Temporarily skip on 32-bit where we have an issue. + if (!Environment.Is64BitProcess) + { + return; + } - var tx2 = TransactionInterop.GetTransactionFromTransmitterPropagationToken(propagationToken); + using var _ = RemoteExecutor.Invoke(() => + { + TransactionManager.ImplicitDistributedTransactions = true; - Assert.Equal(tx.TransactionInformation.DistributedIdentifier, tx2.TransactionInformation.DistributedIdentifier); + Assert.Throws(() => TransactionManager.ImplicitDistributedTransactions = false); + }); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] - public void GetExportCookie() + [ConditionalFact(nameof(IsRemoteExecutorSupportedAndNotNano))] + public void ImplicitDistributedTransactions_cannot_be_changed_after_being_read_as_true() { + // Temporarily skip on 32-bit where we have an issue. if (!Environment.Is64BitProcess) { - return; // Temporarily skip on 32-bit where we have an issue + return; } - using var tx = new CommittableTransaction(); + using var _ = RemoteExecutor.Invoke(() => + { + TransactionManager.ImplicitDistributedTransactions = true; - var whereabouts = TransactionInterop.GetWhereabouts(); + MinimalOleTxScenario(); - Assert.Equal(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + Assert.Throws(() => TransactionManager.ImplicitDistributedTransactions = false); + TransactionManager.ImplicitDistributedTransactions = true; + }); + } - var exportCookie = TransactionInterop.GetExportCookie(tx, whereabouts); + [ConditionalFact(nameof(IsRemoteExecutorSupportedAndNotNano))] + public void ImplicitDistributedTransactions_cannot_be_changed_after_being_read_as_false() + { + // Temporarily skip on 32-bit where we have an issue. + if (!Environment.Is64BitProcess) + { + return; + } - Assert.NotEqual(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + using var _ = RemoteExecutor.Invoke(() => + { + Assert.Throws(MinimalOleTxScenario); - var tx2 = TransactionInterop.GetTransactionFromExportCookie(exportCookie); + Assert.Throws(() => TransactionManager.ImplicitDistributedTransactions = true); + TransactionManager.ImplicitDistributedTransactions = false; + }); + } + + private static void Test(Action action) + { + // Temporarily skip on 32-bit where we have an issue. + if (!Environment.Is64BitProcess) + { + return; + } + + TransactionManager.ImplicitDistributedTransactions = true; + + // In CI, we sometimes get XACT_E_TMNOTAVAILABLE; when it happens, it's typically on the very first + // attempt to connect to MSDTC (flaky/slow on-demand startup of MSDTC), though not only. + // This catches that error and retries. + int nRetries = 60; + + while (true) + { + try + { + action(); + return; + } + catch (TransactionException e) when (e.InnerException is TransactionManagerCommunicationException) + { + if (--nRetries == 0) + { + throw; + } - Assert.Equal(tx.TransactionInformation.DistributedIdentifier, tx2.TransactionInformation.DistributedIdentifier); + Thread.Sleep(500); + } + } } // MSDTC is aynchronous, i.e. Commit/Rollback may return before the transaction has actually completed; @@ -478,46 +626,25 @@ private static void Retry(Action action) } } - public class OleTxFixture + static void MinimalOleTxScenario() { - public OleTxFixture() - { - if (!Environment.Is64BitProcess) - { - return; // Temporarily skip on 32-bit where we have an issue - } - - // In CI, we sometimes get XACT_E_TMNOTAVAILABLE on the very first attempt to connect to MSDTC; - // this is likely due to on-demand slow startup of MSDTC. Perform pre-test connecting with retry - // to ensure that MSDTC is properly up when the first test runs. - int nRetries = 5; - - while (true) - { - try - { - using var tx = new CommittableTransaction(); - - var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); - var enlistment2 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); + using var tx = new CommittableTransaction(); - tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None); - tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None); + var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); + var enlistment2 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); - tx.Commit(); + tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None); + tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None); - return; - } - catch (TransactionException e) when (e.InnerException is TransactionManagerCommunicationException) - { - if (--nRetries == 0) - { - throw; - } + tx.Commit(); + } - Thread.Sleep(100); - } - } - } + public class OleTxFixture + { + // In CI, we sometimes get XACT_E_TMNOTAVAILABLE on the very first attempt to connect to MSDTC; + // this is likely due to on-demand slow startup of MSDTC. Perform pre-test connecting with retry + // to ensure that MSDTC is properly up when the first test runs. + public OleTxFixture() + => Test(MinimalOleTxScenario); } } diff --git a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.txt b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.txt index 93dd3423e9b827..1361a3c323a21c 100644 --- a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.txt +++ b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.txt @@ -28,7 +28,6 @@ CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatfo CannotChangeAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' on 'System.Security.Cryptography.RC2.Create()' changed from '[UnsupportedOSPlatformAttribute("android")]' in the contract to '[UnsupportedOSPlatformAttribute("android")]' in the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Rfc2898DeriveBytes' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Rijndael' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RijndaelManaged' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSA' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSAEncryptionPadding' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSAOAEPKeyExchangeDeformatter' in the contract but not the implementation. @@ -61,7 +60,6 @@ CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.IsReadOnlyAtt CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.IsReadOnlyAttribute' exists on 'System.Numerics.Vector.TryCopyTo(System.Span)' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.RequiresPreviewFeaturesAttribute' exists on 'System.String System.Runtime.CompilerServices.RuntimeFeature.VirtualStaticsInInterfaces' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Aes' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AesManaged' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AsymmetricKeyExchangeDeformatter' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AsymmetricKeyExchangeFormatter' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AsymmetricSignatureDeformatter' in the contract but not the implementation. @@ -93,7 +91,6 @@ CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatfo CannotChangeAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' on 'System.Security.Cryptography.RC2.Create()' changed from '[UnsupportedOSPlatformAttribute("android")]' in the contract to '[UnsupportedOSPlatformAttribute("android")]' in the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Rfc2898DeriveBytes' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Rijndael' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RijndaelManaged' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSA' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSAEncryptionPadding' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSAOAEPKeyExchangeDeformatter' in the contract but not the implementation. @@ -112,7 +109,6 @@ Compat issues with assembly System.Core: CannotRemoveAttribute : Attribute 'System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute' exists on 'System.Linq.EnumerableQuery..ctor(System.Collections.Generic.IEnumerable)' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute' exists on 'System.Linq.EnumerableQuery..ctor(System.Linq.Expressions.Expression)' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Aes' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AesManaged' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.ECCurve' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.ECDiffieHellman' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.ECDsa' in the contract but not the implementation. @@ -156,7 +152,6 @@ MembersMustExist : Member 'public System.Runtime.Intrinsics.Vector256 System. MembersMustExist : Member 'public System.Runtime.Intrinsics.Vector64 System.Runtime.Intrinsics.Vector64.As(System.Runtime.Intrinsics.Vector64)' does not exist in the implementation but it does exist in the contract. Compat issues with assembly System.Security.Cryptography.Algorithms: CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Aes' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AesManaged' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AsymmetricKeyExchangeDeformatter' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AsymmetricKeyExchangeFormatter' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AsymmetricSignatureDeformatter' in the contract but not the implementation. @@ -189,7 +184,6 @@ CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatfo CannotChangeAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' on 'System.Security.Cryptography.RC2.Create()' changed from '[UnsupportedOSPlatformAttribute("android")]' in the contract to '[UnsupportedOSPlatformAttribute("android")]' in the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Rfc2898DeriveBytes' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Rijndael' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RijndaelManaged' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSA' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSAEncryptionPadding' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSAOAEPKeyExchangeDeformatter' in the contract but not the implementation. @@ -203,4 +197,4 @@ Compat issues with assembly System.Security.Cryptography.X509Certificates: CannotChangeAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' on 'System.Security.Cryptography.X509Certificates.PublicKey.GetDSAPublicKey()' changed from '[UnsupportedOSPlatformAttribute("ios")]' in the contract to '[UnsupportedOSPlatformAttribute("browser")]' in the implementation. Compat issues with assembly System.Text.Json: CannotMakeTypeAbstract : Type 'System.Text.Json.Serialization.Metadata.JsonTypeInfo' is abstract in the implementation but is not abstract in the contract. -Total Issues: 190 +Total Issues: 184 diff --git a/src/libraries/oob-all.proj b/src/libraries/oob-all.proj index f84c5b40502513..9164a5846ceb4e 100644 --- a/src/libraries/oob-all.proj +++ b/src/libraries/oob-all.proj @@ -16,8 +16,7 @@ diff --git a/src/libraries/oob-src.proj b/src/libraries/oob-src.proj index 2cbfdf9bb3c554..472a2dfb5e7082 100644 --- a/src/libraries/oob-src.proj +++ b/src/libraries/oob-src.proj @@ -21,8 +21,7 @@ ('$(BuildAllConfigurations)' != 'true' and '$(RuntimeFlavor)' == '$(PrimaryRuntimeFlavor)')" /> - + diff --git a/src/libraries/sendtohelix-wasm.targets b/src/libraries/sendtohelix-wasm.targets index acf037e988b272..ed76264134f306 100644 --- a/src/libraries/sendtohelix-wasm.targets +++ b/src/libraries/sendtohelix-wasm.targets @@ -26,6 +26,7 @@ $([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'src', 'mono', 'wasm', 'build')) $([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'bin', 'NetCoreServer', '$(Configuration)', '$(AspNetCoreAppCurrent)')) $([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'bin', 'RemoteLoopServer', '$(Configuration)', '$(AspNetCoreAppCurrent)')) + <_ShippingPackagesPath>$([MSBuild]::NormalizeDirectory($(ArtifactsDir), 'packages', $(Configuration), 'Shipping')) $(DebuggerHost)- $(Scenario)- @@ -56,6 +57,7 @@ true true true + true true false @@ -121,6 +123,9 @@ + + + @@ -187,6 +192,10 @@ + + + + - + diff --git a/src/libraries/sendtohelixhelp.proj b/src/libraries/sendtohelixhelp.proj index 59cd95650c13e6..0f92fdde638e3f 100644 --- a/src/libraries/sendtohelixhelp.proj +++ b/src/libraries/sendtohelixhelp.proj @@ -60,8 +60,8 @@ $(WaitForWorkItemCompletion) - dotnet-workload - sdk-no-workload + dotnet-net7 + dotnet-none diff --git a/src/libraries/shims/src/mscorlib.csproj b/src/libraries/shims/src/mscorlib.csproj index 718cd1a3f18127..dd9d0fe6105c2b 100644 --- a/src/libraries/shims/src/mscorlib.csproj +++ b/src/libraries/shims/src/mscorlib.csproj @@ -7,4 +7,10 @@ + + + + + + diff --git a/src/libraries/shims/src/mscorlib.forwards.cs b/src/libraries/shims/src/mscorlib.forwards.cs index 5598f5bf8f24dd..76b4543852a3df 100644 --- a/src/libraries/shims/src/mscorlib.forwards.cs +++ b/src/libraries/shims/src/mscorlib.forwards.cs @@ -19,6 +19,95 @@ [assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.OrdinalComparer))] [assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.UnitySerializationHolder))] [assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.Contracts.ContractException))] -// This is required for back-compatibility with legacy Xamarin which had Stack and Queue in mscorlib -[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.Generic.Stack<>))] +// This is required for back-compatibility with legacy Xamarin which had Stack and Queue in mscorlib and also added a few NS2.1 types that didn't exist in .NET Framework +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.HashCode))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.IAsyncDisposable))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Index))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.MathF))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.MemoryExtensions))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Memory<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Range))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.ReadOnlyMemory<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.ReadOnlySpan<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.SequencePosition))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Span<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.ArrayBufferWriter<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.ArrayPool<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.BuffersExtensions))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.IBufferWriter<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.IMemoryOwner<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.IPinnable))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.MemoryHandle))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.MemoryManager<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.MemoryPool<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.OperationStatus))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.ReadOnlySequenceSegment<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.ReadOnlySequence<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.ReadOnlySpanAction<,>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.SequenceReaderExtensions))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.SequenceReader<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.SpanAction<,>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.StandardFormat))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.Binary.BinaryPrimitives))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.Text.Base64))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.Text.Utf8Formatter))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.Text.Utf8Parser))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.Generic.CollectionExtensions))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.Generic.IAsyncEnumerable<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.Generic.IAsyncEnumerator<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] [assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.Generic.Queue<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.Generic.Stack<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.AllowNullAttribute))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.DisallowNullAttribute))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.MaybeNullAttribute))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.NotNullAttribute))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.NotNullWhenAttribute))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.Tracing.DiagnosticCounter))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.Tracing.EventCounter))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.Tracing.IncrementingEventCounter))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.Tracing.IncrementingPollingCounter))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.Tracing.PollingCounter))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Globalization.ISOWeek))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.IO.EnumerationOptions))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.IO.MatchCasing))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.IO.MatchType))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.IO.Enumeration.FileSystemEntry))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.IO.Enumeration.FileSystemEnumerable<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.IO.Enumeration.FileSystemEnumerator<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.IO.Enumeration.FileSystemName))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Numerics.Vector))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Numerics.Vector<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.AssemblyExtensions))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.EventInfoExtensions))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.MemberInfoExtensions))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.MethodInfoExtensions))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.ModuleExtensions))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.PropertyInfoExtensions))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.TypeExtensions))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.AsyncIteratorMethodBuilder))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.AsyncMethodBuilderAttribute))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.ConfiguredAsyncDisposable))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.EnumeratorCancellationAttribute))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.ValueTaskAwaiter))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.ValueTaskAwaiter<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.InteropServices.MemoryMarshal))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.InteropServices.SequenceMarshal))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Threading.Tasks.TaskAsyncEnumerableExtensions))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Threading.Tasks.ValueTask))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Threading.Tasks.ValueTask<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Threading.Tasks.Sources.IValueTaskSource))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Threading.Tasks.Sources.IValueTaskSource<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore<>))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Threading.Tasks.Sources.ValueTaskSourceOnCompletedFlags))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Threading.Tasks.Sources.ValueTaskSourceStatus))] diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index ecffaaf6e0a462..a4f337987354a0 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -4,7 +4,7 @@ $([MSBuild]::ValueOrDefault('$(BuildTargetFramework)', '$(NetCoreAppCurrent)'))-$(TargetOS) - true + true @@ -18,7 +18,9 @@ true false false + false + true false @@ -66,6 +68,8 @@ + + @@ -169,7 +173,6 @@ - @@ -333,7 +336,8 @@ - + + @@ -544,7 +548,12 @@ - + + + + @@ -577,6 +586,11 @@ Condition="'$(TestTrimming)' == 'true'" AdditionalProperties="%(AdditionalProperties);SkipTrimmingProjectsRestore=true" /> + + - - - + - + - diff --git a/src/mono/CMakeLists.txt b/src/mono/CMakeLists.txt index 1629fe4dbcec93..6dbf542183bcdc 100644 --- a/src/mono/CMakeLists.txt +++ b/src/mono/CMakeLists.txt @@ -215,6 +215,7 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(HOST_LINUX 1) add_definitions(-D_GNU_SOURCE -D_REENTRANT) add_definitions(-D_THREAD_SAFE) + set(HAVE_CGROUP_SUPPORT 1) # Enable the "full RELRO" options (RELRO & BIND_NOW) at link time add_link_options(-Wl,-z,relro) add_link_options(-Wl,-z,now) diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index 7eb9314c1ce629..b39c4504440463 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -125,8 +125,8 @@ $(DefineConstants);MONO_FEATURE_SRE true - true - true + true + true true true true diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs index 1fcb29907ae2aa..40265d7b9a2702 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs @@ -38,8 +38,6 @@ internal abstract class RtFieldInfo : FieldInfo internal abstract object UnsafeGetValue(object obj); internal abstract void UnsafeSetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, CultureInfo culture); internal abstract void CheckConsistency(object target); - internal abstract object? GetValueNonEmit(object? obj); - internal abstract void SetValueNonEmit(object? obj, object? value); } [StructLayout(LayoutKind.Sequential)] @@ -51,19 +49,8 @@ internal sealed class RuntimeFieldInfo : RtFieldInfo private string? name; private Type? type; private FieldAttributes attrs; - private FieldAccessor? invoker; #pragma warning restore 649 - private FieldAccessor Invoker - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - invoker ??= new FieldAccessor(this); - return invoker; - } - } - public override Module Module { get @@ -128,13 +115,6 @@ public override void SetValueDirect(TypedReference obj, object value) } } - [DebuggerStepThrough] - [DebuggerHidden] - internal override void SetValueNonEmit(object? obj, object? value) - { - SetValueInternal(this, obj, value); - } - [DebuggerStepThrough] [DebuggerHidden] public override object GetValueDirect(TypedReference obj) @@ -149,13 +129,6 @@ public override object GetValueDirect(TypedReference obj) } } - [DebuggerStepThrough] - [DebuggerHidden] - internal override object? GetValueNonEmit(object? obj) - { - return GetValueInternal(obj); - } - public override FieldAttributes Attributes { get @@ -239,7 +212,7 @@ public override object[] GetCustomAttributes(Type attributeType, bool inherit) if (!IsLiteral) CheckGeneric(); - return Invoker.GetValue(obj); + return GetValueInternal(obj); } public override string ToString() @@ -278,7 +251,7 @@ public override void SetValue(object? obj, object? val, BindingFlags invokeAttr, } } - Invoker.SetValue(obj, val); + SetValueInternal(this, obj, val); } internal RuntimeFieldInfo Clone(string newName) diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs index 146739163199be..08f30f04fca829 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs @@ -802,10 +802,13 @@ internal RuntimeType[] ArgumentTypes } } - private static void InvokeClassConstructor() + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void InvokeClassConstructor(QCallTypeHandle type); + + private void InvokeClassConstructor() { - // [TODO] Mechanism for invoking class constructor - // See https://github.com/dotnet/runtime/issues/40351 + RuntimeType type = (RuntimeType)DeclaringType; + InvokeClassConstructor(new QCallTypeHandle(ref type)); } /* diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs index 153783fa1707c0..06298a30fc3acd 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs @@ -65,7 +65,7 @@ private bool SetTimer(uint actualDuration) // shortest time of all TimerQueues private static void ReplaceNextSetTimeout(long shortestDueTimeMs, long currentTimeMs) { - if (shortestDueTimeMs == int.MaxValue) + if (shortestDueTimeMs == long.MaxValue) { return; } @@ -85,7 +85,7 @@ private static long ShortestDueTime() { if (s_scheduledTimers == null) { - return int.MaxValue; + return long.MaxValue; } long shortestDueTimeMs = long.MaxValue; @@ -112,7 +112,7 @@ private static long PumpTimerQueue(long currentTimeMs) List timersToFire = s_scheduledTimersToFire!; List timers; timers = s_scheduledTimers!; - long shortestDueTimeMs = int.MaxValue; + long shortestDueTimeMs = long.MaxValue; for (int i = timers.Count - 1; i >= 0; --i) { TimerQueue timer = timers[i]; diff --git a/src/mono/cmake/config.h.in b/src/mono/cmake/config.h.in index 81ff6697c2a272..71753f1d47e097 100644 --- a/src/mono/cmake/config.h.in +++ b/src/mono/cmake/config.h.in @@ -612,6 +612,9 @@ /* Define to 1 if `st_atimespec' is a member of `struct stat'. */ #cmakedefine HAVE_STRUCT_STAT_ST_ATIMESPEC 1 +/* Define to 1 if `super_class' is a member of `struct objc_super'. */ +#cmakedefine HAVE_OBJC_SUPER_SUPER_CLASS 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_TIME_H 1 @@ -888,6 +891,9 @@ /* Define to 1 if you have /usr/include/malloc.h. */ #cmakedefine HAVE_USR_INCLUDE_MALLOC_H 1 +/* Define to 1 if you have linux cgroups */ +#cmakedefine HAVE_CGROUP_SUPPORT 1 + /* The architecture this is running on */ #define MONO_ARCHITECTURE @MONO_ARCHITECTURE@ diff --git a/src/mono/cmake/configure.cmake b/src/mono/cmake/configure.cmake index 51cb315d6dcfbc..ae55fd112b321e 100644 --- a/src/mono/cmake/configure.cmake +++ b/src/mono/cmake/configure.cmake @@ -129,6 +129,10 @@ check_struct_has_member("struct sockaddr_in6" sin6_len "netinet/in.h" HAVE_SOCKA check_struct_has_member("struct stat" st_atim "sys/types.h;sys/stat.h;unistd.h" HAVE_STRUCT_STAT_ST_ATIM) check_struct_has_member("struct stat" st_atimespec "sys/types.h;sys/stat.h;unistd.h" HAVE_STRUCT_STAT_ST_ATIMESPEC) +if (HOST_DARWIN) + check_struct_has_member("struct objc_super" super_class "objc/runtime.h;objc/message.h" HAVE_OBJC_SUPER_SUPER_CLASS) +endif() + check_type_size("int" SIZEOF_INT) check_type_size("void*" SIZEOF_VOID_P) check_type_size("long" SIZEOF_LONG) diff --git a/src/mono/dlls/mscordbi/cordb-value.cpp b/src/mono/dlls/mscordbi/cordb-value.cpp index 3078c682eea288..d9aa9fa6625aef 100644 --- a/src/mono/dlls/mscordbi/cordb-value.cpp +++ b/src/mono/dlls/mscordbi/cordb-value.cpp @@ -1180,15 +1180,15 @@ HRESULT STDMETHODCALLTYPE CordbArrayValue::GetDimensions(ULONG32 cdim, ULONG32 d return S_OK; } -HRESULT STDMETHODCALLTYPE CordbArrayValue::HasBaseIndices(BOOL* pbHasBaseIndices) +HRESULT STDMETHODCALLTYPE CordbArrayValue::HasBaseIndicies(BOOL* pbHasBaseIndicies) { - LOG((LF_CORDB, LL_INFO100000, "CordbArrayValue - HasBaseIndices - NOT IMPLEMENTED\n")); + LOG((LF_CORDB, LL_INFO100000, "CordbArrayValue - HasBaseIndicies - NOT IMPLEMENTED\n")); return S_OK; } -HRESULT STDMETHODCALLTYPE CordbArrayValue::GetBaseIndices(ULONG32 cdim, ULONG32 indices[]) +HRESULT STDMETHODCALLTYPE CordbArrayValue::GetBaseIndicies(ULONG32 cdim, ULONG32 indices[]) { - LOG((LF_CORDB, LL_INFO100000, "CordbArrayValue - GetBaseIndices - NOT IMPLEMENTED\n")); + LOG((LF_CORDB, LL_INFO100000, "CordbArrayValue - GetBaseIndicies - NOT IMPLEMENTED\n")); return E_NOTIMPL; } diff --git a/src/mono/dlls/mscordbi/cordb-value.h b/src/mono/dlls/mscordbi/cordb-value.h index 0e6ec70b03c241..4b2ab5718ade89 100644 --- a/src/mono/dlls/mscordbi/cordb-value.h +++ b/src/mono/dlls/mscordbi/cordb-value.h @@ -239,8 +239,8 @@ class CordbArrayValue : public CordbBaseMono, HRESULT STDMETHODCALLTYPE GetRank(ULONG32* pnRank); HRESULT STDMETHODCALLTYPE GetCount(ULONG32* pnCount); HRESULT STDMETHODCALLTYPE GetDimensions(ULONG32 cdim, ULONG32 dims[]); - HRESULT STDMETHODCALLTYPE HasBaseIndices(BOOL* pbHasBaseIndices); - HRESULT STDMETHODCALLTYPE GetBaseIndices(ULONG32 cdim, ULONG32 indices[]); + HRESULT STDMETHODCALLTYPE HasBaseIndicies(BOOL* pbHasBaseIndicies); + HRESULT STDMETHODCALLTYPE GetBaseIndicies(ULONG32 cdim, ULONG32 indices[]); HRESULT STDMETHODCALLTYPE GetElement(ULONG32 cdim, ULONG32 indices[], ICorDebugValue** ppValue); HRESULT STDMETHODCALLTYPE GetElementAtPosition(ULONG32 nPosition, ICorDebugValue** ppValue); }; diff --git a/src/mono/mono.proj b/src/mono/mono.proj index 26ca5545d761bb..41325f5dbea6b5 100644 --- a/src/mono/mono.proj +++ b/src/mono/mono.proj @@ -632,6 +632,7 @@ aarch64-linux-gnu $(MonoObjCrossDir)offsets-aarch-linux-gnu.h $(MonoCrossDir)/usr/lib/gcc/aarch64-linux-gnu/7 + $(MonoCrossDir)/usr/lib/gcc/aarch64-linux-gnu/5 @@ -733,8 +734,7 @@ - - + diff --git a/src/mono/mono/arch/amd64/amd64-codegen.h b/src/mono/mono/arch/amd64/amd64-codegen.h index ccd82d1ac172d8..9ac73b9853c466 100644 --- a/src/mono/mono/arch/amd64/amd64-codegen.h +++ b/src/mono/mono/arch/amd64/amd64-codegen.h @@ -1206,6 +1206,7 @@ typedef union { #define amd64_alu_membase8_imm_size(inst,opc,basereg,disp,imm,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),0,0,(basereg)); x86_alu_membase8_imm((inst),(opc),((basereg)&0x7),(disp),(imm)); amd64_codegen_post(inst); } while (0) #define amd64_alu_mem_reg_size(inst,opc,mem,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),0,0,(reg)); x86_alu_mem_reg((inst),(opc),(mem),((reg)&0x7)); amd64_codegen_post(inst); } while (0) #define amd64_alu_membase_reg_size(inst,opc,basereg,disp,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),(reg),0,(basereg)); x86_alu_membase_reg((inst),(opc),((basereg)&0x7),(disp),((reg)&0x7)); amd64_codegen_post(inst); } while (0) +#define amd64_alu_membase8_reg_size(inst,opc,basereg,disp,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),(reg),0,(basereg)); x86_alu_membase8_reg((inst),(opc),((basereg)&0x7),(disp),((reg)&0x7)); amd64_codegen_post(inst); } while (0) //#define amd64_alu_reg_reg_size(inst,opc,dreg,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),(dreg),0,(reg)); x86_alu_reg_reg((inst),(opc),((dreg)&0x7),((reg)&0x7)); amd64_codegen_post(inst); } while (0) #define amd64_alu_reg8_reg8_size(inst,opc,dreg,reg,is_dreg_h,is_reg_h,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),(dreg),0,(reg)); x86_alu_reg8_reg8((inst),(opc),((dreg)&0x7),((reg)&0x7),(is_dreg_h),(is_reg_h)); amd64_codegen_post(inst); } while (0) #define amd64_alu_reg_mem_size(inst,opc,reg,mem,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),0,0,(reg)); x86_alu_reg_mem((inst),(opc),((reg)&0x7),(mem)); amd64_codegen_post(inst); } while (0) diff --git a/src/mono/mono/arch/x86/x86-codegen.h b/src/mono/mono/arch/x86/x86-codegen.h index 74f96f81af4077..aca2b659ca058b 100644 --- a/src/mono/mono/arch/x86/x86-codegen.h +++ b/src/mono/mono/arch/x86/x86-codegen.h @@ -682,6 +682,13 @@ mono_x86_patch_inline (guchar* code, gpointer target) x86_membase_emit ((inst), (reg), (basereg), (disp)); \ } while (0) +#define x86_alu_membase8_reg(inst,opc,basereg,disp,reg) \ + do { \ + x86_codegen_pre(&(inst), 1 + kMaxMembaseEmitPadding); \ + x86_byte (inst, (((unsigned char)(opc)) << 3)); \ + x86_membase_emit ((inst), (reg), (basereg), (disp)); \ + } while (0) + #define x86_alu_reg_reg(inst,opc,dreg,reg) \ do { \ x86_codegen_pre(&(inst), 2); \ diff --git a/src/mono/mono/component/CMakeLists.txt b/src/mono/mono/component/CMakeLists.txt index d83c9144af07f9..7c864322f5f377 100644 --- a/src/mono/mono/component/CMakeLists.txt +++ b/src/mono/mono/component/CMakeLists.txt @@ -6,12 +6,9 @@ set(MONO_EVENTPIPE_GEN_INCLUDE_PATH "${CMAKE_CURRENT_BINARY_DIR}/eventpipe") set(MONO_HOT_RELOAD_COMPONENT_NAME "hot_reload") set(MONO_DIAGNOSTICS_TRACING_COMPONENT_NAME "diagnostics_tracing") set(MONO_DEBUGGER_COMPONENT_NAME "debugger") -set(MONO_MARSHAL_ILGEN_COMPONENT_NAME "marshal-ilgen") # a list of every component. set(components "") -# a list of components needed by the AOT compiler -set(components_for_aot "") # the sources for each individiable component define a new # component_name-sources list for each component, and a @@ -82,53 +79,17 @@ set(${MONO_DIAGNOSTICS_TRACING_COMPONENT_NAME}-dependencies ${MONO_DIAGNOSTICS_TRACING_COMPONENT_NAME}-gen-sources ) -# marshal-ilgen -list(APPEND components - ${MONO_MARSHAL_ILGEN_COMPONENT_NAME} -) -list(APPEND components_for_aot - ${MONO_MARSHAL_ILGEN_COMPONENT_NAME} -) - -set(${MONO_MARSHAL_ILGEN_COMPONENT_NAME}-sources - ${MONO_COMPONENT_PATH}/marshal-ilgen.c - ${MONO_COMPONENT_PATH}/marshal-ilgen.h - ${MONO_COMPONENT_PATH}/marshal-ilgen-noilgen.c - ${MONO_COMPONENT_PATH}/marshal-ilgen-noilgen.h -) - -# For every component not build into the AOT compiler, build the stub instead -set(stubs_for_aot "") -foreach (component IN LISTS components) - if (NOT (component IN_LIST components_for_aot)) - list(APPEND stubs_for_aot "${component}") - endif() -endforeach() - - -set(${MONO_MARSHAL_ILGEN_COMPONENT_NAME}-stub-sources - ${MONO_COMPONENT_PATH}/marshal-ilgen-stub.c -) - -if (AOT_COMPONENTS) - set(components_to_build ${components_for_aot}) - set(stubs_to_build ${stubs_for_aot}) -else() - set(components_to_build ${components}) - set(stubs_to_build ${components}) -endif() - # from here down, all the components are treated in the same way #define a library for each component and component stub function(define_component_libs) # NOTE: keep library naming pattern in sync with RuntimeComponentManifest.targets - if (AOT_COMPONENTS OR NOT DISABLE_LIBS ) - foreach(component IN LISTS components_to_build) + if (NOT DISABLE_LIBS) + foreach(component IN LISTS components) add_library("mono-component-${component}-static" STATIC $) install(TARGETS "mono-component-${component}-static" LIBRARY) endforeach() - foreach(component IN LISTS stubs_to_build) + foreach(component IN LISTS components) add_library("mono-component-${component}-stub-static" STATIC $) install(TARGETS "mono-component-${component}-stub-static" LIBRARY) endforeach() @@ -142,7 +103,7 @@ target_sources(component_base INTERFACE ) target_link_libraries(component_base INTERFACE monoapi) -if(NOT AOT_COMPONENTS AND (DISABLE_COMPONENTS OR DISABLE_LIBS)) +if(DISABLE_COMPONENTS OR DISABLE_LIBS) set(DISABLE_COMPONENT_OBJECTS 1) endif() @@ -162,7 +123,7 @@ endforeach() if(NOT DISABLE_COMPONENTS AND NOT STATIC_COMPONENTS) # define a shared library for each component - foreach(component IN LISTS components_to_build) + foreach(component IN LISTS components) # NOTE: keep library naming pattern in sync with RuntimeComponentManifest.targets if(HOST_WIN32) add_library("mono-component-${component}" SHARED "${${component}-sources}") @@ -194,14 +155,14 @@ if(NOT DISABLE_COMPONENTS AND NOT STATIC_COMPONENTS) #define a library for each component and component stub define_component_libs() -elseif(AOT_COMPONENTS OR (NOT DISABLE_COMPONENTS AND STATIC_COMPONENTS)) +elseif(NOT DISABLE_COMPONENTS AND STATIC_COMPONENTS) #define a library for each component and component stub define_component_libs() # define a list of mono-components objects for mini if building a shared libmono with static-linked components set(mono-components-objects "") - foreach(component IN LISTS components_to_build) + foreach(component IN LISTS components) list(APPEND mono-components-objects $) endforeach() diff --git a/src/mono/mono/component/debugger-agent.c b/src/mono/mono/component/debugger-agent.c index 45404d5967b3f5..c6688845b1a2f5 100644 --- a/src/mono/mono/component/debugger-agent.c +++ b/src/mono/mono/component/debugger-agent.c @@ -5078,6 +5078,8 @@ buffer_add_value_full (Buffer *buf, MonoType *t, void *addr, MonoDomain *domain, /* This can happen with compiler generated locals */ //PRINT_MSG ("%s\n", mono_type_full_name (t)); buffer_add_byte (buf, VALUE_TYPE_ID_NULL); + if (CHECK_PROTOCOL_VERSION (2, 59)) + buffer_add_info_for_null_value (buf, t, domain); return; } g_assert (*(void**)addr); @@ -5231,6 +5233,8 @@ buffer_add_value_full (Buffer *buf, MonoType *t, void *addr, MonoDomain *domain, } else { /* The client can't handle PARENT_VTYPE */ buffer_add_byte (buf, VALUE_TYPE_ID_NULL); + if (CHECK_PROTOCOL_VERSION (2, 59)) + buffer_add_info_for_null_value (buf, t, domain); } break; } else { @@ -5613,6 +5617,11 @@ decode_value (MonoType *t, MonoDomain *domain, gpointer void_addr, gpointer void ErrorCode err; int type = decode_byte (buf, &buf, limit); + if (m_type_is_byref (t)) { + *(guint8**)addr = (guint8 *)g_malloc (sizeof (void*)); //when the object will be deleted it will delete this memory allocated here together? + addr = *(guint8**)addr; + } + if (t->type == MONO_TYPE_GENERICINST && mono_class_is_nullable (mono_class_from_mono_type_internal (t))) { MonoType *targ = t->data.generic_class->context.class_inst->type_argv [0]; guint8 *nullable_buf; @@ -6082,7 +6091,7 @@ mono_do_invoke_method (DebuggerTlsData *tls, Buffer *buf, InvokeData *invoke, gu } } else { if (!(m->flags & METHOD_ATTRIBUTE_STATIC) || (m->flags & METHOD_ATTRIBUTE_STATIC && !CHECK_PROTOCOL_VERSION (2, 59))) { //on icordbg I couldn't find an object when invoking a static method maybe I can change this later - err = decode_value(m_class_get_byval_arg(m->klass), domain, this_buf, p, &p, end, FALSE); + err = decode_value (m_class_get_byval_arg (m->klass), domain, this_buf, p, &p, end, FALSE); if (err != ERR_NONE) return err; } diff --git a/src/mono/mono/component/debugger-stub.c b/src/mono/mono/component/debugger-stub.c index 615383b2a00ea5..7304b55809d7ee 100644 --- a/src/mono/mono/component/debugger-stub.c +++ b/src/mono/mono/component/debugger-stub.c @@ -161,7 +161,8 @@ stub_debugger_end_exception_filter (MonoException *exc, MonoContext *ctx, MonoCo static void stub_debugger_user_break (void) { - G_BREAKPOINT (); + if (get_mini_debug_options ()->native_debugger_break) + G_BREAKPOINT (); } static void diff --git a/src/mono/mono/component/hot_reload-internals.h b/src/mono/mono/component/hot_reload-internals.h index a668eb295c4388..cc97538749d056 100644 --- a/src/mono/mono/component/hot_reload-internals.h +++ b/src/mono/mono/component/hot_reload-internals.h @@ -86,4 +86,9 @@ typedef struct _MonoClassMetadataUpdateEvent { uint32_t token; /* the Event table token where this event was defined. */ } MonoClassMetadataUpdateEvent; +typedef struct _MonoMethodMetadataUpdateParamInfo { + uint32_t first_param_token; /* a Param token */ + uint32_t param_count; +} MonoMethodMetadataUpdateParamInfo; + #endif/*_MONO_COMPONENT_HOT_RELOAD_INTERNALS_H*/ diff --git a/src/mono/mono/component/hot_reload-stub.c b/src/mono/mono/component/hot_reload-stub.c index e97e90499e3534..94730bbe26b164 100644 --- a/src/mono/mono/component/hot_reload-stub.c +++ b/src/mono/mono/component/hot_reload-stub.c @@ -110,6 +110,9 @@ hot_reload_get_num_methods_added (MonoClass *klass); static const char * hot_reload_get_capabilities (void); +static uint32_t +hot_reload_stub_get_method_params (MonoImage *base_image, uint32_t methoddef_token, uint32_t *out_param_count_opt); + static MonoComponentHotReload fn_table = { { MONO_COMPONENT_ITF_VERSION, &hot_reload_stub_available }, &hot_reload_stub_set_fastpath_data, @@ -142,7 +145,8 @@ static MonoComponentHotReload fn_table = { &hot_reload_stub_added_fields_iter, &hot_reload_get_num_fields_added, &hot_reload_get_num_methods_added, - &hot_reload_get_capabilities + &hot_reload_get_capabilities, + &hot_reload_stub_get_method_params, }; static bool @@ -343,6 +347,12 @@ hot_reload_get_capabilities (void) return ""; } +static uint32_t +hot_reload_stub_get_method_params (MonoImage *base_image, uint32_t methoddef_token, uint32_t *out_param_count_opt) +{ + return 0; +} + 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 09df9461f89f6e..c412aa3efe6662 100644 --- a/src/mono/mono/component/hot_reload.c +++ b/src/mono/mono/component/hot_reload.c @@ -144,8 +144,11 @@ hot_reload_get_num_methods_added (MonoClass *klass); static const char * hot_reload_get_capabilities (void); +static uint32_t +hot_reload_get_method_params (MonoImage *base_image, uint32_t methoddef_token, uint32_t *out_param_count_opt); + static MonoClassMetadataUpdateField * -metadata_update_field_setup_basic_info_and_resolve (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, DeltaInfo *delta_info, MonoClass *parent_klass, uint32_t fielddef_token, uint32_t field_flags, MonoError *error); +metadata_update_field_setup_basic_info (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, DeltaInfo *delta_info, MonoClass *parent_klass, uint32_t fielddef_token, uint32_t field_flags); static MonoComponentHotReload fn_table = { { MONO_COMPONENT_ITF_VERSION, &hot_reload_available }, @@ -179,7 +182,8 @@ static MonoComponentHotReload fn_table = { &hot_reload_added_fields_iter, &hot_reload_get_num_fields_added, &hot_reload_get_num_methods_added, - &hot_reload_get_capabilities + &hot_reload_get_capabilities, + &hot_reload_get_method_params, }; MonoComponentHotReload * @@ -272,6 +276,9 @@ struct _BaselineInfo { /* Parents for added methods, fields, etc */ GHashTable *member_parent; /* maps added methoddef or fielddef tokens to typedef tokens */ + /* Params for added methods */ + GHashTable *method_params; /* maps methoddef tokens to a MonoClassMetadataUpdateMethodParamInfo* */ + /* Skeletons for all newly-added types from every generation. Accessing the array requires the image lock. */ GArray *skeletons; }; @@ -412,6 +419,12 @@ baseline_info_destroy (BaselineInfo *info) if (info->skeletons) g_array_free (info->skeletons, TRUE); + if (info->member_parent) + g_hash_table_destroy (info->member_parent); + + if (info->method_params) + g_hash_table_destroy (info->method_params); + g_free (info); } @@ -627,6 +640,11 @@ add_method_to_baseline (BaselineInfo *base_info, DeltaInfo *delta_info, MonoClas static void add_field_to_baseline (BaselineInfo *base_info, DeltaInfo *delta_info, MonoClass *klass, uint32_t field_token); +/* Add a method->params lookup for new methods in existing classes */ +static void +add_param_info_for_method (BaselineInfo *base_info, uint32_t param_token, uint32_t method_token); + + void hot_reload_init (void) { @@ -2019,6 +2037,7 @@ apply_enclog_pass2 (Pass2Context *ctx, MonoImage *image_base, BaselineInfo *base uint32_t add_member_typedef = 0; uint32_t add_property_propertymap = 0; uint32_t add_event_eventmap = 0; + uint32_t add_field_method = 0; gboolean assemblyref_updated = FALSE; for (guint32 i = 0; i < rows ; ++i) { @@ -2049,6 +2068,7 @@ apply_enclog_pass2 (Pass2Context *ctx, MonoImage *image_base, BaselineInfo *base case ENC_FUNC_ADD_PARAM: { g_assert (token_table == MONO_TABLE_METHOD); + add_field_method = log_token; break; } case ENC_FUNC_ADD_FIELD: { @@ -2115,8 +2135,18 @@ apply_enclog_pass2 (Pass2Context *ctx, MonoImage *image_base, BaselineInfo *base } case MONO_TABLE_METHOD: { /* if adding a param, handle it with the next record */ - if (func_code == ENC_FUNC_ADD_PARAM) + if (func_code == ENC_FUNC_ADD_PARAM) { + g_assert (is_addition); break; + } + g_assert (func_code == ENC_FUNC_DEFAULT); + + if (!base_info->method_table_update) + 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); if (is_addition) { g_assertf (add_member_typedef, "EnC: new method added but I don't know the class, should be caught by pass1"); @@ -2139,14 +2169,6 @@ apply_enclog_pass2 (Pass2Context *ctx, MonoImage *image_base, BaselineInfo *base add_member_typedef = 0; } - if (!base_info->method_table_update) - 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, delta_info, mono_metadata_make_token (token_table, token_index)); guint32 rva = mono_metadata_decode_row_col (&image_dmeta->tables [MONO_TABLE_METHOD], mapped_token - 1, MONO_METHOD_RVA); if (rva < dil_length) { @@ -2190,11 +2212,11 @@ apply_enclog_pass2 (Pass2Context *ctx, MonoImage *image_base, BaselineInfo *base add_field_to_baseline (base_info, delta_info, add_member_klass, log_token); - /* This actually does more than mono_class_setup_basic_field_info and - * resolves MonoClassField:type and sets MonoClassField:offset to -1 to make - * it easier to spot that the field is special. + /* This actually does slightly more than + * mono_class_setup_basic_field_info and sets MonoClassField:offset + * to -1 to make it easier to spot that the field is special. */ - metadata_update_field_setup_basic_info_and_resolve (image_base, base_info, generation, delta_info, add_member_klass, log_token, field_flags, error); + metadata_update_field_setup_basic_info (image_base, base_info, generation, delta_info, add_member_klass, log_token, field_flags); if (!is_ok (error)) { mono_trace (G_LOG_LEVEL_WARNING, MONO_TRACE_METADATA_UPDATE, "Could not setup field (token 0x%08x) due to: %s", log_token, mono_error_get_message (error)); return FALSE; @@ -2367,9 +2389,21 @@ apply_enclog_pass2 (Pass2Context *ctx, MonoImage *image_base, BaselineInfo *base * * So by the time we see the param additions, the methods are already in. * - * FIXME: we need a lookaside table (like member_parent) for every place - * that looks at MONO_METHOD_PARAMLIST */ + if (is_addition) { + g_assert (add_field_method != 0); + uint32_t parent_type_token = hot_reload_method_parent (image_base, add_field_method); + g_assert (parent_type_token != 0); // we added a parameter to a method that was added + if (pass2_context_is_skeleton (ctx, parent_type_token)) { + // it's a parameter on a new method in a brand new class + // FIXME: need to do something here? + } else { + // it's a parameter on a new method in an existing class + add_param_info_for_method (base_info, log_token, add_field_method); + } + add_field_method = 0; + break; + } break; } case MONO_TABLE_INTERFACEIMPL: { @@ -2820,6 +2854,28 @@ hot_reload_field_parent (MonoImage *base_image, uint32_t field_token) } +static void +add_param_info_for_method (BaselineInfo *base_info, uint32_t param_token, uint32_t method_token) +{ + if (!base_info->method_params) { + base_info->method_params = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); + } + MonoMethodMetadataUpdateParamInfo* info = NULL; + info = g_hash_table_lookup (base_info->method_params, GUINT_TO_POINTER (method_token)); + if (!info) { + // FIXME locking + info = g_new0 (MonoMethodMetadataUpdateParamInfo, 1); + g_hash_table_insert (base_info->method_params, GUINT_TO_POINTER (method_token), info); + info->first_param_token = param_token; + info->param_count = 1; + } else { + uint32_t param_index = mono_metadata_token_index (param_token); + // expect params for a single method to be a contiguous sequence of rows + g_assert (mono_metadata_token_index (info->first_param_token) + info->param_count == param_index); + info->param_count++; + } +} + /* HACK - keep in sync with locator_t in metadata/metadata.c */ typedef struct { guint32 idx; /* The index that we are trying to locate */ @@ -2885,7 +2941,7 @@ hot_reload_get_field (MonoClass *klass, uint32_t fielddef_token) { static MonoClassMetadataUpdateField * -metadata_update_field_setup_basic_info_and_resolve (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, DeltaInfo *delta_info, MonoClass *parent_klass, uint32_t fielddef_token, uint32_t field_flags, MonoError *error) +metadata_update_field_setup_basic_info (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, DeltaInfo *delta_info, MonoClass *parent_klass, uint32_t fielddef_token, uint32_t field_flags) { if (!m_class_is_inited (parent_klass)) mono_class_init_internal (parent_klass); @@ -2904,9 +2960,13 @@ metadata_update_field_setup_basic_info_and_resolve (MonoImage *image_base, Basel uint32_t name_idx = mono_metadata_decode_table_row_col (image_base, MONO_TABLE_FIELD, mono_metadata_token_index (fielddef_token) - 1, MONO_FIELD_NAME); field->field.name = mono_metadata_string_heap (image_base, name_idx); - mono_field_resolve_type (&field->field, error); - if (!is_ok (error)) - return NULL; + /* It's important not to try and resolve field->type at this point. If the field's type is a + * newly-added struct, we don't want to resolve it early here if we're going to add fields + * and methods to it. It seems that for nested structs, the field additions come after the + * field addition to the enclosing struct. So if the enclosing struct has a field of the + * nested type, resolving the field type here will make it look like the nested struct has + * no fields. + */ parent_info->added_fields = g_slist_prepend_mem_manager (m_class_get_mem_manager (parent_klass), parent_info->added_fields, field); @@ -3145,6 +3205,32 @@ hot_reload_get_num_methods_added (MonoClass *klass) return count; } +static uint32_t +hot_reload_get_method_params (MonoImage *base_image, uint32_t methoddef_token, uint32_t *out_param_count_opt) +{ + BaselineInfo *base_info = baseline_info_lookup (base_image); + g_assert (base_info); + + /* FIXME: locking in case the hash table grows */ + + if (!base_info->method_params) + return 0; + + MonoMethodMetadataUpdateParamInfo* info = NULL; + info = g_hash_table_lookup (base_info->method_params, GUINT_TO_POINTER (methoddef_token)); + if (!info) { + if (out_param_count_opt) + *out_param_count_opt = 0; + return 0; + } + + if (out_param_count_opt) + *out_param_count_opt = info->param_count; + + return mono_metadata_token_index (info->first_param_token); +} + + static const char * hot_reload_get_capabilities (void) { diff --git a/src/mono/mono/component/hot_reload.h b/src/mono/mono/component/hot_reload.h index f7627245583af5..40c6a0f263f417 100644 --- a/src/mono/mono/component/hot_reload.h +++ b/src/mono/mono/component/hot_reload.h @@ -48,6 +48,7 @@ typedef struct _MonoComponentHotReload { uint32_t (*get_num_fields_added) (MonoClass *klass); uint32_t (*get_num_methods_added) (MonoClass *klass); const char* (*get_capabilities) (void); + uint32_t (*get_method_params) (MonoImage *base_image, uint32_t methoddef_token, uint32_t *out_param_count_opt); } MonoComponentHotReload; MONO_COMPONENT_EXPORT_ENTRYPOINT diff --git a/src/mono/mono/component/marshal-ilgen-noilgen.c b/src/mono/mono/component/marshal-ilgen-noilgen.c deleted file mode 100644 index 6cf9dfd5ac628a..00000000000000 --- a/src/mono/mono/component/marshal-ilgen-noilgen.c +++ /dev/null @@ -1,186 +0,0 @@ -#include "mono/component/marshal-ilgen.h" -#include "mono/component/marshal-ilgen-noilgen.h" - -#ifndef ENABLE_ILGEN -static int -emit_marshal_array_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoType *int_type = mono_get_int_type (); - MonoType *object_type = mono_get_object_type (); - switch (action) { - case MARSHAL_ACTION_CONV_IN: - *conv_arg_type = object_type; - break; - case MARSHAL_ACTION_MANAGED_CONV_IN: - *conv_arg_type = int_type; - break; - } - return conv_arg; -} - -static int -emit_marshal_ptr_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - return conv_arg; -} -#endif - -#if !defined(ENABLE_ILGEN) || defined(DISABLE_NONBLITTABLE) -static int -emit_marshal_vtype_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - return conv_arg; -} - -static int -emit_marshal_string_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoType *int_type = mono_get_int_type (); - switch (action) { - case MARSHAL_ACTION_CONV_IN: - *conv_arg_type = int_type; - break; - case MARSHAL_ACTION_MANAGED_CONV_IN: - *conv_arg_type = int_type; - break; - } - return conv_arg; -} - -static int -emit_marshal_safehandle_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - MonoType *int_type = mono_get_int_type (); - if (action == MARSHAL_ACTION_CONV_IN) - *conv_arg_type = int_type; - return conv_arg; -} - -static int -emit_marshal_handleref_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - MonoType *int_type = mono_get_int_type (); - if (action == MARSHAL_ACTION_CONV_IN) - *conv_arg_type = int_type; - return conv_arg; -} - -static int -emit_marshal_object_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoType *int_type = mono_get_int_type (); - if (action == MARSHAL_ACTION_CONV_IN) - *conv_arg_type = int_type; - return conv_arg; -} - -static int -emit_marshal_variant_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - g_assert_not_reached (); -} - -static int -emit_marshal_asany_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - return conv_arg; -} - -static int -emit_marshal_boolean_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoType *int_type = mono_get_int_type (); - switch (action) { - case MARSHAL_ACTION_CONV_IN: - if (m_type_is_byref (t)) - *conv_arg_type = int_type; - else - *conv_arg_type = mono_marshal_boolean_conv_in_get_local_type (spec, NULL); - break; - - case MARSHAL_ACTION_MANAGED_CONV_IN: { - MonoClass* conv_arg_class = mono_marshal_boolean_managed_conv_in_get_conv_arg_class (spec, NULL); - if (m_type_is_byref (t)) - *conv_arg_type = m_class_get_this_arg (conv_arg_class); - else - *conv_arg_type = m_class_get_byval_arg (conv_arg_class); - break; - } - - } - return conv_arg; -} - -static int -emit_marshal_char_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - return conv_arg; -} - -static int -emit_marshal_custom_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoType *int_type = mono_get_int_type (); - if (action == MARSHAL_ACTION_CONV_IN && t->type == MONO_TYPE_VALUETYPE) - *conv_arg_type = int_type; - return conv_arg; -} -#endif - -#ifndef ENABLE_ILGEN - -void -mono_marshal_noilgen_init_heavyweight (void) -{ - MonoMarshalILgenCallbacks ilgen_cb; - - ilgen_cb.version = MONO_MARSHAL_CALLBACKS_VERSION; - ilgen_cb.emit_marshal_array = emit_marshal_array_noilgen; - ilgen_cb.emit_marshal_vtype = emit_marshal_vtype_noilgen; - ilgen_cb.emit_marshal_string = emit_marshal_string_noilgen; - ilgen_cb.emit_marshal_safehandle = emit_marshal_safehandle_noilgen; - ilgen_cb.emit_marshal_handleref = emit_marshal_handleref_noilgen; - ilgen_cb.emit_marshal_object = emit_marshal_object_noilgen; - ilgen_cb.emit_marshal_variant = emit_marshal_variant_noilgen; - ilgen_cb.emit_marshal_asany = emit_marshal_asany_noilgen; - ilgen_cb.emit_marshal_boolean = emit_marshal_boolean_noilgen; - ilgen_cb.emit_marshal_custom = emit_marshal_custom_noilgen; - ilgen_cb.emit_marshal_ptr = emit_marshal_ptr_noilgen; - - ilgen_cb.emit_marshal_char = emit_marshal_char_noilgen; - mono_install_marshal_callbacks_ilgen(&ilgen_cb); -} - -#endif \ No newline at end of file diff --git a/src/mono/mono/component/marshal-ilgen-noilgen.h b/src/mono/mono/component/marshal-ilgen-noilgen.h deleted file mode 100644 index 5e877c223833c7..00000000000000 --- a/src/mono/mono/component/marshal-ilgen-noilgen.h +++ /dev/null @@ -1,11 +0,0 @@ -/** - * \file - * Copyright 2022 Microsoft - * Licensed under the MIT license. See LICENSE file in the project root for full license information. - */ -#ifndef __MARSHAL_ILGEN_NOILGEN_H__ -#define __MARSHAL_ILGEN_NOILGEN_H__ - -void mono_marshal_noilgen_init_heavyweight (void); - -#endif // __MARSHAL_ILGEN_NOILGEN_H__ \ No newline at end of file diff --git a/src/mono/mono/component/marshal-ilgen-stub.c b/src/mono/mono/component/marshal-ilgen-stub.c deleted file mode 100644 index 8182c95b7e286d..00000000000000 --- a/src/mono/mono/component/marshal-ilgen-stub.c +++ /dev/null @@ -1,41 +0,0 @@ - -#include -#include -#include - -static bool -marshal_ilgen_available (void) -{ - return false; -} - -static int -stub_emit_marshal_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action, MonoMarshalLightweightCallbacks* lightweigth_cb) -{ - return 0; -} - -static void -mono_component_marshal_ilgen_stub_init(void) -{ -} - -static void -stub_mono_marshal_ilgen_install_callbacks_mono (IlgenCallbacksToMono *callbacks) -{ -} - -static MonoComponentMarshalILgen component_func_table = { - { MONO_COMPONENT_ITF_VERSION, &marshal_ilgen_available }, - mono_component_marshal_ilgen_stub_init, - stub_emit_marshal_ilgen, - stub_mono_marshal_ilgen_install_callbacks_mono -}; - -MonoComponentMarshalILgen* -mono_component_marshal_ilgen_init (void) -{ - return &component_func_table; -} diff --git a/src/mono/mono/component/marshal-ilgen.c b/src/mono/mono/component/marshal-ilgen.c deleted file mode 100644 index e6deeea5564097..00000000000000 --- a/src/mono/mono/component/marshal-ilgen.c +++ /dev/null @@ -1,2861 +0,0 @@ - -#include "mono/metadata/debug-helpers.h" -#include "metadata/marshal.h" -#include "component/marshal-ilgen.h" -#include "mono/component/marshal-ilgen.h" -#include "mono/component/marshal-ilgen-noilgen.h" -#include "metadata/marshal-lightweight.h" -#include "metadata/marshal-shared.h" -#include "metadata/method-builder-ilgen.h" -#include "metadata/custom-attrs-internals.h" -#include "metadata/class-init.h" -#include "mono/metadata/class-internals.h" -#include "metadata/reflection-internals.h" -#include "mono/metadata/handle.h" -#include "mono/component/component.h" - -#define OPDEF(a,b,c,d,e,f,g,h,i,j) \ - a = i, - -enum { -#include "mono/cil/opcode.def" - LAST = 0xff -}; -#undef OPDEF - -#define mono_mb_emit_jit_icall(mb, name) (cb_to_mono->mb_emit_icall_id ((mb), MONO_JIT_ICALL_ ## name)) - -static GENERATE_GET_CLASS_WITH_CACHE (date_time, "System", "DateTime"); -static GENERATE_TRY_GET_CLASS_WITH_CACHE (icustom_marshaler, "System.Runtime.InteropServices", "ICustomMarshaler"); - -static void emit_string_free_icall (MonoMethodBuilder *mb, MonoMarshalConv conv); - -static void mono_marshal_ilgen_legacy_init (void); - -static gboolean ilgen_cb_inited = FALSE; -static MonoMarshalILgenCallbacks ilgen_marshal_cb; - -static IlgenCallbacksToMono *cb_to_mono; - -static bool -marshal_ilgen_available (void) -{ - return true; -} - -static MonoComponentMarshalILgen component_func_table = { - { MONO_COMPONENT_ITF_VERSION, &marshal_ilgen_available }, - &mono_marshal_ilgen_init, - &mono_emit_marshal_ilgen, - &mono_marshal_ilgen_install_callbacks_mono -}; - - -MonoComponentMarshalILgen* -mono_component_marshal_ilgen_init (void) -{ - return &component_func_table; -} - -void -mono_install_marshal_callbacks_ilgen (MonoMarshalILgenCallbacks *cb) -{ - g_assert (!ilgen_cb_inited); - g_assert (cb->version == MONO_MARSHAL_CALLBACKS_VERSION); - memcpy (&ilgen_marshal_cb, cb, sizeof (MonoMarshalILgenCallbacks)); - ilgen_cb_inited = TRUE; -} - -void -mono_marshal_ilgen_install_callbacks_mono (IlgenCallbacksToMono *callbacks) -{ - cb_to_mono = callbacks; -} - -static void -emit_struct_free (MonoMethodBuilder *mb, MonoClass *klass, int struct_var) -{ - /* Call DestroyStructure */ - /* FIXME: Only do this if needed */ - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); - cb_to_mono->mb_emit_ldloc (mb, struct_var); - mono_mb_emit_jit_icall (mb, mono_struct_delete_old); -} - -static int -emit_marshal_array_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - MonoClass *klass = mono_class_from_mono_type_internal (t); - MonoMarshalNative encoding; - - encoding = cb_to_mono->get_string_encoding (m->piinfo, spec); - MonoType *int_type = cb_to_mono->get_int_type (); - MonoType *object_type = cb_to_mono->get_object_type (); - - MonoClass *eklass = m_class_get_element_class (klass); - - switch (action) { - case MARSHAL_ACTION_CONV_IN: - *conv_arg_type = object_type; - conv_arg = cb_to_mono->mb_add_local (mb, object_type); - - if (m_class_is_blittable (eklass)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_CONV_ARRAY_LPARRAY, NULL)); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } else { -#ifdef DISABLE_NONBLITTABLE - char *msg = g_strdup ("Non-blittable marshalling conversion is disabled"); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); -#else - guint32 label1, label2, label3; - int index_var, src_var, dest_ptr, esize; - MonoMarshalConv conv; - gboolean is_string = FALSE; - - dest_ptr = cb_to_mono->mb_add_local (mb, int_type); - - if (eklass == cb_to_mono->mono_defaults->string_class) { - is_string = TRUE; - conv = cb_to_mono->get_string_to_ptr_conv (m->piinfo, spec); - } - else if (eklass == cb_to_mono->try_get_stringbuilder_class ()) { - is_string = TRUE; - conv = cb_to_mono->get_stringbuilder_to_ptr_conv (m->piinfo, spec); - } - else - conv = MONO_MARSHAL_CONV_INVALID; - - if (is_string && conv == MONO_MARSHAL_CONV_INVALID) { - char *msg = g_strdup_printf ("string/stringbuilder marshalling conversion %d not implemented", encoding); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - src_var = cb_to_mono->mb_add_local (mb, object_type); - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_stloc (mb, src_var); - - /* Check null */ - cb_to_mono->mb_emit_ldloc (mb, src_var); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - cb_to_mono->mb_emit_ldloc (mb, src_var); - label1 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - if (is_string) - esize = TARGET_SIZEOF_VOID_P; - else if (eklass == cb_to_mono->mono_defaults->char_class) /*can't call mono_marshal_type_size since it causes all sorts of asserts*/ - esize = cb_to_mono->pinvoke_is_unicode (m->piinfo) ? 2 : 1; - else - esize = cb_to_mono->class_native_size (eklass, NULL); - - /* allocate space for the native struct and store the address */ - cb_to_mono->mb_emit_icon (mb, esize); - cb_to_mono->mb_emit_ldloc (mb, src_var); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - - if (eklass == cb_to_mono->mono_defaults->string_class) { - /* Make the array bigger for the terminating null */ - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_1); - cb_to_mono->mb_emit_byte (mb, CEE_ADD); - } - cb_to_mono->mb_emit_byte (mb, CEE_MUL); - cb_to_mono->mb_emit_byte (mb, CEE_PREFIX1); - cb_to_mono->mb_emit_byte (mb, CEE_LOCALLOC); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, dest_ptr); - - /* Emit marshalling loop */ - index_var = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_stloc (mb, index_var); - label2 = cb_to_mono->mb_get_label (mb); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_ldloc (mb, src_var); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - label3 = cb_to_mono->mb_emit_branch (mb, CEE_BGE); - - /* Emit marshalling code */ - - if (is_string) { - int stind_op; - cb_to_mono->mb_emit_ldloc (mb, dest_ptr); - cb_to_mono->mb_emit_ldloc (mb, src_var); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_byte (mb, CEE_LDELEM_REF); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, &stind_op)); - cb_to_mono->mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); - } else { - /* set the src_ptr */ - cb_to_mono->mb_emit_ldloc (mb, src_var); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_op (mb, CEE_LDELEMA, eklass); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* set dst_ptr */ - cb_to_mono->mb_emit_ldloc (mb, dest_ptr); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv_full (mb, eklass, FALSE, 0, eklass == cb_to_mono->mono_defaults->char_class ? encoding : (MonoMarshalNative)-1); - } - - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (dest_ptr), esize); - - cb_to_mono->mb_emit_branch_label (mb, CEE_BR, label2); - - cb_to_mono->mb_patch_branch (mb, label3); - - if (eklass == cb_to_mono->mono_defaults->string_class) { - /* Null terminate */ - cb_to_mono->mb_emit_ldloc (mb, dest_ptr); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_I); - } - - cb_to_mono->mb_patch_branch (mb, label1); -#endif - } - - break; - - case MARSHAL_ACTION_CONV_OUT: { -#ifndef DISABLE_NONBLITTABLE - gboolean need_convert, need_free; - /* Unicode character arrays are implicitly marshalled as [Out] under MS.NET */ - need_convert = ((eklass == cb_to_mono->mono_defaults->char_class) && (encoding == MONO_NATIVE_LPWSTR)) || (eklass == cb_to_mono->try_get_stringbuilder_class ()) || (t->attrs & PARAM_ATTRIBUTE_OUT); - need_free = cb_to_mono->need_free (m_class_get_byval_arg (eklass), m->piinfo, spec); - - if ((t->attrs & PARAM_ATTRIBUTE_OUT) && spec && spec->native == MONO_NATIVE_LPARRAY && spec->data.array_data.param_num != -1) { - int param_num = spec->data.array_data.param_num; - MonoType *param_type; - - param_type = m->sig->params [param_num]; - - if (m_type_is_byref (param_type) && param_type->type != MONO_TYPE_I4) { - char *msg = g_strdup ("Not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - if (m_type_is_byref (t) ) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - - /* Create the managed array */ - cb_to_mono->mb_emit_ldarg (mb, param_num); - if (m_type_is_byref (m->sig->params [param_num])) - // FIXME: Support other types - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I4); - cb_to_mono->mb_emit_byte (mb, CEE_CONV_OVF_I); - cb_to_mono->mb_emit_op (mb, CEE_NEWARR, eklass); - /* Store into argument */ - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } - } - - if (need_convert || need_free) { - /* FIXME: Optimize blittable case */ - guint32 label1, label2, label3; - int index_var, src_ptr, loc, esize; - - if ((eklass == cb_to_mono->try_get_stringbuilder_class ()) || (eklass == cb_to_mono->mono_defaults->string_class)) - esize = TARGET_SIZEOF_VOID_P; - else if (eklass == cb_to_mono->mono_defaults->char_class) - esize = cb_to_mono->pinvoke_is_unicode (m->piinfo) ? 2 : 1; - else - esize = cb_to_mono->class_native_size (eklass, NULL); - src_ptr = cb_to_mono->mb_add_local (mb, int_type); - loc = cb_to_mono->mb_add_local (mb, int_type); - - /* Check null */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - label1 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, src_ptr); - - /* Emit marshalling loop */ - index_var = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_stloc (mb, index_var); - label2 = cb_to_mono->mb_get_label (mb); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - label3 = cb_to_mono->mb_emit_branch (mb, CEE_BGE); - - /* Emit marshalling code */ - - if (eklass == cb_to_mono->try_get_stringbuilder_class ()) { - gboolean need_free2; - MonoMarshalConv conv = cb_to_mono->get_ptr_to_stringbuilder_conv (m->piinfo, spec, &need_free2); - - g_assert (conv != MONO_MARSHAL_CONV_INVALID); - - /* dest */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_byte (mb, CEE_LDELEM_REF); - - /* src */ - cb_to_mono->mb_emit_ldloc (mb, src_ptr); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - - if (need_free) { - /* src */ - cb_to_mono->mb_emit_ldloc (mb, src_ptr); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - mono_mb_emit_jit_icall (mb, mono_marshal_free); - } - } - else if (eklass == cb_to_mono->mono_defaults->string_class) { - if (need_free) { - /* src */ - cb_to_mono->mb_emit_ldloc (mb, src_ptr); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - mono_mb_emit_jit_icall (mb, mono_marshal_free); - } - } - else { - if (need_convert) { - /* set the src_ptr */ - cb_to_mono->mb_emit_ldloc (mb, src_ptr); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* set dst_ptr */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_op (mb, CEE_LDELEMA, eklass); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv_full (mb, eklass, TRUE, 0, eklass == cb_to_mono->mono_defaults->char_class ? encoding : (MonoMarshalNative)-1); - } - - if (need_free) { - cb_to_mono->mb_emit_ldloc (mb, src_ptr); - cb_to_mono->mb_emit_stloc (mb, loc); - - emit_struct_free (mb, eklass, loc); - } - } - - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (src_ptr), esize); - - cb_to_mono->mb_emit_branch_label (mb, CEE_BR, label2); - - cb_to_mono->mb_patch_branch (mb, label1); - cb_to_mono->mb_patch_branch (mb, label3); - } -#endif - - if (m_class_is_blittable (eklass)) { - /* free memory allocated (if any) by MONO_MARSHAL_CONV_ARRAY_LPARRAY */ - - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_FREE_LPARRAY, NULL)); - } - - break; - } - - case MARSHAL_ACTION_PUSH: - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_RESULT: { - cb_to_mono->mb_emit_byte (mb, CEE_POP); - char *msg = g_strdup_printf ("Cannot marshal 'return value': Invalid managed/unmanaged type combination."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - case MARSHAL_ACTION_MANAGED_CONV_IN: { - guint32 label1, label2, label3; - int index_var, src_ptr, esize, param_num, num_elem; - MonoMarshalConv conv; - gboolean is_string = FALSE; - - conv_arg = cb_to_mono->mb_add_local (mb, object_type); - *conv_arg_type = int_type; - - if (m_type_is_byref (t)) { - char *msg = g_strdup ("Byref array marshalling to managed code is not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - if (!spec) { - char *msg = g_strdup ("[MarshalAs] attribute required to marshal arrays to managed code."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - - switch (spec->native) { - case MONO_NATIVE_LPARRAY: - break; - case MONO_NATIVE_SAFEARRAY: -#ifndef DISABLE_COM - if (spec->data.safearray_data.elem_type != MONO_VARIANT_VARIANT) { - char *msg = g_strdup ("Only SAFEARRAY(VARIANT) marshalling to managed code is implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - return mono_cominterop_emit_marshal_safearray (m, argnum, t, spec, conv_arg, conv_arg_type, action); -#endif - default: { - char *msg = g_strdup ("Unsupported array type marshalling to managed code."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - } - - /* FIXME: t is from the method which is wrapped, not the delegate type */ - /* g_assert (t->attrs & PARAM_ATTRIBUTE_IN); */ - - param_num = spec->data.array_data.param_num; - num_elem = spec->data.array_data.num_elem; - if (spec->data.array_data.elem_mult == 0) - /* param_num is not specified */ - param_num = -1; - - if (param_num == -1) { - if (num_elem <= 0) { - char *msg = g_strdup ("Either SizeConst or SizeParamIndex should be specified when marshalling arrays to managed code."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - } - - /* FIXME: Optimize blittable case */ - -#ifndef DISABLE_NONBLITTABLE - if (eklass == cb_to_mono->mono_defaults->string_class) { - is_string = TRUE; - gboolean need_free; - conv = cb_to_mono->get_ptr_to_string_conv (m->piinfo, spec, &need_free); - } - else if (eklass == cb_to_mono->try_get_stringbuilder_class ()) { - is_string = TRUE; - gboolean need_free; - conv = cb_to_mono->get_ptr_to_stringbuilder_conv (m->piinfo, spec, &need_free); - } - else - conv = MONO_MARSHAL_CONV_INVALID; -#endif - - cb_to_mono->load_type_info (eklass); - - if (is_string) - esize = TARGET_SIZEOF_VOID_P; - else - esize = cb_to_mono->class_native_size (eklass, NULL); - src_ptr = cb_to_mono->mb_add_local (mb, int_type); - - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - /* Check param index */ - if (param_num != -1) { - if (param_num >= m->sig->param_count) { - char *msg = g_strdup ("Array size control parameter index is out of range."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - switch (m->sig->params [param_num]->type) { - case MONO_TYPE_I1: - case MONO_TYPE_U1: - case MONO_TYPE_I2: - case MONO_TYPE_U2: - case MONO_TYPE_I4: - case MONO_TYPE_U4: - case MONO_TYPE_I: - case MONO_TYPE_U: - case MONO_TYPE_I8: - case MONO_TYPE_U8: - break; - default: { - char *msg = g_strdup ("Array size control parameter must be an integral type."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - } - } - - /* Check null */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - label1 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_stloc (mb, src_ptr); - - /* Create managed array */ - /* - * The LPArray marshalling spec says that sometimes param_num starts - * from 1, sometimes it starts from 0. But MS seems to allways start - * from 0. - */ - - if (param_num == -1) { - cb_to_mono->mb_emit_icon (mb, num_elem); - } else { - cb_to_mono->mb_emit_ldarg (mb, param_num); - if (num_elem > 0) { - cb_to_mono->mb_emit_icon (mb, num_elem); - cb_to_mono->mb_emit_byte (mb, CEE_ADD); - } - cb_to_mono->mb_emit_byte (mb, CEE_CONV_OVF_I); - } - - cb_to_mono->mb_emit_op (mb, CEE_NEWARR, eklass); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - if (m_class_is_blittable (eklass)) { - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_byte (mb, CEE_CONV_I); - cb_to_mono->mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoArray, vector)); - cb_to_mono->mb_emit_byte (mb, CEE_ADD); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - cb_to_mono->mb_emit_icon (mb, esize); - cb_to_mono->mb_emit_byte (mb, CEE_MUL); - cb_to_mono->mb_emit_byte (mb, CEE_PREFIX1); - cb_to_mono->mb_emit_byte (mb, CEE_CPBLK); - cb_to_mono->mb_patch_branch (mb, label1); - break; - } -#ifdef DISABLE_NONBLITTABLE - else { - char *msg = g_strdup ("Non-blittable marshalling conversion is disabled"); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - } -#else - /* Emit marshalling loop */ - index_var = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_stloc (mb, index_var); - label2 = cb_to_mono->mb_get_label (mb); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - label3 = cb_to_mono->mb_emit_branch (mb, CEE_BGE); - - /* Emit marshalling code */ - if (is_string) { - g_assert (conv != MONO_MARSHAL_CONV_INVALID); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_ldloc (mb, index_var); - - cb_to_mono->mb_emit_ldloc (mb, src_ptr); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - cb_to_mono->mb_emit_byte (mb, CEE_STELEM_REF); - } - else { - char *msg = g_strdup ("Marshalling of non-string and non-blittable arrays to managed code is not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (src_ptr), esize); - - cb_to_mono->mb_emit_branch_label (mb, CEE_BR, label2); - - cb_to_mono->mb_patch_branch (mb, label1); - cb_to_mono->mb_patch_branch (mb, label3); -#endif - - break; - } - case MARSHAL_ACTION_MANAGED_CONV_OUT: { - guint32 label1, label2, label3; - int index_var, dest_ptr, esize, param_num, num_elem; - MonoMarshalConv conv; - gboolean is_string = FALSE; - - if (!spec) - /* Already handled in CONV_IN */ - break; - - /* These are already checked in CONV_IN */ - g_assert (!m_type_is_byref (t)); - g_assert (spec->native == MONO_NATIVE_LPARRAY); - g_assert (t->attrs & PARAM_ATTRIBUTE_OUT); - - param_num = spec->data.array_data.param_num; - num_elem = spec->data.array_data.num_elem; - - if (spec->data.array_data.elem_mult == 0) - /* param_num is not specified */ - param_num = -1; - - if (param_num == -1) { - if (num_elem <= 0) { - g_assert_not_reached (); - } - } - - /* FIXME: Optimize blittable case */ - -#ifndef DISABLE_NONBLITTABLE - if (eklass == cb_to_mono->mono_defaults->string_class) { - is_string = TRUE; - conv = cb_to_mono->get_string_to_ptr_conv (m->piinfo, spec); - } - else if (eklass == cb_to_mono->try_get_stringbuilder_class ()) { - is_string = TRUE; - conv = cb_to_mono->get_stringbuilder_to_ptr_conv (m->piinfo, spec); - } - else - conv = MONO_MARSHAL_CONV_INVALID; -#endif - - cb_to_mono->load_type_info (eklass); - - if (is_string) - esize = TARGET_SIZEOF_VOID_P; - else - esize = cb_to_mono->class_native_size (eklass, NULL); - - dest_ptr = cb_to_mono->mb_add_local (mb, int_type); - - /* Check null */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - label1 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_stloc (mb, dest_ptr); - - if (m_class_is_blittable (eklass)) { - /* dest */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - /* src */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_byte (mb, CEE_CONV_I); - cb_to_mono->mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoArray, vector)); - cb_to_mono->mb_emit_byte (mb, CEE_ADD); - /* length */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - cb_to_mono->mb_emit_icon (mb, esize); - cb_to_mono->mb_emit_byte (mb, CEE_MUL); - cb_to_mono->mb_emit_byte (mb, CEE_PREFIX1); - cb_to_mono->mb_emit_byte (mb, CEE_CPBLK); - cb_to_mono->mb_patch_branch (mb, label1); - break; - } - -#ifndef DISABLE_NONBLITTABLE - /* Emit marshalling loop */ - index_var = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_stloc (mb, index_var); - label2 = cb_to_mono->mb_get_label (mb); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - label3 = cb_to_mono->mb_emit_branch (mb, CEE_BGE); - - /* Emit marshalling code */ - if (is_string) { - int stind_op; - g_assert (conv != MONO_MARSHAL_CONV_INVALID); - - /* dest */ - cb_to_mono->mb_emit_ldloc (mb, dest_ptr); - - /* src */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_ldloc (mb, index_var); - - cb_to_mono->mb_emit_byte (mb, CEE_LDELEM_REF); - - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, &stind_op)); - cb_to_mono->mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); - } - else { - char *msg = g_strdup ("Marshalling of non-string and non-blittable arrays to managed code is not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (dest_ptr), esize); - - cb_to_mono->mb_emit_branch_label (mb, CEE_BR, label2); - - cb_to_mono->mb_patch_branch (mb, label1); - cb_to_mono->mb_patch_branch (mb, label3); -#endif - - break; - } - case MARSHAL_ACTION_MANAGED_CONV_RESULT: { -#ifndef DISABLE_NONBLITTABLE - guint32 label1, label2, label3; - int index_var, src, dest, esize; - MonoMarshalConv conv = MONO_MARSHAL_CONV_INVALID; - gboolean is_string = FALSE; - - g_assert (!m_type_is_byref (t)); - - cb_to_mono->load_type_info (eklass); - - if (eklass == cb_to_mono->mono_defaults->string_class) { - is_string = TRUE; - conv = cb_to_mono->get_string_to_ptr_conv (m->piinfo, spec); - } - else { - g_assert_not_reached (); - } - - if (is_string) - esize = TARGET_SIZEOF_VOID_P; - else if (eklass == cb_to_mono->mono_defaults->char_class) - esize = cb_to_mono->pinvoke_is_unicode (m->piinfo) ? 2 : 1; - else - esize = cb_to_mono->class_native_size (eklass, NULL); - - src = cb_to_mono->mb_add_local (mb, object_type); - dest = cb_to_mono->mb_add_local (mb, int_type); - - cb_to_mono->mb_emit_stloc (mb, src); - cb_to_mono->mb_emit_ldloc (mb, src); - cb_to_mono->mb_emit_stloc (mb, 3); - - /* Check for null */ - cb_to_mono->mb_emit_ldloc (mb, src); - label1 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - /* Allocate native array */ - cb_to_mono->mb_emit_icon (mb, esize); - cb_to_mono->mb_emit_ldloc (mb, src); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - - if (eklass == cb_to_mono->mono_defaults->string_class) { - /* Make the array bigger for the terminating null */ - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_1); - cb_to_mono->mb_emit_byte (mb, CEE_ADD); - } - cb_to_mono->mb_emit_byte (mb, CEE_MUL); - mono_mb_emit_jit_icall (mb, ves_icall_marshal_alloc); - cb_to_mono->mb_emit_stloc (mb, dest); - cb_to_mono->mb_emit_ldloc (mb, dest); - cb_to_mono->mb_emit_stloc (mb, 3); - - /* Emit marshalling loop */ - index_var = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_stloc (mb, index_var); - label2 = cb_to_mono->mb_get_label (mb); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_ldloc (mb, src); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - label3 = cb_to_mono->mb_emit_branch (mb, CEE_BGE); - - /* Emit marshalling code */ - if (is_string) { - int stind_op; - g_assert (conv != MONO_MARSHAL_CONV_INVALID); - - /* dest */ - cb_to_mono->mb_emit_ldloc (mb, dest); - - /* src */ - cb_to_mono->mb_emit_ldloc (mb, src); - cb_to_mono->mb_emit_ldloc (mb, index_var); - - cb_to_mono->mb_emit_byte (mb, CEE_LDELEM_REF); - - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, &stind_op)); - cb_to_mono->mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); - } - else { - char *msg = g_strdup ("Marshalling of non-string arrays to managed code is not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (dest), esize); - - cb_to_mono->mb_emit_branch_label (mb, CEE_BR, label2); - - cb_to_mono->mb_patch_branch (mb, label3); - cb_to_mono->mb_patch_branch (mb, label1); -#endif - break; - } - default: - g_assert_not_reached (); - } - return conv_arg; -} - -static gboolean -emit_native_wrapper_validate_signature (MonoMethodBuilder *mb, MonoMethodSignature* sig, MonoMarshalSpec** mspecs) -{ - if (mspecs) { - for (int i = 0; i < sig->param_count; i ++) { - if (mspecs [i + 1] && mspecs [i + 1]->native == MONO_NATIVE_CUSTOM) { - if (!mspecs [i + 1]->data.custom_data.custom_name || *mspecs [i + 1]->data.custom_data.custom_name == '\0') { - cb_to_mono->mb_emit_exception_full (mb, "System", "TypeLoadException", g_strdup ("Missing ICustomMarshaler type")); - return FALSE; - } - - switch (sig->params[i]->type) { - case MONO_TYPE_CLASS: - case MONO_TYPE_OBJECT: - case MONO_TYPE_STRING: - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: - case MONO_TYPE_VALUETYPE: - break; - - default: - cb_to_mono->mb_emit_exception_full (mb, "System.Runtime.InteropServices", "MarshalDirectiveException", g_strdup_printf ("custom marshalling of type %x is currently not supported", sig->params[i]->type)); - return FALSE; - } - } - else if (sig->params[i]->type == MONO_TYPE_VALUETYPE) { - MonoMarshalType *marshal_type = mono_marshal_load_type_info (mono_class_from_mono_type_internal (sig->params [i])); - for (guint32 field_idx = 0; field_idx < marshal_type->num_fields; ++field_idx) { - if (marshal_type->fields [field_idx].mspec && marshal_type->fields [field_idx].mspec->native == MONO_NATIVE_CUSTOM) { - cb_to_mono->mb_emit_exception_full (mb, "System", "TypeLoadException", g_strdup ("Value type includes custom marshaled fields")); - return FALSE; - } - } - } - } - } - - return TRUE; -} - -static int -emit_marshal_ptr_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - switch (action) { - case MARSHAL_ACTION_CONV_IN: - /* MS seems to allow this in some cases, ie. bxc #158 */ - /* - if (MONO_TYPE_ISSTRUCT (t->data.type) && !mono_class_from_mono_type_internal (t->data.type)->blittable) { - char *msg = g_strdup_printf ("Can not marshal 'parameter #%d': Pointers can not reference marshaled structures. Use byref instead.", argnum + 1); - cb_to_mono->mb_emit_exception_marshal_directive (m->mb, msg); - } - */ - break; - - case MARSHAL_ACTION_PUSH: - cb_to_mono->mb_emit_ldarg (mb, argnum); - break; - - case MARSHAL_ACTION_CONV_RESULT: - /* no conversions necessary */ - cb_to_mono->mb_emit_stloc (mb, 3); - break; - - default: - break; - } - return conv_arg; -} - -static int -emit_marshal_boolean_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - MonoType *int_type = cb_to_mono->get_int_type (); - MonoType *boolean_type = m_class_get_byval_arg (cb_to_mono->mono_defaults->boolean_class); - - switch (action) { - case MARSHAL_ACTION_CONV_IN: { - MonoType *local_type; - int label_false; - guint8 ldc_op = CEE_LDC_I4_1; - - local_type = cb_to_mono->boolean_conv_in_get_local_type (spec, &ldc_op); - if (m_type_is_byref (t)) - *conv_arg_type = int_type; - else - *conv_arg_type = local_type; - conv_arg = cb_to_mono->mb_add_local (mb, local_type); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I1); - label_false = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - cb_to_mono->mb_emit_byte (mb, ldc_op); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - cb_to_mono->mb_patch_branch (mb, label_false); - - break; - } - - case MARSHAL_ACTION_CONV_OUT: - { - int label_false, label_end; - if (!m_type_is_byref (t)) - break; - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - - label_false = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_1); - - label_end = cb_to_mono->mb_emit_branch (mb, CEE_BR); - cb_to_mono->mb_patch_branch (mb, label_false); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_patch_branch (mb, label_end); - - cb_to_mono->mb_emit_byte (mb, CEE_STIND_I1); - break; - } - - case MARSHAL_ACTION_PUSH: - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else if (conv_arg) - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - else - cb_to_mono->mb_emit_ldarg (mb, argnum); - break; - - case MARSHAL_ACTION_CONV_RESULT: - /* maybe we need to make sure that it fits within 8 bits */ - cb_to_mono->mb_emit_stloc (mb, 3); - break; - - case MARSHAL_ACTION_MANAGED_CONV_IN: { - MonoClass* conv_arg_class = cb_to_mono->mono_defaults->int32_class; - guint8 ldop = CEE_LDIND_I4; - int label_null, label_false; - - conv_arg_class = cb_to_mono->boolean_managed_conv_in_get_conv_arg_class (spec, &ldop); - conv_arg = cb_to_mono->mb_add_local (mb, boolean_type); - - if (m_type_is_byref (t)) - *conv_arg_type = m_class_get_this_arg (conv_arg_class); - else - *conv_arg_type = m_class_get_byval_arg (conv_arg_class); - - - cb_to_mono->mb_emit_ldarg (mb, argnum); - - /* Check null */ - if (m_type_is_byref (t)) { - label_null = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, ldop); - } else - label_null = 0; - - label_false = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_1); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - cb_to_mono->mb_patch_branch (mb, label_false); - - if (m_type_is_byref (t)) - cb_to_mono->mb_patch_branch (mb, label_null); - break; - } - - case MARSHAL_ACTION_MANAGED_CONV_OUT: { - guint8 stop = CEE_STIND_I4; - guint8 ldc_op = CEE_LDC_I4_1; - int label_null,label_false, label_end; - - if (!m_type_is_byref (t)) - break; - if (spec) { - switch (spec->native) { - case MONO_NATIVE_I1: - case MONO_NATIVE_U1: - stop = CEE_STIND_I1; - break; - case MONO_NATIVE_VARIANTBOOL: - stop = CEE_STIND_I2; - ldc_op = CEE_LDC_I4_M1; - break; - default: - break; - } - } - - /* Check null */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - label_null = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - - label_false = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - cb_to_mono->mb_emit_byte (mb, ldc_op); - label_end = cb_to_mono->mb_emit_branch (mb, CEE_BR); - - cb_to_mono->mb_patch_branch (mb, label_false); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_patch_branch (mb, label_end); - - cb_to_mono->mb_emit_byte (mb, stop); - cb_to_mono->mb_patch_branch (mb, label_null); - break; - } - - default: - g_assert_not_reached (); - } - return conv_arg; -} - -static int -emit_marshal_char_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - - switch (action) { - case MARSHAL_ACTION_PUSH: - /* fixme: dont know how to marshal that. We cant simply - * convert it to a one byte UTF8 character, because an - * unicode character may need more that one byte in UTF8 */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - break; - - case MARSHAL_ACTION_CONV_RESULT: - /* fixme: we need conversions here */ - cb_to_mono->mb_emit_stloc (mb, 3); - break; - - default: - break; - } - return conv_arg; -} - -static int -emit_marshal_custom_ilgen_throw_exception (MonoMethodBuilder *mb, const char *exc_nspace, const char *exc_name, const char *msg, MarshalAction action) -{ - /* Throw exception and emit compensation code, if neccesary */ - switch (action) { - case MARSHAL_ACTION_CONV_IN: - case MARSHAL_ACTION_MANAGED_CONV_IN: - case MARSHAL_ACTION_CONV_RESULT: - case MARSHAL_ACTION_MANAGED_CONV_RESULT: - if ((action == MARSHAL_ACTION_CONV_RESULT) || (action == MARSHAL_ACTION_MANAGED_CONV_RESULT)) - cb_to_mono->mb_emit_byte (mb, CEE_POP); - - cb_to_mono->mb_emit_exception_full (mb, exc_nspace, exc_name, msg); - - break; - case MARSHAL_ACTION_PUSH: - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - break; - default: - break; - } - - return 0; -} - -static int -emit_marshal_custom_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - ERROR_DECL (error); - MonoType *mtype; - MonoClass *mklass; - static MonoClass *ICustomMarshaler = NULL; - static MonoMethod *cleanup_native, *cleanup_managed; - static MonoMethod *marshal_managed_to_native, *marshal_native_to_managed; - MonoMethodBuilder *mb = m->mb; - MonoAssemblyLoadContext *alc = mono_alc_get_ambient (); - guint32 loc1; - int pos2; - - MonoType *int_type = cb_to_mono->get_int_type (); - MonoType *object_type = cb_to_mono->get_object_type (); - - if (!ICustomMarshaler) { - MonoClass *klass = mono_class_try_get_icustom_marshaler_class (); - if (!klass) - return emit_marshal_custom_ilgen_throw_exception (mb, "System", "ApplicationException", g_strdup ("Current profile doesn't support ICustomMarshaler"), action); - - cleanup_native = cb_to_mono->get_method_nofail (klass, "CleanUpNativeData", 1, 0); - g_assert (cleanup_native); - - cleanup_managed = cb_to_mono->get_method_nofail (klass, "CleanUpManagedData", 1, 0); - g_assert (cleanup_managed); - - marshal_managed_to_native = cb_to_mono->get_method_nofail (klass, "MarshalManagedToNative", 1, 0); - g_assert (marshal_managed_to_native); - - marshal_native_to_managed = cb_to_mono->get_method_nofail (klass, "MarshalNativeToManaged", 1, 0); - g_assert (marshal_native_to_managed); - - cb_to_mono->memory_barrier (); - ICustomMarshaler = klass; - } - - if (spec->data.custom_data.image) - mtype = cb_to_mono->reflection_type_from_name_checked (spec->data.custom_data.custom_name, alc, spec->data.custom_data.image, error); - else - mtype = cb_to_mono->reflection_type_from_name_checked (spec->data.custom_data.custom_name, alc, m->image, error); - - if (!mtype) - return emit_marshal_custom_ilgen_throw_exception (mb, "System", "TypeLoadException", g_strdup ("Failed to load ICustomMarshaler type"), action); - - mklass = mono_class_from_mono_type_internal (mtype); - g_assert (mklass != NULL); - - switch (action) { - case MARSHAL_ACTION_CONV_IN: - switch (t->type) { - case MONO_TYPE_CLASS: - case MONO_TYPE_OBJECT: - case MONO_TYPE_STRING: - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: - case MONO_TYPE_VALUETYPE: - break; - - default: - g_warning ("custom marshalling of type %x is currently not supported", t->type); - g_assert_not_reached (); - break; - } - - conv_arg = cb_to_mono->mb_add_local (mb, int_type); - - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) - break; - - /* Minic MS.NET behavior */ - if (!m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT) && !(t->attrs & PARAM_ATTRIBUTE_IN)) - break; - - /* Check for null */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - - if (t->type == MONO_TYPE_VALUETYPE) { - /* - * Since we can't determine the type of the argument, we - * will assume the unmanaged function takes a pointer. - */ - *conv_arg_type = int_type; - - cb_to_mono->mb_emit_op (mb, CEE_BOX, mono_class_from_mono_type_internal (t)); - } - - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_managed_to_native); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - case MARSHAL_ACTION_CONV_OUT: - /* Check for null */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_OUT)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - cb_to_mono->mb_emit_byte (mb, CEE_DUP); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, cleanup_managed); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } else if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } else if (t->attrs & PARAM_ATTRIBUTE_OUT) { - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); - /* We have nowhere to store the result */ - cb_to_mono->mb_emit_byte (mb, CEE_POP); - } - - // Only call cleanup_native if MARSHAL_ACTION_CONV_IN called marshal_managed_to_native. - if (!(m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) && - !(!m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT) && !(t->attrs & PARAM_ATTRIBUTE_IN))) { - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, cleanup_native); - } - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - case MARSHAL_ACTION_PUSH: - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_RESULT: - cb_to_mono->mb_emit_stloc (mb, 3); - - /* Check for null */ - cb_to_mono->mb_emit_ldloc (mb, 3); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - - cb_to_mono->mb_emit_ldloc (mb, 3); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); - cb_to_mono->mb_emit_stloc (mb, 3); - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - case MARSHAL_ACTION_MANAGED_CONV_IN: - switch (t->type) { - case MONO_TYPE_CLASS: - case MONO_TYPE_OBJECT: - case MONO_TYPE_STRING: - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: - case MONO_TYPE_VALUETYPE: - case MONO_TYPE_BOOLEAN: - break; - - default: - g_warning ("custom marshalling of type %x is currently not supported", t->type); - g_assert_not_reached (); - break; - } - - conv_arg = cb_to_mono->mb_add_local (mb, object_type); - - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - if (m_type_is_byref (t) && t->attrs & PARAM_ATTRIBUTE_OUT) - break; - - /* Check for null */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - case MARSHAL_ACTION_MANAGED_CONV_RESULT: - g_assert (!m_type_is_byref (t)); - - loc1 = cb_to_mono->mb_add_local (mb, object_type); - - cb_to_mono->mb_emit_stloc (mb, 3); - - cb_to_mono->mb_emit_ldloc (mb, 3); - cb_to_mono->mb_emit_stloc (mb, loc1); - - /* Check for null */ - cb_to_mono->mb_emit_ldloc (mb, 3); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - cb_to_mono->mb_emit_byte (mb, CEE_DUP); - - cb_to_mono->mb_emit_ldloc (mb, 3); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_managed_to_native); - cb_to_mono->mb_emit_stloc (mb, 3); - - cb_to_mono->mb_emit_ldloc (mb, loc1); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, cleanup_managed); - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - case MARSHAL_ACTION_MANAGED_CONV_OUT: - - /* Check for null */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - if (m_type_is_byref (t)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_managed_to_native); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_I); - } - - // Only call cleanup_managed if MARSHAL_ACTION_MANAGED_CONV_IN called marshal_native_to_managed. - if (!(m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT))) { - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, cleanup_managed); - } - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - default: - g_assert_not_reached (); - } - return conv_arg; -} - -static int -emit_marshal_asany_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - - MonoType *int_type = cb_to_mono->get_int_type (); - switch (action) { - case MARSHAL_ACTION_CONV_IN: { - MonoMarshalNative encoding = cb_to_mono->get_string_encoding (m->piinfo, NULL); - - g_assert (t->type == MONO_TYPE_OBJECT); - g_assert (!m_type_is_byref (t)); - - conv_arg = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_icon (mb, encoding); - cb_to_mono->mb_emit_icon (mb, t->attrs); - mono_mb_emit_jit_icall (mb, mono_marshal_asany); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - break; - } - - case MARSHAL_ACTION_PUSH: - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_OUT: { - MonoMarshalNative encoding = cb_to_mono->get_string_encoding (m->piinfo, NULL); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_icon (mb, encoding); - cb_to_mono->mb_emit_icon (mb, t->attrs); - mono_mb_emit_jit_icall (mb, mono_marshal_free_asany); - break; - } - - default: - g_assert_not_reached (); - } - return conv_arg; -} - -static int -emit_marshal_vtype_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - MonoClass *klass, *date_time_class; - int pos = 0, pos2; - - klass = mono_class_from_mono_type_internal (t); - - date_time_class = mono_class_get_date_time_class (); - - MonoType *int_type = cb_to_mono->get_int_type (); - MonoType *double_type = m_class_get_byval_arg (cb_to_mono->mono_defaults->double_class); - - switch (action) { - case MARSHAL_ACTION_CONV_IN: - if (klass == date_time_class) { - /* Convert it to an OLE DATE type */ - - conv_arg = cb_to_mono->mb_add_local (mb, double_type); - - if (m_type_is_byref (t)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - } - - if (!(m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && (t->attrs & PARAM_ATTRIBUTE_OUT))) { - if (!m_type_is_byref (t)) - m->csig->params [argnum - m->csig->hasthis] = double_type; - - MONO_STATIC_POINTER_INIT (MonoMethod, to_oadate) - to_oadate = cb_to_mono->get_method_nofail (date_time_class, "ToOADate", 0, 0); - g_assert (to_oadate); - MONO_STATIC_POINTER_INIT_END (MonoMethod, to_oadate) - - cb_to_mono->mb_emit_ldarg_addr (mb, argnum); - cb_to_mono->mb_emit_managed_call (mb, to_oadate, NULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } - - if (m_type_is_byref (t)) - cb_to_mono->mb_patch_branch (mb, pos); - break; - } - - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) - break; - - conv_arg = cb_to_mono->mb_add_local (mb, int_type); - - /* store the address of the source into local variable 0 */ - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldarg (mb, argnum); - else - cb_to_mono->mb_emit_ldarg_addr (mb, argnum); - - cb_to_mono->mb_emit_stloc (mb, 0); - - /* allocate space for the native struct and - * store the address into local variable 1 (dest) */ - cb_to_mono->mb_emit_icon (mb, cb_to_mono->class_native_size (klass, NULL)); - cb_to_mono->mb_emit_byte (mb, CEE_PREFIX1); - cb_to_mono->mb_emit_byte (mb, CEE_LOCALLOC); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - if (m_type_is_byref (t)) { - cb_to_mono->mb_emit_ldloc (mb, 0); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - } - - if (!(m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && (t->attrs & PARAM_ATTRIBUTE_OUT))) { - /* set dst_ptr */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, FALSE); - } - - if (m_type_is_byref (t)) - cb_to_mono->mb_patch_branch (mb, pos); - break; - - case MARSHAL_ACTION_PUSH: - if (spec && spec->native == MONO_NATIVE_LPSTRUCT) { - /* FIXME: */ - g_assert (!m_type_is_byref (t)); - - /* Have to change the signature since the vtype is passed byref */ - m->csig->params [argnum - m->csig->hasthis] = int_type; - - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) - cb_to_mono->mb_emit_ldarg_addr (mb, argnum); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - } - - if (klass == date_time_class) { - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - } - - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - break; - } - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - if (!m_type_is_byref (t)) { - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_LDNATIVEOBJ, klass); - } - break; - - case MARSHAL_ACTION_CONV_OUT: - if (klass == date_time_class) { - /* Convert from an OLE DATE type */ - - if (!m_type_is_byref (t)) - break; - - if (!((t->attrs & PARAM_ATTRIBUTE_IN) && !(t->attrs & PARAM_ATTRIBUTE_OUT))) { - - MONO_STATIC_POINTER_INIT (MonoMethod, from_oadate) - from_oadate = cb_to_mono->get_method_nofail (date_time_class, "FromOADate", 1, 0); - MONO_STATIC_POINTER_INIT_END (MonoMethod, from_oadate) - - g_assert (from_oadate); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_managed_call (mb, from_oadate, NULL); - cb_to_mono->mb_emit_op (mb, CEE_STOBJ, date_time_class); - } - break; - } - - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) - break; - - if (m_type_is_byref (t)) { - /* dst = argument */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_stloc (mb, 1); - - cb_to_mono->mb_emit_ldloc (mb, 1); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - if (!((t->attrs & PARAM_ATTRIBUTE_IN) && !(t->attrs & PARAM_ATTRIBUTE_OUT))) { - /* src = tmp_locals [i] */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, TRUE); - } - } - - emit_struct_free (mb, klass, conv_arg); - - if (m_type_is_byref (t)) - cb_to_mono->mb_patch_branch (mb, pos); - break; - - case MARSHAL_ACTION_CONV_RESULT: - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass)) { - cb_to_mono->mb_emit_stloc (mb, 3); - break; - } - - /* load pointer to returned value type */ - g_assert (m->vtaddr_var); - cb_to_mono->mb_emit_ldloc (mb, m->vtaddr_var); - /* store the address of the source into local variable 0 */ - cb_to_mono->mb_emit_stloc (mb, 0); - /* set dst_ptr */ - cb_to_mono->mb_emit_ldloc_addr (mb, 3); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, TRUE); - break; - - case MARSHAL_ACTION_MANAGED_CONV_IN: - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) { - conv_arg = 0; - break; - } - - conv_arg = cb_to_mono->mb_add_local (mb, m_class_get_byval_arg (klass)); - - if (t->attrs & PARAM_ATTRIBUTE_OUT) - break; - - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldarg (mb, argnum); - else - cb_to_mono->mb_emit_ldarg_addr (mb, argnum); - cb_to_mono->mb_emit_stloc (mb, 0); - - if (m_type_is_byref (t)) { - cb_to_mono->mb_emit_ldloc (mb, 0); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - } - - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, TRUE); - - if (m_type_is_byref (t)) - cb_to_mono->mb_patch_branch (mb, pos); - break; - - case MARSHAL_ACTION_MANAGED_CONV_OUT: - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) - break; - if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_IN) && !(t->attrs & PARAM_ATTRIBUTE_OUT)) - break; - - /* Check for null */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - /* Set src */ - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* Set dest */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, FALSE); - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - case MARSHAL_ACTION_MANAGED_CONV_RESULT: - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) { - cb_to_mono->mb_emit_stloc (mb, 3); - m->retobj_var = 0; - break; - } - - /* load pointer to returned value type */ - g_assert (m->vtaddr_var); - cb_to_mono->mb_emit_ldloc (mb, m->vtaddr_var); - - /* store the address of the source into local variable 0 */ - cb_to_mono->mb_emit_stloc (mb, 0); - /* allocate space for the native struct and - * store the address into dst_ptr */ - m->retobj_var = cb_to_mono->mb_add_local (mb, int_type); - m->retobj_class = klass; - g_assert (m->retobj_var); - cb_to_mono->mb_emit_icon (mb, cb_to_mono->class_native_size (klass, NULL)); - cb_to_mono->mb_emit_byte (mb, CEE_CONV_I); - mono_mb_emit_jit_icall (mb, ves_icall_marshal_alloc); - cb_to_mono->mb_emit_stloc (mb, 1); - cb_to_mono->mb_emit_ldloc (mb, 1); - cb_to_mono->mb_emit_stloc (mb, m->retobj_var); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, FALSE); - break; - - default: - g_assert_not_reached (); - } - return conv_arg; -} - -static void -emit_string_free_icall (MonoMethodBuilder *mb, MonoMarshalConv conv) -{ - if (conv == MONO_MARSHAL_CONV_BSTR_STR || conv == MONO_MARSHAL_CONV_ANSIBSTR_STR || conv == MONO_MARSHAL_CONV_TBSTR_STR) - mono_mb_emit_jit_icall (mb, mono_free_bstr); - else - mono_mb_emit_jit_icall (mb, mono_marshal_free); -} - -static int -emit_marshal_string_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - MonoMarshalNative encoding = cb_to_mono->get_string_encoding (m->piinfo, spec); - MonoMarshalConv conv = cb_to_mono->get_string_to_ptr_conv (m->piinfo, spec); - gboolean need_free; - - MonoType *int_type = cb_to_mono->get_int_type (); - MonoType *object_type = cb_to_mono->get_object_type (); - switch (action) { - case MARSHAL_ACTION_CONV_IN: - *conv_arg_type = int_type; - conv_arg = cb_to_mono->mb_add_local (mb, int_type); - - if (m_type_is_byref (t)) { - if (t->attrs & PARAM_ATTRIBUTE_OUT) - break; - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - } else { - cb_to_mono->mb_emit_ldarg (mb, argnum); - } - - if (conv == MONO_MARSHAL_CONV_INVALID) { - char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - } else { - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } - break; - - case MARSHAL_ACTION_CONV_OUT: - conv = cb_to_mono->get_ptr_to_string_conv (m->piinfo, spec, &need_free); - if (conv == MONO_MARSHAL_CONV_INVALID) { - char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - if (encoding == MONO_NATIVE_VBBYREFSTR) { - - if (!m_type_is_byref (t)) { - char *msg = g_strdup ("VBByRefStr marshalling requires a ref parameter."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - MONO_STATIC_POINTER_INIT (MonoMethod, method) - - method = cb_to_mono->get_method_nofail (cb_to_mono->mono_defaults->string_class, "get_Length", -1, 0); - - MONO_STATIC_POINTER_INIT_END (MonoMethod, method) - - /* - * Have to allocate a new string with the same length as the original, and - * copy the contents of the buffer pointed to by CONV_ARG into it. - */ - g_assert (m_type_is_byref (t)); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_managed_call (mb, method, NULL); - mono_mb_emit_jit_icall (mb, mono_string_new_len_wrapper); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } else if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN))) { - int stind_op; - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, &stind_op)); - cb_to_mono->mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); - need_free = TRUE; - } - - if (need_free) { - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - emit_string_free_icall (mb, conv); - } - break; - - case MARSHAL_ACTION_PUSH: - if (m_type_is_byref (t) && encoding != MONO_NATIVE_VBBYREFSTR) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_RESULT: - cb_to_mono->mb_emit_stloc (mb, 0); - - conv = cb_to_mono->get_ptr_to_string_conv (m->piinfo, spec, &need_free); - if (conv == MONO_MARSHAL_CONV_INVALID) { - char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - cb_to_mono->mb_emit_ldloc (mb, 0); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - cb_to_mono->mb_emit_stloc (mb, 3); - - /* free the string */ - cb_to_mono->mb_emit_ldloc (mb, 0); - emit_string_free_icall (mb, conv); - break; - - case MARSHAL_ACTION_MANAGED_CONV_IN: - conv_arg = cb_to_mono->mb_add_local (mb, object_type); - - *conv_arg_type = int_type; - - if (m_type_is_byref (t)) { - if (t->attrs & PARAM_ATTRIBUTE_OUT) - break; - } - - conv = cb_to_mono->get_ptr_to_string_conv (m->piinfo, spec, &need_free); - if (conv == MONO_MARSHAL_CONV_INVALID) { - char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_MANAGED_CONV_OUT: - if (m_type_is_byref (t)) { - if (conv_arg) { - int stind_op; - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, &stind_op)); - cb_to_mono->mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); - } - } - break; - - case MARSHAL_ACTION_MANAGED_CONV_RESULT: - if (cb_to_mono->conv_to_icall (conv, NULL) == MONO_JIT_ICALL_mono_marshal_string_to_utf16) - /* We need to make a copy so the caller is able to free it */ - mono_mb_emit_jit_icall (mb, mono_marshal_string_to_utf16_copy); - else - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - cb_to_mono->mb_emit_stloc (mb, 3); - break; - - default: - g_assert_not_reached (); - } - return conv_arg; -} - -static int -emit_marshal_safehandle_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - MonoType *int_type = cb_to_mono->get_int_type (); - MonoType *boolean_type = m_class_get_byval_arg (cb_to_mono->mono_defaults->boolean_class); - - switch (action){ - case MARSHAL_ACTION_CONV_IN: { - int dar_release_slot, pos; - - conv_arg = cb_to_mono->mb_add_local (mb, int_type); - *conv_arg_type = int_type; - - if (!*cb_to_mono->get_sh_dangerous_add_ref()) - cb_to_mono->init_safe_handle (); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRTRUE); - cb_to_mono->mb_emit_exception (mb, "ArgumentNullException", NULL); - - cb_to_mono->mb_patch_branch (mb, pos); - - /* Create local to hold the ref parameter to DangerousAddRef */ - dar_release_slot = cb_to_mono->mb_add_local (mb, boolean_type); - - /* set release = false; */ - cb_to_mono->mb_emit_icon (mb, 0); - cb_to_mono->mb_emit_stloc (mb, dar_release_slot); - - if (m_type_is_byref (t)) { - int old_handle_value_slot = cb_to_mono->mb_add_local (mb, int_type); - - if (!cb_to_mono->is_in (t)) { - cb_to_mono->mb_emit_icon (mb, 0); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } else { - /* safehandle.DangerousAddRef (ref release) */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_ldloc_addr (mb, dar_release_slot); - cb_to_mono->mb_emit_managed_call (mb, *cb_to_mono->get_sh_dangerous_add_ref(), NULL); - - /* Pull the handle field from SafeHandle */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_byte (mb, CEE_DUP); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, old_handle_value_slot); - } - } else { - /* safehandle.DangerousAddRef (ref release) */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc_addr (mb, dar_release_slot); - cb_to_mono->mb_emit_managed_call (mb, *cb_to_mono->get_sh_dangerous_add_ref(), NULL); - - /* Pull the handle field from SafeHandle */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } - - break; - } - - case MARSHAL_ACTION_PUSH: - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_OUT: { - /* The slot for the boolean is the next temporary created after conv_arg, see the CONV_IN code */ - int dar_release_slot = conv_arg + 1; - int label_next = 0; - - if (!*cb_to_mono->get_sh_dangerous_release()) - cb_to_mono->init_safe_handle (); - - if (m_type_is_byref (t)) { - /* If there was SafeHandle on input we have to release the reference to it */ - if (cb_to_mono->is_in (t)) { - cb_to_mono->mb_emit_ldloc (mb, dar_release_slot); - label_next = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_managed_call (mb, *cb_to_mono->get_sh_dangerous_release (), NULL); - cb_to_mono->mb_patch_branch (mb, label_next); - } - - if (cb_to_mono->is_out (t)) { - ERROR_DECL (local_error); - MonoMethod *ctor; - - /* - * If the SafeHandle was marshalled on input we can skip the marshalling on - * output if the handle value is identical. - */ - if (cb_to_mono->is_in (t)) { - int old_handle_value_slot = dar_release_slot + 1; - cb_to_mono->mb_emit_ldloc (mb, old_handle_value_slot); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - label_next = cb_to_mono->mb_emit_branch (mb, CEE_BEQ); - } - - /* - * Create an empty SafeHandle (of correct derived type). - * - * FIXME: If an out-of-memory situation or exception happens here we will - * leak the handle. We should move the allocation of the SafeHandle to the - * input marshalling code to prevent that. - */ - ctor = mono_class_get_method_from_name_checked (t->data.klass, ".ctor", 0, 0, local_error); - if (ctor == NULL || !is_ok (local_error)){ - cb_to_mono->mb_emit_exception (mb, "MissingMethodException", "parameterless constructor required"); - mono_error_cleanup (local_error); - break; - } - - /* refval = new SafeHandleDerived ()*/ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_op (mb, CEE_NEWOBJ, ctor); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - - /* refval.handle = returned_handle */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_I); - - if (cb_to_mono->is_in (t) && label_next) { - cb_to_mono->mb_patch_branch (mb, label_next); - } - } - } else { - cb_to_mono->mb_emit_ldloc (mb, dar_release_slot); - label_next = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_managed_call (mb, *cb_to_mono->get_sh_dangerous_release (), NULL); - cb_to_mono->mb_patch_branch (mb, label_next); - } - break; - } - - case MARSHAL_ACTION_CONV_RESULT: { - ERROR_DECL (error); - MonoMethod *ctor = NULL; - int intptr_handle_slot; - - if (mono_class_is_abstract (t->data.klass)) { - cb_to_mono->mb_emit_byte (mb, CEE_POP); - cb_to_mono->mb_emit_exception_marshal_directive (mb, g_strdup ("Returned SafeHandles should not be abstract")); - break; - } - - ctor = mono_class_get_method_from_name_checked (t->data.klass, ".ctor", 0, 0, error); - if (ctor == NULL || !is_ok (error)){ - mono_error_cleanup (error); - cb_to_mono->mb_emit_byte (mb, CEE_POP); - cb_to_mono->mb_emit_exception (mb, "MissingMethodException", "parameterless constructor required"); - break; - } - /* Store the IntPtr results into a local */ - intptr_handle_slot = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_stloc (mb, intptr_handle_slot); - - /* Create return value */ - cb_to_mono->mb_emit_op (mb, CEE_NEWOBJ, ctor); - cb_to_mono->mb_emit_stloc (mb, 3); - - /* Set the return.handle to the value, am using ldflda, not sure if thats a good idea */ - cb_to_mono->mb_emit_ldloc (mb, 3); - cb_to_mono->mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); - cb_to_mono->mb_emit_ldloc (mb, intptr_handle_slot); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_I); - break; - } - - case MARSHAL_ACTION_MANAGED_CONV_IN: - fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_IN\n"); - break; - - case MARSHAL_ACTION_MANAGED_CONV_OUT: - fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_OUT\n"); - break; - - case MARSHAL_ACTION_MANAGED_CONV_RESULT: - fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_RESULT\n"); - break; - default: - printf ("Unhandled case for MarshalAction: %d\n", action); - } - return conv_arg; -} - -static int -emit_marshal_handleref_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - - MonoType *int_type = cb_to_mono->get_int_type (); - switch (action){ - case MARSHAL_ACTION_CONV_IN: { - conv_arg = cb_to_mono->mb_add_local (mb, int_type); - *conv_arg_type = int_type; - - if (m_type_is_byref (t)) { - char *msg = g_strdup ("HandleRefs can not be returned from unmanaged code (or passed by ref)"); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - cb_to_mono->mb_emit_ldarg_addr (mb, argnum); - cb_to_mono->mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoHandleRef, handle)); - cb_to_mono->mb_emit_byte (mb, CEE_ADD); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - break; - } - - case MARSHAL_ACTION_PUSH: - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_OUT: { - /* no resource release required */ - break; - } - - case MARSHAL_ACTION_CONV_RESULT: { - char *msg = g_strdup ("HandleRefs can not be returned from unmanaged code (or passed by ref)"); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - case MARSHAL_ACTION_MANAGED_CONV_IN: - fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_IN\n"); - break; - - case MARSHAL_ACTION_MANAGED_CONV_OUT: - fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_OUT\n"); - break; - - case MARSHAL_ACTION_MANAGED_CONV_RESULT: - fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_RESULT\n"); - break; - default: - fprintf (stderr, "Unhandled case for MarshalAction: %d\n", action); - } - return conv_arg; -} - -static int -emit_marshal_object_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - MonoClass *klass = mono_class_from_mono_type_internal (t); - int pos, pos2, loc; - - MonoType *int_type = cb_to_mono->get_int_type (); - switch (action) { - case MARSHAL_ACTION_CONV_IN: - *conv_arg_type = int_type; - conv_arg = cb_to_mono->mb_add_local (mb, int_type); - - m->orig_conv_args [argnum] = 0; - - if (mono_class_from_mono_type_internal (t) == cb_to_mono->mono_defaults->object_class) { - char *msg = g_strdup_printf ("Marshalling of type object is not implemented"); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - if (m_class_is_delegate (klass)) { - if (m_type_is_byref (t)) { - if (!(t->attrs & PARAM_ATTRIBUTE_OUT)) { - char *msg = g_strdup_printf ("Byref marshalling of delegates is not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - } - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } else { - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_CONV_DEL_FTN, NULL)); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } - } else if (klass == cb_to_mono->try_get_stringbuilder_class ()) { - MonoMarshalNative encoding = cb_to_mono->get_string_encoding (m->piinfo, spec); - MonoMarshalConv conv = cb_to_mono->get_stringbuilder_to_ptr_conv (m->piinfo, spec); - -#if 0 - if (m_type_is_byref (t)) { - if (!(t->attrs & PARAM_ATTRIBUTE_OUT)) { - char *msg = g_strdup_printf ("Byref marshalling of stringbuilders is not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - } - break; - } -#endif - - if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && (t->attrs & PARAM_ATTRIBUTE_OUT)) - break; - - if (conv == MONO_MARSHAL_CONV_INVALID) { - char *msg = g_strdup_printf ("stringbuilder marshalling conversion %d not implemented", encoding); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } else if (m_class_is_blittable (klass)) { - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - cb_to_mono->mb_patch_branch (mb, pos); - break; - } else { - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - if (m_type_is_byref (t)) { - /* we dont need any conversions for out parameters */ - if (t->attrs & PARAM_ATTRIBUTE_OUT) - break; - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - } else { - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_byte (mb, CEE_MONO_OBJADDR); - } - - /* store the address of the source into local variable 0 */ - cb_to_mono->mb_emit_stloc (mb, 0); - cb_to_mono->mb_emit_ldloc (mb, 0); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - /* allocate space for the native struct and store the address */ - cb_to_mono->mb_emit_icon (mb, cb_to_mono->class_native_size (klass, NULL)); - cb_to_mono->mb_emit_byte (mb, CEE_PREFIX1); - cb_to_mono->mb_emit_byte (mb, CEE_LOCALLOC); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - if (m_type_is_byref (t)) { - /* Need to store the original buffer so we can free it later */ - m->orig_conv_args [argnum] = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, m->orig_conv_args [argnum]); - } - - /* set the src_ptr */ - cb_to_mono->mb_emit_ldloc (mb, 0); - cb_to_mono->mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* set dst_ptr */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, FALSE); - - cb_to_mono->mb_patch_branch (mb, pos); - } - break; - - case MARSHAL_ACTION_CONV_OUT: - if (klass == cb_to_mono->try_get_stringbuilder_class ()) { - gboolean need_free; - MonoMarshalNative encoding; - MonoMarshalConv conv; - - encoding = cb_to_mono->get_string_encoding (m->piinfo, spec); - conv = cb_to_mono->get_ptr_to_stringbuilder_conv (m->piinfo, spec, &need_free); - - g_assert (encoding != -1); - - if (m_type_is_byref (t)) { - //g_assert (!(t->attrs & PARAM_ATTRIBUTE_OUT)); - - need_free = TRUE; - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - - switch (encoding) { - case MONO_NATIVE_LPWSTR: - mono_mb_emit_jit_icall (mb, mono_string_utf16_to_builder2); - break; - case MONO_NATIVE_LPSTR: - mono_mb_emit_jit_icall (mb, mono_string_utf8_to_builder2); - break; - case MONO_NATIVE_UTF8STR: - mono_mb_emit_jit_icall (mb, mono_string_utf8_to_builder2); - break; - default: - g_assert_not_reached (); - } - - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } else if (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - } - - if (need_free) { - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - mono_mb_emit_jit_icall (mb, mono_marshal_free); - } - break; - } - - if (m_class_is_delegate (klass)) { - if (m_type_is_byref (t)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_CONV_FTN_DEL, NULL)); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } - break; - } - - if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) { - /* allocate a new object */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_NEWOBJ, klass); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } - - /* dst = *argument */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - cb_to_mono->mb_emit_stloc (mb, 1); - - cb_to_mono->mb_emit_ldloc (mb, 1); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - if (m_type_is_byref (t) || (t->attrs & PARAM_ATTRIBUTE_OUT)) { - cb_to_mono->mb_emit_ldloc (mb, 1); - cb_to_mono->mb_emit_icon (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_byte (mb, CEE_ADD); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* src = tmp_locals [i] */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, TRUE); - - /* Free the structure returned by the native code */ - emit_struct_free (mb, klass, conv_arg); - - if (m->orig_conv_args [argnum]) { - /* - * If the native function changed the pointer, then free - * the original structure plus the new pointer. - */ - cb_to_mono->mb_emit_ldloc (mb, m->orig_conv_args [argnum]); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BEQ); - - if (!(t->attrs & PARAM_ATTRIBUTE_OUT)) { - g_assert (m->orig_conv_args [argnum]); - - emit_struct_free (mb, klass, m->orig_conv_args [argnum]); - } - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - mono_mb_emit_jit_icall (mb, mono_marshal_free); - - cb_to_mono->mb_patch_branch (mb, pos2); - } - } - else - /* Free the original structure passed to native code */ - emit_struct_free (mb, klass, conv_arg); - - cb_to_mono->mb_patch_branch (mb, pos); - break; - - case MARSHAL_ACTION_PUSH: - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_RESULT: - if (m_class_is_delegate (klass)) { - g_assert (!m_type_is_byref (t)); - cb_to_mono->mb_emit_stloc (mb, 0); - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); - cb_to_mono->mb_emit_ldloc (mb, 0); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_CONV_FTN_DEL, NULL)); - cb_to_mono->mb_emit_stloc (mb, 3); - } else if (klass == cb_to_mono->try_get_stringbuilder_class ()) { - // FIXME: - char *msg = g_strdup_printf ("Return marshalling of stringbuilders is not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - } else { - /* set src */ - cb_to_mono->mb_emit_stloc (mb, 0); - - /* Make a copy since emit_conv modifies local 0 */ - loc = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_ldloc (mb, 0); - cb_to_mono->mb_emit_stloc (mb, loc); - - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, 3); - - cb_to_mono->mb_emit_ldloc (mb, 0); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - /* allocate result object */ - - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_NEWOBJ, klass); - cb_to_mono->mb_emit_stloc (mb, 3); - - /* set dst */ - - cb_to_mono->mb_emit_ldloc (mb, 3); - cb_to_mono->mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, TRUE); - - emit_struct_free (mb, klass, loc); - - /* Free the pointer allocated by unmanaged code */ - cb_to_mono->mb_emit_ldloc (mb, loc); - mono_mb_emit_jit_icall (mb, mono_marshal_free); - cb_to_mono->mb_patch_branch (mb, pos); - } - break; - - case MARSHAL_ACTION_MANAGED_CONV_IN: - conv_arg = cb_to_mono->mb_add_local (mb, m_class_get_byval_arg (klass)); - - if (m_class_is_delegate (klass)) { - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_CONV_FTN_DEL, NULL)); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - break; - } - - if (klass == cb_to_mono->try_get_stringbuilder_class ()) { - MonoMarshalNative encoding; - - encoding = cb_to_mono->get_string_encoding (m->piinfo, spec); - - // FIXME: - g_assert (encoding == MONO_NATIVE_LPSTR || encoding == MONO_NATIVE_UTF8STR); - - g_assert (!m_type_is_byref (t)); - g_assert (encoding != -1); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - mono_mb_emit_jit_icall (mb, mono_string_utf8_to_builder2); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - break; - } - - /* The class can not have an automatic layout */ - if (mono_class_is_auto_layout (klass)) { - cb_to_mono->mb_emit_auto_layout_exception (mb, klass); - break; - } - - if (t->attrs & PARAM_ATTRIBUTE_OUT) { - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - break; - } - - /* Set src */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) { - /* Check for NULL and raise an exception */ - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRTRUE); - - cb_to_mono->mb_emit_exception (mb, "ArgumentNullException", NULL); - - cb_to_mono->mb_patch_branch (mb, pos2); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - } - - cb_to_mono->mb_emit_stloc (mb, 0); - - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - cb_to_mono->mb_emit_ldloc (mb, 0); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - /* Create and set dst */ - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_NEWOBJ, klass); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, TRUE); - - cb_to_mono->mb_patch_branch (mb, pos); - break; - - case MARSHAL_ACTION_MANAGED_CONV_OUT: - if (m_class_is_delegate (klass)) { - if (m_type_is_byref (t)) { - int stind_op; - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_CONV_DEL_FTN, &stind_op)); - cb_to_mono->mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); - break; - } - } - - if (m_type_is_byref (t)) { - /* Check for null */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRTRUE); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_I); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BR); - - cb_to_mono->mb_patch_branch (mb, pos); - - /* Set src */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* Allocate and set dest */ - cb_to_mono->mb_emit_icon (mb, cb_to_mono->class_native_size (klass, NULL)); - cb_to_mono->mb_emit_byte (mb, CEE_CONV_I); - mono_mb_emit_jit_icall (mb, ves_icall_marshal_alloc); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* Update argument pointer */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, 1); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_I); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, FALSE); - - cb_to_mono->mb_patch_branch (mb, pos2); - } else if (klass == cb_to_mono->try_get_stringbuilder_class ()) { - // FIXME: What to do here ? - } else { - /* byval [Out] marshalling */ - - /* FIXME: Handle null */ - - /* Set src */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* Set dest */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, FALSE); - } - break; - - case MARSHAL_ACTION_MANAGED_CONV_RESULT: - if (m_class_is_delegate (klass)) { - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_CONV_DEL_FTN, NULL)); - cb_to_mono->mb_emit_stloc (mb, 3); - break; - } - - /* The class can not have an automatic layout */ - if (mono_class_is_auto_layout (klass)) { - cb_to_mono->mb_emit_auto_layout_exception (mb, klass); - break; - } - - cb_to_mono->mb_emit_stloc (mb, 0); - /* Check for null */ - cb_to_mono->mb_emit_ldloc (mb, 0); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRTRUE); - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, 3); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BR); - - cb_to_mono->mb_patch_branch (mb, pos); - - /* Set src */ - cb_to_mono->mb_emit_ldloc (mb, 0); - cb_to_mono->mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* Allocate and set dest */ - cb_to_mono->mb_emit_icon (mb, cb_to_mono->class_native_size (klass, NULL)); - cb_to_mono->mb_emit_byte (mb, CEE_CONV_I); - mono_mb_emit_jit_icall (mb, ves_icall_marshal_alloc); - cb_to_mono->mb_emit_byte (mb, CEE_DUP); - cb_to_mono->mb_emit_stloc (mb, 1); - cb_to_mono->mb_emit_stloc (mb, 3); - - cb_to_mono->emit_struct_conv (mb, klass, FALSE); - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - default: - g_assert_not_reached (); - } - return conv_arg; -} - -static int -emit_marshal_variant_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ -#ifndef DISABLE_COM - MonoMethodBuilder *mb = m->mb; - MonoType *variant_type = m_class_get_byval_arg (mono_class_get_variant_class ()); - MonoType *variant_type_byref = mono_class_get_byref_type (mono_class_get_variant_class ()); - MonoType *object_type = cb_to_mono->get_object_type (); - - switch (action) { - case MARSHAL_ACTION_CONV_IN: { - conv_arg = cb_to_mono->mb_add_local (mb, variant_type); - - if (m_type_is_byref (t)) - *conv_arg_type = variant_type_byref; - else - *conv_arg_type = variant_type; - - if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && t->attrs & PARAM_ATTRIBUTE_OUT) - break; - - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte(mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - cb_to_mono->mb_emit_managed_call (mb, mono_get_Marshal_GetNativeVariantForObject (), NULL); - break; - } - - case MARSHAL_ACTION_CONV_OUT: { - if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN))) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - cb_to_mono->mb_emit_managed_call (mb, mono_get_Marshal_GetObjectForNativeVariant (), NULL); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } - - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - cb_to_mono->mb_emit_managed_call (mb, mono_get_Variant_Clear (), NULL); - break; - } - - case MARSHAL_ACTION_PUSH: - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_RESULT: { - char *msg = g_strdup ("Marshalling of VARIANT not supported as a return type."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - case MARSHAL_ACTION_MANAGED_CONV_IN: { - conv_arg = cb_to_mono->mb_add_local (mb, object_type); - - if (m_type_is_byref (t)) - *conv_arg_type = variant_type_byref; - else - *conv_arg_type = variant_type; - - if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && t->attrs & PARAM_ATTRIBUTE_OUT) - break; - - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldarg (mb, argnum); - else - cb_to_mono->mb_emit_ldarg_addr (mb, argnum); - cb_to_mono->mb_emit_managed_call (mb, mono_get_Marshal_GetObjectForNativeVariant (), NULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - break; - } - - case MARSHAL_ACTION_MANAGED_CONV_OUT: { - if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN))) { - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_managed_call (mb, mono_get_Marshal_GetNativeVariantForObject (), NULL); - } - break; - } - - case MARSHAL_ACTION_MANAGED_CONV_RESULT: { - char *msg = g_strdup ("Marshalling of VARIANT not supported as a return type."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - default: - g_assert_not_reached (); - } -#endif /* DISABLE_COM */ - - return conv_arg; -} - - -static MonoMarshalILgenCallbacks * -get_marshal_cb (void) -{ - if (G_UNLIKELY (!ilgen_cb_inited)) { -#ifdef ENABLE_ILGEN - mono_marshal_ilgen_init (); -#else - mono_marshal_noilgen_init_heavyweight (); -#endif - } - return &ilgen_marshal_cb; -} - -int -mono_emit_marshal_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action, MonoMarshalLightweightCallbacks* lightweigth_cb) -{ - if (spec && spec->native == MONO_NATIVE_CUSTOM) - return get_marshal_cb ()->emit_marshal_custom (m, argnum, t, spec, conv_arg, conv_arg_type, action); - - if (spec && spec->native == MONO_NATIVE_ASANY) - return get_marshal_cb ()->emit_marshal_asany (m, argnum, t, spec, conv_arg, conv_arg_type, action); - - switch (t->type) { - case MONO_TYPE_VALUETYPE: - if (t->data.klass == cb_to_mono->class_try_get_handleref_class ()) - return get_marshal_cb ()->emit_marshal_handleref (m, argnum, t, spec, conv_arg, conv_arg_type, action); - - return get_marshal_cb ()->emit_marshal_vtype (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_STRING: - return get_marshal_cb ()->emit_marshal_string (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_CLASS: - case MONO_TYPE_OBJECT: -#if !defined(DISABLE_COM) - if (spec && spec->native == MONO_NATIVE_STRUCT) - return get_marshal_cb ()->emit_marshal_variant (m, argnum, t, spec, conv_arg, conv_arg_type, action); -#endif - -#if !defined(DISABLE_COM) - if ((spec && (spec->native == MONO_NATIVE_IUNKNOWN || - spec->native == MONO_NATIVE_IDISPATCH || - spec->native == MONO_NATIVE_INTERFACE)) || - (t->type == MONO_TYPE_CLASS && mono_cominterop_is_interface(t->data.klass))) - return mono_cominterop_emit_marshal_com_interface (m, argnum, t, spec, conv_arg, conv_arg_type, action); - if (spec && (spec->native == MONO_NATIVE_SAFEARRAY) && - (spec->data.safearray_data.elem_type == MONO_VARIANT_VARIANT) && - ((action == MARSHAL_ACTION_CONV_OUT) || (action == MARSHAL_ACTION_CONV_IN) || (action == MARSHAL_ACTION_PUSH))) - return mono_cominterop_emit_marshal_safearray (m, argnum, t, spec, conv_arg, conv_arg_type, action); -#endif - - if (cb_to_mono->try_get_safehandle_class () != NULL && t->data.klass && - cb_to_mono->is_subclass_of_internal (t->data.klass, cb_to_mono->try_get_safehandle_class (), FALSE)) - return get_marshal_cb ()->emit_marshal_safehandle (m, argnum, t, spec, conv_arg, conv_arg_type, action); - - return get_marshal_cb ()->emit_marshal_object (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: - return get_marshal_cb ()->emit_marshal_array (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_BOOLEAN: - return get_marshal_cb ()->emit_marshal_boolean (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_PTR: - return get_marshal_cb ()->emit_marshal_ptr (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_CHAR: - return get_marshal_cb ()->emit_marshal_char (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_I1: - case MONO_TYPE_U1: - case MONO_TYPE_I2: - case MONO_TYPE_U2: - case MONO_TYPE_I4: - case MONO_TYPE_U4: - case MONO_TYPE_I: - case MONO_TYPE_U: - case MONO_TYPE_R4: - case MONO_TYPE_R8: - case MONO_TYPE_I8: - case MONO_TYPE_U8: - case MONO_TYPE_FNPTR: - return lightweigth_cb->emit_marshal_scalar (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_GENERICINST: - if (mono_type_generic_inst_is_valuetype (t)) - return get_marshal_cb ()->emit_marshal_vtype (m, argnum, t, spec, conv_arg, conv_arg_type, action); - else - return get_marshal_cb ()->emit_marshal_object (m, argnum, t, spec, conv_arg, conv_arg_type, action); - default: - return conv_arg; - } -} - -void -mono_marshal_ilgen_init (void) -{ - MonoMarshalILgenCallbacks cb; - cb.version = MONO_MARSHAL_CALLBACKS_VERSION; - cb.emit_marshal_array = emit_marshal_array_ilgen; - cb.emit_marshal_ptr = emit_marshal_ptr_ilgen; - cb.emit_marshal_char = emit_marshal_char_ilgen; - cb.emit_marshal_vtype = emit_marshal_vtype_ilgen; - cb.emit_marshal_string = emit_marshal_string_ilgen; - cb.emit_marshal_variant = emit_marshal_variant_ilgen; - cb.emit_marshal_safehandle = emit_marshal_safehandle_ilgen; - cb.emit_marshal_object = emit_marshal_object_ilgen; - cb.emit_marshal_boolean = emit_marshal_boolean_ilgen; - cb.emit_marshal_custom = emit_marshal_custom_ilgen; - cb.emit_marshal_asany = emit_marshal_asany_ilgen; - cb.emit_marshal_handleref = emit_marshal_handleref_ilgen; - -#ifdef DISABLE_NONBLITTABLE - mono_marshal_noilgen_init_blittable (&cb); -#endif - mono_install_marshal_callbacks_ilgen (&cb); -} - - diff --git a/src/mono/mono/eglib/ghashtable.c b/src/mono/mono/eglib/ghashtable.c index 8b5c29a1a9044c..79b24603a560f8 100644 --- a/src/mono/mono/eglib/ghashtable.c +++ b/src/mono/mono/eglib/ghashtable.c @@ -673,7 +673,7 @@ guint g_str_hash (gconstpointer v1) { guint hash = 0; - char *p = (char *) v1; + unsigned char *p = (unsigned char *) v1; while (*p++) hash = (hash << 5) - (hash + *p); diff --git a/src/mono/mono/eventpipe/ep-rt-mono.c b/src/mono/mono/eventpipe/ep-rt-mono.c index ad5d7c67ba92cc..99096484d86e25 100644 --- a/src/mono/mono/eventpipe/ep-rt-mono.c +++ b/src/mono/mono/eventpipe/ep-rt-mono.c @@ -5526,7 +5526,7 @@ mono_profiler_fire_buffered_gc_event_roots ( objects++; // GCRoots.Values[].AddressID. - address_id = (uintptr_t)*objects; + address_id = (uintptr_t)*addresses; memcpy (buffer, &address_id, sizeof (address_id)); buffer += sizeof (address_id); addresses++; diff --git a/src/mono/mono/eventpipe/ep-rt-mono.h b/src/mono/mono/eventpipe/ep-rt-mono.h index dc07922690cce5..73cce439bdc7ff 100644 --- a/src/mono/mono/eventpipe/ep-rt-mono.h +++ b/src/mono/mono/eventpipe/ep-rt-mono.h @@ -1026,6 +1026,21 @@ ep_rt_config_value_get_rundown (void) return value_uint32_t; } +static +inline +bool +ep_rt_config_value_get_enable_stackwalk (void) +{ + uint32_t value_uint32_t = 1; + gchar *value = g_getenv ("DOTNET_EventPipeEnableStackwalk"); + if (!value) + value = g_getenv ("COMPlus_EventPipeEnableStackwalk"); + if (value) + value_uint32_t = (uint32_t)atoi (value); + g_free (value); + return value_uint32_t != 0; +} + /* * EventPipeSampleProfiler. */ diff --git a/src/mono/mono/metadata/CMakeLists.txt b/src/mono/mono/metadata/CMakeLists.txt index 69394f0aba1d08..3eb15313804f32 100644 --- a/src/mono/mono/metadata/CMakeLists.txt +++ b/src/mono/mono/metadata/CMakeLists.txt @@ -16,6 +16,8 @@ set(ilgen_base_sources method-builder-ilgen.c method-builder-ilgen.h method-builder-ilgen-internals.h + marshal-ilgen.c + marshal-ilgen.h marshal-lightweight.c marshal-lightweight.h marshal-shared.c @@ -95,7 +97,6 @@ set(metadata_common_sources marshal.h marshal-internals.h marshal-noilgen.c - marshal-noilgen.h mempool.c mempool.h mempool-internals.h diff --git a/src/mono/mono/metadata/assembly.c b/src/mono/mono/metadata/assembly.c index 43826b5a9236af..4142e3ce70ff81 100644 --- a/src/mono/mono/metadata/assembly.c +++ b/src/mono/mono/metadata/assembly.c @@ -268,7 +268,7 @@ mono_assembly_names_equal_flags (MonoAssemblyName *l, MonoAssemblyName *r, MonoA * if \p r is a lower version than \p l, or zero if \p l and \p r are equal * versions (comparing upto \p maxcomps components). * - * Components are \c major, \c minor, \c revision, and \c build. \p maxcomps 1 means just compare + * Components are \c major, \c minor, \c build, and \c revision. \p maxcomps 1 means just compare * majors. 2 means majors then minors. etc. */ static int @@ -284,9 +284,9 @@ assembly_names_compare_versions (MonoAssemblyName *l, MonoAssemblyName *r, int m ++i; CMP (minor); ++i; - CMP (revision); - ++i; CMP (build); + ++i; + CMP (revision); #undef CMP return 0; } @@ -2025,6 +2025,8 @@ mono_assembly_request_load_from (MonoImage *image, const char *fname, mono_image_fixup_vtable (image); #endif + *status = MONO_IMAGE_OK; + mono_assembly_invoke_load_hook_internal (req->alc, ass); MONO_PROFILER_RAISE (assembly_loaded, (ass)); diff --git a/src/mono/mono/metadata/class-setup-vtable.c b/src/mono/mono/metadata/class-setup-vtable.c index 2c29e30d05a05b..e9dc6997d89e7b 100644 --- a/src/mono/mono/metadata/class-setup-vtable.c +++ b/src/mono/mono/metadata/class-setup-vtable.c @@ -932,7 +932,8 @@ mono_class_setup_vtable_full (MonoClass *klass, GList *in_setup) context = mono_class_get_context (klass); type_token = mono_class_get_generic_class (klass)->container_class->type_token; } else { - context = (MonoGenericContext *) mono_class_try_get_generic_container (klass); //FIXME is this a case of a try? + MonoGenericContainer *container = mono_class_try_get_generic_container (klass); //FIXME is this a case of a try? + context = container ? &container->context : NULL; type_token = klass->type_token; } @@ -1010,30 +1011,14 @@ apply_override (MonoClass *klass, MonoClass *override_class, MonoMethod **vtable MonoMethod *prev_override = (MonoMethod*)g_hash_table_lookup (map, decl); MonoClass *prev_override_class = (MonoClass*)g_hash_table_lookup (class_map, decl); + g_assert (override_class == override->klass); + g_hash_table_insert (map, decl, override); g_hash_table_insert (class_map, decl, override_class); /* Collect potentially conflicting overrides which are introduced by default interface methods */ if (prev_override) { - ERROR_DECL (error); - - /* - * The override methods are part of the generic definition, need to inflate them so their - * parent class becomes the actual interface/class containing the override, i.e. - * IFace in: - * class Foo : IFace - * This is needed so the mono_class_is_assignable_from_internal () calls in the - * conflict resolution work. - */ - if (mono_class_is_ginst (override_class)) { - override = mono_class_inflate_generic_method_checked (override, &mono_class_get_generic_class (override_class)->context, error); - mono_error_assert_ok (error); - } - - if (mono_class_is_ginst (prev_override_class)) { - prev_override = mono_class_inflate_generic_method_checked (prev_override, &mono_class_get_generic_class (prev_override_class)->context, error); - mono_error_assert_ok (error); - } + g_assert (prev_override->klass == prev_override_class); if (!*conflict_map) *conflict_map = g_hash_table_new (mono_aligned_addr_hash, NULL); @@ -1671,6 +1656,10 @@ check_vtable_covariant_override_impls (MonoClass *klass, MonoMethod **vtable, in break; MonoMethod *prev_impl = cur_class->vtable[slot]; + // if the current class re-abstracted the method, it may not be there. + if (!prev_impl) + continue; + if (prev_impl != last_checked_prev_override) { /* * the new impl should be subsumed by the prior one, ie this @@ -1771,10 +1760,9 @@ mono_class_setup_vtable_general (MonoClass *klass, MonoMethod **overrides, int o MonoMethod *override = iface_overrides [i*2 + 1]; if (mono_class_is_gtd (override->klass)) { override = mono_class_inflate_generic_method_full_checked (override, ic, mono_class_get_context (ic), error); - } else if (decl->is_inflated) { - override = mono_class_inflate_generic_method_checked (override, mono_method_get_context (decl), error); - mono_error_assert_ok (error); - } + } + // there used to be code here to inflate decl if decl->is_inflated, but in https://github.com/dotnet/runtime/pull/64102#discussion_r790019545 we + // think that this does not correspond to any real code. if (!apply_override (klass, ic, vtable, decl, override, &override_map, &override_class_map, &conflict_map)) goto fail; } @@ -1786,6 +1774,18 @@ mono_class_setup_vtable_general (MonoClass *klass, MonoMethod **overrides, int o MonoMethod *decl = overrides [i*2]; MonoMethod *override = overrides [i*2 + 1]; if (MONO_CLASS_IS_INTERFACE_INTERNAL (decl->klass)) { + /* + * We expect override methods that are part of a generic definition, to have + * their parent class be the actual interface/class containing the override, + * i.e. + * + * IFace in: + * class Foo : IFace + * + * This is needed so the mono_class_is_assignable_from_internal () calls in the + * conflict resolution work. + */ + g_assert (override->klass == klass); if (!apply_override (klass, klass, vtable, decl, override, &override_map, &override_class_map, &conflict_map)) goto fail; } diff --git a/src/mono/mono/metadata/class.c b/src/mono/mono/metadata/class.c index 1e9e045b0a7c4a..dc72646333f7ff 100644 --- a/src/mono/mono/metadata/class.c +++ b/src/mono/mono/metadata/class.c @@ -4218,11 +4218,11 @@ mono_class_is_assignable_from_general (MonoClass *klass, MonoClass *oklass, gboo } if (m_class_get_byval_arg (klass)->type == MONO_TYPE_FNPTR) { - /* - * if both klass and oklass are fnptr, and they're equal, we would have returned at the - * beginning. - */ - /* Is this right? or do we need to look at signature compatibility? */ + if (mono_metadata_signature_equal (klass_byval_arg->data.method, oklass_byval_arg->data.method)) { + *result = TRUE; + return; + } + *result = FALSE; return; } @@ -6604,8 +6604,14 @@ mono_field_resolve_type (MonoClassField *field, MonoError *error) static guint32 mono_field_resolve_flags (MonoClassField *field) { - /* Fields in metadata updates are pre-resolved, so this method should not be called. */ - g_assert (!m_field_is_from_update (field)); + if (G_UNLIKELY (m_field_is_from_update (field))) { + /* metadata-update: Just resolve the whole field, for simplicity. */ + ERROR_DECL (error); + mono_field_resolve_type (field, error); + mono_error_assert_ok (error); + g_assert (field->type); + return field->type->attrs; + } MonoClass *klass = m_field_get_parent (field); MonoImage *image = m_class_get_image (klass); diff --git a/src/mono/mono/metadata/components.c b/src/mono/mono/metadata/components.c index 204c3d9c462f8b..f6f94a0b507c9e 100644 --- a/src/mono/mono/metadata/components.c +++ b/src/mono/mono/metadata/components.c @@ -43,9 +43,6 @@ typedef struct _MonoComponentEntry { #define DEBUGGER_LIBRARY_NAME "debugger" #define DEBUGGER_COMPONENT_NAME DEBUGGER_LIBRARY_NAME -#define MARSHAL_ILGEN_LIBRARY_NAME "marshal-ilgen" -#define MARSHAL_ILGEN_COMPONENT_NAME "marshal_ilgen" - MonoComponentHotReload *mono_component_hot_reload_private_ptr = NULL; MonoComponentDebugger *mono_component_debugger_private_ptr = NULL; @@ -53,8 +50,6 @@ MonoComponentDebugger *mono_component_debugger_private_ptr = NULL; MonoComponentEventPipe *mono_component_event_pipe_private_ptr = NULL; MonoComponentDiagnosticsServer *mono_component_diagnostics_server_private_ptr = NULL; -MonoComponentMarshalILgen* mono_component_marshal_ilgen_private_ptr = NULL; - // DiagnosticsServer/EventPipe components currently hosted by diagnostics_tracing library. #define DIAGNOSTICS_TRACING_LIBRARY_NAME "diagnostics_tracing" #define EVENT_PIPE_COMPONENT_NAME "event_pipe" @@ -66,7 +61,6 @@ MonoComponentEntry components[] = { { HOT_RELOAD_LIBRARY_NAME, HOT_RELOAD_COMPONENT_NAME, COMPONENT_INIT_FUNC (hot_reload), (MonoComponent**)&mono_component_hot_reload_private_ptr, NULL }, { DIAGNOSTICS_TRACING_LIBRARY_NAME, EVENT_PIPE_COMPONENT_NAME, COMPONENT_INIT_FUNC (event_pipe), (MonoComponent**)&mono_component_event_pipe_private_ptr, NULL }, { DIAGNOSTICS_TRACING_LIBRARY_NAME, DIAGNOSTICS_SERVER_COMPONENT_NAME, COMPONENT_INIT_FUNC (diagnostics_server), (MonoComponent**)&mono_component_diagnostics_server_private_ptr, NULL }, - { MARSHAL_ILGEN_LIBRARY_NAME, MARSHAL_ILGEN_COMPONENT_NAME, COMPONENT_INIT_FUNC (marshal_ilgen), (MonoComponent**)&mono_component_marshal_ilgen_private_ptr, NULL } }; #ifndef STATIC_COMPONENTS diff --git a/src/mono/mono/metadata/components.h b/src/mono/mono/metadata/components.h index f6b8696d194f05..aba03174589432 100644 --- a/src/mono/mono/metadata/components.h +++ b/src/mono/mono/metadata/components.h @@ -8,7 +8,6 @@ #include #include #include -#include #include #include @@ -25,7 +24,6 @@ extern MonoComponentHotReload *mono_component_hot_reload_private_ptr; extern MonoComponentEventPipe *mono_component_event_pipe_private_ptr; extern MonoComponentDiagnosticsServer *mono_component_diagnostics_server_private_ptr; extern MonoComponentDebugger *mono_component_debugger_private_ptr; -extern MonoComponentMarshalILgen *mono_component_marshal_ilgen_private_ptr; /* Declare each component's getter function here */ static inline @@ -56,11 +54,4 @@ mono_component_debugger (void) return mono_component_debugger_private_ptr; } -static inline -MonoComponentMarshalILgen* -mono_component_marshal_ilgen (void) -{ - return mono_component_marshal_ilgen_private_ptr; -} - -#endif/*_MONO_METADATA_COMPONENTS_H*/ \ No newline at end of file +#endif/*_MONO_METADATA_COMPONENTS_H*/ diff --git a/src/mono/mono/metadata/custom-attrs.c b/src/mono/mono/metadata/custom-attrs.c index 98dbd70d6b9334..2bce3b3e79781c 100644 --- a/src/mono/mono/metadata/custom-attrs.c +++ b/src/mono/mono/metadata/custom-attrs.c @@ -2248,15 +2248,12 @@ mono_custom_attrs_from_param_checked (MonoMethod *method, guint32 param, MonoErr method_index = mono_method_get_index (method); if (!method_index) return NULL; - ca = &image->tables [MONO_TABLE_METHOD]; - /* FIXME: metadata-update */ - param_list = mono_metadata_decode_row_col (ca, method_index - 1, MONO_METHOD_PARAMLIST); - if (method_index == table_info_get_rows (ca)) { - param_last = table_info_get_rows (&image->tables [MONO_TABLE_PARAM]) + 1; - } else { - param_last = mono_metadata_decode_row_col (ca, method_index, MONO_METHOD_PARAMLIST); - } + param_list = mono_metadata_get_method_params (image, method_index, ¶m_last); + + if (!param_list) + return NULL; + ca = &image->tables [MONO_TABLE_PARAM]; found = FALSE; diff --git a/src/mono/mono/metadata/icall-def.h b/src/mono/mono/metadata/icall-def.h index 5f47166ab1a646..073590ef5a9f11 100644 --- a/src/mono/mono/metadata/icall-def.h +++ b/src/mono/mono/metadata/icall-def.h @@ -357,6 +357,7 @@ HANDLES(RASSEM_7, "InternalGetReferencedAssemblies", ves_icall_System_Reflection ICALL_TYPE(MCMETH, "System.Reflection.RuntimeConstructorInfo", MCMETH_1) HANDLES(MCMETH_1, "GetGenericMethodDefinition_impl", ves_icall_RuntimeMethodInfo_GetGenericMethodDefinition, MonoReflectionMethod, 1, (MonoReflectionMethod)) HANDLES(MCMETH_2, "InternalInvoke", ves_icall_InternalInvoke, MonoObject, 4, (MonoReflectionMethod, MonoObject, MonoSpanOfObjects_ref, MonoExceptionOut)) +HANDLES(MCMETH_5, "InvokeClassConstructor", ves_icall_InvokeClassConstructor, void, 1, (MonoQCallTypeHandle)) HANDLES_REUSE_WRAPPER(MCMETH_4, "get_metadata_token", ves_icall_reflection_get_token) ICALL_TYPE(CATTR_DATA, "System.Reflection.RuntimeCustomAttributeData", CATTR_DATA_1) diff --git a/src/mono/mono/metadata/icall.c b/src/mono/mono/metadata/icall.c index 2fbdb8f330fe70..f8592363402cf4 100644 --- a/src/mono/mono/metadata/icall.c +++ b/src/mono/mono/metadata/icall.c @@ -949,7 +949,7 @@ ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_GetSpanDataFrom (MonoCl return NULL; } - MonoType *type = targetTypeHandle; + MonoType *type = mono_type_get_underlying_type (targetTypeHandle); if (MONO_TYPE_IS_REFERENCE (type) || type->type == MONO_TYPE_VALUETYPE) { mono_error_set_argument (error, "array", "Cannot initialize array of non-primitive type"); return NULL; @@ -2809,6 +2809,18 @@ ves_icall_RuntimeTypeHandle_IsComObject (MonoQCallTypeHandle type_handle, MonoEr return mono_class_is_com_object (klass); } +void +ves_icall_InvokeClassConstructor (MonoQCallTypeHandle type_handle, MonoError *error) +{ + MonoType *type = type_handle.type; + MonoClass *klass = mono_class_from_mono_type_internal (type); + + MonoVTable *vtable = mono_class_vtable_checked (klass, error); + return_if_nok (error); + + mono_runtime_class_init_full (vtable, error); +} + guint32 ves_icall_reflection_get_token (MonoObjectHandle obj, MonoError *error) { @@ -4020,8 +4032,6 @@ property_accessor_nonpublic (MonoMethod* accessor, gboolean start_klass) GPtrArray* ves_icall_RuntimeType_GetPropertiesByName_native (MonoQCallTypeHandle type_handle, gchar *propname, guint32 bflags, guint32 mlisttype, MonoError *error) { - // Fetch non-public properties as well because they can hide public properties with the same name in base classes - bflags |= BFLAGS_NonPublic; MonoType *type = type_handle.type; if (m_type_is_byref (type)) @@ -4060,11 +4070,9 @@ ves_icall_RuntimeType_GetPropertiesByName_native (MonoQCallTypeHandle type_handl (prop->set && ((prop->set->flags & METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == METHOD_ATTRIBUTE_PUBLIC))) { if (bflags & BFLAGS_Public) match++; - } else if (bflags & BFLAGS_NonPublic) { - if (property_accessor_nonpublic(prop->get, startklass == klass) || + } else if (property_accessor_nonpublic(prop->get, startklass == klass) || property_accessor_nonpublic(prop->set, startklass == klass)) { match++; - } } if (!match) continue; @@ -4094,8 +4102,6 @@ ves_icall_RuntimeType_GetPropertiesByName_native (MonoQCallTypeHandle type_handl g_hash_table_insert (properties, prop, prop); } if (!(bflags & BFLAGS_DeclaredOnly) && (klass = m_class_get_parent (klass))) { - // BFLAGS_NonPublic should be excluded for base classes - bflags &= ~BFLAGS_NonPublic; goto handle_parent; } @@ -7177,7 +7183,7 @@ ves_icall_System_Threading_Thread_YieldInternal (void) gint32 ves_icall_System_Environment_get_ProcessorCount (void) { - return mono_cpu_count (); + return mono_cpu_limit (); } // Generate wrappers. diff --git a/src/mono/mono/metadata/loader.c b/src/mono/mono/metadata/loader.c index 86b60c0a6e3236..51803fa5808f72 100644 --- a/src/mono/mono/metadata/loader.c +++ b/src/mono/mono/metadata/loader.c @@ -1409,7 +1409,6 @@ mono_method_get_param_names (MonoMethod *method, const char **names) { int i, lastp; MonoClass *klass; - MonoTableInfo *methodt; MonoTableInfo *paramt; MonoMethodSignature *signature; guint32 idx; @@ -1463,19 +1462,18 @@ mono_method_get_param_names (MonoMethod *method, const char **names) return; } - methodt = &klass_image->tables [MONO_TABLE_METHOD]; paramt = &klass_image->tables [MONO_TABLE_PARAM]; idx = mono_method_get_index (method); if (idx > 0) { + guint32 cols [MONO_PARAM_SIZE]; guint param_index; - param_index = mono_metadata_decode_row_col (methodt, idx - 1, MONO_METHOD_PARAMLIST); + param_index = mono_metadata_get_method_params (klass_image, idx, (uint32_t*)&lastp); + + if (!param_index) + return; - if (idx < table_info_get_rows (methodt)) - lastp = mono_metadata_decode_row_col (methodt, idx, MONO_METHOD_PARAMLIST); - else - lastp = table_info_get_rows (paramt) + 1; for (i = param_index; i < lastp; ++i) { mono_metadata_decode_row (paramt, i -1, cols, MONO_PARAM_SIZE); if (cols [MONO_PARAM_SEQUENCE] && cols [MONO_PARAM_SEQUENCE] <= signature->param_count) /* skip return param spec and bounds check*/ @@ -1491,7 +1489,6 @@ guint32 mono_method_get_param_token (MonoMethod *method, int index) { MonoClass *klass = method->klass; - MonoTableInfo *methodt; guint32 idx; mono_class_init_internal (klass); @@ -1499,11 +1496,10 @@ mono_method_get_param_token (MonoMethod *method, int index) MonoImage *klass_image = m_class_get_image (klass); g_assert (!image_is_dynamic (klass_image)); - methodt = &klass_image->tables [MONO_TABLE_METHOD]; idx = mono_method_get_index (method); if (idx > 0) { - guint param_index = mono_metadata_decode_row_col (methodt, idx - 1, MONO_METHOD_PARAMLIST); - + guint param_index = mono_metadata_get_method_params (klass_image, idx, NULL); + if (index == -1) /* Return value */ return mono_metadata_make_token (MONO_TABLE_PARAM, 0); @@ -1522,7 +1518,6 @@ mono_method_get_marshal_info (MonoMethod *method, MonoMarshalSpec **mspecs) { int i, lastp; MonoClass *klass = method->klass; - MonoTableInfo *methodt; MonoTableInfo *paramt; MonoMethodSignature *signature; guint32 idx; @@ -1560,17 +1555,14 @@ mono_method_get_marshal_info (MonoMethod *method, MonoMarshalSpec **mspecs) mono_class_init_internal (klass); MonoImage *klass_image = m_class_get_image (klass); - methodt = &klass_image->tables [MONO_TABLE_METHOD]; paramt = &klass_image->tables [MONO_TABLE_PARAM]; idx = mono_method_get_index (method); if (idx > 0) { guint32 cols [MONO_PARAM_SIZE]; - guint param_index = mono_metadata_decode_row_col (methodt, idx - 1, MONO_METHOD_PARAMLIST); + guint param_index = mono_metadata_get_method_params (klass_image, idx, (uint32_t*)&lastp); - if (idx < table_info_get_rows (methodt)) - lastp = mono_metadata_decode_row_col (methodt, idx, MONO_METHOD_PARAMLIST); - else - lastp = table_info_get_rows (paramt) + 1; + if (!param_index) + return; for (i = param_index; i < lastp; ++i) { mono_metadata_decode_row (paramt, i -1, cols, MONO_PARAM_SIZE); @@ -1595,7 +1587,6 @@ mono_method_has_marshal_info (MonoMethod *method) { int i, lastp; MonoClass *klass = method->klass; - MonoTableInfo *methodt; MonoTableInfo *paramt; guint32 idx; @@ -1614,17 +1605,15 @@ mono_method_has_marshal_info (MonoMethod *method) mono_class_init_internal (klass); - methodt = &m_class_get_image (klass)->tables [MONO_TABLE_METHOD]; - paramt = &m_class_get_image (klass)->tables [MONO_TABLE_PARAM]; + MonoImage *klass_image = m_class_get_image (klass); + paramt = &klass_image->tables [MONO_TABLE_PARAM]; idx = mono_method_get_index (method); if (idx > 0) { guint32 cols [MONO_PARAM_SIZE]; - guint param_index = mono_metadata_decode_row_col (methodt, idx - 1, MONO_METHOD_PARAMLIST); + guint param_index = mono_metadata_get_method_params (klass_image, idx, (uint32_t*)&lastp); - if (idx + 1 < table_info_get_rows (methodt)) - lastp = mono_metadata_decode_row_col (methodt, idx, MONO_METHOD_PARAMLIST); - else - lastp = table_info_get_rows (paramt) + 1; + if (!param_index) + return FALSE; for (i = param_index; i < lastp; ++i) { mono_metadata_decode_row (paramt, i -1, cols, MONO_PARAM_SIZE); diff --git a/src/mono/mono/metadata/marshal-ilgen.c b/src/mono/mono/metadata/marshal-ilgen.c new file mode 100644 index 00000000000000..a67290f8580403 --- /dev/null +++ b/src/mono/mono/metadata/marshal-ilgen.c @@ -0,0 +1,2824 @@ +#include "mono/metadata/debug-helpers.h" +#include "metadata/marshal.h" +#include "metadata/marshal-ilgen.h" +#include "metadata/marshal-lightweight.h" +#include "metadata/marshal-shared.h" +#include "metadata/method-builder-ilgen.h" +#include "metadata/custom-attrs-internals.h" +#include "metadata/class-init.h" +#include "mono/metadata/class-internals.h" +#include "metadata/reflection-internals.h" +#include "mono/metadata/handle.h" + + + +#define OPDEF(a,b,c,d,e,f,g,h,i,j) \ + a = i, + +enum { +#include "mono/cil/opcode.def" + LAST = 0xff +}; +#undef OPDEF + +static GENERATE_GET_CLASS_WITH_CACHE (date_time, "System", "DateTime"); +static GENERATE_TRY_GET_CLASS_WITH_CACHE (icustom_marshaler, "System.Runtime.InteropServices", "ICustomMarshaler"); + +static void emit_string_free_icall (MonoMethodBuilder *mb, MonoMarshalConv conv); + +// TODO: Does this need to loose the mono_ prefix? +static void mono_marshal_ilgen_legacy_init (void); + +static gboolean ilgen_cb_inited = FALSE; +static MonoMarshalIlgenCallbacks ilgen_marshal_cb; + +void +mono_install_marshal_callbacks_ilgen (MonoMarshalIlgenCallbacks *cb) +{ + g_assert (!ilgen_cb_inited); + g_assert (cb->version == MONO_MARSHAL_CALLBACKS_VERSION); + memcpy (&ilgen_marshal_cb, cb, sizeof (MonoMarshalIlgenCallbacks)); + ilgen_cb_inited = TRUE; +} + + +static void +emit_struct_free (MonoMethodBuilder *mb, MonoClass *klass, int struct_var) +{ + /* Call DestroyStructure */ + /* FIXME: Only do this if needed */ + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); + mono_mb_emit_ldloc (mb, struct_var); + mono_mb_emit_icall (mb, mono_struct_delete_old); +} + +static int +emit_marshal_array_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + MonoClass *klass = mono_class_from_mono_type_internal (t); + MonoMarshalNative encoding; + + encoding = mono_marshal_get_string_encoding (m->piinfo, spec); + MonoType *int_type = mono_get_int_type (); + MonoType *object_type = mono_get_object_type (); + + MonoClass *eklass = m_class_get_element_class (klass); + + switch (action) { + case MARSHAL_ACTION_CONV_IN: + *conv_arg_type = object_type; + conv_arg = mono_mb_add_local (mb, object_type); + + if (m_class_is_blittable (eklass)) { + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_CONV_ARRAY_LPARRAY, NULL)); + mono_mb_emit_stloc (mb, conv_arg); + } else { +#ifdef DISABLE_NONBLITTABLE + char *msg = g_strdup ("Non-blittable marshalling conversion is disabled"); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); +#else + guint32 label1, label2, label3; + int index_var, src_var, dest_ptr, esize; + MonoMarshalConv conv; + gboolean is_string = FALSE; + + dest_ptr = mono_mb_add_local (mb, int_type); + + if (eklass == mono_defaults.string_class) { + is_string = TRUE; + conv = mono_marshal_get_string_to_ptr_conv (m->piinfo, spec); + } + else if (eklass == mono_class_try_get_stringbuilder_class ()) { + is_string = TRUE; + conv = mono_marshal_get_stringbuilder_to_ptr_conv (m->piinfo, spec); + } + else + conv = MONO_MARSHAL_CONV_INVALID; + + if (is_string && conv == MONO_MARSHAL_CONV_INVALID) { + char *msg = g_strdup_printf ("string/stringbuilder marshalling conversion %d not implemented", encoding); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + src_var = mono_mb_add_local (mb, object_type); + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_stloc (mb, src_var); + + /* Check null */ + mono_mb_emit_ldloc (mb, src_var); + mono_mb_emit_stloc (mb, conv_arg); + mono_mb_emit_ldloc (mb, src_var); + label1 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + if (is_string) + esize = TARGET_SIZEOF_VOID_P; + else if (eklass == mono_defaults.char_class) /*can't call mono_marshal_type_size since it causes all sorts of asserts*/ + esize = mono_pinvoke_is_unicode (m->piinfo) ? 2 : 1; + else + esize = mono_class_native_size (eklass, NULL); + + /* allocate space for the native struct and store the address */ + mono_mb_emit_icon (mb, esize); + mono_mb_emit_ldloc (mb, src_var); + mono_mb_emit_byte (mb, CEE_LDLEN); + + if (eklass == mono_defaults.string_class) { + /* Make the array bigger for the terminating null */ + mono_mb_emit_byte (mb, CEE_LDC_I4_1); + mono_mb_emit_byte (mb, CEE_ADD); + } + mono_mb_emit_byte (mb, CEE_MUL); + mono_mb_emit_byte (mb, CEE_PREFIX1); + mono_mb_emit_byte (mb, CEE_LOCALLOC); + mono_mb_emit_stloc (mb, conv_arg); + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_stloc (mb, dest_ptr); + + /* Emit marshalling loop */ + index_var = mono_mb_add_local (mb, int_type); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_stloc (mb, index_var); + label2 = mono_mb_get_label (mb); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_ldloc (mb, src_var); + mono_mb_emit_byte (mb, CEE_LDLEN); + label3 = mono_mb_emit_branch (mb, CEE_BGE); + + /* Emit marshalling code */ + + if (is_string) { + int stind_op; + mono_mb_emit_ldloc (mb, dest_ptr); + mono_mb_emit_ldloc (mb, src_var); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_byte (mb, CEE_LDELEM_REF); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, &stind_op)); + mono_mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); + } else { + /* set the src_ptr */ + mono_mb_emit_ldloc (mb, src_var); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_op (mb, CEE_LDELEMA, eklass); + mono_mb_emit_stloc (mb, 0); + + /* set dst_ptr */ + mono_mb_emit_ldloc (mb, dest_ptr); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv_full (mb, eklass, FALSE, 0, eklass == mono_defaults.char_class ? encoding : (MonoMarshalNative)-1); + } + + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (dest_ptr), esize); + + mono_mb_emit_branch_label (mb, CEE_BR, label2); + + mono_mb_patch_branch (mb, label3); + + if (eklass == mono_defaults.string_class) { + /* Null terminate */ + mono_mb_emit_ldloc (mb, dest_ptr); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_byte (mb, CEE_STIND_I); + } + + mono_mb_patch_branch (mb, label1); +#endif + } + + break; + + case MARSHAL_ACTION_CONV_OUT: { +#ifndef DISABLE_NONBLITTABLE + gboolean need_convert, need_free; + /* Unicode character arrays are implicitly marshalled as [Out] under MS.NET */ + need_convert = ((eklass == mono_defaults.char_class) && (encoding == MONO_NATIVE_LPWSTR)) || (eklass == mono_class_try_get_stringbuilder_class ()) || (t->attrs & PARAM_ATTRIBUTE_OUT); + need_free = mono_marshal_need_free (m_class_get_byval_arg (eklass), m->piinfo, spec); + + if ((t->attrs & PARAM_ATTRIBUTE_OUT) && spec && spec->native == MONO_NATIVE_LPARRAY && spec->data.array_data.param_num != -1) { + int param_num = spec->data.array_data.param_num; + MonoType *param_type; + + param_type = m->sig->params [param_num]; + + if (m_type_is_byref (param_type) && param_type->type != MONO_TYPE_I4) { + char *msg = g_strdup ("Not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + if (m_type_is_byref (t) ) { + mono_mb_emit_ldarg (mb, argnum); + + /* Create the managed array */ + mono_mb_emit_ldarg (mb, param_num); + if (m_type_is_byref (m->sig->params [param_num])) + // FIXME: Support other types + mono_mb_emit_byte (mb, CEE_LDIND_I4); + mono_mb_emit_byte (mb, CEE_CONV_OVF_I); + mono_mb_emit_op (mb, CEE_NEWARR, eklass); + /* Store into argument */ + mono_mb_emit_byte (mb, CEE_STIND_REF); + } + } + + if (need_convert || need_free) { + /* FIXME: Optimize blittable case */ + guint32 label1, label2, label3; + int index_var, src_ptr, loc, esize; + + if ((eklass == mono_class_try_get_stringbuilder_class ()) || (eklass == mono_defaults.string_class)) + esize = TARGET_SIZEOF_VOID_P; + else if (eklass == mono_defaults.char_class) + esize = mono_pinvoke_is_unicode (m->piinfo) ? 2 : 1; + else + esize = mono_class_native_size (eklass, NULL); + src_ptr = mono_mb_add_local (mb, int_type); + loc = mono_mb_add_local (mb, int_type); + + /* Check null */ + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + label1 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_stloc (mb, src_ptr); + + /* Emit marshalling loop */ + index_var = mono_mb_add_local (mb, int_type); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_stloc (mb, index_var); + label2 = mono_mb_get_label (mb); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_REF); + mono_mb_emit_byte (mb, CEE_LDLEN); + label3 = mono_mb_emit_branch (mb, CEE_BGE); + + /* Emit marshalling code */ + + if (eklass == mono_class_try_get_stringbuilder_class ()) { + gboolean need_free2; + MonoMarshalConv conv = mono_marshal_get_ptr_to_stringbuilder_conv (m->piinfo, spec, &need_free2); + + g_assert (conv != MONO_MARSHAL_CONV_INVALID); + + /* dest */ + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_byte (mb, CEE_LDELEM_REF); + + /* src */ + mono_mb_emit_ldloc (mb, src_ptr); + mono_mb_emit_byte (mb, CEE_LDIND_I); + + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + + if (need_free) { + /* src */ + mono_mb_emit_ldloc (mb, src_ptr); + mono_mb_emit_byte (mb, CEE_LDIND_I); + + mono_mb_emit_icall (mb, mono_marshal_free); + } + } + else if (eklass == mono_defaults.string_class) { + if (need_free) { + /* src */ + mono_mb_emit_ldloc (mb, src_ptr); + mono_mb_emit_byte (mb, CEE_LDIND_I); + + mono_mb_emit_icall (mb, mono_marshal_free); + } + } + else { + if (need_convert) { + /* set the src_ptr */ + mono_mb_emit_ldloc (mb, src_ptr); + mono_mb_emit_stloc (mb, 0); + + /* set dst_ptr */ + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_REF); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_op (mb, CEE_LDELEMA, eklass); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv_full (mb, eklass, TRUE, 0, eklass == mono_defaults.char_class ? encoding : (MonoMarshalNative)-1); + } + + if (need_free) { + mono_mb_emit_ldloc (mb, src_ptr); + mono_mb_emit_stloc (mb, loc); + + emit_struct_free (mb, eklass, loc); + } + } + + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (src_ptr), esize); + + mono_mb_emit_branch_label (mb, CEE_BR, label2); + + mono_mb_patch_branch (mb, label1); + mono_mb_patch_branch (mb, label3); + } +#endif + + if (m_class_is_blittable (eklass)) { + /* free memory allocated (if any) by MONO_MARSHAL_CONV_ARRAY_LPARRAY */ + + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_REF); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_FREE_LPARRAY, NULL)); + } + + break; + } + + case MARSHAL_ACTION_PUSH: + if (m_type_is_byref (t)) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_RESULT: { + mono_mb_emit_byte (mb, CEE_POP); + char *msg = g_strdup_printf ("Cannot marshal 'return value': Invalid managed/unmanaged type combination."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + case MARSHAL_ACTION_MANAGED_CONV_IN: { + guint32 label1, label2, label3; + int index_var, src_ptr, esize, param_num, num_elem; + MonoMarshalConv conv; + gboolean is_string = FALSE; + + conv_arg = mono_mb_add_local (mb, object_type); + *conv_arg_type = int_type; + + if (m_type_is_byref (t)) { + char *msg = g_strdup ("Byref array marshalling to managed code is not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + if (!spec) { + char *msg = g_strdup ("[MarshalAs] attribute required to marshal arrays to managed code."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + + switch (spec->native) { + case MONO_NATIVE_LPARRAY: + break; + case MONO_NATIVE_SAFEARRAY: +#ifndef DISABLE_COM + if (spec->data.safearray_data.elem_type != MONO_VARIANT_VARIANT) { + char *msg = g_strdup ("Only SAFEARRAY(VARIANT) marshalling to managed code is implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + return mono_cominterop_emit_marshal_safearray (m, argnum, t, spec, conv_arg, conv_arg_type, action); +#endif + default: { + char *msg = g_strdup ("Unsupported array type marshalling to managed code."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + } + + /* FIXME: t is from the method which is wrapped, not the delegate type */ + /* g_assert (t->attrs & PARAM_ATTRIBUTE_IN); */ + + param_num = spec->data.array_data.param_num; + num_elem = spec->data.array_data.num_elem; + if (spec->data.array_data.elem_mult == 0) + /* param_num is not specified */ + param_num = -1; + + if (param_num == -1) { + if (num_elem <= 0) { + char *msg = g_strdup ("Either SizeConst or SizeParamIndex should be specified when marshalling arrays to managed code."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + } + + /* FIXME: Optimize blittable case */ + +#ifndef DISABLE_NONBLITTABLE + if (eklass == mono_defaults.string_class) { + is_string = TRUE; + gboolean need_free; + conv = mono_marshal_get_ptr_to_string_conv (m->piinfo, spec, &need_free); + } + else if (eklass == mono_class_try_get_stringbuilder_class ()) { + is_string = TRUE; + gboolean need_free; + conv = mono_marshal_get_ptr_to_stringbuilder_conv (m->piinfo, spec, &need_free); + } + else + conv = MONO_MARSHAL_CONV_INVALID; +#endif + + mono_marshal_load_type_info (eklass); + + if (is_string) + esize = TARGET_SIZEOF_VOID_P; + else + esize = mono_class_native_size (eklass, NULL); + src_ptr = mono_mb_add_local (mb, int_type); + + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, conv_arg); + + /* Check param index */ + if (param_num != -1) { + if (param_num >= m->sig->param_count) { + char *msg = g_strdup ("Array size control parameter index is out of range."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + switch (m->sig->params [param_num]->type) { + case MONO_TYPE_I1: + case MONO_TYPE_U1: + case MONO_TYPE_I2: + case MONO_TYPE_U2: + case MONO_TYPE_I4: + case MONO_TYPE_U4: + case MONO_TYPE_I: + case MONO_TYPE_U: + case MONO_TYPE_I8: + case MONO_TYPE_U8: + break; + default: { + char *msg = g_strdup ("Array size control parameter must be an integral type."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + } + } + + /* Check null */ + mono_mb_emit_ldarg (mb, argnum); + label1 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_stloc (mb, src_ptr); + + /* Create managed array */ + /* + * The LPArray marshalling spec says that sometimes param_num starts + * from 1, sometimes it starts from 0. But MS seems to always start + * from 0. + */ + + if (param_num == -1) { + mono_mb_emit_icon (mb, num_elem); + } else { + mono_mb_emit_ldarg (mb, param_num); + if (num_elem > 0) { + mono_mb_emit_icon (mb, num_elem); + mono_mb_emit_byte (mb, CEE_ADD); + } + mono_mb_emit_byte (mb, CEE_CONV_OVF_I); + } + + mono_mb_emit_op (mb, CEE_NEWARR, eklass); + mono_mb_emit_stloc (mb, conv_arg); + + if (m_class_is_blittable (eklass)) { + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_byte (mb, CEE_CONV_I); + mono_mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoArray, vector)); + mono_mb_emit_byte (mb, CEE_ADD); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_byte (mb, CEE_LDLEN); + mono_mb_emit_icon (mb, esize); + mono_mb_emit_byte (mb, CEE_MUL); + mono_mb_emit_byte (mb, CEE_PREFIX1); + mono_mb_emit_byte (mb, CEE_CPBLK); + mono_mb_patch_branch (mb, label1); + break; + } +#ifdef DISABLE_NONBLITTABLE + else { + char *msg = g_strdup ("Non-blittable marshalling conversion is disabled"); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + } +#else + /* Emit marshalling loop */ + index_var = mono_mb_add_local (mb, int_type); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_stloc (mb, index_var); + label2 = mono_mb_get_label (mb); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_byte (mb, CEE_LDLEN); + label3 = mono_mb_emit_branch (mb, CEE_BGE); + + /* Emit marshalling code */ + if (is_string) { + g_assert (conv != MONO_MARSHAL_CONV_INVALID); + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_ldloc (mb, index_var); + + mono_mb_emit_ldloc (mb, src_ptr); + mono_mb_emit_byte (mb, CEE_LDIND_I); + + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + mono_mb_emit_byte (mb, CEE_STELEM_REF); + } + else { + char *msg = g_strdup ("Marshalling of non-string and non-blittable arrays to managed code is not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (src_ptr), esize); + + mono_mb_emit_branch_label (mb, CEE_BR, label2); + + mono_mb_patch_branch (mb, label1); + mono_mb_patch_branch (mb, label3); +#endif + + break; + } + case MARSHAL_ACTION_MANAGED_CONV_OUT: { + guint32 label1, label2, label3; + int index_var, dest_ptr, esize, param_num, num_elem; + MonoMarshalConv conv; + gboolean is_string = FALSE; + + if (!spec) + /* Already handled in CONV_IN */ + break; + + /* These are already checked in CONV_IN */ + g_assert (!m_type_is_byref (t)); + g_assert (spec->native == MONO_NATIVE_LPARRAY); + g_assert (t->attrs & PARAM_ATTRIBUTE_OUT); + + param_num = spec->data.array_data.param_num; + num_elem = spec->data.array_data.num_elem; + + if (spec->data.array_data.elem_mult == 0) + /* param_num is not specified */ + param_num = -1; + + if (param_num == -1) { + if (num_elem <= 0) { + g_assert_not_reached (); + } + } + + /* FIXME: Optimize blittable case */ + +#ifndef DISABLE_NONBLITTABLE + if (eklass == mono_defaults.string_class) { + is_string = TRUE; + conv = mono_marshal_get_string_to_ptr_conv (m->piinfo, spec); + } + else if (eklass == mono_class_try_get_stringbuilder_class ()) { + is_string = TRUE; + conv = mono_marshal_get_stringbuilder_to_ptr_conv (m->piinfo, spec); + } + else + conv = MONO_MARSHAL_CONV_INVALID; +#endif + + mono_marshal_load_type_info (eklass); + + if (is_string) + esize = TARGET_SIZEOF_VOID_P; + else + esize = mono_class_native_size (eklass, NULL); + + dest_ptr = mono_mb_add_local (mb, int_type); + + /* Check null */ + mono_mb_emit_ldloc (mb, conv_arg); + label1 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_stloc (mb, dest_ptr); + + if (m_class_is_blittable (eklass)) { + /* dest */ + mono_mb_emit_ldarg (mb, argnum); + /* src */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_byte (mb, CEE_CONV_I); + mono_mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoArray, vector)); + mono_mb_emit_byte (mb, CEE_ADD); + /* length */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_byte (mb, CEE_LDLEN); + mono_mb_emit_icon (mb, esize); + mono_mb_emit_byte (mb, CEE_MUL); + mono_mb_emit_byte (mb, CEE_PREFIX1); + mono_mb_emit_byte (mb, CEE_CPBLK); + mono_mb_patch_branch (mb, label1); + break; + } + +#ifndef DISABLE_NONBLITTABLE + /* Emit marshalling loop */ + index_var = mono_mb_add_local (mb, int_type); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_stloc (mb, index_var); + label2 = mono_mb_get_label (mb); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_byte (mb, CEE_LDLEN); + label3 = mono_mb_emit_branch (mb, CEE_BGE); + + /* Emit marshalling code */ + if (is_string) { + int stind_op; + g_assert (conv != MONO_MARSHAL_CONV_INVALID); + + /* dest */ + mono_mb_emit_ldloc (mb, dest_ptr); + + /* src */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_ldloc (mb, index_var); + + mono_mb_emit_byte (mb, CEE_LDELEM_REF); + + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, &stind_op)); + mono_mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); + } + else { + char *msg = g_strdup ("Marshalling of non-string and non-blittable arrays to managed code is not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (dest_ptr), esize); + + mono_mb_emit_branch_label (mb, CEE_BR, label2); + + mono_mb_patch_branch (mb, label1); + mono_mb_patch_branch (mb, label3); +#endif + + break; + } + case MARSHAL_ACTION_MANAGED_CONV_RESULT: { +#ifndef DISABLE_NONBLITTABLE + guint32 label1, label2, label3; + int index_var, src, dest, esize; + MonoMarshalConv conv = MONO_MARSHAL_CONV_INVALID; + gboolean is_string = FALSE; + + g_assert (!m_type_is_byref (t)); + + mono_marshal_load_type_info (eklass); + + if (eklass == mono_defaults.string_class) { + is_string = TRUE; + conv = mono_marshal_get_string_to_ptr_conv (m->piinfo, spec); + } + else { + g_assert_not_reached (); + } + + if (is_string) + esize = TARGET_SIZEOF_VOID_P; + else if (eklass == mono_defaults.char_class) + esize = mono_pinvoke_is_unicode (m->piinfo) ? 2 : 1; + else + esize = mono_class_native_size (eklass, NULL); + + src = mono_mb_add_local (mb, object_type); + dest = mono_mb_add_local (mb, int_type); + + mono_mb_emit_stloc (mb, src); + mono_mb_emit_ldloc (mb, src); + mono_mb_emit_stloc (mb, 3); + + /* Check for null */ + mono_mb_emit_ldloc (mb, src); + label1 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + /* Allocate native array */ + mono_mb_emit_icon (mb, esize); + mono_mb_emit_ldloc (mb, src); + mono_mb_emit_byte (mb, CEE_LDLEN); + + if (eklass == mono_defaults.string_class) { + /* Make the array bigger for the terminating null */ + mono_mb_emit_byte (mb, CEE_LDC_I4_1); + mono_mb_emit_byte (mb, CEE_ADD); + } + mono_mb_emit_byte (mb, CEE_MUL); + mono_mb_emit_icall (mb, ves_icall_marshal_alloc); + mono_mb_emit_stloc (mb, dest); + mono_mb_emit_ldloc (mb, dest); + mono_mb_emit_stloc (mb, 3); + + /* Emit marshalling loop */ + index_var = mono_mb_add_local (mb, int_type); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_stloc (mb, index_var); + label2 = mono_mb_get_label (mb); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_ldloc (mb, src); + mono_mb_emit_byte (mb, CEE_LDLEN); + label3 = mono_mb_emit_branch (mb, CEE_BGE); + + /* Emit marshalling code */ + if (is_string) { + int stind_op; + g_assert (conv != MONO_MARSHAL_CONV_INVALID); + + /* dest */ + mono_mb_emit_ldloc (mb, dest); + + /* src */ + mono_mb_emit_ldloc (mb, src); + mono_mb_emit_ldloc (mb, index_var); + + mono_mb_emit_byte (mb, CEE_LDELEM_REF); + + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, &stind_op)); + mono_mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); + } + else { + char *msg = g_strdup ("Marshalling of non-string arrays to managed code is not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (dest), esize); + + mono_mb_emit_branch_label (mb, CEE_BR, label2); + + mono_mb_patch_branch (mb, label3); + mono_mb_patch_branch (mb, label1); +#endif + break; + } + default: + g_assert_not_reached (); + } + return conv_arg; +} + +static gboolean +emit_native_wrapper_validate_signature (MonoMethodBuilder *mb, MonoMethodSignature* sig, MonoMarshalSpec** mspecs) +{ + if (mspecs) { + for (int i = 0; i < sig->param_count; i ++) { + if (mspecs [i + 1] && mspecs [i + 1]->native == MONO_NATIVE_CUSTOM) { + if (!mspecs [i + 1]->data.custom_data.custom_name || *mspecs [i + 1]->data.custom_data.custom_name == '\0') { + mono_mb_emit_exception_full (mb, "System", "TypeLoadException", g_strdup ("Missing ICustomMarshaler type")); + return FALSE; + } + + switch (sig->params[i]->type) { + case MONO_TYPE_CLASS: + case MONO_TYPE_OBJECT: + case MONO_TYPE_STRING: + case MONO_TYPE_ARRAY: + case MONO_TYPE_SZARRAY: + case MONO_TYPE_VALUETYPE: + break; + + default: + mono_mb_emit_exception_full (mb, "System.Runtime.InteropServices", "MarshalDirectiveException", g_strdup_printf ("custom marshalling of type %x is currently not supported", sig->params[i]->type)); + return FALSE; + } + } + else if (sig->params[i]->type == MONO_TYPE_VALUETYPE) { + MonoMarshalType *marshal_type = mono_marshal_load_type_info (mono_class_from_mono_type_internal (sig->params [i])); + for (guint32 field_idx = 0; field_idx < marshal_type->num_fields; ++field_idx) { + if (marshal_type->fields [field_idx].mspec && marshal_type->fields [field_idx].mspec->native == MONO_NATIVE_CUSTOM) { + mono_mb_emit_exception_full (mb, "System", "TypeLoadException", g_strdup ("Value type includes custom marshaled fields")); + return FALSE; + } + } + } + } + } + + return TRUE; +} + +static int +emit_marshal_ptr_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + switch (action) { + case MARSHAL_ACTION_CONV_IN: + /* MS seems to allow this in some cases, ie. bxc #158 */ + /* + if (MONO_TYPE_ISSTRUCT (t->data.type) && !mono_class_from_mono_type_internal (t->data.type)->blittable) { + char *msg = g_strdup_printf ("Can not marshal 'parameter #%d': Pointers can not reference marshaled structures. Use byref instead.", argnum + 1); + mono_marshal_shared_mb_emit_exception_marshal_directive (m->mb, msg); + } + */ + break; + + case MARSHAL_ACTION_PUSH: + mono_mb_emit_ldarg (mb, argnum); + break; + + case MARSHAL_ACTION_CONV_RESULT: + /* no conversions necessary */ + mono_mb_emit_stloc (mb, 3); + break; + + default: + break; + } + return conv_arg; +} + +static int +emit_marshal_boolean_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + MonoType *int_type = mono_get_int_type (); + MonoType *boolean_type = m_class_get_byval_arg (mono_defaults.boolean_class); + + switch (action) { + case MARSHAL_ACTION_CONV_IN: { + MonoType *local_type; + int label_false; + guint8 ldc_op = CEE_LDC_I4_1; + + local_type = mono_marshal_boolean_conv_in_get_local_type (spec, &ldc_op); + if (m_type_is_byref (t)) + *conv_arg_type = int_type; + else + *conv_arg_type = local_type; + conv_arg = mono_mb_add_local (mb, local_type); + + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I1); + label_false = mono_mb_emit_branch (mb, CEE_BRFALSE); + mono_mb_emit_byte (mb, ldc_op); + mono_mb_emit_stloc (mb, conv_arg); + mono_mb_patch_branch (mb, label_false); + + break; + } + + case MARSHAL_ACTION_CONV_OUT: + { + int label_false, label_end; + if (!m_type_is_byref (t)) + break; + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + + label_false = mono_mb_emit_branch (mb, CEE_BRFALSE); + mono_mb_emit_byte (mb, CEE_LDC_I4_1); + + label_end = mono_mb_emit_branch (mb, CEE_BR); + mono_mb_patch_branch (mb, label_false); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_patch_branch (mb, label_end); + + mono_mb_emit_byte (mb, CEE_STIND_I1); + break; + } + + case MARSHAL_ACTION_PUSH: + if (m_type_is_byref (t)) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else if (conv_arg) + mono_mb_emit_ldloc (mb, conv_arg); + else + mono_mb_emit_ldarg (mb, argnum); + break; + + case MARSHAL_ACTION_CONV_RESULT: + /* maybe we need to make sure that it fits within 8 bits */ + mono_mb_emit_stloc (mb, 3); + break; + + case MARSHAL_ACTION_MANAGED_CONV_IN: { + MonoClass* conv_arg_class = mono_defaults.int32_class; + guint8 ldop = CEE_LDIND_I4; + int label_null, label_false; + + conv_arg_class = mono_marshal_boolean_managed_conv_in_get_conv_arg_class (spec, &ldop); + conv_arg = mono_mb_add_local (mb, boolean_type); + + if (m_type_is_byref (t)) + *conv_arg_type = m_class_get_this_arg (conv_arg_class); + else + *conv_arg_type = m_class_get_byval_arg (conv_arg_class); + + + mono_mb_emit_ldarg (mb, argnum); + + /* Check null */ + if (m_type_is_byref (t)) { + label_null = mono_mb_emit_branch (mb, CEE_BRFALSE); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, ldop); + } else + label_null = 0; + + label_false = mono_mb_emit_branch (mb, CEE_BRFALSE); + mono_mb_emit_byte (mb, CEE_LDC_I4_1); + mono_mb_emit_stloc (mb, conv_arg); + mono_mb_patch_branch (mb, label_false); + + if (m_type_is_byref (t)) + mono_mb_patch_branch (mb, label_null); + break; + } + + case MARSHAL_ACTION_MANAGED_CONV_OUT: { + guint8 stop = CEE_STIND_I4; + guint8 ldc_op = CEE_LDC_I4_1; + int label_null,label_false, label_end; + + if (!m_type_is_byref (t)) + break; + if (spec) { + switch (spec->native) { + case MONO_NATIVE_I1: + case MONO_NATIVE_U1: + stop = CEE_STIND_I1; + break; + case MONO_NATIVE_VARIANTBOOL: + stop = CEE_STIND_I2; + ldc_op = CEE_LDC_I4_M1; + break; + default: + break; + } + } + + /* Check null */ + mono_mb_emit_ldarg (mb, argnum); + label_null = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + + label_false = mono_mb_emit_branch (mb, CEE_BRFALSE); + mono_mb_emit_byte (mb, ldc_op); + label_end = mono_mb_emit_branch (mb, CEE_BR); + + mono_mb_patch_branch (mb, label_false); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_patch_branch (mb, label_end); + + mono_mb_emit_byte (mb, stop); + mono_mb_patch_branch (mb, label_null); + break; + } + + default: + g_assert_not_reached (); + } + return conv_arg; +} + +static int +emit_marshal_char_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + + switch (action) { + case MARSHAL_ACTION_PUSH: + /* fixme: dont know how to marshal that. We cant simply + * convert it to a one byte UTF8 character, because an + * unicode character may need more that one byte in UTF8 */ + mono_mb_emit_ldarg (mb, argnum); + break; + + case MARSHAL_ACTION_CONV_RESULT: + /* fixme: we need conversions here */ + mono_mb_emit_stloc (mb, 3); + break; + + default: + break; + } + return conv_arg; +} + +static int +emit_marshal_custom_ilgen_throw_exception (MonoMethodBuilder *mb, const char *exc_nspace, const char *exc_name, const char *msg, MarshalAction action) +{ + /* Throw exception and emit compensation code, if necessary */ + switch (action) { + case MARSHAL_ACTION_CONV_IN: + case MARSHAL_ACTION_MANAGED_CONV_IN: + case MARSHAL_ACTION_CONV_RESULT: + case MARSHAL_ACTION_MANAGED_CONV_RESULT: + if ((action == MARSHAL_ACTION_CONV_RESULT) || (action == MARSHAL_ACTION_MANAGED_CONV_RESULT)) + mono_mb_emit_byte (mb, CEE_POP); + + mono_mb_emit_exception_full (mb, exc_nspace, exc_name, msg); + + break; + case MARSHAL_ACTION_PUSH: + mono_mb_emit_byte (mb, CEE_LDNULL); + break; + default: + break; + } + + return 0; +} + +static int +emit_marshal_custom_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + ERROR_DECL (error); + MonoType *mtype; + MonoClass *mklass; + static MonoClass *ICustomMarshaler = NULL; + static MonoMethod *cleanup_native, *cleanup_managed; + static MonoMethod *marshal_managed_to_native, *marshal_native_to_managed; + MonoMethodBuilder *mb = m->mb; + MonoAssemblyLoadContext *alc = mono_alc_get_ambient (); + guint32 loc1; + int pos2; + + MonoType *int_type = mono_get_int_type (); + MonoType *object_type = mono_get_object_type (); + + if (!ICustomMarshaler) { + MonoClass *klass = mono_class_try_get_icustom_marshaler_class (); + if (!klass) + return emit_marshal_custom_ilgen_throw_exception (mb, "System", "ApplicationException", g_strdup ("Current profile doesn't support ICustomMarshaler"), action); + + cleanup_native = mono_marshal_shared_get_method_nofail (klass, "CleanUpNativeData", 1, 0); + g_assert (cleanup_native); + + cleanup_managed = mono_marshal_shared_get_method_nofail (klass, "CleanUpManagedData", 1, 0); + g_assert (cleanup_managed); + + marshal_managed_to_native = mono_marshal_shared_get_method_nofail (klass, "MarshalManagedToNative", 1, 0); + g_assert (marshal_managed_to_native); + + marshal_native_to_managed = mono_marshal_shared_get_method_nofail (klass, "MarshalNativeToManaged", 1, 0); + g_assert (marshal_native_to_managed); + + mono_memory_barrier (); + ICustomMarshaler = klass; + } + + if (spec->data.custom_data.image) + mtype = mono_reflection_type_from_name_checked (spec->data.custom_data.custom_name, alc, spec->data.custom_data.image, error); + else + mtype = mono_reflection_type_from_name_checked (spec->data.custom_data.custom_name, alc, m->image, error); + + if (!mtype) + return emit_marshal_custom_ilgen_throw_exception (mb, "System", "TypeLoadException", g_strdup ("Failed to load ICustomMarshaler type"), action); + + mklass = mono_class_from_mono_type_internal (mtype); + g_assert (mklass != NULL); + + switch (action) { + case MARSHAL_ACTION_CONV_IN: + switch (t->type) { + case MONO_TYPE_CLASS: + case MONO_TYPE_OBJECT: + case MONO_TYPE_STRING: + case MONO_TYPE_ARRAY: + case MONO_TYPE_SZARRAY: + case MONO_TYPE_VALUETYPE: + break; + + default: + g_warning ("custom marshalling of type %x is currently not supported", t->type); + g_assert_not_reached (); + break; + } + + conv_arg = mono_mb_add_local (mb, int_type); + + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, conv_arg); + + if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) + break; + + /* Minic MS.NET behavior */ + if (!m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT) && !(t->attrs & PARAM_ATTRIBUTE_IN)) + break; + + /* Check for null */ + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + pos2 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_REF); + + if (t->type == MONO_TYPE_VALUETYPE) { + /* + * Since we can't determine the type of the argument, we + * will assume the unmanaged function takes a pointer. + */ + *conv_arg_type = int_type; + + mono_mb_emit_op (mb, CEE_BOX, mono_class_from_mono_type_internal (t)); + } + + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_managed_to_native); + mono_mb_emit_stloc (mb, conv_arg); + + mono_mb_patch_branch (mb, pos2); + break; + + case MARSHAL_ACTION_CONV_OUT: + /* Check for null */ + mono_mb_emit_ldloc (mb, conv_arg); + pos2 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_OUT)) { + mono_mb_emit_ldarg (mb, argnum); + + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + mono_mb_emit_byte (mb, CEE_DUP); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_REF); + mono_mb_emit_op (mb, CEE_CALLVIRT, cleanup_managed); + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); + mono_mb_emit_byte (mb, CEE_STIND_REF); + } else if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) { + mono_mb_emit_ldarg (mb, argnum); + + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); + mono_mb_emit_byte (mb, CEE_STIND_REF); + } else if (t->attrs & PARAM_ATTRIBUTE_OUT) { + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); + /* We have nowhere to store the result */ + mono_mb_emit_byte (mb, CEE_POP); + } + + // Only call cleanup_native if MARSHAL_ACTION_CONV_IN called marshal_managed_to_native. + if (!(m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) && + !(!m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT) && !(t->attrs & PARAM_ATTRIBUTE_IN))) { + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + + mono_mb_emit_ldloc (mb, conv_arg); + + mono_mb_emit_op (mb, CEE_CALLVIRT, cleanup_native); + } + + mono_mb_patch_branch (mb, pos2); + break; + + case MARSHAL_ACTION_PUSH: + if (m_type_is_byref (t)) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_RESULT: + mono_mb_emit_stloc (mb, 3); + + /* Check for null */ + mono_mb_emit_ldloc (mb, 3); + pos2 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + + mono_mb_emit_ldloc (mb, 3); + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); + mono_mb_emit_stloc (mb, 3); + + mono_mb_patch_branch (mb, pos2); + break; + + case MARSHAL_ACTION_MANAGED_CONV_IN: + switch (t->type) { + case MONO_TYPE_CLASS: + case MONO_TYPE_OBJECT: + case MONO_TYPE_STRING: + case MONO_TYPE_ARRAY: + case MONO_TYPE_SZARRAY: + case MONO_TYPE_VALUETYPE: + case MONO_TYPE_BOOLEAN: + break; + + default: + g_warning ("custom marshalling of type %x is currently not supported", t->type); + g_assert_not_reached (); + break; + } + + conv_arg = mono_mb_add_local (mb, object_type); + + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, conv_arg); + + if (m_type_is_byref (t) && t->attrs & PARAM_ATTRIBUTE_OUT) + break; + + /* Check for null */ + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + pos2 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); + mono_mb_emit_stloc (mb, conv_arg); + + mono_mb_patch_branch (mb, pos2); + break; + + case MARSHAL_ACTION_MANAGED_CONV_RESULT: + g_assert (!m_type_is_byref (t)); + + loc1 = mono_mb_add_local (mb, object_type); + + mono_mb_emit_stloc (mb, 3); + + mono_mb_emit_ldloc (mb, 3); + mono_mb_emit_stloc (mb, loc1); + + /* Check for null */ + mono_mb_emit_ldloc (mb, 3); + pos2 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + mono_mb_emit_byte (mb, CEE_DUP); + + mono_mb_emit_ldloc (mb, 3); + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_managed_to_native); + mono_mb_emit_stloc (mb, 3); + + mono_mb_emit_ldloc (mb, loc1); + mono_mb_emit_op (mb, CEE_CALLVIRT, cleanup_managed); + + mono_mb_patch_branch (mb, pos2); + break; + + case MARSHAL_ACTION_MANAGED_CONV_OUT: + + /* Check for null */ + mono_mb_emit_ldloc (mb, conv_arg); + pos2 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + if (m_type_is_byref (t)) { + mono_mb_emit_ldarg (mb, argnum); + + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_managed_to_native); + mono_mb_emit_byte (mb, CEE_STIND_I); + } + + // Only call cleanup_managed if MARSHAL_ACTION_MANAGED_CONV_IN called marshal_native_to_managed. + if (!(m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT))) { + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_op (mb, CEE_CALLVIRT, cleanup_managed); + } + + mono_mb_patch_branch (mb, pos2); + break; + + default: + g_assert_not_reached (); + } + return conv_arg; +} + +static int +emit_marshal_asany_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + + MonoType *int_type = mono_get_int_type (); + switch (action) { + case MARSHAL_ACTION_CONV_IN: { + MonoMarshalNative encoding = mono_marshal_get_string_encoding (m->piinfo, NULL); + + g_assert (t->type == MONO_TYPE_OBJECT); + g_assert (!m_type_is_byref (t)); + + conv_arg = mono_mb_add_local (mb, int_type); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_icon (mb, encoding); + mono_mb_emit_icon (mb, t->attrs); + mono_mb_emit_icall (mb, mono_marshal_asany); + mono_mb_emit_stloc (mb, conv_arg); + break; + } + + case MARSHAL_ACTION_PUSH: + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_OUT: { + MonoMarshalNative encoding = mono_marshal_get_string_encoding (m->piinfo, NULL); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icon (mb, encoding); + mono_mb_emit_icon (mb, t->attrs); + mono_mb_emit_icall (mb, mono_marshal_free_asany); + break; + } + + default: + g_assert_not_reached (); + } + return conv_arg; +} + +static int +emit_marshal_vtype_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + MonoClass *klass, *date_time_class; + int pos = 0, pos2; + + klass = mono_class_from_mono_type_internal (t); + + date_time_class = mono_class_get_date_time_class (); + + MonoType *int_type = mono_get_int_type (); + MonoType *double_type = m_class_get_byval_arg (mono_defaults.double_class); + + switch (action) { + case MARSHAL_ACTION_CONV_IN: + if (klass == date_time_class) { + /* Convert it to an OLE DATE type */ + + conv_arg = mono_mb_add_local (mb, double_type); + + if (m_type_is_byref (t)) { + mono_mb_emit_ldarg (mb, argnum); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + } + + if (!(m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && (t->attrs & PARAM_ATTRIBUTE_OUT))) { + if (!m_type_is_byref (t)) + m->csig->params [argnum - m->csig->hasthis] = double_type; + + MONO_STATIC_POINTER_INIT (MonoMethod, to_oadate) + to_oadate = mono_marshal_shared_get_method_nofail (date_time_class, "ToOADate", 0, 0); + g_assert (to_oadate); + MONO_STATIC_POINTER_INIT_END (MonoMethod, to_oadate) + + mono_mb_emit_ldarg_addr (mb, argnum); + mono_mb_emit_managed_call (mb, to_oadate, NULL); + mono_mb_emit_stloc (mb, conv_arg); + } + + if (m_type_is_byref (t)) + mono_mb_patch_branch (mb, pos); + break; + } + + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) + break; + + conv_arg = mono_mb_add_local (mb, int_type); + + /* store the address of the source into local variable 0 */ + if (m_type_is_byref (t)) + mono_mb_emit_ldarg (mb, argnum); + else + mono_mb_emit_ldarg_addr (mb, argnum); + + mono_mb_emit_stloc (mb, 0); + + /* allocate space for the native struct and + * store the address into local variable 1 (dest) */ + mono_mb_emit_icon (mb, mono_class_native_size (klass, NULL)); + mono_mb_emit_byte (mb, CEE_PREFIX1); + mono_mb_emit_byte (mb, CEE_LOCALLOC); + mono_mb_emit_stloc (mb, conv_arg); + + if (m_type_is_byref (t)) { + mono_mb_emit_ldloc (mb, 0); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + } + + if (!(m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && (t->attrs & PARAM_ATTRIBUTE_OUT))) { + /* set dst_ptr */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, FALSE); + } + + if (m_type_is_byref (t)) + mono_mb_patch_branch (mb, pos); + break; + + case MARSHAL_ACTION_PUSH: + if (spec && spec->native == MONO_NATIVE_LPSTRUCT) { + /* FIXME: */ + g_assert (!m_type_is_byref (t)); + + /* Have to change the signature since the vtype is passed byref */ + m->csig->params [argnum - m->csig->hasthis] = int_type; + + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) + mono_mb_emit_ldarg_addr (mb, argnum); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + } + + if (klass == date_time_class) { + if (m_type_is_byref (t)) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + } + + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) { + mono_mb_emit_ldarg (mb, argnum); + break; + } + mono_mb_emit_ldloc (mb, conv_arg); + if (!m_type_is_byref (t)) { + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_LDNATIVEOBJ, klass); + } + break; + + case MARSHAL_ACTION_CONV_OUT: + if (klass == date_time_class) { + /* Convert from an OLE DATE type */ + + if (!m_type_is_byref (t)) + break; + + if (!((t->attrs & PARAM_ATTRIBUTE_IN) && !(t->attrs & PARAM_ATTRIBUTE_OUT))) { + + MONO_STATIC_POINTER_INIT (MonoMethod, from_oadate) + from_oadate = mono_marshal_shared_get_method_nofail (date_time_class, "FromOADate", 1, 0); + MONO_STATIC_POINTER_INIT_END (MonoMethod, from_oadate) + + g_assert (from_oadate); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_managed_call (mb, from_oadate, NULL); + mono_mb_emit_op (mb, CEE_STOBJ, date_time_class); + } + break; + } + + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) + break; + + if (m_type_is_byref (t)) { + /* dst = argument */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_stloc (mb, 1); + + mono_mb_emit_ldloc (mb, 1); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + + if (!((t->attrs & PARAM_ATTRIBUTE_IN) && !(t->attrs & PARAM_ATTRIBUTE_OUT))) { + /* src = tmp_locals [i] */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_stloc (mb, 0); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, TRUE); + } + } + + emit_struct_free (mb, klass, conv_arg); + + if (m_type_is_byref (t)) + mono_mb_patch_branch (mb, pos); + break; + + case MARSHAL_ACTION_CONV_RESULT: + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass)) { + mono_mb_emit_stloc (mb, 3); + break; + } + + /* load pointer to returned value type */ + g_assert (m->vtaddr_var); + mono_mb_emit_ldloc (mb, m->vtaddr_var); + /* store the address of the source into local variable 0 */ + mono_mb_emit_stloc (mb, 0); + /* set dst_ptr */ + mono_mb_emit_ldloc_addr (mb, 3); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, TRUE); + break; + + case MARSHAL_ACTION_MANAGED_CONV_IN: + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) { + conv_arg = 0; + break; + } + + conv_arg = mono_mb_add_local (mb, m_class_get_byval_arg (klass)); + + if (t->attrs & PARAM_ATTRIBUTE_OUT) + break; + + if (m_type_is_byref (t)) + mono_mb_emit_ldarg (mb, argnum); + else + mono_mb_emit_ldarg_addr (mb, argnum); + mono_mb_emit_stloc (mb, 0); + + if (m_type_is_byref (t)) { + mono_mb_emit_ldloc (mb, 0); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + } + + mono_mb_emit_ldloc_addr (mb, conv_arg); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, TRUE); + + if (m_type_is_byref (t)) + mono_mb_patch_branch (mb, pos); + break; + + case MARSHAL_ACTION_MANAGED_CONV_OUT: + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) + break; + if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_IN) && !(t->attrs & PARAM_ATTRIBUTE_OUT)) + break; + + /* Check for null */ + mono_mb_emit_ldarg (mb, argnum); + pos2 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + /* Set src */ + mono_mb_emit_ldloc_addr (mb, conv_arg); + mono_mb_emit_stloc (mb, 0); + + /* Set dest */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, FALSE); + + mono_mb_patch_branch (mb, pos2); + break; + + case MARSHAL_ACTION_MANAGED_CONV_RESULT: + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) { + mono_mb_emit_stloc (mb, 3); + m->retobj_var = 0; + break; + } + + /* load pointer to returned value type */ + g_assert (m->vtaddr_var); + mono_mb_emit_ldloc (mb, m->vtaddr_var); + + /* store the address of the source into local variable 0 */ + mono_mb_emit_stloc (mb, 0); + /* allocate space for the native struct and + * store the address into dst_ptr */ + m->retobj_var = mono_mb_add_local (mb, int_type); + m->retobj_class = klass; + g_assert (m->retobj_var); + mono_mb_emit_icon (mb, mono_class_native_size (klass, NULL)); + mono_mb_emit_byte (mb, CEE_CONV_I); + mono_mb_emit_icall (mb, ves_icall_marshal_alloc); + mono_mb_emit_stloc (mb, 1); + mono_mb_emit_ldloc (mb, 1); + mono_mb_emit_stloc (mb, m->retobj_var); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, FALSE); + break; + + default: + g_assert_not_reached (); + } + return conv_arg; +} + +static void +emit_string_free_icall (MonoMethodBuilder *mb, MonoMarshalConv conv) +{ + if (conv == MONO_MARSHAL_CONV_BSTR_STR || conv == MONO_MARSHAL_CONV_ANSIBSTR_STR || conv == MONO_MARSHAL_CONV_TBSTR_STR) + mono_mb_emit_icall (mb, mono_free_bstr); + else + mono_mb_emit_icall (mb, mono_marshal_free); +} + +static int +emit_marshal_string_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + MonoMarshalNative encoding = mono_marshal_get_string_encoding (m->piinfo, spec); + MonoMarshalConv conv = mono_marshal_get_string_to_ptr_conv (m->piinfo, spec); + gboolean need_free; + + MonoType *int_type = mono_get_int_type (); + MonoType *object_type = mono_get_object_type (); + switch (action) { + case MARSHAL_ACTION_CONV_IN: + *conv_arg_type = int_type; + conv_arg = mono_mb_add_local (mb, int_type); + + if (m_type_is_byref (t)) { + if (t->attrs & PARAM_ATTRIBUTE_OUT) + break; + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_I); + } else { + mono_mb_emit_ldarg (mb, argnum); + } + + if (conv == MONO_MARSHAL_CONV_INVALID) { + char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + } else { + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + + mono_mb_emit_stloc (mb, conv_arg); + } + break; + + case MARSHAL_ACTION_CONV_OUT: + conv = mono_marshal_get_ptr_to_string_conv (m->piinfo, spec, &need_free); + if (conv == MONO_MARSHAL_CONV_INVALID) { + char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + if (encoding == MONO_NATIVE_VBBYREFSTR) { + + if (!m_type_is_byref (t)) { + char *msg = g_strdup ("VBByRefStr marshalling requires a ref parameter."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + MONO_STATIC_POINTER_INIT (MonoMethod, method) + + method = mono_marshal_shared_get_method_nofail (mono_defaults.string_class, "get_Length", -1, 0); + + MONO_STATIC_POINTER_INIT_END (MonoMethod, method) + + /* + * Have to allocate a new string with the same length as the original, and + * copy the contents of the buffer pointed to by CONV_ARG into it. + */ + g_assert (m_type_is_byref (t)); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_managed_call (mb, method, NULL); + mono_mb_emit_icall (mb, mono_string_new_len_wrapper); + mono_mb_emit_byte (mb, CEE_STIND_REF); + } else if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN))) { + int stind_op; + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, &stind_op)); + mono_mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); + need_free = TRUE; + } + + if (need_free) { + mono_mb_emit_ldloc (mb, conv_arg); + emit_string_free_icall (mb, conv); + } + break; + + case MARSHAL_ACTION_PUSH: + if (m_type_is_byref (t) && encoding != MONO_NATIVE_VBBYREFSTR) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_RESULT: + mono_mb_emit_stloc (mb, 0); + + conv = mono_marshal_get_ptr_to_string_conv (m->piinfo, spec, &need_free); + if (conv == MONO_MARSHAL_CONV_INVALID) { + char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + mono_mb_emit_ldloc (mb, 0); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + mono_mb_emit_stloc (mb, 3); + + /* free the string */ + mono_mb_emit_ldloc (mb, 0); + emit_string_free_icall (mb, conv); + break; + + case MARSHAL_ACTION_MANAGED_CONV_IN: + conv_arg = mono_mb_add_local (mb, object_type); + + *conv_arg_type = int_type; + + if (m_type_is_byref (t)) { + if (t->attrs & PARAM_ATTRIBUTE_OUT) + break; + } + + conv = mono_marshal_get_ptr_to_string_conv (m->piinfo, spec, &need_free); + if (conv == MONO_MARSHAL_CONV_INVALID) { + char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + mono_mb_emit_stloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_MANAGED_CONV_OUT: + if (m_type_is_byref (t)) { + if (conv_arg) { + int stind_op; + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, &stind_op)); + mono_mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); + } + } + break; + + case MARSHAL_ACTION_MANAGED_CONV_RESULT: + if (mono_marshal_shared_conv_to_icall (conv, NULL) == MONO_JIT_ICALL_mono_marshal_string_to_utf16) + /* We need to make a copy so the caller is able to free it */ + mono_mb_emit_icall (mb, mono_marshal_string_to_utf16_copy); + else + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + mono_mb_emit_stloc (mb, 3); + break; + + default: + g_assert_not_reached (); + } + return conv_arg; +} + +static int +emit_marshal_safehandle_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + MonoType *int_type = mono_get_int_type (); + MonoType *boolean_type = m_class_get_byval_arg (mono_defaults.boolean_class); + + switch (action){ + case MARSHAL_ACTION_CONV_IN: { + int dar_release_slot, pos; + + conv_arg = mono_mb_add_local (mb, int_type); + *conv_arg_type = int_type; + + if (!*mono_marshal_shared_get_sh_dangerous_add_ref()) + mono_marshal_shared_init_safe_handle (); + + mono_mb_emit_ldarg (mb, argnum); + pos = mono_mb_emit_branch (mb, CEE_BRTRUE); + mono_mb_emit_exception (mb, "ArgumentNullException", NULL); + + mono_mb_patch_branch (mb, pos); + + /* Create local to hold the ref parameter to DangerousAddRef */ + dar_release_slot = mono_mb_add_local (mb, boolean_type); + + /* set release = false; */ + mono_mb_emit_icon (mb, 0); + mono_mb_emit_stloc (mb, dar_release_slot); + + if (m_type_is_byref (t)) { + int old_handle_value_slot = mono_mb_add_local (mb, int_type); + + if (!mono_marshal_shared_is_in (t)) { + mono_mb_emit_icon (mb, 0); + mono_mb_emit_stloc (mb, conv_arg); + } else { + /* safehandle.DangerousAddRef (ref release) */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_REF); + mono_mb_emit_ldloc_addr (mb, dar_release_slot); + mono_mb_emit_managed_call (mb, *mono_marshal_shared_get_sh_dangerous_add_ref(), NULL); + + /* Pull the handle field from SafeHandle */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_REF); + mono_mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_byte (mb, CEE_DUP); + mono_mb_emit_stloc (mb, conv_arg); + mono_mb_emit_stloc (mb, old_handle_value_slot); + } + } else { + /* safehandle.DangerousAddRef (ref release) */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc_addr (mb, dar_release_slot); + mono_mb_emit_managed_call (mb, *mono_marshal_shared_get_sh_dangerous_add_ref(), NULL); + + /* Pull the handle field from SafeHandle */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_stloc (mb, conv_arg); + } + + break; + } + + case MARSHAL_ACTION_PUSH: + if (m_type_is_byref (t)) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_OUT: { + /* The slot for the boolean is the next temporary created after conv_arg, see the CONV_IN code */ + int dar_release_slot = conv_arg + 1; + int label_next = 0; + + if (!*mono_marshal_shared_get_sh_dangerous_release()) + mono_marshal_shared_init_safe_handle (); + + if (m_type_is_byref (t)) { + /* If there was SafeHandle on input we have to release the reference to it */ + if (mono_marshal_shared_is_in (t)) { + mono_mb_emit_ldloc (mb, dar_release_slot); + label_next = mono_mb_emit_branch (mb, CEE_BRFALSE); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_managed_call (mb, *mono_marshal_shared_get_sh_dangerous_release (), NULL); + mono_mb_patch_branch (mb, label_next); + } + + if (mono_marshal_shared_is_out (t)) { + ERROR_DECL (local_error); + MonoMethod *ctor; + + /* + * If the SafeHandle was marshalled on input we can skip the marshalling on + * output if the handle value is identical. + */ + if (mono_marshal_shared_is_in (t)) { + int old_handle_value_slot = dar_release_slot + 1; + mono_mb_emit_ldloc (mb, old_handle_value_slot); + mono_mb_emit_ldloc (mb, conv_arg); + label_next = mono_mb_emit_branch (mb, CEE_BEQ); + } + + /* + * Create an empty SafeHandle (of correct derived type). + * + * FIXME: If an out-of-memory situation or exception happens here we will + * leak the handle. We should move the allocation of the SafeHandle to the + * input marshalling code to prevent that. + */ + ctor = mono_class_get_method_from_name_checked (t->data.klass, ".ctor", 0, 0, local_error); + if (ctor == NULL || !is_ok (local_error)){ + mono_mb_emit_exception (mb, "MissingMethodException", "parameterless constructor required"); + mono_error_cleanup (local_error); + break; + } + + /* refval = new SafeHandleDerived ()*/ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_op (mb, CEE_NEWOBJ, ctor); + mono_mb_emit_byte (mb, CEE_STIND_REF); + + /* refval.handle = returned_handle */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_REF); + mono_mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_byte (mb, CEE_STIND_I); + + if (mono_marshal_shared_is_in (t) && label_next) { + mono_mb_patch_branch (mb, label_next); + } + } + } else { + mono_mb_emit_ldloc (mb, dar_release_slot); + label_next = mono_mb_emit_branch (mb, CEE_BRFALSE); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_managed_call (mb, *mono_marshal_shared_get_sh_dangerous_release (), NULL); + mono_mb_patch_branch (mb, label_next); + } + break; + } + + case MARSHAL_ACTION_CONV_RESULT: { + ERROR_DECL (error); + MonoMethod *ctor = NULL; + int intptr_handle_slot; + + if (mono_class_is_abstract (t->data.klass)) { + mono_mb_emit_byte (mb, CEE_POP); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, g_strdup ("Returned SafeHandles should not be abstract")); + break; + } + + ctor = mono_class_get_method_from_name_checked (t->data.klass, ".ctor", 0, 0, error); + if (ctor == NULL || !is_ok (error)){ + mono_error_cleanup (error); + mono_mb_emit_byte (mb, CEE_POP); + mono_mb_emit_exception (mb, "MissingMethodException", "parameterless constructor required"); + break; + } + /* Store the IntPtr results into a local */ + intptr_handle_slot = mono_mb_add_local (mb, int_type); + mono_mb_emit_stloc (mb, intptr_handle_slot); + + /* Create return value */ + mono_mb_emit_op (mb, CEE_NEWOBJ, ctor); + mono_mb_emit_stloc (mb, 3); + + /* Set the return.handle to the value, am using ldflda, not sure if thats a good idea */ + mono_mb_emit_ldloc (mb, 3); + mono_mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); + mono_mb_emit_ldloc (mb, intptr_handle_slot); + mono_mb_emit_byte (mb, CEE_STIND_I); + break; + } + + case MARSHAL_ACTION_MANAGED_CONV_IN: + fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_IN\n"); + break; + + case MARSHAL_ACTION_MANAGED_CONV_OUT: + fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_OUT\n"); + break; + + case MARSHAL_ACTION_MANAGED_CONV_RESULT: + fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_RESULT\n"); + break; + default: + printf ("Unhandled case for MarshalAction: %d\n", action); + } + return conv_arg; +} + +static int +emit_marshal_handleref_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + + MonoType *int_type = mono_get_int_type (); + switch (action){ + case MARSHAL_ACTION_CONV_IN: { + conv_arg = mono_mb_add_local (mb, int_type); + *conv_arg_type = int_type; + + if (m_type_is_byref (t)) { + char *msg = g_strdup ("HandleRefs can not be returned from unmanaged code (or passed by ref)"); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + mono_mb_emit_ldarg_addr (mb, argnum); + mono_mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoHandleRef, handle)); + mono_mb_emit_byte (mb, CEE_ADD); + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_stloc (mb, conv_arg); + break; + } + + case MARSHAL_ACTION_PUSH: + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_OUT: { + /* no resource release required */ + break; + } + + case MARSHAL_ACTION_CONV_RESULT: { + char *msg = g_strdup ("HandleRefs can not be returned from unmanaged code (or passed by ref)"); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + case MARSHAL_ACTION_MANAGED_CONV_IN: + fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_IN\n"); + break; + + case MARSHAL_ACTION_MANAGED_CONV_OUT: + fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_OUT\n"); + break; + + case MARSHAL_ACTION_MANAGED_CONV_RESULT: + fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_RESULT\n"); + break; + default: + fprintf (stderr, "Unhandled case for MarshalAction: %d\n", action); + } + return conv_arg; +} + +static int +emit_marshal_object_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + MonoClass *klass = mono_class_from_mono_type_internal (t); + int pos, pos2, loc; + + MonoType *int_type = mono_get_int_type (); + switch (action) { + case MARSHAL_ACTION_CONV_IN: + *conv_arg_type = int_type; + conv_arg = mono_mb_add_local (mb, int_type); + + m->orig_conv_args [argnum] = 0; + + if (mono_class_from_mono_type_internal (t) == mono_defaults.object_class) { + char *msg = g_strdup_printf ("Marshalling of type object is not implemented"); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + if (m_class_is_delegate (klass)) { + if (m_type_is_byref (t)) { + if (!(t->attrs & PARAM_ATTRIBUTE_OUT)) { + char *msg = g_strdup_printf ("Byref marshalling of delegates is not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + } + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, conv_arg); + } else { + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_CONV_DEL_FTN, NULL)); + mono_mb_emit_stloc (mb, conv_arg); + } + } else if (klass == mono_class_try_get_stringbuilder_class ()) { + MonoMarshalNative encoding = mono_marshal_get_string_encoding (m->piinfo, spec); + MonoMarshalConv conv = mono_marshal_get_stringbuilder_to_ptr_conv (m->piinfo, spec); + +#if 0 + if (m_type_is_byref (t)) { + if (!(t->attrs & PARAM_ATTRIBUTE_OUT)) { + char *msg = g_strdup_printf ("Byref marshalling of stringbuilders is not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + } + break; + } +#endif + + if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && (t->attrs & PARAM_ATTRIBUTE_OUT)) + break; + + if (conv == MONO_MARSHAL_CONV_INVALID) { + char *msg = g_strdup_printf ("stringbuilder marshalling conversion %d not implemented", encoding); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + mono_mb_emit_stloc (mb, conv_arg); + } else if (m_class_is_blittable (klass)) { + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, conv_arg); + + mono_mb_emit_ldarg (mb, argnum); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_stloc (mb, conv_arg); + + mono_mb_patch_branch (mb, pos); + break; + } else { + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, conv_arg); + + if (m_type_is_byref (t)) { + /* we dont need any conversions for out parameters */ + if (t->attrs & PARAM_ATTRIBUTE_OUT) + break; + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_I); + + } else { + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_byte (mb, CEE_MONO_OBJADDR); + } + + /* store the address of the source into local variable 0 */ + mono_mb_emit_stloc (mb, 0); + mono_mb_emit_ldloc (mb, 0); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + + /* allocate space for the native struct and store the address */ + mono_mb_emit_icon (mb, mono_class_native_size (klass, NULL)); + mono_mb_emit_byte (mb, CEE_PREFIX1); + mono_mb_emit_byte (mb, CEE_LOCALLOC); + mono_mb_emit_stloc (mb, conv_arg); + + if (m_type_is_byref (t)) { + /* Need to store the original buffer so we can free it later */ + m->orig_conv_args [argnum] = mono_mb_add_local (mb, int_type); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_stloc (mb, m->orig_conv_args [argnum]); + } + + /* set the src_ptr */ + mono_mb_emit_ldloc (mb, 0); + mono_mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_stloc (mb, 0); + + /* set dst_ptr */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, FALSE); + + mono_mb_patch_branch (mb, pos); + } + break; + + case MARSHAL_ACTION_CONV_OUT: + if (klass == mono_class_try_get_stringbuilder_class ()) { + gboolean need_free; + MonoMarshalNative encoding; + MonoMarshalConv conv; + + encoding = mono_marshal_get_string_encoding (m->piinfo, spec); + conv = mono_marshal_get_ptr_to_stringbuilder_conv (m->piinfo, spec, &need_free); + + g_assert (encoding != -1); + + if (m_type_is_byref (t)) { + //g_assert (!(t->attrs & PARAM_ATTRIBUTE_OUT)); + + need_free = TRUE; + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + + switch (encoding) { + case MONO_NATIVE_LPWSTR: + mono_mb_emit_icall (mb, mono_string_utf16_to_builder2); + break; + case MONO_NATIVE_LPSTR: + mono_mb_emit_icall (mb, mono_string_utf8_to_builder2); + break; + case MONO_NATIVE_UTF8STR: + mono_mb_emit_icall (mb, mono_string_utf8_to_builder2); + break; + default: + g_assert_not_reached (); + } + + mono_mb_emit_byte (mb, CEE_STIND_REF); + } else if (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN)) { + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + } + + if (need_free) { + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icall (mb, mono_marshal_free); + } + break; + } + + if (m_class_is_delegate (klass)) { + if (m_type_is_byref (t)) { + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_CONV_FTN_DEL, NULL)); + mono_mb_emit_byte (mb, CEE_STIND_REF); + } + break; + } + + if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) { + /* allocate a new object */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_NEWOBJ, klass); + mono_mb_emit_byte (mb, CEE_STIND_REF); + } + + /* dst = *argument */ + mono_mb_emit_ldarg (mb, argnum); + + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + + mono_mb_emit_stloc (mb, 1); + + mono_mb_emit_ldloc (mb, 1); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + + if (m_type_is_byref (t) || (t->attrs & PARAM_ATTRIBUTE_OUT)) { + mono_mb_emit_ldloc (mb, 1); + mono_mb_emit_icon (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_byte (mb, CEE_ADD); + mono_mb_emit_stloc (mb, 1); + + /* src = tmp_locals [i] */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_stloc (mb, 0); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, TRUE); + + /* Free the structure returned by the native code */ + emit_struct_free (mb, klass, conv_arg); + + if (m->orig_conv_args [argnum]) { + /* + * If the native function changed the pointer, then free + * the original structure plus the new pointer. + */ + mono_mb_emit_ldloc (mb, m->orig_conv_args [argnum]); + mono_mb_emit_ldloc (mb, conv_arg); + pos2 = mono_mb_emit_branch (mb, CEE_BEQ); + + if (!(t->attrs & PARAM_ATTRIBUTE_OUT)) { + g_assert (m->orig_conv_args [argnum]); + + emit_struct_free (mb, klass, m->orig_conv_args [argnum]); + } + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icall (mb, mono_marshal_free); + + mono_mb_patch_branch (mb, pos2); + } + } + else + /* Free the original structure passed to native code */ + emit_struct_free (mb, klass, conv_arg); + + mono_mb_patch_branch (mb, pos); + break; + + case MARSHAL_ACTION_PUSH: + if (m_type_is_byref (t)) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_RESULT: + if (m_class_is_delegate (klass)) { + g_assert (!m_type_is_byref (t)); + mono_mb_emit_stloc (mb, 0); + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); + mono_mb_emit_ldloc (mb, 0); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_CONV_FTN_DEL, NULL)); + mono_mb_emit_stloc (mb, 3); + } else if (klass == mono_class_try_get_stringbuilder_class ()) { + // FIXME: + char *msg = g_strdup_printf ("Return marshalling of stringbuilders is not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + } else { + /* set src */ + mono_mb_emit_stloc (mb, 0); + + /* Make a copy since emit_conv modifies local 0 */ + loc = mono_mb_add_local (mb, int_type); + mono_mb_emit_ldloc (mb, 0); + mono_mb_emit_stloc (mb, loc); + + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, 3); + + mono_mb_emit_ldloc (mb, 0); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + + /* allocate result object */ + + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_NEWOBJ, klass); + mono_mb_emit_stloc (mb, 3); + + /* set dst */ + + mono_mb_emit_ldloc (mb, 3); + mono_mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_stloc (mb, 1); + + /* emit conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, TRUE); + + emit_struct_free (mb, klass, loc); + + /* Free the pointer allocated by unmanaged code */ + mono_mb_emit_ldloc (mb, loc); + mono_mb_emit_icall (mb, mono_marshal_free); + mono_mb_patch_branch (mb, pos); + } + break; + + case MARSHAL_ACTION_MANAGED_CONV_IN: + conv_arg = mono_mb_add_local (mb, m_class_get_byval_arg (klass)); + + if (m_class_is_delegate (klass)) { + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_CONV_FTN_DEL, NULL)); + mono_mb_emit_stloc (mb, conv_arg); + break; + } + + if (klass == mono_class_try_get_stringbuilder_class ()) { + MonoMarshalNative encoding; + + encoding = mono_marshal_get_string_encoding (m->piinfo, spec); + + // FIXME: + g_assert (encoding == MONO_NATIVE_LPSTR || encoding == MONO_NATIVE_UTF8STR); + + g_assert (!m_type_is_byref (t)); + g_assert (encoding != -1); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_icall (mb, mono_string_utf8_to_builder2); + mono_mb_emit_stloc (mb, conv_arg); + break; + } + + /* The class can not have an automatic layout */ + if (mono_class_is_auto_layout (klass)) { + mono_mb_emit_auto_layout_exception (mb, klass); + break; + } + + if (t->attrs & PARAM_ATTRIBUTE_OUT) { + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, conv_arg); + break; + } + + /* Set src */ + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) { + /* Check for NULL and raise an exception */ + pos2 = mono_mb_emit_branch (mb, CEE_BRTRUE); + + mono_mb_emit_exception (mb, "ArgumentNullException", NULL); + + mono_mb_patch_branch (mb, pos2); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_I); + } + + mono_mb_emit_stloc (mb, 0); + + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_stloc (mb, conv_arg); + + mono_mb_emit_ldloc (mb, 0); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + + /* Create and set dst */ + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_NEWOBJ, klass); + mono_mb_emit_stloc (mb, conv_arg); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, TRUE); + + mono_mb_patch_branch (mb, pos); + break; + + case MARSHAL_ACTION_MANAGED_CONV_OUT: + if (m_class_is_delegate (klass)) { + if (m_type_is_byref (t)) { + int stind_op; + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_CONV_DEL_FTN, &stind_op)); + mono_mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); + break; + } + } + + if (m_type_is_byref (t)) { + /* Check for null */ + mono_mb_emit_ldloc (mb, conv_arg); + pos = mono_mb_emit_branch (mb, CEE_BRTRUE); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_byte (mb, CEE_STIND_I); + pos2 = mono_mb_emit_branch (mb, CEE_BR); + + mono_mb_patch_branch (mb, pos); + + /* Set src */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_stloc (mb, 0); + + /* Allocate and set dest */ + mono_mb_emit_icon (mb, mono_class_native_size (klass, NULL)); + mono_mb_emit_byte (mb, CEE_CONV_I); + mono_mb_emit_icall (mb, ves_icall_marshal_alloc); + mono_mb_emit_stloc (mb, 1); + + /* Update argument pointer */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, 1); + mono_mb_emit_byte (mb, CEE_STIND_I); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, FALSE); + + mono_mb_patch_branch (mb, pos2); + } else if (klass == mono_class_try_get_stringbuilder_class ()) { + // FIXME: What to do here ? + } else { + /* byval [Out] marshalling */ + + /* FIXME: Handle null */ + + /* Set src */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_stloc (mb, 0); + + /* Set dest */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, FALSE); + } + break; + + case MARSHAL_ACTION_MANAGED_CONV_RESULT: + if (m_class_is_delegate (klass)) { + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_CONV_DEL_FTN, NULL)); + mono_mb_emit_stloc (mb, 3); + break; + } + + /* The class can not have an automatic layout */ + if (mono_class_is_auto_layout (klass)) { + mono_mb_emit_auto_layout_exception (mb, klass); + break; + } + + mono_mb_emit_stloc (mb, 0); + /* Check for null */ + mono_mb_emit_ldloc (mb, 0); + pos = mono_mb_emit_branch (mb, CEE_BRTRUE); + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, 3); + pos2 = mono_mb_emit_branch (mb, CEE_BR); + + mono_mb_patch_branch (mb, pos); + + /* Set src */ + mono_mb_emit_ldloc (mb, 0); + mono_mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_stloc (mb, 0); + + /* Allocate and set dest */ + mono_mb_emit_icon (mb, mono_class_native_size (klass, NULL)); + mono_mb_emit_byte (mb, CEE_CONV_I); + mono_mb_emit_icall (mb, ves_icall_marshal_alloc); + mono_mb_emit_byte (mb, CEE_DUP); + mono_mb_emit_stloc (mb, 1); + mono_mb_emit_stloc (mb, 3); + + mono_marshal_shared_emit_struct_conv (mb, klass, FALSE); + + mono_mb_patch_branch (mb, pos2); + break; + + default: + g_assert_not_reached (); + } + return conv_arg; +} + +static int +emit_marshal_variant_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ +#ifndef DISABLE_COM + MonoMethodBuilder *mb = m->mb; + MonoType *variant_type = m_class_get_byval_arg (mono_class_get_variant_class ()); + MonoType *variant_type_byref = mono_class_get_byref_type (mono_class_get_variant_class ()); + MonoType *object_type = mono_get_object_type (); + + switch (action) { + case MARSHAL_ACTION_CONV_IN: { + conv_arg = mono_mb_add_local (mb, variant_type); + + if (m_type_is_byref (t)) + *conv_arg_type = variant_type_byref; + else + *conv_arg_type = variant_type; + + if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && t->attrs & PARAM_ATTRIBUTE_OUT) + break; + + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte(mb, CEE_LDIND_REF); + mono_mb_emit_ldloc_addr (mb, conv_arg); + mono_mb_emit_managed_call (mb, mono_get_Marshal_GetNativeVariantForObject (), NULL); + break; + } + + case MARSHAL_ACTION_CONV_OUT: { + if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN))) { + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc_addr (mb, conv_arg); + mono_mb_emit_managed_call (mb, mono_get_Marshal_GetObjectForNativeVariant (), NULL); + mono_mb_emit_byte (mb, CEE_STIND_REF); + } + + mono_mb_emit_ldloc_addr (mb, conv_arg); + mono_mb_emit_managed_call (mb, mono_get_Variant_Clear (), NULL); + break; + } + + case MARSHAL_ACTION_PUSH: + if (m_type_is_byref (t)) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_RESULT: { + char *msg = g_strdup ("Marshalling of VARIANT not supported as a return type."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + case MARSHAL_ACTION_MANAGED_CONV_IN: { + conv_arg = mono_mb_add_local (mb, object_type); + + if (m_type_is_byref (t)) + *conv_arg_type = variant_type_byref; + else + *conv_arg_type = variant_type; + + if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && t->attrs & PARAM_ATTRIBUTE_OUT) + break; + + if (m_type_is_byref (t)) + mono_mb_emit_ldarg (mb, argnum); + else + mono_mb_emit_ldarg_addr (mb, argnum); + mono_mb_emit_managed_call (mb, mono_get_Marshal_GetObjectForNativeVariant (), NULL); + mono_mb_emit_stloc (mb, conv_arg); + break; + } + + case MARSHAL_ACTION_MANAGED_CONV_OUT: { + if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN))) { + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_managed_call (mb, mono_get_Marshal_GetNativeVariantForObject (), NULL); + } + break; + } + + case MARSHAL_ACTION_MANAGED_CONV_RESULT: { + char *msg = g_strdup ("Marshalling of VARIANT not supported as a return type."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + default: + g_assert_not_reached (); + } +#endif /* DISABLE_COM */ + + return conv_arg; +} + +static MonoMarshalIlgenCallbacks * +get_marshal_cb (void) +{ + g_assert(ilgen_cb_inited); + return &ilgen_marshal_cb; +} + +int +mono_emit_marshal_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action, MonoMarshalLightweightCallbacks* lightweigth_cb) +{ + if (spec && spec->native == MONO_NATIVE_CUSTOM) + return get_marshal_cb ()->emit_marshal_custom (m, argnum, t, spec, conv_arg, conv_arg_type, action); + + if (spec && spec->native == MONO_NATIVE_ASANY) + return get_marshal_cb ()->emit_marshal_asany (m, argnum, t, spec, conv_arg, conv_arg_type, action); + + switch (t->type) { + case MONO_TYPE_VALUETYPE: + if (t->data.klass == mono_class_try_get_handleref_class ()) + return get_marshal_cb ()->emit_marshal_handleref (m, argnum, t, spec, conv_arg, conv_arg_type, action); + + return get_marshal_cb ()->emit_marshal_vtype (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_STRING: + return get_marshal_cb ()->emit_marshal_string (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_CLASS: + case MONO_TYPE_OBJECT: +#if !defined(DISABLE_COM) + if (spec && spec->native == MONO_NATIVE_STRUCT) + return get_marshal_cb ()->emit_marshal_variant (m, argnum, t, spec, conv_arg, conv_arg_type, action); +#endif + +#if !defined(DISABLE_COM) + if ((spec && (spec->native == MONO_NATIVE_IUNKNOWN || + spec->native == MONO_NATIVE_IDISPATCH || + spec->native == MONO_NATIVE_INTERFACE)) || + (t->type == MONO_TYPE_CLASS && mono_cominterop_is_interface(t->data.klass))) + return mono_cominterop_emit_marshal_com_interface (m, argnum, t, spec, conv_arg, conv_arg_type, action); + if (spec && (spec->native == MONO_NATIVE_SAFEARRAY) && + (spec->data.safearray_data.elem_type == MONO_VARIANT_VARIANT) && + ((action == MARSHAL_ACTION_CONV_OUT) || (action == MARSHAL_ACTION_CONV_IN) || (action == MARSHAL_ACTION_PUSH))) + return mono_cominterop_emit_marshal_safearray (m, argnum, t, spec, conv_arg, conv_arg_type, action); +#endif + + if (mono_class_try_get_safehandle_class () != NULL && t->data.klass && + mono_class_is_subclass_of_internal (t->data.klass, mono_class_try_get_safehandle_class (), FALSE)) + return get_marshal_cb ()->emit_marshal_safehandle (m, argnum, t, spec, conv_arg, conv_arg_type, action); + + return get_marshal_cb ()->emit_marshal_object (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_ARRAY: + case MONO_TYPE_SZARRAY: + return get_marshal_cb ()->emit_marshal_array (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_BOOLEAN: + return get_marshal_cb ()->emit_marshal_boolean (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_PTR: + return get_marshal_cb ()->emit_marshal_ptr (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_CHAR: + return get_marshal_cb ()->emit_marshal_char (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_I1: + case MONO_TYPE_U1: + case MONO_TYPE_I2: + case MONO_TYPE_U2: + case MONO_TYPE_I4: + case MONO_TYPE_U4: + case MONO_TYPE_I: + case MONO_TYPE_U: + case MONO_TYPE_R4: + case MONO_TYPE_R8: + case MONO_TYPE_I8: + case MONO_TYPE_U8: + case MONO_TYPE_FNPTR: + return lightweigth_cb->emit_marshal_scalar (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_GENERICINST: + if (mono_type_generic_inst_is_valuetype (t)) + return get_marshal_cb ()->emit_marshal_vtype (m, argnum, t, spec, conv_arg, conv_arg_type, action); + else + return get_marshal_cb ()->emit_marshal_object (m, argnum, t, spec, conv_arg, conv_arg_type, action); + default: + return conv_arg; + } +} + +void +mono_marshal_ilgen_init_internal (void) +{ + MonoMarshalIlgenCallbacks cb; + cb.version = MONO_MARSHAL_CALLBACKS_VERSION; + cb.emit_marshal_array = emit_marshal_array_ilgen; + cb.emit_marshal_ptr = emit_marshal_ptr_ilgen; + cb.emit_marshal_char = emit_marshal_char_ilgen; + cb.emit_marshal_vtype = emit_marshal_vtype_ilgen; + cb.emit_marshal_string = emit_marshal_string_ilgen; + cb.emit_marshal_variant = emit_marshal_variant_ilgen; + cb.emit_marshal_safehandle = emit_marshal_safehandle_ilgen; + cb.emit_marshal_object = emit_marshal_object_ilgen; + cb.emit_marshal_boolean = emit_marshal_boolean_ilgen; + cb.emit_marshal_custom = emit_marshal_custom_ilgen; + cb.emit_marshal_asany = emit_marshal_asany_ilgen; + cb.emit_marshal_handleref = emit_marshal_handleref_ilgen; + +#ifdef DISABLE_NONBLITTABLE + mono_marshal_noilgen_init_blittable (&cb); +#endif + mono_install_marshal_callbacks_ilgen (&cb); +} + + diff --git a/src/mono/mono/component/marshal-ilgen.h b/src/mono/mono/metadata/marshal-ilgen.h similarity index 73% rename from src/mono/mono/component/marshal-ilgen.h rename to src/mono/mono/metadata/marshal-ilgen.h index 08768a374483a7..07ec9906dfe3ff 100644 --- a/src/mono/mono/component/marshal-ilgen.h +++ b/src/mono/mono/metadata/marshal-ilgen.h @@ -1,24 +1,9 @@ -/** - * \file - * Copyright 2022 Microsoft - * Licensed under the MIT license. See LICENSE file in the project root for full license information. - */ + #ifndef __MARSHAL_ILGEN_H__ #define __MARSHAL_ILGEN_H__ #include "metadata/marshal-lightweight.h" #include "metadata/marshal.h" -#include "mono/component/component.h" - -typedef struct MonoComponentMarshalILgen { - MonoComponent component; - void (*ilgen_init) (void); - int (*emit_marshal_ilgen) (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action, MonoMarshalLightweightCallbacks* lightweigth_cb); - void (*install_callbacks_mono) (IlgenCallbacksToMono *callbacks); - -} MonoComponentMarshalILgen; typedef struct { int version; @@ -34,23 +19,28 @@ typedef struct { int (*emit_marshal_custom) (EmitMarshalContext *m, int argnum, MonoType *t, MonoMarshalSpec *spec, int conv_arg, MonoType **conv_arg_type, MarshalAction action); int (*emit_marshal_asany) (EmitMarshalContext *m, int argnum, MonoType *t, MonoMarshalSpec *spec, int conv_arg, MonoType **conv_arg_type, MarshalAction action); int (*emit_marshal_handleref) (EmitMarshalContext *m, int argnum, MonoType *t, MonoMarshalSpec *spec, int conv_arg, MonoType **conv_arg_type, MarshalAction action); -} MonoMarshalILgenCallbacks; - -MONO_COMPONENT_EXPORT_ENTRYPOINT -MonoComponentMarshalILgen* mono_component_marshal_ilgen_init (void); +} MonoMarshalIlgenCallbacks; void -mono_install_marshal_callbacks_ilgen (MonoMarshalILgenCallbacks *cb); +mono_install_marshal_callbacks_ilgen (MonoMarshalIlgenCallbacks *cb); + MONO_API void mono_marshal_ilgen_init (void); +void +mono_marshal_noilgen_init_heavyweight (void); + +void +mono_marshal_noilgen_init_lightweight (void); + int mono_emit_marshal_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, MonoMarshalSpec *spec, int conv_arg, MonoType **conv_arg_type, MarshalAction action, MonoMarshalLightweightCallbacks* lightweigth_cb); -void -mono_marshal_ilgen_install_callbacks_mono (IlgenCallbacksToMono *callbacks); +void +mono_marshal_ilgen_init_internal (void); + #endif // __MARSHAL_ILGEN_H__ \ No newline at end of file diff --git a/src/mono/mono/metadata/marshal-lightweight.c b/src/mono/mono/metadata/marshal-lightweight.c index 0c9a5258063caa..cc79171907dc38 100644 --- a/src/mono/mono/metadata/marshal-lightweight.c +++ b/src/mono/mono/metadata/marshal-lightweight.c @@ -8,13 +8,14 @@ #include #endif -#include "mono/metadata/method-builder-ilgen.h" -#include "mono/metadata/method-builder-ilgen-internals.h" +#include "metadata/method-builder-ilgen.h" +#include "metadata/method-builder-ilgen-internals.h" #include #include #include "cil-coff.h" #include "metadata/marshal.h" #include "metadata/marshal-internals.h" +#include "metadata/marshal-ilgen.h" #include "metadata/marshal-lightweight.h" #include "metadata/marshal-shared.h" #include "metadata/tabledefs.h" @@ -23,7 +24,6 @@ #include "mono/metadata/abi-details.h" #include "mono/metadata/class-abi-details.h" #include "mono/metadata/class-init.h" -#include "mono/metadata/components.h" #include "mono/metadata/debug-helpers.h" #include "mono/metadata/threads.h" #include "mono/metadata/monitor.h" @@ -68,6 +68,19 @@ get_method_image (MonoMethod *method) return m_class_get_image (method->klass); } +static gboolean ilgen_callbacks_requested = FALSE; +MONO_API void +mono_marshal_ilgen_init (void) +{ + ilgen_callbacks_requested = TRUE; +} + +gboolean +mono_marshal_is_ilgen_requested (void) +{ + return ilgen_callbacks_requested; +} + /** * mono_mb_strdup: * \param mb the MethodBuilder diff --git a/src/mono/mono/metadata/marshal-lightweight.h b/src/mono/mono/metadata/marshal-lightweight.h index b25d9cc9f2aba5..ff33baed393c55 100644 --- a/src/mono/mono/metadata/marshal-lightweight.h +++ b/src/mono/mono/metadata/marshal-lightweight.h @@ -5,9 +5,11 @@ */ #ifndef __MONO_MARSHAL_LIGHTWEIGHT_H__ #define __MONO_MARSHAL_LIGHTWEIGHT_H__ -#include MONO_API void mono_marshal_lightweight_init (void); +gboolean +mono_marshal_is_ilgen_requested (void); + #endif // __MONO_MARSHAL_LIGHTWEIGHT_H__ diff --git a/src/mono/mono/metadata/marshal-noilgen.c b/src/mono/mono/metadata/marshal-noilgen.c index 8d6f75981cd87d..ae073fcaf7f8ec 100644 --- a/src/mono/mono/metadata/marshal-noilgen.c +++ b/src/mono/mono/metadata/marshal-noilgen.c @@ -1,13 +1,38 @@ #include "config.h" + #include -#include -#include -#include +#include "metadata/marshal-internals.h" +#include "metadata/marshal.h" +#include "metadata/marshal-ilgen.h" #include "utils/mono-compiler.h" #ifndef ENABLE_ILGEN +static int +emit_marshal_array_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoType *int_type = mono_get_int_type (); + MonoType *object_type = mono_get_object_type (); + switch (action) { + case MARSHAL_ACTION_CONV_IN: + *conv_arg_type = object_type; + break; + case MARSHAL_ACTION_MANAGED_CONV_IN: + *conv_arg_type = int_type; + break; + } + return conv_arg; +} - +static int +emit_marshal_ptr_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + return conv_arg; +} static int emit_marshal_scalar_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, @@ -18,6 +43,136 @@ emit_marshal_scalar_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, } #endif +#if !defined(ENABLE_ILGEN) || defined(DISABLE_NONBLITTABLE) +static int +emit_marshal_boolean_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoType *int_type = mono_get_int_type (); + switch (action) { + case MARSHAL_ACTION_CONV_IN: + if (m_type_is_byref (t)) + *conv_arg_type = int_type; + else + *conv_arg_type = mono_marshal_boolean_conv_in_get_local_type (spec, NULL); + break; + + case MARSHAL_ACTION_MANAGED_CONV_IN: { + MonoClass* conv_arg_class = mono_marshal_boolean_managed_conv_in_get_conv_arg_class (spec, NULL); + if (m_type_is_byref (t)) + *conv_arg_type = m_class_get_this_arg (conv_arg_class); + else + *conv_arg_type = m_class_get_byval_arg (conv_arg_class); + break; + } + + } + return conv_arg; +} + +static int +emit_marshal_char_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + return conv_arg; +} + +static int +emit_marshal_custom_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoType *int_type = mono_get_int_type (); + if (action == MARSHAL_ACTION_CONV_IN && t->type == MONO_TYPE_VALUETYPE) + *conv_arg_type = int_type; + return conv_arg; +} + +static int +emit_marshal_asany_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + return conv_arg; +} + +static int +emit_marshal_vtype_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + return conv_arg; +} + +static int +emit_marshal_string_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoType *int_type = mono_get_int_type (); + switch (action) { + case MARSHAL_ACTION_CONV_IN: + *conv_arg_type = int_type; + break; + case MARSHAL_ACTION_MANAGED_CONV_IN: + *conv_arg_type = int_type; + break; + } + return conv_arg; +} + +static int +emit_marshal_safehandle_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + MonoType *int_type = mono_get_int_type (); + if (action == MARSHAL_ACTION_CONV_IN) + *conv_arg_type = int_type; + return conv_arg; +} + + +static int +emit_marshal_handleref_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + MonoType *int_type = mono_get_int_type (); + if (action == MARSHAL_ACTION_CONV_IN) + *conv_arg_type = int_type; + return conv_arg; +} + +static int +emit_marshal_object_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoType *int_type = mono_get_int_type (); + if (action == MARSHAL_ACTION_CONV_IN) + *conv_arg_type = int_type; + return conv_arg; +} + +static int +emit_marshal_variant_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + g_assert_not_reached (); +} +#endif + #ifndef ENABLE_ILGEN static void emit_managed_wrapper_noilgen (MonoMethodBuilder *mb, MonoMethodSignature *invoke_sig, MonoMarshalSpec **mspecs, EmitMarshalContext* m, MonoMethod *method, MonoGCHandle target_handle, MonoError *error) @@ -251,13 +406,38 @@ mono_marshal_noilgen_init_lightweight (void) } +void +mono_marshal_noilgen_init_heavyweight (void) +{ + MonoMarshalIlgenCallbacks ilgen_cb; + + ilgen_cb.version = MONO_MARSHAL_CALLBACKS_VERSION; + ilgen_cb.emit_marshal_array = emit_marshal_array_noilgen; + ilgen_cb.emit_marshal_vtype = emit_marshal_vtype_noilgen; + ilgen_cb.emit_marshal_string = emit_marshal_string_noilgen; + ilgen_cb.emit_marshal_safehandle = emit_marshal_safehandle_noilgen; + ilgen_cb.emit_marshal_handleref = emit_marshal_handleref_noilgen; + ilgen_cb.emit_marshal_object = emit_marshal_object_noilgen; + ilgen_cb.emit_marshal_variant = emit_marshal_variant_noilgen; + ilgen_cb.emit_marshal_asany = emit_marshal_asany_noilgen; + ilgen_cb.emit_marshal_boolean = emit_marshal_boolean_noilgen; + ilgen_cb.emit_marshal_custom = emit_marshal_custom_noilgen; + ilgen_cb.emit_marshal_ptr = emit_marshal_ptr_noilgen; + + ilgen_cb.emit_marshal_char = emit_marshal_char_noilgen; + mono_install_marshal_callbacks_ilgen(&ilgen_cb); +} + #else void mono_marshal_noilgen_init_lightweight (void) { } - +void +mono_marshal_noilgen_init_heavyweight (void) +{ +} #endif #ifdef DISABLE_NONBLITTABLE diff --git a/src/mono/mono/metadata/marshal-noilgen.h b/src/mono/mono/metadata/marshal-noilgen.h deleted file mode 100644 index c9cafee09fbbd7..00000000000000 --- a/src/mono/mono/metadata/marshal-noilgen.h +++ /dev/null @@ -1,15 +0,0 @@ -/** - * \file - * Copyright 2022 Microsoft - * Licensed under the MIT license. See LICENSE file in the project root for full license information. - */ -#ifndef __MARSHAL_NOILGEN_H__ -#define __MARSHAL_NOILGEN_H__ - -void -mono_marshal_noilgen_init_lightweight (void); - -void -mono_marshal_noilgen_init_heavyweight (void); - -#endif // __MARSHAL_NOILGEN_H__ \ No newline at end of file diff --git a/src/mono/mono/metadata/marshal.c b/src/mono/mono/metadata/marshal.c index 47b1a66a979cab..db28b53d3483bd 100644 --- a/src/mono/mono/metadata/marshal.c +++ b/src/mono/mono/metadata/marshal.c @@ -32,7 +32,7 @@ MONO_PRAGMA_WARNING_POP() #include "cil-coff.h" #include "metadata/marshal.h" #include "metadata/marshal-internals.h" -#include "metadata/marshal-shared.h" +#include "metadata/marshal-ilgen.h" #include "metadata/marshal-lightweight.h" #include "metadata/method-builder.h" #include "metadata/method-builder-internals.h" @@ -41,10 +41,8 @@ MONO_PRAGMA_WARNING_POP() #include #include "mono/metadata/abi-details.h" #include "mono/metadata/class-abi-details.h" -#include "mono/metadata/components.h" #include "mono/metadata/debug-helpers.h" #include "mono/metadata/threads.h" -#include "mono/metadata/marshal-noilgen.h" #include "mono/metadata/monitor.h" #include "mono/metadata/class-init.h" #include "mono/metadata/class-internals.h" @@ -129,66 +127,6 @@ static GENERATE_TRY_GET_CLASS_WITH_CACHE (unmanaged_callconv_attribute, "System. static gboolean type_is_blittable (MonoType *type); -static IlgenCallbacksToMono ilgenCallbacksToMono = { - &mono_get_object_type, - &mono_marshal_get_ptr_to_string_conv, - &mono_class_is_subclass_of_internal, - &mono_class_native_size, - &mono_class_try_get_handleref_class, - &mono_class_try_get_safehandle_class, - &mono_class_try_get_stringbuilder_class, - &mono_defaults, - &mono_marshal_boolean_conv_in_get_local_type, - &mono_marshal_boolean_managed_conv_in_get_conv_arg_class, - &mono_marshal_get_ptr_to_stringbuilder_conv, - &mono_marshal_get_string_encoding, - &mono_marshal_get_string_to_ptr_conv, - &mono_marshal_get_stringbuilder_to_ptr_conv, - &mono_marshal_load_type_info, - &mono_marshal_shared_conv_to_icall, - &mono_marshal_shared_emit_marshal_custom_get_instance, - &mono_marshal_shared_emit_struct_conv, - &mono_marshal_shared_emit_struct_conv_full, - &mono_marshal_shared_get_method_nofail, - &mono_marshal_shared_get_sh_dangerous_add_ref, - &mono_marshal_shared_get_sh_dangerous_release, - &mono_marshal_shared_init_safe_handle, - &mono_marshal_shared_is_in, - &mono_marshal_shared_is_out, - &mono_marshal_shared_mb_emit_exception_marshal_directive, - &mono_mb_add_local, - &mono_mb_emit_add_to_local, - &mono_mb_emit_auto_layout_exception, - &mono_mb_emit_branch, - &mono_mb_emit_branch_label, - &mono_mb_emit_byte, - &mono_mb_emit_exception, - &mono_mb_emit_exception_full, - &mono_mb_emit_icall_id, - &mono_mb_emit_icon, - &mono_mb_emit_ldarg, - &mono_mb_emit_ldarg_addr, - &mono_mb_emit_ldflda, - &mono_mb_emit_ldloc, - &mono_mb_emit_ldloc_addr, - &mono_mb_emit_managed_call, - &mono_mb_emit_op, - &mono_mb_emit_stloc, - &mono_mb_get_label, - &mono_mb_patch_branch, - &mono_pinvoke_is_unicode, - &mono_reflection_type_from_name_checked, - &mono_memory_barrier, - &mono_marshal_need_free, - &mono_get_int_type -}; - -IlgenCallbacksToMono* -mono_marshal_get_mono_callbacks_for_ilgen (void) -{ - return &ilgenCallbacksToMono; -} - static MonoImage* get_method_image (MonoMethod *method) { @@ -3222,9 +3160,8 @@ mono_emit_marshal (EmitMarshalContext *m, int argnum, MonoType *t, if (!m->runtime_marshalling_enabled) return mono_emit_disabled_marshal (m, argnum, t, spec, conv_arg, conv_arg_type, action); - mono_component_marshal_ilgen()->install_callbacks_mono(mono_marshal_get_mono_callbacks_for_ilgen()); - return mono_component_marshal_ilgen()->emit_marshal_ilgen(m, argnum, t, spec, conv_arg, conv_arg_type, action, get_marshal_cb()); -} + return mono_emit_marshal_ilgen(m, argnum, t, spec, conv_arg, conv_arg_type, action, get_marshal_cb()); +} static void mono_marshal_set_callconv_for_type(MonoType *type, MonoMethodSignature *csig, gboolean *skip_gc_trans /*out*/) @@ -3860,17 +3797,11 @@ mono_marshal_get_native_func_wrapper_indirect (MonoClass *caller_class, MonoMeth if ((res = mono_marshal_find_in_cache (cache, sig))) return res; -#if 0 - fprintf (stderr, "generating wrapper for signature %s\n", mono_signature_full_name (sig)); -#endif - - /* FIXME: better wrapper name */ - char * name = g_strdup_printf ("wrapper_native_indirect_%p", sig); + char *name = mono_signature_to_name (sig, "wrapper_native_indirect"); MonoMethodBuilder *mb = mono_mb_new (caller_class, name, MONO_WRAPPER_MANAGED_TO_NATIVE); mb->method->save_lmf = 1; WrapperInfo *info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_NATIVE_FUNC_INDIRECT); - //info->d.managed_to_native.method = NULL; info->d.native_func.klass = caller_class; info->d.native_func.sig = sig; @@ -3882,6 +3813,7 @@ mono_marshal_get_native_func_wrapper_indirect (MonoClass *caller_class, MonoMeth mono_marshal_emit_native_wrapper (image, mb, sig, piinfo, mspecs, /*func*/NULL, flags); g_free (mspecs); + /* Add an extra argument which the caller will use to pass in the ftnptr to call */ MonoMethodSignature *csig = mono_metadata_signature_dup_add_this (image, sig, mono_defaults.int_class); csig->pinvoke = 0; @@ -4681,6 +4613,8 @@ is_monomorphic_array (MonoClass *klass) return FALSE; element_class = m_class_get_element_class (klass); + if (m_class_get_byval_arg (element_class)->type == MONO_TYPE_FNPTR) + return FALSE; return mono_class_is_sealed (element_class) || m_class_is_valuetype (element_class); } @@ -6315,14 +6249,7 @@ mono_install_marshal_callbacks (MonoMarshalLightweightCallbacks *cb) static MonoMarshalLightweightCallbacks * get_marshal_cb (void) { - - if (G_UNLIKELY (!lightweight_cb_inited)) { -#ifdef ENABLE_ILGEN - mono_marshal_lightweight_init (); -#else - mono_marshal_noilgen_init_lightweight (); -#endif - } + g_assert (lightweight_cb_inited); return &marshal_lightweight_cb; } diff --git a/src/mono/mono/metadata/marshal.h b/src/mono/mono/metadata/marshal.h index e4244ad07c7b47..93aeb28667de56 100644 --- a/src/mono/mono/metadata/marshal.h +++ b/src/mono/mono/metadata/marshal.h @@ -348,60 +348,6 @@ typedef struct { int (*emit_marshal_scalar) (EmitMarshalContext *m, int argnum, MonoType *t, MonoMarshalSpec *spec, int conv_arg, MonoType **conv_arg_type, MarshalAction action); } MonoMarshalLightweightCallbacks; -typedef struct { - MonoType* (*get_object_type) (void); - MonoMarshalConv (*get_ptr_to_string_conv) (MonoMethodPInvoke *piinfo, MonoMarshalSpec *spec, gboolean *need_free); - gboolean (*is_subclass_of_internal) (MonoClass *klass, MonoClass *klassc, gboolean check_interfaces); - gint32 (*class_native_size) (MonoClass *klass, guint32 *align); - MonoClass* (*class_try_get_handleref_class) (void); - MonoClass* (*try_get_safehandle_class) (void); - MonoClass* (*try_get_stringbuilder_class) (void); - MonoDefaults* mono_defaults; - MonoType* (*boolean_conv_in_get_local_type) (MonoMarshalSpec *spec, guint8 *ldc_op /*out*/); - MonoClass* (*boolean_managed_conv_in_get_conv_arg_class) (MonoMarshalSpec *spec, guint8 *ldop/*out*/); - MonoMarshalConv (*get_ptr_to_stringbuilder_conv) (MonoMethodPInvoke *piinfo, MonoMarshalSpec *spec, gboolean *need_free); - MonoMarshalNative (*get_string_encoding) (MonoMethodPInvoke *piinfo, MonoMarshalSpec *spec); - MonoMarshalConv (*get_string_to_ptr_conv) (MonoMethodPInvoke *piinfo, MonoMarshalSpec *spec); - MonoMarshalConv (*get_stringbuilder_to_ptr_conv) (MonoMethodPInvoke *piinfo, MonoMarshalSpec *spec); - MonoMarshalType* (*load_type_info) (MonoClass* klass); - MonoJitICallId (*conv_to_icall) (MonoMarshalConv conv, int *ind_store_type); - void (*emit_marshal_custom_get_instance) (MonoMethodBuilder *mb, MonoClass *klass, MonoMarshalSpec *spec); - void (*emit_struct_conv) (MonoMethodBuilder *mb, MonoClass *klass, gboolean to_object); - void (*emit_struct_conv_full) (MonoMethodBuilder *mb, MonoClass *klass, gboolean to_object, int offset_of_first_child_field, MonoMarshalNative string_encoding); - MonoMethod* (*get_method_nofail) (MonoClass *klass, const char *method_name, int num_params, int flags); - MonoMethod** (*get_sh_dangerous_add_ref) (void); - MonoMethod** (*get_sh_dangerous_release) (void); - void (*init_safe_handle) (void); - gboolean (*is_in) (const MonoType *t); - gboolean (*is_out) (const MonoType *t); - void (*mb_emit_exception_marshal_directive) (MonoMethodBuilder *mb, char *msg); - int (*mb_add_local) (MonoMethodBuilder *mb, MonoType *type); - void (*mb_emit_add_to_local) (MonoMethodBuilder *mb, guint16 local, gint32 incr); - void (*mb_emit_auto_layout_exception) (MonoMethodBuilder *mb, MonoClass *klass); - guint32 (*mb_emit_branch) (MonoMethodBuilder *mb, guint8 op); - void (*mb_emit_branch_label) (MonoMethodBuilder *mb, guint8 op, guint32 label); - void (*mb_emit_byte) (MonoMethodBuilder *mb, guint8 op); - void (*mb_emit_exception) (MonoMethodBuilder *mb, const char *exc_name, const char *msg); - void (*mb_emit_exception_full) (MonoMethodBuilder *mb, const char *exc_nspace, const char *exc_name, const char *msg); - void (*mb_emit_icall_id) (MonoMethodBuilder *mb, MonoJitICallId jit_icall_id); - void (*mb_emit_icon) (MonoMethodBuilder *mb, gint32 value); - void (*mb_emit_ldarg) (MonoMethodBuilder *mb, guint argnum); - void (*mb_emit_ldarg_addr) (MonoMethodBuilder *mb, guint argnum); - void (*mb_emit_ldflda) (MonoMethodBuilder *mb, gint32 offset); - void (*mb_emit_ldloc) (MonoMethodBuilder *mb, guint num); - void (*mb_emit_ldloc_addr) (MonoMethodBuilder *mb, guint argnum); - void (*mb_emit_managed_call) (MonoMethodBuilder *mb, MonoMethod *method, MonoMethodSignature *opt_sig); - void (*mb_emit_op) (MonoMethodBuilder *mb, guint8 op, gpointer data); - void (*mb_emit_stloc) (MonoMethodBuilder *mb, guint num); - int (*mb_get_label) (MonoMethodBuilder *mb); - void (*mb_patch_branch) (MonoMethodBuilder *mb, guint32 pos); - gboolean (*pinvoke_is_unicode) (MonoMethodPInvoke *piinfo); - MonoType* (*reflection_type_from_name_checked) (char *name, MonoAssemblyLoadContext *alc, MonoImage *image, MonoError *error); - void (*memory_barrier) (void); - gboolean (*need_free) (MonoType *t, MonoMethodPInvoke *piinfo, MonoMarshalSpec *spec); - MonoType* (*get_int_type) (void); -} IlgenCallbacksToMono; - /*type of the function pointer of methods returned by mono_marshal_get_runtime_invoke*/ typedef MonoObject *(*RuntimeInvokeFunction) (MonoObject *this_obj, void **params, MonoObject **exc, void* compiled_method); @@ -756,7 +702,4 @@ mono_mb_create_and_cache_full (GHashTable *cache, gpointer key, MonoMethodBuilder *mb, MonoMethodSignature *sig, int max_stack, WrapperInfo *info, gboolean *out_found); -IlgenCallbacksToMono* -mono_marshal_get_mono_callbacks_for_ilgen (void); - #endif /* __MONO_MARSHAL_H__ */ diff --git a/src/mono/mono/metadata/metadata-internals.h b/src/mono/mono/metadata/metadata-internals.h index 5c6ea98fca365c..91845152d3ecce 100644 --- a/src/mono/mono/metadata/metadata-internals.h +++ b/src/mono/mono/metadata/metadata-internals.h @@ -1248,4 +1248,7 @@ mono_metadata_table_to_ptr_table (int table_num) } } +uint32_t +mono_metadata_get_method_params (MonoImage *image, uint32_t method_idx, uint32_t *last_param_out); + #endif /* __MONO_METADATA_INTERNALS_H__ */ diff --git a/src/mono/mono/metadata/metadata-update.c b/src/mono/mono/metadata/metadata-update.c index 0bfb211c6f0caa..9541a25384c00a 100644 --- a/src/mono/mono/metadata/metadata-update.c +++ b/src/mono/mono/metadata/metadata-update.c @@ -229,4 +229,10 @@ uint32_t mono_metadata_update_get_num_methods_added (MonoClass *klass) { return mono_component_hot_reload()->get_num_methods_added (klass); -} \ No newline at end of file +} + +uint32_t +mono_metadata_update_get_method_params (MonoImage *image, uint32_t methoddef_token, uint32_t *out_param_count_opt) +{ + return mono_component_hot_reload()->get_method_params (image, methoddef_token, out_param_count_opt); +} diff --git a/src/mono/mono/metadata/metadata-update.h b/src/mono/mono/metadata/metadata-update.h index 05cc4e5c80316c..e68ca1a8ad71ae 100644 --- a/src/mono/mono/metadata/metadata-update.h +++ b/src/mono/mono/metadata/metadata-update.h @@ -93,4 +93,7 @@ mono_metadata_update_get_num_fields_added (MonoClass *klass); uint32_t mono_metadata_update_get_num_methods_added (MonoClass *klass); + +uint32_t +mono_metadata_update_get_method_params (MonoImage *image, uint32_t methoddef_token, uint32_t *out_param_count_opt); #endif /*__MONO_METADATA_UPDATE_H__*/ diff --git a/src/mono/mono/metadata/metadata.c b/src/mono/mono/metadata/metadata.c index 690df1e8157398..50948c060061ba 100644 --- a/src/mono/mono/metadata/metadata.c +++ b/src/mono/mono/metadata/metadata.c @@ -2277,17 +2277,12 @@ gboolean mono_metadata_method_has_param_attrs (MonoImage *m, int def) { MonoTableInfo *paramt = &m->tables [MONO_TABLE_PARAM]; - MonoTableInfo *methodt = &m->tables [MONO_TABLE_METHOD]; - guint lastp, i, param_index = mono_metadata_decode_row_col (methodt, def - 1, MONO_METHOD_PARAMLIST); + guint lastp, i, param_index; - if (param_index == 0) - return FALSE; + param_index = mono_metadata_get_method_params (m, def, (uint32_t*)&lastp); - /* FIXME: metadata-update */ - if (GINT_TO_UINT32(def) < table_info_get_rows (methodt)) - lastp = mono_metadata_decode_row_col (methodt, def, MONO_METHOD_PARAMLIST); - else - lastp = table_info_get_rows (&m->tables [MONO_TABLE_PARAM]) + 1; + if (!param_index) + return FALSE; for (i = param_index; i < lastp; ++i) { guint32 flags = mono_metadata_decode_row_col (paramt, i - 1, MONO_PARAM_FLAGS); @@ -2313,20 +2308,14 @@ int* mono_metadata_get_param_attrs (MonoImage *m, int def, guint32 param_count) { MonoTableInfo *paramt = &m->tables [MONO_TABLE_PARAM]; - MonoTableInfo *methodt = &m->tables [MONO_TABLE_METHOD]; guint32 cols [MONO_PARAM_SIZE]; - guint lastp, i, param_index = mono_metadata_decode_row_col (methodt, def - 1, MONO_METHOD_PARAMLIST); + guint lastp, i, param_index; int *pattrs = NULL; - /* hot reload deltas may specify 0 for the param table index */ - if (param_index == 0) - return NULL; + param_index = mono_metadata_get_method_params (m, def, (uint32_t*)&lastp); - /* FIXME: metadata-update */ - if (GINT_TO_UINT32(def) < mono_metadata_table_num_rows (m, MONO_TABLE_METHOD)) - lastp = mono_metadata_decode_row_col (methodt, def, MONO_METHOD_PARAMLIST); - else - lastp = table_info_get_rows (paramt) + 1; + if (!param_index) + return NULL; for (i = param_index; i < lastp; ++i) { mono_metadata_decode_row (paramt, i - 1, cols, MONO_PARAM_SIZE); @@ -5530,7 +5519,8 @@ guint mono_metadata_str_hash (gconstpointer v1) { /* Same as g_str_hash () in glib */ - char *p = (char *) v1; + /* note: signed/unsigned char matters - we feed UTF-8 to this function, so the high bit will give diferent results if we don't match. */ + unsigned char *p = (unsigned char *) v1; guint hash = *p; while (*p++) { @@ -7013,18 +7003,26 @@ mono_class_get_overrides_full (MonoImage *image, guint32 type_token, MonoMethod if (num_overrides) *num_overrides = 0; - if (!tdef->base) + if (!tdef->base && !image->has_updates) return; loc.t = tdef; loc.col_idx = MONO_METHODIMPL_CLASS; loc.idx = mono_metadata_token_index (type_token); + loc.result = 0; - /* FIXME metadata-update */ - - if (!mono_binary_search (&loc, tdef->base, table_info_get_rows (tdef), tdef->row_size, table_locator)) + gboolean found = tdef->base && mono_binary_search (&loc, tdef->base, table_info_get_rows (tdef), tdef->row_size, table_locator) != NULL; + if (!found && !image->has_updates) return; + if (G_UNLIKELY (image->has_updates)) { + if (!found && !mono_metadata_update_metadata_linear_search (image, tdef, &loc, table_locator)) { + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, "NO Found interfaces for class 0x%08x", type_token); + return; + } + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, "Found interfaces for class 0x%08x starting at 0x%08x", type_token, loc.result); + } + start = loc.result; end = start + 1; /* @@ -7036,7 +7034,7 @@ mono_class_get_overrides_full (MonoImage *image, guint32 type_token, MonoMethod else break; } - guint32 rows = table_info_get_rows (tdef); + guint32 rows = mono_metadata_table_num_rows (image, MONO_TABLE_METHODIMPL); while (end < rows) { if (loc.idx == mono_metadata_decode_row_col (tdef, end, MONO_METHODIMPL_CLASS)) end++; @@ -8124,3 +8122,40 @@ mono_metadata_get_class_guid (MonoClass* klass, guint8* guid, MonoError *error) g_warning ("Generated GUIDs only implemented for interfaces!"); #endif } + +uint32_t +mono_metadata_get_method_params (MonoImage *image, uint32_t method_idx, uint32_t *last_param_out) +{ + if (last_param_out) + *last_param_out = 0; + if (!method_idx) + return 0; + + MonoTableInfo *methodt = &image->tables [MONO_TABLE_METHOD]; + + uint32_t param_index, lastp; + + param_index = mono_metadata_decode_row_col (methodt, method_idx - 1, MONO_METHOD_PARAMLIST); + + if (G_UNLIKELY (param_index == 0 && image->has_updates)) { + uint32_t count; + param_index = mono_metadata_update_get_method_params (image, mono_metadata_make_token (MONO_TABLE_METHOD, method_idx), &count); + if (!param_index) + return 0; + lastp = param_index + count; + } else { + /* lastp is the starting param index for the next method in the table, or + * one past the last row if this is the last method + */ + + if (method_idx < table_info_get_rows (methodt)) + lastp = mono_metadata_decode_row_col (methodt, method_idx, MONO_METHOD_PARAMLIST); + else + lastp = table_info_get_rows (&image->tables [MONO_TABLE_PARAM]) + 1; + } + + if (last_param_out) + *last_param_out = lastp; + + return param_index; +} diff --git a/src/mono/mono/metadata/native-library.c b/src/mono/mono/metadata/native-library.c index b2d85270cd3fc0..fc35c3723b02ca 100644 --- a/src/mono/mono/metadata/native-library.c +++ b/src/mono/mono/metadata/native-library.c @@ -532,11 +532,22 @@ netcore_probe_for_module (MonoImage *image, const char *file_name, int flags, Mo // If the difference becomes a problem, overhaul this algorithm to match theirs exactly ERROR_DECL (bad_image_error); + gboolean probe_first_without_prepend = FALSE; - // Try without any path additions - module = netcore_probe_for_module_variations (NULL, file_name, lflags, error); - if (!module && !is_ok (error) && mono_error_get_error_code (error) == MONO_ERROR_BAD_IMAGE) - mono_error_move (bad_image_error, error); +#if defined(HOST_ANDROID) + // On Android, try without any path additions first. It is sensitive to probing that will always miss + // and lookup for some libraries is required to use a relative path + probe_first_without_prepend = TRUE; +#else + if (file_name != NULL && g_path_is_absolute (file_name)) + probe_first_without_prepend = TRUE; +#endif + + if (module == NULL && probe_first_without_prepend) { + module = netcore_probe_for_module_variations (NULL, file_name, lflags, error); + if (!module && !is_ok (error) && mono_error_get_error_code (error) == MONO_ERROR_BAD_IMAGE) + mono_error_move (bad_image_error, error); + } // Check the NATIVE_DLL_SEARCH_DIRECTORIES for (int i = 0; i < pinvoke_search_directories_count && module == NULL; ++i) { @@ -560,6 +571,14 @@ netcore_probe_for_module (MonoImage *image, const char *file_name, int flags, Mo g_free (mdirname); } + // Try without any path additions, if we didn't try it already + if (module == NULL && !probe_first_without_prepend) + { + module = netcore_probe_for_module_variations (NULL, file_name, lflags, error); + if (!module && !is_ok (error) && mono_error_get_error_code (error) == MONO_ERROR_BAD_IMAGE) + mono_error_move (bad_image_error, error); + } + // TODO: Pass remaining flags on to LoadLibraryEx on Windows where appropriate, see https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.dllimportsearchpath?view=netcore-3.1 if (!module && !is_ok (bad_image_error)) { @@ -584,6 +603,40 @@ netcore_probe_for_module_nofail (MonoImage *image, const char *file_name, int fl return result; } +static MonoDl* +netcore_lookup_self_native_handle (void) +{ + ERROR_DECL (load_error); + if (!internal_module) + internal_module = mono_dl_open_self (load_error); + + if (!internal_module) + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, "DllImport error loading library '__Internal': '%s'.", mono_error_get_message_without_fields (load_error)); + + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_DLLIMPORT, "Native library found via __Internal."); + mono_error_cleanup (load_error); + + return internal_module; +} + +static MonoDl* native_handle_lookup_wrapper (gpointer handle) +{ + MonoDl *result = NULL; + + if (!internal_module) + netcore_lookup_self_native_handle (); + + if (internal_module->handle == handle) { + result = internal_module; + } else { + native_library_lock (); + result = netcore_handle_lookup (handle); + native_library_unlock (); + } + + return result; +} + static MonoDl * netcore_resolve_with_dll_import_resolver (MonoAssemblyLoadContext *alc, MonoAssembly *assembly, const char *scope, guint32 flags, MonoError *error) { @@ -631,9 +684,7 @@ netcore_resolve_with_dll_import_resolver (MonoAssemblyLoadContext *alc, MonoAsse mono_runtime_invoke_checked (resolve, NULL, args, error); goto_if_nok (error, leave); - native_library_lock (); - result = netcore_handle_lookup (lib); - native_library_unlock (); + result = native_handle_lookup_wrapper (lib); leave: HANDLE_FUNCTION_RETURN_VAL (result); @@ -688,9 +739,7 @@ netcore_resolve_with_load (MonoAssemblyLoadContext *alc, const char *scope, Mono mono_runtime_invoke_checked (resolve, NULL, args, error); goto_if_nok (error, leave); - native_library_lock (); - result = netcore_handle_lookup (lib); - native_library_unlock (); + result = native_handle_lookup_wrapper (lib); leave: HANDLE_FUNCTION_RETURN_VAL (result); @@ -755,9 +804,7 @@ netcore_resolve_with_resolving_event (MonoAssemblyLoadContext *alc, MonoAssembly mono_runtime_invoke_checked (resolve, NULL, args, error); goto_if_nok (error, leave); - native_library_lock (); - result = netcore_handle_lookup (lib); - native_library_unlock (); + result = native_handle_lookup_wrapper (lib); leave: HANDLE_FUNCTION_RETURN_VAL (result); @@ -802,22 +849,6 @@ netcore_check_alc_cache (MonoAssemblyLoadContext *alc, const char *scope) return result; } -static MonoDl* -netcore_lookup_self_native_handle (void) -{ - ERROR_DECL (load_error); - if (!internal_module) - internal_module = mono_dl_open_self (load_error); - - if (!internal_module) - mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, "DllImport error loading library '__Internal': '%s'.", mono_error_get_message_without_fields (load_error)); - - mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_DLLIMPORT, "Native library found via __Internal."); - mono_error_cleanup (load_error); - - return internal_module; -} - static MonoDl * netcore_lookup_native_library (MonoAssemblyLoadContext *alc, MonoImage *image, const char *scope, guint32 flags) { diff --git a/src/mono/mono/metadata/reflection.c b/src/mono/mono/metadata/reflection.c index 0149b9d5c6a6da..52107a74225441 100644 --- a/src/mono/mono/metadata/reflection.c +++ b/src/mono/mono/metadata/reflection.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -1391,7 +1392,6 @@ get_default_param_value_blobs (MonoMethod *method, char **blobs, guint32 *types) MonoMethodSignature *methodsig = mono_method_signature_internal (method); MonoTableInfo *constt; - MonoTableInfo *methodt; MonoTableInfo *paramt; if (!methodsig->param_count) @@ -1411,22 +1411,16 @@ get_default_param_value_blobs (MonoMethod *method, char **blobs, guint32 *types) return; } - methodt = &image->tables [MONO_TABLE_METHOD]; paramt = &image->tables [MONO_TABLE_PARAM]; constt = &image->tables [MONO_TABLE_CONSTANT]; idx = mono_method_get_index (method); g_assert (idx != 0); - /* lastp is the starting param index for the next method in the table, or - * one past the last row if this is the last method - */ - /* FIXME: metadata-update : will this work with added methods ? */ - param_index = mono_metadata_decode_row_col (methodt, idx - 1, MONO_METHOD_PARAMLIST); - if (!mono_metadata_table_bounds_check (image, MONO_TABLE_METHOD, idx + 1)) - lastp = mono_metadata_decode_row_col (methodt, idx, MONO_METHOD_PARAMLIST); - else - lastp = table_info_get_rows (paramt) + 1; + param_index = mono_metadata_get_method_params (image, idx, &lastp); + + if (!param_index) + return; for (i = param_index; i < lastp; ++i) { guint32 paramseq; diff --git a/src/mono/mono/metadata/sgen-client-mono.h b/src/mono/mono/metadata/sgen-client-mono.h index 89943c3d9241b0..853f3fa3d16af9 100644 --- a/src/mono/mono/metadata/sgen-client-mono.h +++ b/src/mono/mono/metadata/sgen-client-mono.h @@ -40,13 +40,6 @@ struct _SgenClientThreadInfo { gboolean skip, suspend_done; volatile int in_critical_region; -#ifdef SGEN_POSIX_STW - /* This is -1 until the first suspend. */ - int signal; - /* FIXME: kill this, we only use signals on systems that have rt-posix, which doesn't have issues with duplicates. */ - unsigned int stop_count; /* to catch duplicate signals. */ -#endif - gpointer runtime_data; void *stack_end; diff --git a/src/mono/mono/metadata/sgen-mono.c b/src/mono/mono/metadata/sgen-mono.c index 82c3d51c7d7acb..44dfc42f2d3ab6 100644 --- a/src/mono/mono/metadata/sgen-mono.c +++ b/src/mono/mono/metadata/sgen-mono.c @@ -2153,11 +2153,6 @@ sgen_client_thread_attach (SgenThreadInfo* info) info->client_info.stack_start = NULL; -#ifdef SGEN_POSIX_STW - info->client_info.stop_count = -1; - info->client_info.signal = 0; -#endif - memset (&info->client_info.ctx, 0, sizeof (MonoContext)); if (mono_gc_get_gc_callbacks ()->thread_attach_func) diff --git a/src/mono/mono/mini/CMakeLists.txt b/src/mono/mono/mini/CMakeLists.txt index 6ef0ebf57b4ea2..9e74fd9e16fb43 100644 --- a/src/mono/mono/mini/CMakeLists.txt +++ b/src/mono/mono/mini/CMakeLists.txt @@ -349,7 +349,7 @@ add_library(monosgen-static STATIC $;$method), FALSE, TRUE, TRUE); } + for (GSList *l = cfg->pinvoke_calli_signatures; l; l = l->next) { + MonoMethodSignature *sig = mono_metadata_signature_dup ((MonoMethodSignature*)l->data); + + MonoMethod *wrapper = mono_marshal_get_native_func_wrapper_indirect (cfg->method->klass, sig, TRUE); + add_extra_method (acfg, wrapper); + } + if (cfg->llvm_only) acfg->stats.llvm_count ++; @@ -9709,6 +9716,9 @@ append_mangled_wrapper_subtype (GString *s, WrapperSubtype subtype) case WRAPPER_SUBTYPE_LLVM_FUNC: label = "llvm_func"; break; + case WRAPPER_SUBTYPE_NATIVE_FUNC_INDIRECT: + label = "native_func_indirect"; + break; default: g_assert_not_reached (); } @@ -9872,6 +9882,9 @@ append_mangled_wrapper (GString *s, MonoMethod *method) append_sig = FALSE; } else if (info->subtype == WRAPPER_SUBTYPE_NATIVE_FUNC_AOT) { success = success && append_mangled_method (s, info->d.managed_to_native.method); + } else if (info->subtype == WRAPPER_SUBTYPE_NATIVE_FUNC_INDIRECT) { + append_mangled_signature (s, info->d.native_func.sig); + append_sig = FALSE; } else { g_assert (info->subtype == WRAPPER_SUBTYPE_NONE || info->subtype == WRAPPER_SUBTYPE_PINVOKE); success = success && append_mangled_method (s, info->d.managed_to_native.method); @@ -12737,7 +12750,12 @@ compile_asm (MonoAotCompile *acfg) if (ld_binary_name == NULL) { ld_binary_name = LD_NAME; } - g_string_append_printf (str, "%s%s %s", tool_prefix, ld_binary_name, LD_OPTIONS); + if (acfg->aot_opts.tool_prefix) + g_string_append_printf (str, "\"%s%s\" %s", tool_prefix, ld_binary_name, LD_OPTIONS); + else if (acfg->aot_opts.llvm_only) + g_string_append_printf (str, "%s", acfg->aot_opts.clangxx); + else + g_string_append_printf (str, "\"%s%s\" %s", tool_prefix, ld_binary_name, LD_OPTIONS); #else if (ld_binary_name == NULL) { ld_binary_name = "ld"; diff --git a/src/mono/mono/mini/aot-runtime.c b/src/mono/mono/mini/aot-runtime.c index 353b0ee4f06f2b..50639c2c397af2 100644 --- a/src/mono/mono/mini/aot-runtime.c +++ b/src/mono/mono/mini/aot-runtime.c @@ -4445,6 +4445,11 @@ mono_aot_can_dedup (MonoMethod *method) /* Handled using linkonce */ return FALSE; #endif + MonoMethodSignature *sig = mono_method_signature_internal (method); + if (sig->ret->has_cmods) { + // FIXME: + return FALSE; + } return TRUE; } default: diff --git a/src/mono/mono/mini/branch-opts.c b/src/mono/mono/mini/branch-opts.c index 5d14e517c5f092..ca6038844f6a37 100644 --- a/src/mono/mono/mini/branch-opts.c +++ b/src/mono/mono/mini/branch-opts.c @@ -428,6 +428,9 @@ mono_if_conversion (MonoCompile *cfg) mono_bblock_insert_before_ins (bb, compare, ins2); mono_bblock_insert_before_ins (bb, ins2, ins1); + bb->needs_decompose |= true_bb->needs_decompose; + bb->needs_decompose |= false_bb->needs_decompose; + /* Add cmov instruction */ MONO_INST_NEW (cfg, cmov, OP_NOP); cmov->dreg = dreg; diff --git a/src/mono/mono/mini/cpu-arm.mdesc b/src/mono/mono/mini/cpu-arm.mdesc index 01a9c7c99b6f6a..c6813e4fa8b005 100644 --- a/src/mono/mono/mini/cpu-arm.mdesc +++ b/src/mono/mono/mini/cpu-arm.mdesc @@ -60,7 +60,7 @@ seq_point: len:52 clob:c il_seq_point: len:0 throw: src1:i len:24 -rethrow: src1:i len:20 +rethrow: src1:i len:24 start_handler: len:20 endfinally: len:32 call_handler: len:16 clob:c diff --git a/src/mono/mono/mini/cpu-ppc64.mdesc b/src/mono/mono/mini/cpu-ppc64.mdesc index d2437a34fda45e..77d99656d72151 100644 --- a/src/mono/mono/mini/cpu-ppc64.mdesc +++ b/src/mono/mono/mini/cpu-ppc64.mdesc @@ -45,18 +45,18 @@ # # See the code in mini-x86.c for more details on how the specifiers are used. # -tailcall: len:124 clob:c +tailcall: len:152 clob:c tailcall_parameter: len:0 # PowerPC outputs a nice fixed size memcpy loop for larger stack_usage, so 0. memory_barrier: len:4 nop: len:4 relaxed_nop: len:4 -break: len:40 +break: len:44 seq_point: len:48 il_seq_point: len:0 -call: dest:a clob:c len:36 +call: dest:a clob:c len:40 br: len:4 -throw: src1:i len:40 -rethrow: src1:i len:40 +throw: src1:i len:44 +rethrow: src1:i len:44 ckfinite: dest:f src1:f ppc_check_finite: src1:i len:16 add_ovf_carry: dest:i src1:i src2:i len:16 @@ -77,16 +77,16 @@ fcompare: src1:f src2:f len:12 arglist: src1:i len:12 setlret: src1:i src2:i len:12 check_this: src1:b len:4 -voidcall: len:36 clob:c +voidcall: len:40 clob:c voidcall_reg: src1:i len:16 clob:c voidcall_membase: src1:b len:16 clob:c -fcall: dest:g len:36 clob:c +fcall: dest:g len:40 clob:c fcall_reg: dest:g src1:i len:16 clob:c fcall_membase: dest:g src1:b len:16 clob:c -lcall: dest:a len:36 clob:c +lcall: dest:a len:40 clob:c lcall_reg: dest:a src1:i len:16 clob:c lcall_membase: dest:a src1:b len:16 clob:c -vcall: len:16 clob:c +vcall: len:20 clob:c vcall_reg: src1:i len:16 clob:c vcall_membase: src1:b len:12 clob:c call_reg: dest:a src1:i len:16 clob:c @@ -404,7 +404,7 @@ int_max_un: dest:i src1:i src2:i len:8 clob:1 #long_conv_to_ovf_i4_2: dest:i src1:i src2:i len:30 -vcall2: len:36 clob:c +vcall2: len:40 clob:c vcall2_reg: src1:i len:16 clob:c vcall2_membase: src1:b len:16 clob:c diff --git a/src/mono/mono/mini/exceptions-ppc.c b/src/mono/mono/mini/exceptions-ppc.c index 99dc4b447d2128..69f6c490c13cb5 100644 --- a/src/mono/mono/mini/exceptions-ppc.c +++ b/src/mono/mono/mini/exceptions-ppc.c @@ -564,9 +564,9 @@ mono_arch_unwind_frame (MonoJitTlsData *jit_tls, unwind_info = mono_jinfo_get_unwind_info (ji, &unwind_info_len); - sframe = (MonoPPCStackFrame*)MONO_CONTEXT_GET_SP (ctx); - MONO_CONTEXT_SET_BP (new_ctx, sframe->sp); if (!ji->is_trampoline && jinfo_get_method (ji)->save_lmf) { + sframe = (MonoPPCStackFrame*)MONO_CONTEXT_GET_SP (ctx); + MONO_CONTEXT_SET_BP (new_ctx, sframe->sp); /* sframe->sp points just past the end of the LMF */ guint8 *lmf_addr = (guint8*)sframe->sp - sizeof (MonoLMF); memcpy (&new_ctx->fregs [MONO_PPC_FIRST_SAVED_FREG], lmf_addr + G_STRUCT_OFFSET (MonoLMF, fregs), sizeof (double) * MONO_SAVED_FREGS); diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c index b89f8a7a181c7f..8d562d2fcd6ef2 100644 --- a/src/mono/mono/mini/interp/interp.c +++ b/src/mono/mono/mini/interp/interp.c @@ -94,6 +94,7 @@ struct FrameClauseArgs { const guint16 *end_at_ip; /* Frame that is executing this clause */ InterpFrame *exec_frame; + gboolean run_until_end; }; /* @@ -3595,6 +3596,13 @@ interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClauseArgs INIT_INTERP_STATE (frame, clause_args); + if (clause_args && clause_args->run_until_end) + /* + * Called from run_with_il_state to run the method until the end. + * Clear this out so it doesn't confuse the rest of the code. + */ + clause_args = NULL; + #ifdef ENABLE_EXPERIMENT_TIERED mini_tiered_inc (frame->imethod->method, &frame->imethod->tiered_counter, 0); #endif @@ -3797,7 +3805,12 @@ interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClauseArgs } else if ((m_method_is_virtual (del_imethod->method) && !m_method_is_static (del_imethod->method)) && !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); + MonoObject *obj = LOCAL_VAR (call_args_offset + MINT_STACK_SLOT_SIZE, MonoObject*); + del_imethod = get_virtual_method (del_imethod, obj->vtable); + if (m_class_is_valuetype (obj->vtable->klass) && m_class_is_valuetype (del_imethod->method->klass)) { + // We are calling into a value type method, `this` needs to be unboxed + LOCAL_VAR (call_args_offset + MINT_STACK_SLOT_SIZE, gpointer) = mono_object_unbox_internal (obj); + } } else { del->interp_invoke_impl = del_imethod; } @@ -7067,7 +7080,7 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK; MINT_IN_CASE(MINT_TIER_ENTER_METHOD) { frame->imethod->entry_count++; - if (frame->imethod->entry_count > INTERP_TIER_ENTRY_LIMIT) + if (frame->imethod->entry_count > INTERP_TIER_ENTRY_LIMIT && !clause_args) ip = mono_interp_tier_up_frame_enter (frame, context); else ip++; @@ -7075,7 +7088,7 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK; } MINT_IN_CASE(MINT_TIER_PATCHPOINT) { frame->imethod->entry_count++; - if (frame->imethod->entry_count > INTERP_TIER_ENTRY_LIMIT) + if (frame->imethod->entry_count > INTERP_TIER_ENTRY_LIMIT && !clause_args) ip = mono_interp_tier_up_frame_patchpoint (frame, context, ip [1]); else ip += 2; @@ -7656,10 +7669,13 @@ interp_run_clause_with_il_state (gpointer il_state_ptr, int clause_index, MonoOb clause_args.start_with_ip = (const guint16*)ei->data.filter; else clause_args.start_with_ip = (const guint16*)ei->handler_start; - if (clause_type == MONO_EXCEPTION_CLAUSE_NONE || clause_type == MONO_EXCEPTION_CLAUSE_FILTER) - clause_args.end_at_ip = (const guint16*)clause_args.start_with_ip + 0xffffff; - else + if (clause_type == MONO_EXCEPTION_CLAUSE_NONE || clause_type == MONO_EXCEPTION_CLAUSE_FILTER) { + /* Run until the end */ + clause_args.end_at_ip = NULL; + clause_args.run_until_end = TRUE; + } else { clause_args.end_at_ip = (const guint16*)ei->data.handler_end; + } clause_args.exec_frame = &frame; if (clause_type == MONO_EXCEPTION_CLAUSE_NONE || clause_type == MONO_EXCEPTION_CLAUSE_FILTER) diff --git a/src/mono/mono/mini/main-core.c b/src/mono/mono/mini/main-core.c index 66e85d713c9473..d9e26269e68a94 100644 --- a/src/mono/mono/mini/main-core.c +++ b/src/mono/mono/mini/main-core.c @@ -20,10 +20,16 @@ #pragma comment(linker, "/export:coreclr_execute_assembly=_coreclr_execute_assembly@24") #pragma comment(linker, "/export:coreclr_shutdown_2=_coreclr_shutdown_2@12") #pragma comment(linker, "/export:coreclr_create_delegate=_coreclr_create_delegate@24") +#pragma comment(linker, "/export:coreclr_set_error_writer=_coreclr_set_error_writer@4") #undef MONO_API #define MONO_API MONO_EXTERN_C #endif +// +// Type of the callback function that can be set by the coreclr_set_error_writer +// +typedef void (*coreclr_error_writer_callback_fn) (const char *message); + MONO_API int STDAPICALLTYPE coreclr_initialize (const char* exePath, const char* appDomainFriendlyName, int propertyCount, const char** propertyKeys, const char** propertyValues, void** hostHandle, unsigned int* domainId); @@ -38,6 +44,8 @@ MONO_API int STDAPICALLTYPE coreclr_create_delegate (void* hostHandle, unsigned const char* entryPointAssemblyName, const char* entryPointTypeName, const char* entryPointMethodName, void** delegate); +MONO_API int STDAPICALLTYPE coreclr_set_error_writer(coreclr_error_writer_callback_fn error_writer); + // // Initialize the CoreCLR. Creates and starts CoreCLR host and creates an app domain // @@ -117,3 +125,18 @@ int STDAPICALLTYPE coreclr_create_delegate (void* hostHandle, unsigned int domai { return monovm_create_delegate (entryPointAssemblyName, entryPointTypeName, entryPointMethodName, delegate); } + +// +// Set callback for writing error logging +// +// Parameters: +// errorWriter - callback that will be called for each line of the error info +// - passing in NULL removes a callback that was previously set +// +// Returns: +// S_OK +// +int STDAPICALLTYPE coreclr_set_error_writer(coreclr_error_writer_callback_fn error_writer) +{ + return 0; // S_OK +} diff --git a/src/mono/mono/mini/method-to-ir.c b/src/mono/mono/mini/method-to-ir.c index cfb8f7cc1ff888..e3719b1e5a7f1a 100644 --- a/src/mono/mono/mini/method-to-ir.c +++ b/src/mono/mono/mini/method-to-ir.c @@ -2232,7 +2232,16 @@ static MonoInst* mono_emit_widen_call_res (MonoCompile *cfg, MonoInst *ins, MonoMethodSignature *fsig) { if (!MONO_TYPE_IS_VOID (fsig->ret)) { - if ((fsig->pinvoke || LLVM_ENABLED) && !m_type_is_byref (fsig->ret)) { + // FIXME + // LLVM code doesn't uses zero extend the full word while jit expects it. + // A proper fix would be to detect if we are actually using llvm code from aot images + // or make sure llvm code actually zero extends the return. +#ifdef MONO_ARCH_LLVM_SUPPORTED + gboolean might_use_llvm = TRUE; +#else + gboolean might_use_llvm = FALSE; +#endif + if ((fsig->pinvoke || might_use_llvm) && !m_type_is_byref (fsig->ret)) { int widen_op = -1; /* @@ -6545,8 +6554,6 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b if (cfg->llvm_only && cfg->interp && cfg->method == method && !cfg->deopt && !cfg->interp_entry_only) { if (header->num_clauses) { - /* deopt is only disabled for gsharedvt */ - g_assert (cfg->gsharedvt); for (guint i = 0; i < header->num_clauses; ++i) { MonoExceptionClause *clause = &header->clauses [i]; /* Finally clauses are checked after the remove_finally pass */ @@ -7299,6 +7306,10 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b #if 0 fprintf (stderr, "generating wrapper for calli in method %s with wrapper type %s\n", method->name, mono_wrapper_type_to_str (method->wrapper_type)); #endif + + if (cfg->compile_aot) + cfg->pinvoke_calli_signatures = g_slist_prepend_mempool (cfg->mempool, cfg->pinvoke_calli_signatures, fsig); + /* Call the wrapper that will do the GC transition instead */ MonoMethod *wrapper = mono_marshal_get_native_func_wrapper_indirect (method->klass, fsig, cfg->compile_aot); diff --git a/src/mono/mono/mini/mini-amd64.c b/src/mono/mono/mini/mini-amd64.c index 16f6f06db84706..bc4eedd8f86c5e 100644 --- a/src/mono/mono/mini/mini-amd64.c +++ b/src/mono/mono/mini/mini-amd64.c @@ -2165,6 +2165,12 @@ mono_arch_get_llvm_call_info (MonoCompile *cfg, MonoMethodSignature *sig) return linfo; } +#if 0 + /* FIXME: the non-LLVM codegen should also pass arguments in registers or + * else there could a mismatch when LLVM code calls non-LLVM code + * + * See https://github.com/dotnet/runtime/issues/73454 + */ if ((t->type == MONO_TYPE_GENERICINST) && !cfg->full_aot && !sig->pinvoke) { MonoClass *klass = mono_class_from_mono_type_internal (t); if (MONO_CLASS_IS_SIMD (cfg, klass)) { @@ -2172,6 +2178,7 @@ mono_arch_get_llvm_call_info (MonoCompile *cfg, MonoMethodSignature *sig) break; } } +#endif linfo->args [i].storage = LLVMArgVtypeInReg; for (j = 0; j < 2; ++j) @@ -5504,7 +5511,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) } case OP_CHECK_THIS: /* ensure ins->sreg1 is not NULL */ - amd64_alu_membase_imm_size (code, X86_CMP, ins->sreg1, 0, 0, 4); + amd64_alu_membase8_reg_size (code, X86_CMP, ins->sreg1, 0, ins->sreg1, 1); break; case OP_ARGLIST: { amd64_lea_membase (code, AMD64_R11, cfg->frame_reg, cfg->sig_cookie); diff --git a/src/mono/mono/mini/mini-arm64.c b/src/mono/mono/mini/mini-arm64.c index 081e5bc151aeca..65a6bb42e1fc7e 100644 --- a/src/mono/mono/mini/mini-arm64.c +++ b/src/mono/mono/mini/mini-arm64.c @@ -2521,6 +2521,12 @@ mono_arch_get_llvm_call_info (MonoCompile *cfg, MonoMethodSignature *sig) break; } case ArgVtypeInIRegs: +#if 0 + /* FIXME: the non-LLVM codegen should also pass arguments in registers or + * else there could a mismatch when LLVM code calls non-LLVM code + * + * See https://github.com/dotnet/runtime/issues/73454 + */ if ((t->type == MONO_TYPE_GENERICINST) && !cfg->full_aot && !sig->pinvoke) { MonoClass *klass = mono_class_from_mono_type_internal (t); if (MONO_CLASS_IS_SIMD (cfg, klass)) { @@ -2528,6 +2534,7 @@ mono_arch_get_llvm_call_info (MonoCompile *cfg, MonoMethodSignature *sig) break; } } +#endif lainfo->storage = LLVMArgAsIArgs; lainfo->nslots = ainfo->nregs; diff --git a/src/mono/mono/mini/mini-llvm.c b/src/mono/mono/mini/mini-llvm.c index 3d7a809c2173a8..e6224e598f6701 100644 --- a/src/mono/mono/mini/mini-llvm.c +++ b/src/mono/mono/mini/mini-llvm.c @@ -6856,7 +6856,7 @@ MONO_RESTORE_WARNING } case OP_CHECK_THIS: - LLVMBuildLoad2 (builder, IntPtrType (), convert (ctx, lhs, pointer_type (IntPtrType ())), ""); + LLVMBuildLoad2 (builder, LLVMInt8Type (), convert (ctx, lhs, pointer_type (LLVMInt8Type ())), ""); break; case OP_OUTARG_VTRETADDR: break; diff --git a/src/mono/mono/mini/mini-ppc.c b/src/mono/mono/mini/mini-ppc.c index 93abd63562ff53..ced67ccc78d27b 100644 --- a/src/mono/mono/mini/mini-ppc.c +++ b/src/mono/mono/mini/mini-ppc.c @@ -470,10 +470,54 @@ mono_arch_get_delegate_invoke_impl (MonoMethodSignature *sig, gboolean has_targe return start; } +/** + * + * @brief Architecture-specific delegation virtual trampoline processing + * + * @param[in] @sig - Method signature + * @param[in] @method - Method + * @param[in] @offset - Offset into vtable + * @param[in] @load_imt_reg - Whether to load the LMT register + * @returns Trampoline + * + * Return a pointer to a delegation virtual trampoline + */ + gpointer mono_arch_get_delegate_virtual_invoke_impl (MonoMethodSignature *sig, MonoMethod *method, int offset, gboolean load_imt_reg) { - return NULL; + guint8 *code, *start; + int size = 32; + + start = code = (guint8 *) mono_global_codeman_reserve (size); + + /* + * Replace the "this" argument with the target + */ + ppc_mr (code, ppc_r12, ppc_r3); + ppc_ldptr (code, ppc_r3, MONO_STRUCT_OFFSET(MonoDelegate, target), ppc_r12); + + /* + * Load the IMT register, if needed + */ + if (load_imt_reg) { + ppc_ldptr (code, MONO_ARCH_IMT_REG, MONO_STRUCT_OFFSET(MonoDelegate, method), ppc_r12); + } + + /* + * Load the vTable + */ + ppc_ldptr (code, ppc_r12, MONO_STRUCT_OFFSET(MonoObject, vtable), ppc_r3); + if (!ppc_is_imm16(offset)) + ppc_addis (code, ppc_r12, ppc_r12, ppc_ha(offset)); + ppc_ldptr (code, ppc_r12, offset, ppc_r12); + ppc_mtctr (code, ppc_r12); + ppc_bcctr (code, PPC_BR_ALWAYS, 0); + + mono_arch_flush_icache (start, code - start); + MONO_PROFILER_RAISE (jit_code_buffer, (start, code - start, MONO_PROFILER_CODE_BUFFER_DELEGATE_INVOKE, NULL)); + + return(start); } gpointer @@ -1891,7 +1935,7 @@ typedef struct { if (0 && ins->inst_true_bb->native_offset) { \ ppc_bc (code, (b0), (b1), (code - cfg->native_code + ins->inst_true_bb->native_offset) & 0xffff); \ } else { \ - int br_disp = ins->inst_true_bb->max_offset - offset; \ + int br_disp = ins->inst_true_bb->max_offset - cpos; \ if (!ppc_is_imm16 (br_disp + 8 * 1024) || !ppc_is_imm16 (br_disp - 8 * 1024)) { \ MonoOvfJump *ovfj = mono_mempool_alloc (cfg->mempool, sizeof (MonoOvfJump)); \ ovfj->data.bb = ins->inst_true_bb; \ @@ -1915,7 +1959,7 @@ if (0 && ins->inst_true_bb->native_offset) { \ */ #define EMIT_COND_SYSTEM_EXCEPTION_FLAGS(b0,b1,exc_name) \ do { \ - int br_disp = cfg->bb_exit->max_offset - offset; \ + int br_disp = cfg->bb_exit->max_offset - cpos; \ if (!ppc_is_imm16 (br_disp + 1024) || ! ppc_is_imm16 (ppc_is_imm16 (br_disp - 1024))) { \ MonoOvfJump *ovfj = mono_mempool_alloc (cfg->mempool, sizeof (MonoOvfJump)); \ ovfj->data.exception = (exc_name); \ @@ -2732,6 +2776,9 @@ handle_thunk (MonoCompile *cfg, guchar *code, const guchar *target) if (!cfg->arch.thunks) { cfg->arch.thunks = cfg->thunks; cfg->arch.thunks_size = cfg->thunk_area; +#ifdef THUNK_ADDR_ALIGNMENT + cfg->arch.thunks = (guint8 *)ALIGN_TO(cfg->arch.thunks, THUNK_ADDR_ALIGNMENT); +#endif } thunks = cfg->arch.thunks; thunks_size = cfg->arch.thunks_size; @@ -3779,23 +3826,11 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) ppc_addis (code, ppc_r12, cfg->frame_reg, ppc_ha(cfg->stack_usage)); ppc_addi (code, ppc_r12, ppc_r12, cfg->stack_usage); } - if (!cfg->method->save_lmf) { - pos = 0; - for (i = 31; i >= 13; --i) { - if (cfg->used_int_regs & (1 << i)) { - pos += sizeof (target_mgreg_t); - ppc_ldptr (code, i, -pos, ppc_r12); - } - } - } else { - /* FIXME restore from MonoLMF: though this can't happen yet */ - } /* Copy arguments on the stack to our argument area */ if (call->stack_usage) { code = emit_memcpy (code, call->stack_usage, ppc_r12, PPC_STACK_PARAM_OFFSET, ppc_sp, PPC_STACK_PARAM_OFFSET); /* r12 was clobbered */ - g_assert (cfg->frame_reg == ppc_sp); if (ppc_is_imm16 (cfg->stack_usage)) { ppc_addi (code, ppc_r12, cfg->frame_reg, cfg->stack_usage); } else { @@ -3806,6 +3841,18 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) } } + if (!cfg->method->save_lmf) { + pos = 0; + for (i = 31; i >= 13; --i) { + if (cfg->used_int_regs & (1 << i)) { + pos += sizeof (target_mgreg_t); + ppc_ldptr (code, i, -pos, ppc_r12); + } + } + } else { + /* FIXME restore from MonoLMF: though this can't happen yet */ + } + ppc_mr (code, ppc_sp, ppc_r12); mono_add_patch_info (cfg, (guint8*) code - cfg->native_code, MONO_PATCH_INFO_METHOD_JUMP, call->method); cfg->thunk_area += THUNK_SIZE; @@ -3834,7 +3881,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) } case OP_CHECK_THIS: /* ensure ins->sreg1 is not NULL */ - ppc_ldptr (code, ppc_r0, 0, ins->sreg1); + ppc_lbz (code, ppc_r0, 0, ins->sreg1); break; case OP_ARGLIST: { long cookie_offset = cfg->sig_cookie + cfg->stack_usage; @@ -3907,11 +3954,11 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) if (cfg->compile_aot && ins->sreg1 == ppc_r12) { /* The trampolines clobber this */ ppc_mr (code, ppc_r29, ins->sreg1); - ppc_ldptr (code, ppc_r0, ins->inst_offset, ppc_r29); + ppc_ldptr (code, ppc_r12, ins->inst_offset, ppc_r29); } else { - ppc_ldptr (code, ppc_r0, ins->inst_offset, ins->sreg1); + ppc_ldptr (code, ppc_r12, ins->inst_offset, ins->sreg1); } - ppc_mtlr (code, ppc_r0); + ppc_mtlr (code, ppc_r12); ppc_blrl (code); /* FIXME: this should be handled somewhere else in the new jit */ code = emit_move_return_value (cfg, ins, code); @@ -5142,8 +5189,8 @@ mono_arch_emit_prolog (MonoCompile *cfg) int soffset = 0; int cur_reg; int size = 0; - g_assert (ppc_is_imm16 (inst->inst_offset)); - g_assert (ppc_is_imm16 (inst->inst_offset + ainfo->vtregs * sizeof (target_mgreg_t))); + g_assert (ppc_is_imm32 (inst->inst_offset)); + g_assert (ppc_is_imm32 (inst->inst_offset + ainfo->vtregs * sizeof (target_mgreg_t))); /* FIXME: what if there is no class? */ if (sig->pinvoke && !sig->marshalling_disabled && mono_class_from_mono_type_internal (inst->inst_vtype)) size = mono_class_native_size (mono_class_from_mono_type_internal (inst->inst_vtype), NULL); @@ -5171,21 +5218,39 @@ mono_arch_emit_prolog (MonoCompile *cfg) (sizeof (target_mgreg_t) - ainfo->bytes) * 8); ppc_stptr (code, ppc_r0, doffset, inst->inst_basereg); #else - if (mono_class_native_size (inst->klass, NULL) == 1) { - ppc_stb (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); - } else if (mono_class_native_size (inst->klass, NULL) == 2) { - ppc_sth (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); - } else if (mono_class_native_size (inst->klass, NULL) == 4) { // WDS -- maybe <=4? - ppc_stw (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); - } else { - ppc_stptr (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); // WDS -- Better way? + if (ppc_is_imm16 (inst->inst_offset)) { + if (mono_class_native_size (inst->klass, NULL) == 1) { + ppc_stb (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); + } else if (mono_class_native_size (inst->klass, NULL) == 2) { + ppc_sth (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); + } else if (mono_class_native_size (inst->klass, NULL) == 4) { // WDS -- maybe <=4? + ppc_stw (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); + } else { + ppc_stptr (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); // WDS -- Better way? + } + } + else if (ppc_is_imm32 (inst->inst_offset)) { + ppc_addis (code, ppc_r12, inst->inst_basereg, ppc_ha(doffset)); + ppc_stptr (code, ainfo->reg + cur_reg, doffset, ppc_r12); + } + else { + g_assert_not_reached(); } #endif } else #endif { - ppc_stptr (code, ainfo->reg + cur_reg, doffset, - inst->inst_basereg); + if (ppc_is_imm16 (inst->inst_offset)) { + ppc_stptr (code, ainfo->reg + cur_reg, doffset, + inst->inst_basereg); + } + else if (ppc_is_imm32 (inst->inst_offset)) { + ppc_addis (code, ppc_r12, inst->inst_basereg, ppc_ha(doffset)); + ppc_stptr (code, ainfo->reg + cur_reg, doffset, ppc_r12); + } + else { + g_assert_not_reached(); + } } } soffset += sizeof (target_mgreg_t); @@ -5538,6 +5603,14 @@ mono_arch_emit_exceptions (MonoCompile *cfg) } set_code_cursor (cfg, code); + +#ifdef THUNK_ADDR_ALIGNMENT + /* We need to align thunks_offset to 8 byte boundary, hence allocating first 8 bytes + for padding purpose */ + if (cfg->thunk_area != 0) { + cfg->thunk_area += THUNK_ADDR_ALIGNMENT; + } +#endif } #endif @@ -5857,7 +5930,7 @@ host_mgreg_t* mono_arch_context_get_int_reg_address (MonoContext *ctx, int reg) { if (reg == ppc_r1) - return (host_mgreg_t)(gsize)MONO_CONTEXT_GET_SP (ctx); + return (host_mgreg_t *)(gsize)&ctx->sc_sp; return &ctx->regs [reg]; } diff --git a/src/mono/mono/mini/mini-ppc.h b/src/mono/mono/mini/mini-ppc.h index 0b962aac233d24..e872c4b99c5387 100644 --- a/src/mono/mono/mini/mini-ppc.h +++ b/src/mono/mono/mini/mini-ppc.h @@ -35,6 +35,7 @@ #ifdef TARGET_POWERPC64 #if !defined(PPC_USES_FUNCTION_DESCRIPTOR) #define THUNK_SIZE 8 +#define THUNK_ADDR_ALIGNMENT 8 #define GET_MEMORY_SLOT_THUNK_ADDRESS(c) \ ((guint64)(((c)) [0] & 0x0000ffff) << 48) \ + ((guint64)(((c)) [1] & 0x0000ffff) << 32) \ diff --git a/src/mono/mono/mini/mini-runtime.c b/src/mono/mono/mini/mini-runtime.c index 5aa0502bc0800e..fb76aef5496398 100644 --- a/src/mono/mono/mini/mini-runtime.c +++ b/src/mono/mono/mini/mini-runtime.c @@ -38,9 +38,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -2022,7 +2024,7 @@ static clockid_t clock_id = CLOCK_MONOTONIC; enum { JIT_DUMP_MAGIC = 0x4A695444, - JIT_DUMP_VERSION = 2, + JIT_DUMP_VERSION = 1, #if HOST_X86 ELF_MACHINE = EM_386, #elif HOST_AMD64 @@ -2038,7 +2040,8 @@ enum { #elif HOST_RISCV ELF_MACHINE = EM_RISCV, #endif - JIT_CODE_LOAD = 0 + JIT_CODE_LOAD = 0, + JIT_DEBUG_INFO = 2 }; typedef struct { @@ -2069,7 +2072,22 @@ typedef struct // Null terminated function name // Native code } JitCodeLoadRecord; +typedef struct +{ + guint64 code_addr; + guint32 line; + guint32 discrim; + char name[]; +} DebugEntry; +typedef struct +{ + RecordHeader header; + guint64 code_addr; + guint64 nr_entry; + DebugEntry debug_entry[]; +} JitCodeDebug; +static void add_basic_JitCodeDebug_info (JitCodeDebug *record); static void add_file_header_info (FileHeader *header); static void add_basic_JitCodeLoadRecord_info (JitCodeLoadRecord *record); @@ -2089,7 +2107,7 @@ mono_enable_jit_dump (void) g_snprintf (name, sizeof (name), "/tmp/jit-%d.dump", perf_dump_pid); unlink (name); - perf_dump_file = fopen (name, "w"); + perf_dump_file = fopen (name, "w+"); add_file_header_info (&header); if (perf_dump_file) { @@ -2135,7 +2153,72 @@ mono_emit_jit_dump (MonoJitInfo *jinfo, gpointer code) record.code_index = ++code_index; - // TODO: write debugInfo and unwindInfo immediately before the JitCodeLoadRecord (while lock is held). + DebugEntry ent; + JitCodeDebug rec; + MonoDebugMethodInfo *minfo; + MonoDebugMethodJitInfo *dmji; + MonoDebugSourceLocation *loc; + int i; + + memset (&rec, 0, sizeof (rec)); + + //populating info relating debug methods + minfo = mono_debug_lookup_method (jinfo->d.method); + dmji = mono_debug_find_method (jinfo->d.method, NULL); + + add_basic_JitCodeDebug_info (&rec); + rec.code_addr = (guint64)dmji->code_start; + rec.header.total_size = sizeof (rec) + sizeof (ent) + 1; + rec.nr_entry = 1; + for (i = 0; i < dmji->num_line_numbers; ++i){ + + loc = mono_debug_lookup_source_location_by_il (jinfo->d.method, dmji->line_numbers[i].il_offset, NULL); + + if(!loc) + continue; + + if(!loc->source_file){ + mono_debug_free_source_location (loc); + continue; + } + + rec.header.total_size += sizeof (ent) + strlen (loc->source_file) + 1; + rec.nr_entry++; + } + + fwrite (&rec, sizeof (rec), 1, perf_dump_file); + + + for( i = 0; i < dmji->num_line_numbers; ++i){ + + //get the line number using il offset + loc = mono_debug_lookup_source_location_by_il (jinfo->d.method, dmji->line_numbers[i].il_offset, NULL); + + if(!loc) + continue; + + if(!loc->source_file){ + + mono_debug_free_source_location (loc); + continue; + } + + ent.code_addr = (guint64)dmji->code_start + dmji->line_numbers[i].native_offset; + ent.discrim = 0; + ent.line = (guint32)loc->row; + + fwrite (&ent, sizeof(ent), 1, perf_dump_file); + fwrite (loc->source_file, strlen (loc->source_file) + 1, 1, perf_dump_file); + } + + + ent.code_addr = (guint64)jinfo->code_start + jinfo->code_size; + ent.discrim = 0; + ent.line = 0; + fwrite (&ent, sizeof (ent), 1, perf_dump_file); + fwrite ("", 1, 1, perf_dump_file); + + // TODO: write unwindInfo immediately before the JitCodeLoadRecord (while lock is held). record.header.timestamp = mono_clock_get_time_ns (clock_id); @@ -2146,7 +2229,13 @@ mono_emit_jit_dump (MonoJitInfo *jinfo, gpointer code) mono_os_mutex_unlock (&perf_dump_mutex); } } +static void +add_basic_JitCodeDebug_info (JitCodeDebug *record) +{ + record->header.id = JIT_DEBUG_INFO; + record->header.timestamp = mono_clock_get_time_ns (clock_id); +} static void add_basic_JitCodeLoadRecord_info (JitCodeLoadRecord *record) { @@ -4467,6 +4556,22 @@ mini_init (const char *filename) mono_component_event_pipe_100ns_ticks_start (); + +#ifdef ENABLE_ILGEN + mono_marshal_lightweight_init (); + mono_marshal_ilgen_init_internal (); +#else + if (mono_marshal_is_ilgen_requested ()) + { + mono_marshal_lightweight_init (); + mono_marshal_ilgen_init_internal (); + } + else{ + mono_marshal_noilgen_init_lightweight(); + mono_marshal_noilgen_init_heavyweight (); + } +#endif + MONO_VES_INIT_BEGIN (); CHECKED_MONO_INIT (); diff --git a/src/mono/mono/mini/mini-s390x.c b/src/mono/mono/mini/mini-s390x.c index 7c7a936ed97014..26e6ba1d32a3b2 100644 --- a/src/mono/mono/mini/mini-s390x.c +++ b/src/mono/mono/mini/mini-s390x.c @@ -3594,8 +3594,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) break; case OP_CHECK_THIS: { /* ensure ins->sreg1 is not NULL */ - s390_lg (code, s390_r0, 0, ins->sreg1, 0); - s390_ltgr (code, s390_r0, s390_r0); + s390_llgc (code, s390_r0, 0, ins->sreg1, 0); } break; case OP_ARGLIST: { diff --git a/src/mono/mono/mini/mini-x86.c b/src/mono/mono/mini/mini-x86.c index 6680b08f72d287..1006eabf989e54 100644 --- a/src/mono/mono/mini/mini-x86.c +++ b/src/mono/mono/mini/mini-x86.c @@ -3192,11 +3192,8 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) break; } case OP_CHECK_THIS: - /* ensure ins->sreg1 is not NULL - * note that cmp DWORD PTR [eax], eax is one byte shorter than - * cmp DWORD PTR [eax], 0 - */ - x86_alu_membase_reg (code, X86_CMP, ins->sreg1, 0, ins->sreg1); + /* ensure ins->sreg1 is not NULL */ + x86_alu_membase8_reg (code, X86_CMP, ins->sreg1, 0, ins->sreg1); break; case OP_ARGLIST: { int hreg = ins->sreg1 == X86_EAX? X86_ECX: X86_EAX; diff --git a/src/mono/mono/mini/mini.c b/src/mono/mono/mini/mini.c index 168d3d3a3c093c..92d158c1ab4108 100644 --- a/src/mono/mono/mini/mini.c +++ b/src/mono/mono/mini/mini.c @@ -3303,9 +3303,31 @@ mini_method_compile (MonoMethod *method, guint32 opts, JitFlags flags, int parts } if (cfg->llvm_only && cfg->interp && !cfg->interp_entry_only && header->num_clauses) { - cfg->deopt = TRUE; - /* Can't reconstruct inlined state */ - cfg->disable_inline = TRUE; + gboolean can_deopt = TRUE; + /* + * Can't handle catch clauses inside finally clauses right now. + * When the ENDFINALLY opcode of the outer clause is encountered + * while executing the inner catch clause from run_with_il_state (), + * it will assert since it doesn't know where to continue execution. + */ + for (guint i = 0; i < cfg->header->num_clauses; ++i) { + for (guint j = 0; j < cfg->header->num_clauses; ++j) { + MonoExceptionClause *clause1 = &cfg->header->clauses [i]; + MonoExceptionClause *clause2 = &cfg->header->clauses [j]; + + if (i != j && clause1->try_offset >= clause2->try_offset && clause1->handler_offset <= clause2->handler_offset) { + if (clause1->flags == MONO_EXCEPTION_CLAUSE_NONE && clause2->flags != MONO_EXCEPTION_CLAUSE_NONE) { + can_deopt = FALSE; + break; + } + } + } + } + if (can_deopt) { + cfg->deopt = TRUE; + /* Can't reconstruct inlined state */ + cfg->disable_inline = TRUE; + } } #ifdef ENABLE_LLVM diff --git a/src/mono/mono/mini/mini.h b/src/mono/mono/mini/mini.h index 3d02bd18a0230c..3a94052307ad5c 100644 --- a/src/mono/mono/mini/mini.h +++ b/src/mono/mono/mini/mini.h @@ -1609,6 +1609,7 @@ typedef struct { GSList *signatures; GSList *interp_in_signatures; + GSList *pinvoke_calli_signatures; /* GC Maps */ diff --git a/src/mono/mono/mini/simd-intrinsics.c b/src/mono/mono/mini/simd-intrinsics.c index 4ce3b622a47679..094fbffe0a2ae0 100644 --- a/src/mono/mono/mini/simd-intrinsics.c +++ b/src/mono/mono/mini/simd-intrinsics.c @@ -718,6 +718,13 @@ emit_hardware_intrinsics ( if (!info) goto support_probe_complete; id = info->id; + +#ifdef TARGET_ARM64 + if (!(cfg->compile_aot && cfg->full_aot && !cfg->interp) && !intrin_group->jit_supported) { + goto support_probe_complete; + } +#endif + // Hardware intrinsics are LLVM-only. if (!COMPILE_LLVM (cfg) && !intrin_group->jit_supported) goto support_probe_complete; @@ -986,10 +993,15 @@ is_element_type_primitive (MonoType *vector_type) static MonoInst* emit_sri_vector (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature *fsig, MonoInst **args) -{ +{ if (!COMPILE_LLVM (cfg)) return NULL; +#ifdef TARGET_ARM64 + if (!(cfg->compile_aot && cfg->full_aot && !cfg->interp)) + return NULL; +#endif + int id = lookup_intrins (sri_vector_methods, sizeof (sri_vector_methods), cmethod); if (id == -1) { //check_no_intrinsic_cattr (cmethod); diff --git a/src/mono/mono/mini/tramp-ppc.c b/src/mono/mono/mini/tramp-ppc.c index 59bcb275a48609..e0bc7f8eca42fc 100644 --- a/src/mono/mono/mini/tramp-ppc.c +++ b/src/mono/mono/mini/tramp-ppc.c @@ -672,10 +672,10 @@ mono_arch_get_call_target (guint8 *code) } #if defined(TARGET_POWERPC64) && !defined(PPC_USES_FUNCTION_DESCRIPTOR) else if (((guint32*)(code - 32)) [0] >> 26 == 15) { - guint8 *thunk = GET_MEMORY_SLOT_THUNK_ADDRESS((guint32*)(code - 32)); + guint8 *thunk = (guint8 *)GET_MEMORY_SLOT_THUNK_ADDRESS((guint32*)(code - 32)); return thunk; } else if (((guint32*)(code - 4)) [0] >> 26 == 15) { - guint8 *thunk = GET_MEMORY_SLOT_THUNK_ADDRESS((guint32*)(code - 4)); + guint8 *thunk = (guint8 *)GET_MEMORY_SLOT_THUNK_ADDRESS((guint32*)(code - 4)); return thunk; } #endif diff --git a/src/mono/mono/sgen/sgen-gc.h b/src/mono/mono/sgen/sgen-gc.h index 69fd11d8a3d338..4e18fce08babc8 100644 --- a/src/mono/mono/sgen/sgen-gc.h +++ b/src/mono/mono/sgen/sgen-gc.h @@ -51,10 +51,6 @@ typedef enum { NurseryClearPolicy sgen_get_nursery_clear_policy (void); -#if !defined(__MACH__) && !MONO_MACH_ARCH_SUPPORTED && defined(HAVE_PTHREAD_KILL) -#define SGEN_POSIX_STW 1 -#endif - /* * The nursery section uses this struct. */ diff --git a/src/mono/mono/sgen/sgen-marksweep.c b/src/mono/mono/sgen/sgen-marksweep.c index 49840611b5b54d..be5da41863dfc2 100644 --- a/src/mono/mono/sgen/sgen-marksweep.c +++ b/src/mono/mono/sgen/sgen-marksweep.c @@ -2861,7 +2861,7 @@ sgen_marksweep_init_internal (SgenMajorCollector *collector, gboolean is_concurr sgen_register_fixed_internal_mem_type (INTERNAL_MEM_MS_BLOCK_INFO, SIZEOF_MS_BLOCK_INFO); - if (mono_cpu_count () <= 1) + if (mono_cpu_limit () <= 1) is_parallel = FALSE; num_block_obj_sizes = ms_calculate_block_obj_sizes (MS_BLOCK_OBJ_SIZE_FACTOR, NULL); @@ -3027,7 +3027,7 @@ sgen_marksweep_init_internal (SgenMajorCollector *collector, gboolean is_concurr #ifndef DISABLE_SGEN_MAJOR_MARKSWEEP_CONC if (is_concurrent && is_parallel) - sgen_workers_create_context (GENERATION_OLD, mono_cpu_count ()); + sgen_workers_create_context (GENERATION_OLD, mono_cpu_limit ()); else if (is_concurrent) sgen_workers_create_context (GENERATION_OLD, 1); diff --git a/src/mono/mono/sgen/sgen-memory-governor.c b/src/mono/mono/sgen/sgen-memory-governor.c index 2c0a0e9ede3ec0..27b56bf67e7a97 100644 --- a/src/mono/mono/sgen/sgen-memory-governor.c +++ b/src/mono/mono/sgen/sgen-memory-governor.c @@ -144,8 +144,6 @@ update_gc_info (mword used_slots_size) sgen_gc_info.heap_size_bytes = major_size + sgen_los_memory_usage_total; sgen_gc_info.fragmented_bytes = sgen_gc_info.heap_size_bytes - sgen_los_memory_usage - major_size_in_use; - guint64 physical_ram_size = mono_determine_physical_ram_size (); - sgen_gc_info.memory_load_bytes = physical_ram_size ? sgen_gc_info.total_available_memory_bytes - (guint64)(((double)sgen_gc_info.total_available_memory_bytes*mono_determine_physical_ram_available_size ())/physical_ram_size) : 0; sgen_gc_info.total_committed_bytes = major_size_in_use + sgen_los_memory_usage; sgen_gc_info.total_promoted_bytes = sgen_total_promoted_size - total_promoted_size_start; sgen_gc_info.total_major_size_bytes = major_size; @@ -154,6 +152,14 @@ update_gc_info (mword used_slots_size) sgen_gc_info.total_los_size_in_use_bytes = sgen_los_memory_usage; } +static void +update_gc_info_memory_load (void) +{ + // We update this separately because it is not safe to do it during GC stw + guint64 physical_ram_size = mono_determine_physical_ram_size (); + sgen_gc_info.memory_load_bytes = physical_ram_size ? sgen_gc_info.total_available_memory_bytes - (guint64)(((double)sgen_gc_info.total_available_memory_bytes*mono_determine_physical_ram_available_size ())/physical_ram_size) : 0; +} + gboolean sgen_need_major_collection (mword space_needed, gboolean *forced) { @@ -387,6 +393,8 @@ sgen_memgov_collection_end (int generation, gint64 stw_time) sgen_pointer_queue_clear (&log_entries); mono_os_mutex_unlock (&log_entries_mutex); } + + update_gc_info_memory_load (); } /* diff --git a/src/mono/mono/sgen/sgen-simple-nursery.c b/src/mono/mono/sgen/sgen-simple-nursery.c index 18a771d0abafce..8a63d8f66942e5 100644 --- a/src/mono/mono/sgen/sgen-simple-nursery.c +++ b/src/mono/mono/sgen/sgen-simple-nursery.c @@ -148,7 +148,7 @@ fill_parallel_with_concurrent_major_ops (SgenObjectOperations *ops) void sgen_simple_nursery_init (SgenMinorCollector *collector, gboolean parallel) { - if (mono_cpu_count () <= 1) + if (mono_cpu_limit () <= 1) parallel = FALSE; #ifdef DISABLE_SGEN_MAJOR_MARKSWEEP_CONC diff --git a/src/mono/mono/utils/CMakeLists.txt b/src/mono/mono/utils/CMakeLists.txt index 7ceb82e8eed230..4d552b6e033dd9 100644 --- a/src/mono/mono/utils/CMakeLists.txt +++ b/src/mono/mono/utils/CMakeLists.txt @@ -32,6 +32,7 @@ set(utils_common_sources mono-sha1.c mono-logger.c mono-logger-internals.h + mono-cgroup.c mono-codeman.c mono-counters.c mono-compiler.h diff --git a/src/mono/mono/utils/memfuncs.c b/src/mono/mono/utils/memfuncs.c index 4fc78079626abc..a38dcec17b1b63 100644 --- a/src/mono/mono/utils/memfuncs.c +++ b/src/mono/mono/utils/memfuncs.c @@ -27,6 +27,7 @@ #include #include #include +#include #if defined (__APPLE__) #include @@ -69,6 +70,7 @@ __d [__i] = NULL; \ } while (0) +#define MINMEMSZ 20971520 /* Minimum restricted memory size - 20MB */ /** * mono_gc_bzero_aligned: @@ -273,24 +275,59 @@ mono_determine_physical_ram_size (void) return (guint64)value; #elif defined (HAVE_SYSCONF) - gint64 page_size = -1, num_pages = -1; + guint64 page_size = 0, num_pages = 0, memsize; /* sysconf works on most *NIX operating systems, if your system doesn't have it or if it * reports invalid values, please add your OS specific code below. */ #ifdef _SC_PAGESIZE - page_size = (gint64)sysconf (_SC_PAGESIZE); + page_size = (guint64)sysconf (_SC_PAGESIZE); #endif #ifdef _SC_PHYS_PAGES - num_pages = (gint64)sysconf (_SC_PHYS_PAGES); + num_pages = (guint64)sysconf (_SC_PHYS_PAGES); #endif - if (page_size == -1 || num_pages == -1) { + if (!page_size || !num_pages) { g_warning ("Your operating system's sysconf (3) function doesn't correctly report physical memory size!"); return _DEFAULT_MEM_SIZE; } - return (guint64)page_size * (guint64)num_pages; +#if defined(_SC_AVPHYS_PAGES) + memsize = sysconf(_SC_AVPHYS_PAGES) * page_size; +#else + memsize = page_size * num_pages; /* Calculate physical memory size */ +#endif + +#if HAVE_CGROUP_SUPPORT + gint64 restricted_limit = mono_get_restricted_memory_limit(); /* Check for any cgroup limit */ + if (restricted_limit != 0) { + gchar *heapHardLimit = getenv("DOTNET_GCHeapHardLimit"); /* See if user has set a limit */ + if (heapHardLimit == NULL) + heapHardLimit = getenv("COMPlus_GCHeapHardLimit"); /* Check old envvar name */ + errno = 0; + if (heapHardLimit != NULL) { + guint64 gcLimit = strtoull(heapHardLimit, NULL, 16); + if ((errno == 0) && (gcLimit != 0)) + restricted_limit = (restricted_limit < gcLimit ? restricted_limit : (gint64) gcLimit); + } else { + gchar *heapHardLimitPct = getenv("DOTNET_GCHeapHardLimitPercent"); /* User % limit? */ + if (heapHardLimitPct == NULL) + heapHardLimitPct = getenv("COMPlus_GCHeapHardLimitPercent"); /* Check old envvar name */ + if (heapHardLimitPct != NULL) { + int gcLimit = strtoll(heapHardLimitPct, NULL, 16); + if ((gcLimit > 0) && (gcLimit <= 100)) + restricted_limit = (gcLimit * restricted_limit) / 100; + else + restricted_limit = (3 * restricted_limit) / 4; /* Use 75% limit of container */ + } else { + restricted_limit = (3 * restricted_limit) / 4; /* Use 75% limit of container */ + } + } + return (restricted_limit < MINMEMSZ ? MINMEMSZ : /* Use at least 20MB */ + (restricted_limit < memsize ? restricted_limit : memsize)); + } +#endif + return memsize; #else return _DEFAULT_MEM_SIZE; #endif @@ -343,25 +380,28 @@ mono_determine_physical_ram_available_size (void) host_page_size (host, &page_size); return (guint64) vmstat.free_count * page_size; +#elif HAVE_CGROUP_SUPPORT + return (mono_get_memory_avail()); + #elif defined (HAVE_SYSCONF) - gint64 page_size = -1, num_pages = -1; + guint64 page_size = 0, num_pages = 0; /* sysconf works on most *NIX operating systems, if your system doesn't have it or if it * reports invalid values, please add your OS specific code below. */ #ifdef _SC_PAGESIZE - page_size = (gint64)sysconf (_SC_PAGESIZE); + page_size = (guint64)sysconf (_SC_PAGESIZE); #endif #ifdef _SC_AVPHYS_PAGES - num_pages = (gint64)sysconf (_SC_AVPHYS_PAGES); + num_pages = (guint64)sysconf (_SC_AVPHYS_PAGES); #endif - if (page_size == -1 || num_pages == -1) { + if (!page_size || !num_pages) { g_warning ("Your operating system's sysconf (3) function doesn't correctly report physical memory size!"); return _DEFAULT_MEM_SIZE; } - return (guint64)page_size * (guint64)num_pages; + return page_size * num_pages; #else return _DEFAULT_MEM_SIZE; #endif diff --git a/src/mono/mono/utils/memfuncs.h b/src/mono/mono/utils/memfuncs.h index 904b28c96562c4..c8b934e3f7aacb 100644 --- a/src/mono/mono/utils/memfuncs.h +++ b/src/mono/mono/utils/memfuncs.h @@ -24,5 +24,10 @@ MONO_COMPONENT_API void mono_gc_memmove_atomic (void *dest, const void *src, siz void mono_gc_memmove_aligned (void *dest, const void *src, size_t size); guint64 mono_determine_physical_ram_size (void); guint64 mono_determine_physical_ram_available_size (void); +#if HAVE_CGROUP_SUPPORT +size_t mono_get_restricted_memory_limit(void); +gboolean mono_get_memory_used(size_t *); +size_t mono_get_memory_avail(void); +#endif #endif diff --git a/src/mono/mono/utils/mono-cgroup.c b/src/mono/mono/utils/mono-cgroup.c new file mode 100644 index 00000000000000..337abbe38b2350 --- /dev/null +++ b/src/mono/mono/utils/mono-cgroup.c @@ -0,0 +1,972 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/*++ + +Module Name: + + mono-cgroup.c + +Abstract: + Read the memory limit for the current process + + Adapted from runtime src/coreclr/gc/unix/cgroup.cpp + - commit 28ec20194010c2a3d06f2217998cfcb8e8b8fb5e +--*/ +#ifdef __FreeBSD__ +#define _WITH_GETLINE +#endif + +#include +#include + +#if HAVE_CGROUP_SUPPORT + +#include +#include +#include +#include +#include +#include +#include +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#include +#else +#include +#endif +#include +#include + +#include + +#ifndef SIZE_T_MAX +# define SIZE_T_MAX (~(size_t)0) +#endif + +#define CGROUP2_SUPER_MAGIC 0x63677270 +#define TMPFS_MAGIC 0x01021994 + +#define PROC_MOUNTINFO_FILENAME "/proc/self/mountinfo" +#define PROC_CGROUP_FILENAME "/proc/self/cgroup" +#define PROC_STATM_FILENAME "/proc/self/statm" +#define CGROUP1_MEMORY_LIMIT_FILENAME "/memory.limit_in_bytes" +#define CGROUP2_MEMORY_LIMIT_FILENAME "/memory.max" +#define CGROUP_MEMORY_STAT_FILENAME "/memory.stat" +#define CGROUP1_MEMORY_USAGE_FILENAME "/memory.usage_in_bytes" +#define CGROUP2_MEMORY_USAGE_FILENAME "/memory.current" +#define CGROUP1_MEMORY_STAT_INACTIVE_FIELD "total_inactive_file " +#define CGROUP2_MEMORY_STAT_INACTIVE_FIELD "inactive_file " +#define CGROUP1_CFS_QUOTA_FILENAME "/cpu.cfs_quota_us" +#define CGROUP1_CFS_PERIOD_FILENAME "/cpu.cfs_period_us" +#define CGROUP2_CPU_MAX_FILENAME "/cpu.max" + +static void initialize(void); +static gboolean readMemoryValueFromFile(const char *, size_t *); +static gboolean getPhysicalMemoryLimit(size_t *); +static gboolean getPhysicalMemoryUsage(size_t *); +static int findCGroupVersion(void); +static gboolean isCGroup1MemorySubsystem(const char *); +static gboolean isCGroup1CpuSubsystem(const char *); +static char *findCGroupPath(gboolean (*is_subsystem)(const char *)); +static void findHierarchyMount(gboolean (*is_subsystem)(const char *), char **, char **); +static char *findCGroupPathForSubsystem(gboolean (*is_subsystem)(const char *)); +static gboolean getCGroupMemoryLimit(size_t *, const char *); +static gboolean getCGroupMemoryUsage(size_t *, const char *, const char *); +static size_t getPhysicalMemoryTotal(size_t); +static long long readCpuCGroupValue(const char *); +static void computeCpuLimit(long long, long long, guint32 *); + +size_t mono_get_restricted_memory_limit(void); +gboolean mono_get_memory_used(size_t *); +size_t mono_get_memory_avail(void); +gboolean mono_get_cpu_limit(guint *); +static gboolean readLongLongValueFromFile(const char *, long long *); + +// the cgroup version number or 0 to indicate cgroups are not found or not enabled +static int s_cgroup_version = -1; + +static char *s_memory_cgroup_path = NULL; +static char *s_cpu_cgroup_path = NULL; + +static long pageSize; + +/** + * @brief Initialize variables used by the calculation routines. + * + */ +static void +initialize() +{ + s_cgroup_version = findCGroupVersion (); + s_memory_cgroup_path = findCGroupPath (s_cgroup_version == 1 ? &isCGroup1MemorySubsystem : NULL); + s_cpu_cgroup_path = findCGroupPath (s_cgroup_version == 1 ? &isCGroup1CpuSubsystem : NULL); + + if (s_cgroup_version == 0) + return; + + pageSize = sysconf (_SC_PAGE_SIZE); +} + +/** + * + * @brief Read a value from a specified /sys/fs/cgroup/memory file + * + * @param[in] filename - name of file containing value + * @param[out] val - pointer to the result area + * @returns True or False depending if value was found + * + */ +static gboolean +readMemoryValueFromFile(const char* filename, size_t* val) +{ + gboolean result = FALSE; + char *line = NULL; + size_t lineLen = 0; + char *endptr = NULL; + FILE *file = NULL; + + if (val != NULL) { + file = fopen (filename, "r"); + if (file != NULL) { + if (getline (&line, &lineLen, file) != -1) { + errno = 0; + *val = strtoull (line, &endptr, 0); + result = TRUE; + } + } + } + + if (file) + fclose (file); + free (line); + return result; +} + +/** + * + * @brief Interrogate the cgroup memory values to determine if there's + * a limit on physical memory. + * + * @param[out] val - pointer to the result area + * @returns True or False depending if a limit was found + * + */ +static gboolean +getPhysicalMemoryLimit(size_t *val) +{ + if (s_cgroup_version == 0) + return FALSE; + else if (s_cgroup_version == 1) + return getCGroupMemoryLimit (val, CGROUP1_MEMORY_LIMIT_FILENAME); + else if (s_cgroup_version == 2) + return getCGroupMemoryLimit (val, CGROUP2_MEMORY_LIMIT_FILENAME); + else { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Unknown cgroup version."); + return FALSE; + } +} + +/** + * + * @brief Interrogate the cgroup memory values to determine how much + * memory is in use. + * + * @param[out] val - pointer to the result area + * @returns True or False depending if a usage value was found + * + */ +static gboolean +getPhysicalMemoryUsage(size_t *val) +{ + if (s_cgroup_version == 0) + return FALSE; + else if (s_cgroup_version == 1) + return getCGroupMemoryUsage (val, CGROUP1_MEMORY_USAGE_FILENAME, CGROUP1_MEMORY_STAT_INACTIVE_FIELD); + else if (s_cgroup_version == 2) + return getCGroupMemoryUsage (val, CGROUP2_MEMORY_USAGE_FILENAME, CGROUP2_MEMORY_STAT_INACTIVE_FIELD); + else { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Unknown cgroup version."); + return FALSE; + } +} + +/** + * + * @brief Inspect the /sys/fs/cgroup hierachy to determine what version of + * group we are using + * + * @returns cgroup version + * + */ +static int +findCGroupVersion() +{ + // It is possible to have both cgroup v1 and v2 enabled on a system. + // Most non-bleeding-edge Linux distributions fall in this group. We + // look at the file system type of /sys/fs/cgroup to determine which + // one is the default. For more details, see: + // https://systemd.io/CGROUP_DELEGATION/#three-different-tree-setups- + // We dont care about the difference between the "legacy" and "hybrid" + // modes because both of those involve cgroup v1 controllers managing + // resources. + + + struct statfs stats; + int result = statfs ("/sys/fs/cgroup", &stats); + if (result != 0) + return 0; + + switch (stats.f_type) { + case TMPFS_MAGIC: return 1; + case CGROUP2_SUPER_MAGIC: return 2; + default: return 0; + } +} + +/** + * + * @brief Check if we've found the memory component of /sys/fs/cgroup + * + * @param[in] strTok - Token for comparison + * @returns True if token matches "memory" + * + */ +static gboolean +isCGroup1MemorySubsystem(const char *strTok) +{ + return strcmp ("memory", strTok) == 0; +} + +/** + * + * @brief Check if we've found the CPU component of /sys/fs/cgroup + * + * @param[in] strTok - Token for comparison + * @returns True if token matches "cpu" + * + */ +static gboolean +isCGroup1CpuSubsystem(const char *strTok) +{ + return strcmp ("cpu", strTok) == 0; +} + +/** + * + * @brief Navigate the /sys/fs/cgroup to try and find the correct cgroup path + * + * @param[in] is_subsystem - Function used to compare tokens + * @returns Path to cgroup + * + */ +static char * +findCGroupPath(gboolean (*is_subsystem)(const char *)) +{ + char *cgroup_path = NULL; + char *hierarchy_mount = NULL; + char *hierarchy_root = NULL; + char *cgroup_path_relative_to_mount = NULL; + size_t common_path_prefix_len; + + findHierarchyMount (is_subsystem, &hierarchy_mount, &hierarchy_root); + if (hierarchy_mount != NULL && hierarchy_root != NULL) { + + cgroup_path_relative_to_mount = findCGroupPathForSubsystem (is_subsystem); + if (cgroup_path_relative_to_mount != NULL) { + + cgroup_path = (char*)malloc (strlen (hierarchy_mount) + strlen (cgroup_path_relative_to_mount) + 1); + if (cgroup_path != NULL) { + + strcpy (cgroup_path, hierarchy_mount); + // For a host cgroup, we need to append the relative path. + // The root and cgroup path can share a common prefix of the path that should not be appended. + // Example 1 (docker): + // hierarchy_mount: /sys/fs/cgroup/cpu + // hierarchy_root: /docker/87ee2de57e51bc75175a4d2e81b71d162811b179d549d6601ed70b58cad83578 + // cgroup_path_relative_to_mount: /docker/87ee2de57e51bc75175a4d2e81b71d162811b179d549d6601ed70b58cad83578/my_named_cgroup + // append do the cgroup_path: /my_named_cgroup + // final cgroup_path: /sys/fs/cgroup/cpu/my_named_cgroup + // + // Example 2 (out of docker) + // hierarchy_mount: /sys/fs/cgroup/cpu + // hierarchy_root: / + // cgroup_path_relative_to_mount: /my_named_cgroup + // append do the cgroup_path: /my_named_cgroup + // final cgroup_path: /sys/fs/cgroup/cpu/my_named_cgroup + common_path_prefix_len = strlen (hierarchy_root); + if ((common_path_prefix_len == 1) || + (strncmp (hierarchy_root, cgroup_path_relative_to_mount, common_path_prefix_len) != 0)) + common_path_prefix_len = 0; + + g_assert((cgroup_path_relative_to_mount[common_path_prefix_len] == '/') || + (cgroup_path_relative_to_mount[common_path_prefix_len] == '\0')); + + strcat (cgroup_path, cgroup_path_relative_to_mount + common_path_prefix_len); + } + } + } + + free (hierarchy_mount); + free (hierarchy_root); + free (cgroup_path_relative_to_mount); + return cgroup_path; +} + +/** + * + * @brief Check the /proc filesystem to determine the root and mount + * path of /sys/fs/cgroup data + * + * @param[in] is_subsystem - Comparison function + * @param[out] pmountpath - + * @param[out] pmountroot - + * + */ +static void +findHierarchyMount(gboolean (*is_subsystem)(const char *), char** pmountpath, char** pmountroot) +{ + char *line = NULL; + size_t lineLen = 0, maxLineLen = 0; + char *filesystemType = NULL; + char *options = NULL; + char *mountpath = NULL; + char *mountroot = NULL; + + FILE *mountinfofile = fopen (PROC_MOUNTINFO_FILENAME, "r"); + if (mountinfofile == NULL) + goto done; + + while (getline (&line, &lineLen, mountinfofile) != -1) { + if (filesystemType == NULL || lineLen > maxLineLen) { + free (filesystemType); + filesystemType = NULL; + free (options); + options = NULL; + filesystemType = (char*)malloc (lineLen+1); + if (filesystemType == NULL) + goto done; + options = (char*)malloc (lineLen+1); + if (options == NULL) + goto done; + maxLineLen = lineLen; + } + + char *separatorChar = strstr (line, " - "); + + // See man page of proc to get format for /proc/self/mountinfo file + int sscanfRet = sscanf (separatorChar, + " - %s %*s %s", + filesystemType, + options); + if (sscanfRet != 2) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, "Failed to parse mount info file contents with sscanf."); + goto done; + } + + if (strncmp(filesystemType, "cgroup", 6) == 0) { + gboolean isSubsystemMatch = is_subsystem == NULL; + if (!isSubsystemMatch) { + char *context = NULL; + char *strTok = strtok_r (options, ",", &context); + while (!isSubsystemMatch && strTok != NULL) + { + isSubsystemMatch = is_subsystem (strTok); + strTok = strtok_r (NULL, ",", &context); + } + } + if (isSubsystemMatch) { + mountpath = (char*)malloc (lineLen+1); + if (mountpath == NULL) + goto done; + mountroot = (char*)malloc (lineLen+1); + if (mountroot == NULL) + goto done; + + sscanfRet = sscanf (line, + "%*s %*s %*s %s %s ", + mountroot, + mountpath); + if (sscanfRet != 2) + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Failed to parse mount info file contents with sscanf."); + + // assign the output arguments and clear the locals so we don't free them. + *pmountpath = mountpath; + *pmountroot = mountroot; + mountpath = mountroot = NULL; + } + } + } +done: + free (mountpath); + free (mountroot); + free (filesystemType); + free (options); + free (line); + if (mountinfofile) + fclose (mountinfofile); +} + +/** + * + * @brief + * Check the /proc filesystem to determine the root and mount path + * of /sys/fs/cgroup data + * + * @param[in] is_subsystem - Comparison function + * @returns cgroup path for the memory subsystem + * + */ +static char * +findCGroupPathForSubsystem(gboolean (*is_subsystem)(const char *)) +{ + char *line = NULL; + size_t lineLen = 0; + size_t maxLineLen = 0; + char *subsystem_list = NULL; + char *cgroup_path = NULL; + gboolean result = FALSE; + + FILE *cgroupfile = fopen (PROC_CGROUP_FILENAME, "r"); + if (cgroupfile == NULL) + goto done; + + while (!result && getline (&line, &lineLen, cgroupfile) != -1) { + if (subsystem_list == NULL || lineLen > maxLineLen) { + free (subsystem_list); + subsystem_list = NULL; + free (cgroup_path); + cgroup_path = NULL; + subsystem_list = (char*)malloc (lineLen+1); + if (subsystem_list == NULL) + goto done; + cgroup_path = (char*)malloc (lineLen+1); + if (cgroup_path == NULL) + goto done; + maxLineLen = lineLen; + } + + if (s_cgroup_version == 1) { + // See man page of proc to get format for /proc/self/cgroup file + int sscanfRet = sscanf (line, + "%*[^:]:%[^:]:%s", + subsystem_list, + cgroup_path); + if (sscanfRet != 2) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Failed to parse cgroup info file contents with sscanf."); + goto done; + } + + char* context = NULL; + char* strTok = strtok_r (subsystem_list, ",", &context); + while (strTok != NULL) { + if (is_subsystem (strTok)) { + result = TRUE; + break; + } + strTok = strtok_r (NULL, ",", &context); + } + } else if (s_cgroup_version == 2) { + // See https://www.kernel.org/doc/Documentation/cgroup-v2.txt + // Look for a "0::/some/path" + int sscanfRet = sscanf (line, + "0::%s", + cgroup_path); + if (sscanfRet == 1) + { + result = TRUE; + } + } else { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Unknown cgroup version in mountinfo."); + goto done; + } + } +done: + free (subsystem_list); + if (!result) { + free (cgroup_path); + cgroup_path = NULL; + } + free (line); + if (cgroupfile) + fclose (cgroupfile); + return cgroup_path; +} + +/** + * + * @brief Extract memory limit from specified /sys/fs/cgroup/memory file + * + * @param[out] val - Memory limit + * @param[in] filename - name of file from which to extract limit + * @returns True if value found + * + */ +static gboolean +getCGroupMemoryLimit(size_t *val, const char *filename) +{ + if (s_memory_cgroup_path == NULL) + return FALSE; + + char* mem_limit_filename = NULL; + if (asprintf (&mem_limit_filename, "%s%s", s_memory_cgroup_path, filename) < 0) + return FALSE; + + gboolean result = readMemoryValueFromFile (mem_limit_filename, val); + free (mem_limit_filename); + return result; +} + +/** + * + * @brief Extract memory usage from /sys/fs/cgroup/memory.stat file + * + * @param[out] val - Memory limit + * @returns True if value found + * + */ +static gboolean +getCGroupMemoryUsage(size_t *val, const char *filename, const char *inactiveFileFieldName) +{ + /* + * Use the same way to calculate memory load as popular container tools (Docker, Kubernetes, Containerd etc.) + * For cgroup v1: value of 'memory.usage_in_bytes' minus 'total_inactive_file' value of 'memory.stat' + * For cgroup v2: value of 'memory.current' minus 'inactive_file' value of 'memory.stat' + */ + + char *mem_usage_filename = NULL; + if (asprintf (&mem_usage_filename, "%s%s", s_memory_cgroup_path, filename) < 0) + return FALSE; + + size_t temp = 0; + size_t usage = 0; + + gboolean result = readMemoryValueFromFile (mem_usage_filename, &temp); + if (result) { + if (temp > SIZE_T_MAX) + usage = SIZE_T_MAX; + else + usage = temp; + } + + free (mem_usage_filename); + + if (!result) + return result; + + if (s_memory_cgroup_path == NULL) + return FALSE; + + char *stat_filename = NULL; + if (asprintf (&stat_filename, "%s%s", s_memory_cgroup_path, CGROUP_MEMORY_STAT_FILENAME) < 0) + return FALSE; + + FILE *stat_file = fopen (stat_filename, "r"); + free (stat_filename); + if (stat_file == NULL) + return FALSE; + + char *line = NULL; + size_t lineLen = 0; + gboolean foundInactiveFileValue = FALSE; + char *endptr; + + size_t inactiveFileFieldNameLength = strlen (inactiveFileFieldName); + + while (getline (&line, &lineLen, stat_file) != -1) { + if (strncmp (line, inactiveFileFieldName, inactiveFileFieldNameLength) == 0) { + errno = 0; + const char *startptr = line + inactiveFileFieldNameLength; + size_t inactiveFileValue = strtoll (startptr, &endptr, 10); + if (endptr != startptr && errno == 0) { + foundInactiveFileValue = TRUE; + *val = usage - inactiveFileValue; + } + break; + } + } + + fclose (stat_file); + free (line); + + return foundInactiveFileValue; +} + +/** + * + * @brief Determine if there are any limits on memory and return the value + * + * @returns Physical memory limit + * + * Zero represents no limit. + */ +size_t +mono_get_restricted_memory_limit() +{ + size_t physical_memory_limit = 0; + + if (s_cgroup_version == -1) + initialize(); + + if (s_cgroup_version == 0) + return 0; + + if (!getPhysicalMemoryLimit (&physical_memory_limit)) + return 0; + + // If there's no memory limit specified on the container this + // actually returns 0x7FFFFFFFFFFFF000 (2^63-1 rounded down to + // 4k which is a common page size). So we know we are not + // running in a memory restricted environment. + if (physical_memory_limit > 0x7FFFFFFF00000000) + return 0; + + return (getPhysicalMemoryTotal (physical_memory_limit)); +} + +/** + * + * @brief Check the input limit against any system limits or actual memory on system + * + * @param[in] physical_memory_limit - The max memory on the system + * @returns Physical memory total + * + */ +static size_t +getPhysicalMemoryTotal(size_t physical_memory_limit) +{ + struct rlimit curr_rlimit; + size_t rlimit_soft_limit = (size_t)RLIM_INFINITY; + if (getrlimit (RLIMIT_AS, &curr_rlimit) == 0) + rlimit_soft_limit = curr_rlimit.rlim_cur; + physical_memory_limit = (physical_memory_limit < rlimit_soft_limit) ? + physical_memory_limit : rlimit_soft_limit; + + // Ensure that limit is not greater than real memory size + long pages = sysconf (_SC_PHYS_PAGES); + if (pages != -1) { + if (pageSize != -1) { + physical_memory_limit = (physical_memory_limit < (size_t)pages * pageSize) ? + physical_memory_limit : (size_t)pages * pageSize; + } + } + + if (physical_memory_limit > ULONG_MAX) { + // It is observed in practice when the memory is unrestricted, Linux control + // group returns a physical limit that is bigger than the address space + return ULONG_MAX; + } else + return physical_memory_limit; +} + +/** + * + * @brief Determine the amount of memory in use + * + * @param[out] val - pointer to the memory usage value + * @returns True if we are able to determine usage + * + */ +gboolean +mono_get_memory_used(size_t *val) +{ + gboolean result = FALSE; + size_t linelen; + char *line = NULL; + + if (val == NULL) + return FALSE; + + // Linux uses cgroup usage to trigger oom kills. + if (getPhysicalMemoryUsage (val)) + return TRUE; + + // process resident set size. + FILE* file = fopen (PROC_STATM_FILENAME, "r"); + if (file != NULL && getline (&line, &linelen, file) != -1) { + char* context = NULL; + char* strTok = strtok_r (line, " ", &context); + strTok = strtok_r (NULL, " ", &context); + + errno = 0; + *val = strtoull (strTok, NULL, 0); + if (errno == 0) { + if (pageSize != -1) { + *val = *val * pageSize; + result = TRUE; + } + } + } + + if (file) + fclose (file); + free (line); + return result; +} + +/** + * + * @brief Determine the amount of memory available by examininig any + * limits and checking what memory is in use. + * + * @returns Amount of memory available + * + */ +size_t +mono_get_memory_avail() +{ + size_t max, used, avail, sysAvail; +#ifdef _SC_AVPHYS_PAGES // If this isn't defined then we don't get called + + max = mono_get_restricted_memory_limit (); + + if (max == 0) + max = getPhysicalMemoryTotal (ULONG_MAX); + + if (mono_get_memory_used (&used)) + avail = max - used; + else + avail = max; + + sysAvail = sysconf (_SC_AVPHYS_PAGES) * pageSize; + return (avail < sysAvail ? avail : sysAvail); +#else + return (0); +#endif +} + +/** + * + * @brief Return any limits on CPU use + * + * @returns Number of CPU usable + * + */ +static gboolean +getCGroup1CpuLimit(guint32 *val) +{ + long long quota; + long long period; + + quota = readCpuCGroupValue (CGROUP1_CFS_QUOTA_FILENAME); + if (quota <= 0) + return FALSE; + + period = readCpuCGroupValue (CGROUP1_CFS_PERIOD_FILENAME); + if (period <= 0) + return FALSE; + + computeCpuLimit (period, quota, val); + + return TRUE; +} + +/** + * + * @brief Return any limits on CPU use + * + * @returns Number of CPU usable + * + */ +static gboolean +getCGroup2CpuLimit(guint32 *val) +{ + char *filename = NULL; + FILE *file = NULL; + char *endptr = NULL; + char *max_quota_string = NULL; + char *period_string = NULL; + char *context = NULL; + char *line = NULL; + size_t lineLen = 0; + + long long quota = 0; + long long period = 0; + + + gboolean result = FALSE; + + if (s_cpu_cgroup_path == NULL) + return FALSE; + + if (asprintf (&filename, "%s%s", s_cpu_cgroup_path, CGROUP2_CPU_MAX_FILENAME) < 0) + return FALSE; + + file = fopen (filename, "r"); + if (file == NULL) + goto done; + + if (getline (&line, &lineLen, file) == -1) + goto done; + + // The expected format is: + // $MAX $PERIOD + // Where "$MAX" may be the string literal "max" + + max_quota_string = strtok_r (line, " ", &context); + if (max_quota_string == NULL) + { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Unable to parse " CGROUP2_CPU_MAX_FILENAME " file contents."); + goto done; + } + period_string = strtok_r (NULL, " ", &context); + if (period_string == NULL) + { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Unable to parse " CGROUP2_CPU_MAX_FILENAME " file contents."); + goto done; + } + + // "max" means no cpu limit + if (strcmp ("max", max_quota_string) == 0) + goto done; + + errno = 0; + quota = strtoll (max_quota_string, &endptr, 10); + if (max_quota_string == endptr || errno != 0) + goto done; + + period = strtoll (period_string, &endptr, 10); + if (period_string == endptr || errno != 0) + goto done; + + computeCpuLimit (period, quota, val); + result = TRUE; + +done: + if (file) + fclose (file); + free (filename); + free (line); + + return result; +} + +/** + * + * @brief Compute the CPU limit based on the CGroup data + * + * @param[in] period + * @param[in] quota - Limit found in sysfs + * @param[out] Number of CPU usable + * + */ +static void +computeCpuLimit(long long period, long long quota, uint32_t *val) +{ + // Cannot have less than 1 CPU + if (quota <= period) { + *val = 1; + return; + } + + // Calculate cpu count based on quota and round it up + double cpu_count = (double) quota / period + 0.999999999; + *val = (cpu_count < UINT32_MAX) ? (uint32_t)cpu_count : UINT32_MAX; +} + +/** + * + * @brief Read the CGroup CPU data from sysfs + * + * @param[in] subsystemFileName - sysfs File containing data + * @returns CPU CGroup value + * + */ +static long long +readCpuCGroupValue(const char *subsystemFilename) +{ + char *filename = NULL; + gboolean result = FALSE; + long long val = -1; + + if (s_cpu_cgroup_path == NULL) + return -1; + + if (asprintf (&filename, "%s%s", s_cpu_cgroup_path, subsystemFilename) < 0) + return -1; + + result = readLongLongValueFromFile (filename, &val); + free (filename); + if (!result) + return -1; + + return val; +} + +/** + * + * @brief Read a long long value from a file + * + * @param[in] fileName - sysfs File containing data + * @param[out] val - Value read + * @returns Success indicator + * + */ +static gboolean +readLongLongValueFromFile(const char *filename, long long *val) +{ + gboolean result = FALSE; + char *line = NULL; + size_t lineLen = 0; + char *endptr = NULL; + + if (val == NULL) + return FALSE; + + FILE *file = fopen (filename, "r"); + if (file == NULL) + return FALSE; + + if (getline (&line, &lineLen, file) != -1) { + errno = 0; + *val = strtoll (line, &endptr, 10); + if (line != endptr && errno == 0) + result = TRUE; + } + + fclose (file); + free (line); + return result; +} + +/** + * + * @brief Interrogate the cgroup CPU values to determine if there's + * a limit on CPUs + * + * @param[out] val - pointer to the result area + * @returns True or False depending if a limit was found + * + * Interrogate the cgroup CPU values to determine if there's + * a limit on CPUs + */ +gboolean +mono_get_cpu_limit(guint *val) +{ + if (s_cgroup_version == -1) + initialize(); + + if (s_cgroup_version == 0) + return FALSE; + else if (s_cgroup_version == 1) + return getCGroup1CpuLimit ((guint32 *)val); + else if (s_cgroup_version == 2) + return getCGroup2CpuLimit ((guint32 *)val); + else { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Unknown cgroup version."); + return FALSE; + } +} +#else + +MONO_EMPTY_SOURCE_FILE (mono_cgroup); + +#endif diff --git a/src/mono/mono/utils/mono-context.c b/src/mono/mono/utils/mono-context.c index 83258c781ab008..9585a0c7272d78 100644 --- a/src/mono/mono/utils/mono-context.c +++ b/src/mono/mono/utils/mono-context.c @@ -534,11 +534,32 @@ mono_sigctx_to_monoctx (void *sigctx, MonoContext *mctx) #endif #ifdef __linux__ struct fpsimd_context *fpctx = (struct fpsimd_context*)&((ucontext_t*)sigctx)->uc_mcontext.__reserved; - int i; - g_assert (fpctx->head.magic == FPSIMD_MAGIC); - for (i = 0; i < 32; ++i) - mctx->fregs [i] = fpctx->vregs [i]; + size_t size = 0; + do { + struct fpsimd_context *fpctx_temp = (struct fpsimd_context*)&(((ucontext_t*)sigctx)->uc_mcontext.__reserved[size]); + + if (fpctx_temp->head.magic == FPSIMD_MAGIC) + { + g_assert (fpctx_temp->head.size >= sizeof (struct fpsimd_context)); + g_assert (size + fpctx_temp->head.size <= sizeof (((ucontext_t*)sigctx)->uc_mcontext.__reserved)); + + fpctx = fpctx_temp; + break; + } + + if (fpctx_temp->head.size == 0) + break; + + size += fpctx_temp->head.size; + } while (size + sizeof (struct fpsimd_context) <= sizeof (((ucontext_t*)sigctx)->uc_mcontext.__reserved)); + + if (fpctx->head.magic == FPSIMD_MAGIC) + for (int i = 0; i < 32; ++i) + mctx->fregs [i] = fpctx->vregs [i]; + else + for (int i = 0; i < 32; ++i) + mctx->fregs [i] = 0; #endif /* FIXME: apple */ #endif diff --git a/src/mono/mono/utils/mono-dl.c b/src/mono/mono/utils/mono-dl.c index 512ce7205e606a..c4e5d5eafad243 100644 --- a/src/mono/mono/utils/mono-dl.c +++ b/src/mono/mono/utils/mono-dl.c @@ -176,8 +176,9 @@ fix_libc_name (const char *name) * mono_dl_open_self: * \param error pointer to MonoError * - * Returns a handle to the main program, on android x86 it's not possible to - * call dl_open(null), it returns a null handle, so this function returns RTLD_DEFAULT + * Returns a handle to the main program, on Android it's not possible to + * call dl_open(null) with RTLD_LAZY, it returns a null handle, so this + * function uses RTLD_NOW. * handle in this platform. * \p error points to MonoError where an error will be stored in * case of failure. The error needs to be cleared when done using it, \c mono_error_cleanup. @@ -195,7 +196,7 @@ mono_dl_open_self (MonoError *error) return NULL; } mono_refcount_init (module, NULL); - module->handle = RTLD_DEFAULT; + module->handle = dlopen(NULL, RTLD_NOW); module->dl_fallback = NULL; module->full_name = NULL; return module; diff --git a/src/mono/mono/utils/mono-os-mutex.h b/src/mono/mono/utils/mono-os-mutex.h index 30f914b0893a29..20055973f81cf8 100644 --- a/src/mono/mono/utils/mono-os-mutex.h +++ b/src/mono/mono/utils/mono-os-mutex.h @@ -36,7 +36,7 @@ #if !defined(HOST_WIN32) -#if !defined(CLOCK_MONOTONIC) || defined(HOST_DARWIN) || defined(HOST_WASM) +#if !defined(CLOCK_MONOTONIC) || defined(HOST_DARWIN) || defined(HOST_WASI) #define BROKEN_CLOCK_SOURCE #endif diff --git a/src/mono/mono/utils/mono-proclib.c b/src/mono/mono/utils/mono-proclib.c index 40404b44f85e3b..a683e3d90295cc 100644 --- a/src/mono/mono/utils/mono-proclib.c +++ b/src/mono/mono/utils/mono-proclib.c @@ -12,6 +12,7 @@ #include #include #include +#include #ifdef HOST_WIN32 #include @@ -190,3 +191,44 @@ mono_cpu_count (void) return 1; #endif } + +/** + * mono_cpu_limit: + * \returns the number of processors available to this process + */ +int +mono_cpu_limit (void) +{ +#if defined(HOST_WIN32) + return mono_cpu_count (); +#else + static int limit = -1; /* Value will be cached for future calls */ + + /* + * If 1st time through then check if user has mandated a value and use it, + * otherwise we check for any cgroup limit and use the min of actual number + * and that limit + */ + if (limit == -1) { + char *dotnetProcCnt = getenv ("DOTNET_PROCESSOR_COUNT"); + if (dotnetProcCnt != NULL) { + errno = 0; + limit = (int) strtol (dotnetProcCnt, NULL, 0); + if ((errno == 0) && (limit > 0)) /* If it's in range and positive */ + return limit; + } + limit = mono_cpu_count (); +#if HAVE_CGROUP_SUPPORT + int count = 0; + if (mono_get_cpu_limit (&count)) + limit = (limit < count ? limit : count); +#endif + } + + /* + * Just return the cached value + */ + return limit; +#endif +} + diff --git a/src/mono/mono/utils/mono-proclib.h b/src/mono/mono/utils/mono-proclib.h index f14be3b9f3375b..9c72b47b239004 100644 --- a/src/mono/mono/utils/mono-proclib.h +++ b/src/mono/mono/utils/mono-proclib.h @@ -19,5 +19,12 @@ MONO_API int mono_cpu_count (void); +MONO_API +int +mono_cpu_limit (void); + +gboolean +mono_get_cpu_limit(int *limit); + #endif /* __MONO_PROC_LIB_H__ */ diff --git a/src/mono/mono/utils/mono-threads-mach-helper.c b/src/mono/mono/utils/mono-threads-mach-helper.c index 0efe506faf52ae..64b49ceabef3af 100644 --- a/src/mono/mono/utils/mono-threads-mach-helper.c +++ b/src/mono/mono/utils/mono-threads-mach-helper.c @@ -59,10 +59,10 @@ mono_dead_letter_dealloc (id self, SEL _cmd) { struct objc_super super; super.receiver = self; -#if !defined(__cplusplus) && !__OBJC2__ - super.class = nsobject; -#else +#if defined(__cplusplus) || defined(HAVE_OBJC_SUPER_SUPER_CLASS) super.super_class = nsobject; +#else + super.class = nsobject; #endif void (*objc_msgSendSuper_op)(struct objc_super *, SEL) = (void (*)(struct objc_super *, SEL)) objc_msgSendSuper; objc_msgSendSuper_op (&super, dealloc); diff --git a/src/mono/mono/utils/mono-time.c b/src/mono/mono/utils/mono-time.c index 68831393bc7c34..2a89bd9d3d0772 100644 --- a/src/mono/mono/utils/mono-time.c +++ b/src/mono/mono/utils/mono-time.c @@ -119,9 +119,10 @@ gint64 mono_msec_boottime (void) { /* clock_gettime () is found by configure on Apple builds, but its only present from ios 10, macos 10.12, tvos 10 and watchos 3 */ -#if !defined (TARGET_WASM) && ((defined(HAVE_CLOCK_MONOTONIC_COARSE) || defined(HAVE_CLOCK_MONOTONIC)) && !(defined(TARGET_IOS) || defined(TARGET_OSX) || defined(TARGET_WATCHOS) || defined(TARGET_TVOS))) +#if ((defined(HAVE_CLOCK_MONOTONIC_COARSE) || defined(HAVE_CLOCK_MONOTONIC)) && !(defined(TARGET_IOS) || defined(TARGET_OSX) || defined(TARGET_WATCHOS) || defined(TARGET_TVOS))) clockid_t clockType = -#if HAVE_CLOCK_MONOTONIC_COARSE + /* emscripten exposes CLOCK_MONOTONIC_COARSE but doesn't implement it */ +#if defined(HAVE_CLOCK_MONOTONIC_COARSE) && !defined(TARGET_WASM) CLOCK_MONOTONIC_COARSE; /* good enough resolution, fastest speed */ #else CLOCK_MONOTONIC; diff --git a/src/mono/msbuild/android/build/AndroidApp.targets b/src/mono/msbuild/android/build/AndroidApp.targets index 3d6024763b0f95..41ca304c042b00 100644 --- a/src/mono/msbuild/android/build/AndroidApp.targets +++ b/src/mono/msbuild/android/build/AndroidApp.targets @@ -1,4 +1,9 @@ + + $(GenerateAppBundle) + true + + @@ -119,13 +124,8 @@ - - - marshal-ilgen - - - - - - marshal-ilgen - - true - - - 7.0.0 - $(PackageVersion) diff --git a/src/mono/nuget/Directory.Build.targets b/src/mono/nuget/Directory.Build.targets index 32e2e4251e792f..c3af51a7f58b05 100644 --- a/src/mono/nuget/Directory.Build.targets +++ b/src/mono/nuget/Directory.Build.targets @@ -1,5 +1,13 @@ + + + + $(ProductVersion) + $(PackageVersion) + + + diff --git a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Sdk/AutoImport.props b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Sdk/AutoImport.props index 9831506418661e..93712989d412ba 100644 --- a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Sdk/AutoImport.props +++ b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Sdk/AutoImport.props @@ -1,5 +1,6 @@ - true + net7.0 + true 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 deleted file mode 100644 index 64d946adca32e7..00000000000000 --- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.json.in +++ /dev/null @@ -1,397 +0,0 @@ -{ - "version": "${WorkloadVersion}", - "depends-on": { - "Microsoft.NET.Workload.Emscripten": "${EmscriptenVersion}" - }, - "workloads": { - "wasm-tools": { - "description": ".NET WebAssembly build tools", - "packs": [ - "Microsoft.NET.Runtime.WebAssembly.Sdk", - "Microsoft.NETCore.App.Runtime.Mono.browser-wasm", - "Microsoft.NETCore.App.Runtime.AOT.Cross.browser-wasm" - ], - "extends": [ "microsoft-net-runtime-mono-tooling", "microsoft-net-sdk-emscripten" ], - "platforms": [ "win-x64", "linux-x64", "osx-x64", "osx-arm64" ] - }, - "wasm-experimental": { - "description": ".NET WebAssembly experimental", - "packs": [ - "Microsoft.NET.Runtime.WebAssembly.Templates", - "Microsoft.NETCore.App.Runtime.Mono.multithread.browser-wasm", - "Microsoft.NETCore.App.Runtime.Mono.perftrace.browser-wasm" - ], - "extends": [ "wasm-tools" ], - "platforms": [ "win-x64", "linux-x64", "osx-x64", "osx-arm64" ] - }, - "microsoft-net-runtime-android": { - "abstract": true, - "description": "Android Mono Runtime", - "packs": [ - "Microsoft.NETCore.App.Runtime.Mono.android-arm", - "Microsoft.NETCore.App.Runtime.Mono.android-arm64", - "Microsoft.NETCore.App.Runtime.Mono.android-x64", - "Microsoft.NETCore.App.Runtime.Mono.android-x86" - ], - "extends": [ "microsoft-net-runtime-mono-tooling" ], - "platforms": [ "win-x64", "linux-x64", "osx-x64", "osx-arm64" ] - }, - "microsoft-net-runtime-android-aot": { - "abstract": true, - "description": "Android Mono AOT Workload", - "packs": [ - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-x86", - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-x64", - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-arm", - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-arm64" - ], - "extends": [ "microsoft-net-runtime-android" ], - "platforms": [ "win-x64", "linux-x64", "osx-x64", "osx-arm64" ] - }, - "microsoft-net-runtime-ios": { - "abstract": true, - "description": "iOS Mono Runtime and AOT Workload", - "packs": [ - "Microsoft.NETCore.App.Runtime.AOT.Cross.ios-arm", - "Microsoft.NETCore.App.Runtime.AOT.Cross.ios-arm64", - "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": [ "runtimes-ios" ], - "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] - }, - "runtimes-ios": { - "abstract": true, - "description": "iOS Mono Runtime Packs", - "packs": [ - "Microsoft.NETCore.App.Runtime.Mono.ios-arm", - "Microsoft.NETCore.App.Runtime.Mono.ios-arm64", - "Microsoft.NETCore.App.Runtime.Mono.iossimulator-arm64", - "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x64", - "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x86" - ], - "extends": [ "microsoft-net-runtime-mono-tooling" ], - "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] - }, - "microsoft-net-runtime-maccatalyst": { - "abstract": true, - "description": "MacCatalyst Mono Runtime and AOT Workload", - "packs": [ - "Microsoft.NETCore.App.Runtime.AOT.Cross.maccatalyst-arm64", - "Microsoft.NETCore.App.Runtime.AOT.Cross.maccatalyst-x64" - ], - "extends": [ "runtimes-maccatalyst" ], - "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] - }, - "runtimes-maccatalyst": { - "abstract": true, - "description": "MacCatalyst Mono Runtime Packs", - "packs": [ - "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-arm64", - "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-x64" - ], - "extends": [ "microsoft-net-runtime-mono-tooling" ], - "platforms": [ "win-x64", "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" ] - }, - "microsoft-net-runtime-tvos": { - "abstract": true, - "description": "tvOS Mono Runtime and AOT Workload", - "packs": [ - "Microsoft.NETCore.App.Runtime.AOT.Cross.tvos-arm64", - "Microsoft.NETCore.App.Runtime.AOT.Cross.tvossimulator-arm64", - "Microsoft.NETCore.App.Runtime.AOT.Cross.tvossimulator-x64" - ], - "extends": [ "runtimes-tvos" ], - "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] - }, - "runtimes-tvos": { - "abstract": true, - "description": "tvOS Mono Runtime Packs", - "packs": [ - "Microsoft.NETCore.App.Runtime.Mono.tvos-arm64", - "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-arm64", - "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-x64" - ], - "extends": [ "microsoft-net-runtime-mono-tooling" ], - "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] - }, - "runtimes-windows": { - "description": "Windows Runtime Packs", - "packs": [ - "Microsoft.NETCore.App.Runtime.win-x64", - "Microsoft.NETCore.App.Runtime.win-x86", - "Microsoft.NETCore.App.Runtime.win-arm", - "Microsoft.NETCore.App.Runtime.win-arm64" - ] - }, - "microsoft-net-runtime-mono-tooling": { - "abstract": true, - "description": "Shared native build tooling for Mono runtime", - "packs": [ - "Microsoft.NET.Runtime.MonoAOTCompiler.Task", - "Microsoft.NET.Runtime.MonoTargets.Sdk", - ], - } - }, - "packs": { - "Microsoft.NET.Runtime.MonoAOTCompiler.Task": { - "kind": "Sdk", - "version": "${PackageVersion}" - }, - "Microsoft.NET.Runtime.MonoTargets.Sdk": { - "kind": "Sdk", - "version": "${PackageVersion}" - }, - "Microsoft.NET.Runtime.WebAssembly.Sdk": { - "kind": "Sdk", - "version": "${PackageVersion}" - }, - "Microsoft.NET.Runtime.WebAssembly.Templates": { - "kind": "template", - "version": "${PackageVersion}" - }, - "Microsoft.NETCore.App.Runtime.Mono.android-arm": { - "kind": "framework", - "version": "${PackageVersion}" - }, - "Microsoft.NETCore.App.Runtime.Mono.android-arm64": { - "kind": "framework", - "version": "${PackageVersion}" - }, - "Microsoft.NETCore.App.Runtime.Mono.android-x64": { - "kind": "framework", - "version": "${PackageVersion}" - }, - "Microsoft.NETCore.App.Runtime.Mono.android-x86": { - "kind": "framework", - "version": "${PackageVersion}" - }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-x86": { - "kind": "Sdk", - "version": "${PackageVersion}", - "alias-to": { - "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-x86", - "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-x86", - "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x86", - "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x86" - } - }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-x64": { - "kind": "Sdk", - "version": "${PackageVersion}", - "alias-to": { - "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-x64", - "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-x64", - "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x64", - "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x64" - } - }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-arm": { - "kind": "Sdk", - "version": "${PackageVersion}", - "alias-to": { - "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-arm", - "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-arm", - "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm", - "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm" - } - }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-arm64": { - "kind": "Sdk", - "version": "${PackageVersion}", - "alias-to": { - "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-arm64", - "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-arm64", - "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm64", - "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm64" - } - }, - "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-arm64": { - "kind": "framework", - "version": "${PackageVersion}", - }, - "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-x64": { - "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}", - }, - "Microsoft.NETCore.App.Runtime.Mono.ios-arm" : { - "kind": "framework", - "version": "${PackageVersion}" - }, - "Microsoft.NETCore.App.Runtime.Mono.ios-arm64" : { - "kind": "framework", - "version": "${PackageVersion}" - }, - "Microsoft.NETCore.App.Runtime.Mono.iossimulator-arm64" : { - "kind": "framework", - "version": "${PackageVersion}", - }, - "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x64" : { - "kind": "framework", - "version": "${PackageVersion}", - }, - "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x86" : { - "kind": "framework", - "version": "${PackageVersion}", - }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.tvos-arm64": { - "kind": "Sdk", - "version": "${PackageVersion}", - "alias-to": { - "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvos-arm64", - "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvos-arm64", - } - }, - "Microsoft.NETCore.App.Runtime.Mono.tvos-arm64" : { - "kind": "framework", - "version": "${PackageVersion}" - }, - "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.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-x64": { - "kind": "Sdk", - "version": "${PackageVersion}", - "alias-to": { - "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-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" - } - }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.ios-arm": { - "kind": "Sdk", - "version": "${PackageVersion}", - "alias-to": { - "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm", - "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm", - } - }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.ios-arm64": { - "kind": "Sdk", - "version": "${PackageVersion}", - "alias-to": { - "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm64", - "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm64", - } - }, - "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" - } - }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator-x86": { - "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" - } - }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.browser-wasm": { - "kind": "Sdk", - "version": "${PackageVersion}", - "alias-to": { - "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.browser-wasm", - "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.browser-wasm", - "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.browser-wasm", - "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.browser-wasm" - } - }, - "Microsoft.NETCore.App.Runtime.Mono.browser-wasm" : { - "kind": "framework", - "version": "${PackageVersion}" - }, - "Microsoft.NETCore.App.Runtime.Mono.multithread.browser-wasm" : { - "kind": "framework", - "version": "${PackageVersion}" - }, - "Microsoft.NETCore.App.Runtime.Mono.perftrace.browser-wasm" : { - "kind": "framework", - "version": "${PackageVersion}" - }, - "Microsoft.NETCore.App.Runtime.win-x64" : { - "kind": "framework", - "version": "${PackageVersion}" - }, - "Microsoft.NETCore.App.Runtime.win-x86" : { - "kind": "framework", - "version": "${PackageVersion}" - }, - "Microsoft.NETCore.App.Runtime.win-arm" : { - "kind": "framework", - "version": "${PackageVersion}" - }, - "Microsoft.NETCore.App.Runtime.win-arm64" : { - "kind": "framework", - "version": "${PackageVersion}" - } - } -} diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets.in deleted file mode 100644 index a07122acd7b25a..00000000000000 --- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets.in +++ /dev/null @@ -1,120 +0,0 @@ - - - ${PackageVersion} - true - - - - - true - $(WasmNativeWorkload) - - - - false - false - - - - false - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_MonoWorkloadTargetsMobile>true - <_MonoWorkloadRuntimePackPackageVersion>$(RuntimePackInWorkloadVersion) - - - - - **FromWorkload** - - Microsoft.NETCore.App.Runtime.Mono.multithread.**RID** - Microsoft.NETCore.App.Runtime.Mono.perftrace.**RID** - - - - - - - - - - - - - - - - - - - diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest.pkgproj b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest.pkgproj new file mode 100644 index 00000000000000..7ce452fe6229c4 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest.pkgproj @@ -0,0 +1,55 @@ + + + + + Internal toolchain package not meant for direct consumption. Please do not reference directly. + + + + + + Microsoft.NET.Workload.Mono.ToolChain.net6.Manifest-$(SdkBandVersion) + + + + $(IntermediateOutputPath)WorkloadManifest.json + $(IntermediateOutputPath)WorkloadManifest.targets + + + + + + + + + + data/localize + + + + + + + + PackageVersion=$(PackageVersion); + + + + <_WorkloadManifestValues Include="WorkloadVersion" Value="$(PackageVersion)" /> + <_WorkloadManifestValues Include="PackageVersionNet6" Value="$(PackageVersionNet6)" /> + <_WorkloadManifestValues Include="EmscriptenVersion" Value="$(MicrosoftNETRuntimeEmscriptenVersion)" /> + + + + + + + + + diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/WorkloadManifest.json.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/WorkloadManifest.json.in new file mode 100644 index 00000000000000..88fab9d3631c6b --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/WorkloadManifest.json.in @@ -0,0 +1,458 @@ +{ + "version": "${WorkloadVersion}", + "depends-on": { + "Microsoft.NET.Workload.Emscripten.net6": "${EmscriptenVersion}" + }, + "workloads": { + "wasm-tools-net6": { + "description": ".NET WebAssembly build tools for net6.0", + "packs": [ + "Microsoft.NET.Runtime.WebAssembly.Sdk.net6", + "Microsoft.NETCore.App.Runtime.Mono.net6.browser-wasm", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.browser-wasm" + ], + "extends": [ "microsoft-net-runtime-mono-tooling-net6", "microsoft-net-sdk-emscripten-net6" ], + "platforms": [ "win-x64", "win-arm64", "linux-x64", "osx-x64", "osx-arm64" ] + }, + "microsoft-net-runtime-android-net6": { + "abstract": true, + "description": "Android Mono Runtime", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net6.android-arm", + "Microsoft.NETCore.App.Runtime.Mono.net6.android-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net6.android-x64", + "Microsoft.NETCore.App.Runtime.Mono.net6.android-x86" + ], + "extends": [ "microsoft-net-runtime-mono-tooling-net6" ], + "platforms": [ "win-x64", "win-arm64", "linux-x64", "osx-x64", "osx-arm64" ] + }, + "microsoft-net-runtime-android-aot-net6": { + "abstract": true, + "description": "Android Mono AOT Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-x86", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-x64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-arm", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-arm64" + ], + "extends": [ "microsoft-net-runtime-android-net6" ], + "platforms": [ "win-x64", "win-arm64", "linux-x64", "osx-x64", "osx-arm64" ] + }, + "microsoft-net-runtime-ios-net6": { + "abstract": true, + "description": "iOS Mono Runtime and AOT Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.ios-arm", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.ios-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.iossimulator-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.iossimulator-x64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.iossimulator-x86" + ], + "extends": [ "runtimes-ios-net6" ], + "platforms": [ "win-x64", "win-arm64", "osx-arm64", "osx-x64" ] + }, + "runtimes-ios-net6": { + "abstract": true, + "description": "iOS Mono Runtime Packs", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net6.ios-arm", + "Microsoft.NETCore.App.Runtime.Mono.net6.ios-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net6.iossimulator-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net6.iossimulator-x64", + "Microsoft.NETCore.App.Runtime.Mono.net6.iossimulator-x86" + ], + "extends": [ "microsoft-net-runtime-mono-tooling-net6" ], + "platforms": [ "win-x64", "win-arm64", "osx-arm64", "osx-x64" ] + }, + "microsoft-net-runtime-maccatalyst-net6": { + "abstract": true, + "description": "MacCatalyst Mono Runtime and AOT Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.maccatalyst-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.maccatalyst-x64" + ], + "extends": [ "runtimes-maccatalyst-net6" ], + "platforms": [ "win-x64", "win-arm64", "osx-arm64", "osx-x64" ] + }, + "runtimes-maccatalyst-net6": { + "abstract": true, + "description": "MacCatalyst Mono Runtime Packs", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net6.maccatalyst-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net6.maccatalyst-x64" + ], + "extends": [ "microsoft-net-runtime-mono-tooling-net6" ], + "platforms": [ "win-x64", "win-arm64", "osx-arm64", "osx-x64" ] + }, + "microsoft-net-runtime-macos-net6": { + "abstract": true, + "description": "MacOS CoreCLR and Mono Runtime Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net6.osx-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net6.osx-x64", + "Microsoft.NETCore.App.Runtime.net6.osx-arm64", + "Microsoft.NETCore.App.Runtime.net6.osx-x64" + ], + "extends": [ "microsoft-net-runtime-mono-tooling-net6" ], + "platforms": [ "osx-arm64", "osx-x64" ] + }, + "microsoft-net-runtime-tvos-net6": { + "abstract": true, + "description": "tvOS Mono Runtime and AOT Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.tvos-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.tvossimulator-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.tvossimulator-x64" + ], + "extends": [ "runtimes-tvos-net6" ], + "platforms": [ "win-x64", "win-arm64", "osx-arm64", "osx-x64" ] + }, + "runtimes-tvos-net6": { + "abstract": true, + "description": "tvOS Mono Runtime Packs", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net6.tvos-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net6.tvossimulator-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net6.tvossimulator-x64" + ], + "extends": [ "microsoft-net-runtime-mono-tooling-net6" ], + "platforms": [ "win-x64", "win-arm64", "osx-arm64", "osx-x64" ] + }, + "runtimes-windows-net6": { + "description": "Windows Runtime Packs", + "packs": [ + "Microsoft.NETCore.App.Runtime.net6.win-x64", + "Microsoft.NETCore.App.Runtime.net6.win-x86", + "Microsoft.NETCore.App.Runtime.net6.win-arm", + "Microsoft.NETCore.App.Runtime.net6.win-arm64" + ] + }, + "microsoft-net-runtime-mono-tooling-net6": { + "abstract": true, + "description": "Shared native build tooling for Mono runtime", + "packs": [ + "Microsoft.NET.Runtime.MonoAOTCompiler.Task.net6", + "Microsoft.NET.Runtime.MonoTargets.Sdk.net6" + ] + } + }, + "packs": { + "Microsoft.NET.Runtime.MonoAOTCompiler.Task.net6": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NET.Runtime.MonoAOTCompiler.Task" + } + }, + "Microsoft.NET.Runtime.MonoTargets.Sdk.net6": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NET.Runtime.MonoTargets.Sdk" + } + }, + "Microsoft.NET.Runtime.WebAssembly.Sdk.net6": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NET.Runtime.WebAssembly.Sdk" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.android-arm": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-arm" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.android-arm64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.android-x64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-x64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.android-x86": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-x86" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-x86": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-x86", + "win-arm64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-x86", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-x86", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x86", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x86" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-x64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-x64", + "win-arm64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-x64", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-x64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-arm": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-arm", + "win-arm64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-arm", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-arm", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-arm64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-arm64", + "win-arm64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-arm64", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-arm64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.maccatalyst-arm64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.maccatalyst-x64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-x64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.osx-arm64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.osx-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.osx-x64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.osx-x64" + } + }, + "Microsoft.NETCore.App.Runtime.net6.osx-arm64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.osx-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.net6.osx-x64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.osx-x64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.ios-arm" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.ios-arm" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.ios-arm64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.ios-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.iossimulator-arm64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.iossimulator-x64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.iossimulator-x86" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x86" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.tvos-arm64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvos-arm64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvos-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.tvos-arm64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.tvos-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.tvossimulator-arm64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.tvossimulator-x64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-x64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.maccatalyst-arm64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "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.net6.maccatalyst-x64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "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.net6.tvossimulator-arm64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "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.net6.tvossimulator-x64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "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" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.ios-arm": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.ios-arm64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.iossimulator-arm64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "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.net6.iossimulator-x64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "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" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.iossimulator-x86": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "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" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.browser-wasm": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.browser-wasm", + "win-arm64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.browser-wasm", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.browser-wasm", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.browser-wasm", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.browser-wasm" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.browser-wasm" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.browser-wasm" + } + }, + "Microsoft.NETCore.App.Runtime.net6.win-x64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-x64" + } + }, + "Microsoft.NETCore.App.Runtime.net6.win-x86" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-x86" + } + }, + "Microsoft.NETCore.App.Runtime.net6.win-arm" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-arm" + } + }, + "Microsoft.NETCore.App.Runtime.net6.win-arm64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-arm64" + } + } + } +} diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/WorkloadManifest.targets.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/WorkloadManifest.targets.in new file mode 100644 index 00000000000000..56da5abc971de4 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/WorkloadManifest.targets.in @@ -0,0 +1,133 @@ + + + <_RuntimePackInWorkloadVersion6>${PackageVersionNet6} + true + true + + + + + false + + + + + true + $(WasmNativeWorkload) + + + + false + false + + + + false + true + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_MonoWorkloadTargetsMobile>true + <_MonoWorkloadRuntimePackPackageVersion>$(_RuntimePackInWorkloadVersion6) + + + + + $(_MonoWorkloadRuntimePackPackageVersion) + + + + + + + + + + + + + + + + diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.cs.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.cs.json new file mode 100644 index 00000000000000..63f42875d04eb8 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.cs.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": "Nástroje pro sestavení .NET WebAssembly" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.de.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.de.json new file mode 100644 index 00000000000000..8e47a8ed518fe2 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.de.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": ".NET WebAssembly-Buildtools" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.en.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.en.json new file mode 100644 index 00000000000000..2b693d54c3fb53 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.en.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": ".NET WebAssembly build tools" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.es.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.es.json new file mode 100644 index 00000000000000..5244945464f4a3 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.es.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": "Herramientas de compilación de WebAssembly de .NET" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.fr.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.fr.json new file mode 100644 index 00000000000000..7492e913330dd7 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.fr.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": "Outils de construction .NET WebAssembly" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.it.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.it.json new file mode 100644 index 00000000000000..fbf5627f4c5802 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.it.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": "Strumenti di compilazione WebAssembly .NET" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ja.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ja.json new file mode 100644 index 00000000000000..e9e8b38a9ebb67 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ja.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": ".NET WebAssembly ビルド ツール" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ko.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ko.json new file mode 100644 index 00000000000000..788574ae8536d4 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ko.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": ".NET WebAssembly 빌드 도구" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.pl.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.pl.json new file mode 100644 index 00000000000000..90a04e1e219650 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.pl.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": "Narzędzia kompilacji zestawu WebAssembly platformy .NET" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.pt-BR.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.pt-BR.json new file mode 100644 index 00000000000000..9869a7e82489bb --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.pt-BR.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": "Ferramentas de build do .NET WebAssembly" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ru.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ru.json new file mode 100644 index 00000000000000..dae1bab0be634c --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ru.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": "Средства сборки WebAssembly .NET" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.tr.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.tr.json new file mode 100644 index 00000000000000..ec5dbdf0f5ead4 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.tr.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": ".NET WebAssembly derleme araçları" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.zh-Hans.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.zh-Hans.json new file mode 100644 index 00000000000000..849ab454aca627 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.zh-Hans.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": ".NET WebAssembly 生成工具" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.zh-Hant.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.zh-Hant.json new file mode 100644 index 00000000000000..da192170661373 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.zh-Hant.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": ".NET WebAssembly 組建工具" +} \ No newline at end of file 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.net7.Manifest/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest.pkgproj similarity index 96% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Manifest.pkgproj rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest.pkgproj index 940dd5cd4f3bfc..55d801786d53ec 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.net7.Manifest/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest.pkgproj @@ -8,7 +8,7 @@ - Microsoft.NET.Workload.Mono.ToolChain.Manifest-$(SdkBandVersion) + Microsoft.NET.Workload.Mono.ToolChain.net7.Manifest-$(SdkBandVersion) diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/WorkloadManifest.json.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/WorkloadManifest.json.in new file mode 100644 index 00000000000000..806bf5a8abbb7a --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/WorkloadManifest.json.in @@ -0,0 +1,489 @@ +{ + "version": "${WorkloadVersion}", + "depends-on": { + "Microsoft.NET.Workload.Emscripten.net7": "${EmscriptenVersion}" + }, + "workloads": { + "wasm-tools": { + "description": ".NET WebAssembly build tools", + "packs": [ + "Microsoft.NET.Runtime.WebAssembly.Sdk.net7", + "Microsoft.NETCore.App.Runtime.Mono.net7.browser-wasm", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.browser-wasm" + ], + "extends": [ "microsoft-net-runtime-mono-tooling", "microsoft-net-sdk-emscripten-net7" ], + "platforms": [ "win-x64", "win-arm64", "linux-x64", "osx-x64", "osx-arm64" ] + }, + "wasm-experimental": { + "description": ".NET WebAssembly experimental tooling", + "packs": [ + "Microsoft.NET.Runtime.WebAssembly.Templates.net7", + "Microsoft.NETCore.App.Runtime.Mono.multithread.net7.browser-wasm", + "Microsoft.NETCore.App.Runtime.Mono.perftrace.net7.browser-wasm" + ], + "extends": [ "wasm-tools" ], + "platforms": [ "win-x64", "win-arm64", "linux-x64", "osx-x64", "osx-arm64" ] + }, + "microsoft-net-runtime-android": { + "abstract": true, + "description": "Android Mono Runtime", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net7.android-arm", + "Microsoft.NETCore.App.Runtime.Mono.net7.android-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net7.android-x64", + "Microsoft.NETCore.App.Runtime.Mono.net7.android-x86" + ], + "extends": [ "microsoft-net-runtime-mono-tooling" ], + "platforms": [ "win-x64", "win-arm64", "linux-x64", "osx-x64", "osx-arm64" ] + }, + "microsoft-net-runtime-android-aot": { + "abstract": true, + "description": "Android Mono AOT Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-x86", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-x64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-arm", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-arm64" + ], + "extends": [ "microsoft-net-runtime-android" ], + "platforms": [ "win-x64", "win-arm64", "linux-x64", "osx-x64", "osx-arm64" ] + }, + "microsoft-net-runtime-ios": { + "abstract": true, + "description": "iOS Mono Runtime and AOT Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.ios-arm", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.ios-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.iossimulator-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.iossimulator-x64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.iossimulator-x86" + ], + "extends": [ "runtimes-ios" ], + "platforms": [ "win-x64", "win-arm64", "osx-arm64", "osx-x64" ] + }, + "runtimes-ios": { + "abstract": true, + "description": "iOS Mono Runtime Packs", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net7.ios-arm", + "Microsoft.NETCore.App.Runtime.Mono.net7.ios-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net7.iossimulator-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net7.iossimulator-x64", + "Microsoft.NETCore.App.Runtime.Mono.net7.iossimulator-x86" + ], + "extends": [ "microsoft-net-runtime-mono-tooling" ], + "platforms": [ "win-x64", "win-arm64", "osx-arm64", "osx-x64" ] + }, + "microsoft-net-runtime-maccatalyst": { + "abstract": true, + "description": "MacCatalyst Mono Runtime and AOT Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.maccatalyst-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.maccatalyst-x64" + ], + "extends": [ "runtimes-maccatalyst" ], + "platforms": [ "win-x64", "win-arm64", "osx-arm64", "osx-x64" ] + }, + "runtimes-maccatalyst": { + "abstract": true, + "description": "MacCatalyst Mono Runtime Packs", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net7.maccatalyst-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net7.maccatalyst-x64" + ], + "extends": [ "microsoft-net-runtime-mono-tooling" ], + "platforms": [ "win-x64", "win-arm64", "osx-arm64", "osx-x64" ] + }, + "microsoft-net-runtime-macos": { + "abstract": true, + "description": "MacOS CoreCLR and Mono Runtime Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net7.osx-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net7.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" ] + }, + "microsoft-net-runtime-tvos": { + "abstract": true, + "description": "tvOS Mono Runtime and AOT Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.tvos-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.tvossimulator-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.tvossimulator-x64" + ], + "extends": [ "runtimes-tvos" ], + "platforms": [ "win-x64", "win-arm64", "osx-arm64", "osx-x64" ] + }, + "runtimes-tvos": { + "abstract": true, + "description": "tvOS Mono Runtime Packs", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net7.tvos-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net7.tvossimulator-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net7.tvossimulator-x64" + ], + "extends": [ "microsoft-net-runtime-mono-tooling" ], + "platforms": [ "win-x64", "win-arm64", "osx-arm64", "osx-x64" ] + }, + "runtimes-windows": { + "description": "Windows Runtime Packs", + "packs": [ + "Microsoft.NETCore.App.Runtime.net7.win-x64", + "Microsoft.NETCore.App.Runtime.net7.win-x86", + "Microsoft.NETCore.App.Runtime.net7.win-arm", + "Microsoft.NETCore.App.Runtime.net7.win-arm64" + ] + }, + "microsoft-net-runtime-mono-tooling": { + "abstract": true, + "description": "Shared native build tooling for Mono runtime", + "packs": [ + "Microsoft.NET.Runtime.MonoAOTCompiler.Task.net7", + "Microsoft.NET.Runtime.MonoTargets.Sdk.net7" + ] + } + }, + "packs": { + "Microsoft.NET.Runtime.MonoAOTCompiler.Task.net7": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NET.Runtime.MonoAOTCompiler.Task" + } + }, + "Microsoft.NET.Runtime.MonoTargets.Sdk.net7": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NET.Runtime.MonoTargets.Sdk" + } + }, + "Microsoft.NET.Runtime.WebAssembly.Sdk.net7": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NET.Runtime.WebAssembly.Sdk" + } + }, + "Microsoft.NET.Runtime.WebAssembly.Templates.net7": { + "kind": "template", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NET.Runtime.WebAssembly.Templates" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.android-arm": { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-arm" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.android-arm64": { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.android-x64": { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-x64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.android-x86": { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-x86" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-x86": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-x86", + "win-arm64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-x86", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-x86", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x86", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x86" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-x64": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-x64", + "win-arm64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-x64", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-x64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-arm": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-arm", + "win-arm64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-arm", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-arm", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-arm64": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-arm64", + "win-arm64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-arm64", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-arm64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.maccatalyst-arm64": { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.maccatalyst-x64": { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-x64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.osx-arm64": { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.osx-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.osx-x64": { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.osx-x64" + } + }, + "Microsoft.NETCore.App.Runtime.net7.osx-arm64": { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.osx-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.net7.osx-x64": { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.osx-x64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.ios-arm" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.ios-arm" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.ios-arm64" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.ios-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.iossimulator-arm64" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.iossimulator-x64" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.iossimulator-x86" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x86" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.tvos-arm64": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvos-arm64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvos-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.tvos-arm64" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.tvos-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.tvossimulator-arm64" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.tvossimulator-x64" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-x64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.maccatalyst-arm64": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "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.net7.maccatalyst-x64": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "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.net7.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.net7.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" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.ios-arm": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.ios-arm64": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.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.net7.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" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.iossimulator-x86": { + "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" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.browser-wasm": { + "kind": "Sdk", + "version": "${PackageVersion}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.browser-wasm", + "win-arm64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.browser-wasm", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.browser-wasm", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.browser-wasm", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.browser-wasm" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net7.browser-wasm" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.browser-wasm" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.multithread.net7.browser-wasm" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.multithread.browser-wasm" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.perftrace.net7.browser-wasm" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.perftrace.browser-wasm" + } + }, + "Microsoft.NETCore.App.Runtime.net7.win-x64" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-x64" + } + }, + "Microsoft.NETCore.App.Runtime.net7.win-x86" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-x86" + } + }, + "Microsoft.NETCore.App.Runtime.net7.win-arm" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-arm" + } + }, + "Microsoft.NETCore.App.Runtime.net7.win-arm64" : { + "kind": "framework", + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-arm64" + } + } + } +} diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/WorkloadManifest.targets.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/WorkloadManifest.targets.in new file mode 100644 index 00000000000000..66e2f3076d0be1 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/WorkloadManifest.targets.in @@ -0,0 +1,140 @@ + + + <_RuntimePackInWorkloadVersion7>${PackageVersion} + <_BrowserWorkloadDisabled7>$(BrowserWorkloadDisabled) + <_BrowserWorkloadDisabled7 Condition="'$(_BrowserWorkloadDisabled7)' == '' and + '$(RuntimeIdentifier)' == 'browser-wasm' and + '$(TargetFrameworkIdentifier)' == '.NETCoreApp' and + !$([MSBuild]::VersionEquals('$(TargetFrameworkVersion)', '7.0'))">true + true + + + + + true + false + + + + + true + $(WasmNativeWorkload7) + + + + false + false + false + + + + false + true + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_MonoWorkloadTargetsMobile>true + <_MonoWorkloadRuntimePackPackageVersion>$(_RuntimePackInWorkloadVersion7) + + + + + $(_MonoWorkloadRuntimePackPackageVersion) + + Microsoft.NETCore.App.Runtime.Mono.multithread.**RID** + Microsoft.NETCore.App.Runtime.Mono.perftrace.**RID** + + + + + + + + + + + + + + + + + + + + diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.cs.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.cs.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.cs.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.cs.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.de.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.de.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.de.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.de.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.en.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.en.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.en.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.en.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.es.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.es.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.es.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.es.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.fr.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.fr.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.fr.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.fr.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.it.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.it.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.it.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.it.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.ja.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.ja.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.ja.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.ja.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.ko.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.ko.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.ko.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.ko.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.pl.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.pl.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.pl.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.pl.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.pt-BR.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.pt-BR.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.pt-BR.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.pt-BR.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.ru.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.ru.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.ru.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.ru.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.tr.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.tr.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.tr.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.tr.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.zh-Hans.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.zh-Hans.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.zh-Hans.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.zh-Hans.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.zh-Hant.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.zh-Hant.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.zh-Hant.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.zh-Hant.json diff --git a/src/mono/nuget/Microsoft.NETCore.BrowserDebugHost.Transport/Microsoft.NETCore.BrowserDebugHost.Transport.pkgproj b/src/mono/nuget/Microsoft.NETCore.BrowserDebugHost.Transport/Microsoft.NETCore.BrowserDebugHost.Transport.pkgproj index 3a80df8dfb54ef..7a6b8326bcfb31 100644 --- a/src/mono/nuget/Microsoft.NETCore.BrowserDebugHost.Transport/Microsoft.NETCore.BrowserDebugHost.Transport.pkgproj +++ b/src/mono/nuget/Microsoft.NETCore.BrowserDebugHost.Transport/Microsoft.NETCore.BrowserDebugHost.Transport.pkgproj @@ -17,6 +17,8 @@ <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.CSharp.dll" /> <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.dll" /> <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Newtonsoft.Json.dll" /> + <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.CSharp.Scripting.dll" /> + <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.Scripting.dll" /> diff --git a/src/mono/nuget/mono-packages.proj b/src/mono/nuget/mono-packages.proj index 1c1c7c0bff9e3f..319028efb91b6b 100644 --- a/src/mono/nuget/mono-packages.proj +++ b/src/mono/nuget/mono-packages.proj @@ -15,7 +15,8 @@ - + + diff --git a/src/mono/sample/iOS/Program.csproj b/src/mono/sample/iOS/Program.csproj index 206d8f02fe863c..ec1f84c354d9cc 100644 --- a/src/mono/sample/iOS/Program.csproj +++ b/src/mono/sample/iOS/Program.csproj @@ -67,10 +67,6 @@ - - marshal-ilgen - - <_SampleProject>Wasm.Browser.Sample.csproj - + diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index afe23356a73951..cf71a8e416b30b 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -8,12 +8,28 @@ function sub(a, b) { return a - b; } +let testError = true; +let testAbort = true; try { const { runtimeBuildInfo, setModuleImports, getAssemblyExports, runMain, getConfig } = await dotnet .withConsoleForwarding() .withElementOnExit() .withModuleConfig({ configSrc: "./mono-config.json", + imports: { + fetch: (url, fetchArgs) => { + // we are testing that we can retry loading of the assembly + if (testAbort && url.indexOf('System.Private.Uri.dll') != -1) { + testAbort = false; + return fetch(url + "?testAbort=true", fetchArgs); + } + if (testError && url.indexOf('System.Console.dll') != -1) { + testError = false; + return fetch(url + "?testError=true", fetchArgs); + } + return fetch(url, fetchArgs); + } + }, onConfigLoaded: (config) => { // This is called during emscripten `dotnet.wasm` instantiation, after we fetched config. console.log('user code Module.onConfigLoaded'); diff --git a/src/mono/sample/wasm/node-webpack/.npmrc b/src/mono/sample/wasm/node-webpack/.npmrc new file mode 100644 index 00000000000000..33312472ae5bf4 --- /dev/null +++ b/src/mono/sample/wasm/node-webpack/.npmrc @@ -0,0 +1 @@ +registry=https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/ \ No newline at end of file diff --git a/src/mono/sample/wasm/simple-server/Program.cs b/src/mono/sample/wasm/simple-server/Program.cs index 70ef1e573093f1..7a26f4406fb0dd 100644 --- a/src/mono/sample/wasm/simple-server/Program.cs +++ b/src/mono/sample/wasm/simple-server/Program.cs @@ -159,12 +159,36 @@ private async void ServeAsync(HttpListenerContext context) if (path.EndsWith(".js") || path.EndsWith(".mjs") || path.EndsWith(".cjs")) contentType = "text/javascript"; + var stream = context.Response.OutputStream; + + // test download re-try + if (url.Query.Contains("testError")) + { + Console.WriteLine("Faking 500 " + url); + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + await stream.WriteAsync(buffer, 0, 0).ConfigureAwait(false); + await stream.FlushAsync(); + context.Response.Close(); + return; + } + if (contentType != null) context.Response.ContentType = contentType; context.Response.ContentLength64 = buffer.Length; context.Response.AppendHeader("cache-control", "public, max-age=31536000"); - var stream = context.Response.OutputStream; + + // test download re-try + if (url.Query.Contains("testAbort")) + { + Console.WriteLine("Faking abort " + url); + await stream.WriteAsync(buffer, 0, 10).ConfigureAwait(false); + await stream.FlushAsync(); + await Task.Delay(100); + context.Response.Abort(); + return; + } + try { await stream.WriteAsync(buffer).ConfigureAwait(false); diff --git a/src/mono/wasi/mono-wasi-driver/driver.c b/src/mono/wasi/mono-wasi-driver/driver.c index 78b1bfa94e95c0..d8138a46902316 100644 --- a/src/mono/wasi/mono-wasi-driver/driver.c +++ b/src/mono/wasi/mono-wasi-driver/driver.c @@ -324,15 +324,6 @@ static PinvokeImport SystemGlobalizationNativeImports [] = { {NULL, NULL} }; -int SystemCryptoNativeBrowser_CanUseSubtleCryptoImpl() { - return 0; -} - -static PinvokeImport SystemSecurityCryptographyNativeBrowserImports [] = { - {"SystemCryptoNativeBrowser_CanUseSubtleCryptoImpl", SystemCryptoNativeBrowser_CanUseSubtleCryptoImpl }, - {NULL, NULL} -}; - static void* wasm_dl_load (const char *name, int flags, char **err, void *user_data) { @@ -340,8 +331,6 @@ wasm_dl_load (const char *name, int flags, char **err, void *user_data) return SystemNativeImports; if (!strcmp (name, "libSystem.Globalization.Native")) return SystemGlobalizationNativeImports; - if (!strcmp (name, "libSystem.Security.Cryptography.Native.Browser")) - return SystemSecurityCryptographyNativeBrowserImports; //printf("In wasm_dl_load for name %s but treating as NOT FOUND\n", name); return 0; @@ -492,7 +481,6 @@ mono_wasm_load_runtime (const char *argv, int debug_level) mono_jit_set_aot_mode (MONO_AOT_MODE_INTERP_ONLY); mono_ee_interp_init (interp_opts); - mono_marshal_lightweight_init (); mono_marshal_ilgen_init (); mono_method_builder_ilgen_init (); mono_sgen_mono_ilgen_init (); diff --git a/src/mono/wasm/README.md b/src/mono/wasm/README.md index f8e2344fbf3295..ee88413c8c0790 100644 --- a/src/mono/wasm/README.md +++ b/src/mono/wasm/README.md @@ -203,7 +203,7 @@ Bumping Emscripten version involves these steps: * bump docker images in https://github.com/dotnet/icu, update emscripten files in eng/patches/ * update version number in docs * update `Microsoft.NET.Runtime.Emscripten..Node.win-x64` package name, version and sha hash in https://github.com/dotnet/runtime/blob/main/eng/Version.Details.xml and in https://github.com/dotnet/runtime/blob/main/eng/Versions.props. the sha is the commit hash in https://github.com/dotnet/emsdk and the package version can be found at https://dev.azure.com/dnceng/public/_packaging?_a=feed&feed=dotnet6 -* update packages in the workload manifest https://github.com/dotnet/runtime/blob/main/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.json.in +* update packages in the workload manifest https://github.com/dotnet/runtime/blob/main/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/WorkloadManifest.json.in ## Upgrading NPM packages In folder `src\mono\wasm\runtime\` diff --git a/src/mono/wasm/build/WasmApp.InTree.props b/src/mono/wasm/build/WasmApp.InTree.props index 38e99c6a6cbc33..4949caa40e6eda 100644 --- a/src/mono/wasm/build/WasmApp.InTree.props +++ b/src/mono/wasm/build/WasmApp.InTree.props @@ -18,6 +18,5 @@ <_MonoRuntimeComponentDontLink Include="libmono-component-diagnostics_tracing-static.a" Condition="'$(FeatureWasmPerfTracing)' != 'true' and $(FeatureWasmThreads) != 'true'"/> <_MonoRuntimeComponentDontLink Include="libmono-component-hot_reload-stub-static.a" /> - <_MonoRuntimeComponentDontLink Include="libmono-component-marshal-ilgen-stub-static.a" /> diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index f45230c3346e45..1b0a006d636111 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -31,9 +31,6 @@ <_MonoComponent Include="hot_reload;debugger" /> - - <_MonoComponent Include="marshal-ilgen" /> - @@ -111,6 +108,8 @@ true + true + true false @@ -121,6 +120,7 @@ true true + true false @@ -185,7 +185,6 @@ <_EmccLinkRsp>$(_WasmIntermediateOutputPath)emcc-link.rsp $(EmccTotalMemory) - 536870912 @@ -196,9 +195,9 @@ <_EmccCommonFlags Include="-s EXPORT_ES6=1" /> <_EmccCommonFlags Include="-g" Condition="'$(WasmNativeStrip)' == 'false'" /> <_EmccCommonFlags Include="-v" Condition="'$(EmccVerbose)' != 'false'" /> - <_EmccCommonFlags Include="-s DISABLE_EXCEPTION_CATCHING=0" Condition="'$(WasmExceptionHandling)' == 'false'" /> - <_EmccCommonFlags Include="-fwasm-exceptions" Condition="'$(WasmExceptionHandling)' == 'true'" /> - <_EmccCommonFlags Include="-msimd128" Condition="'$(WasmSIMD)' == 'true'" /> + <_EmccCommonFlags Include="-s DISABLE_EXCEPTION_CATCHING=0" Condition="'$(WasmEnableExceptionHandling)' == 'false'" /> + <_EmccCommonFlags Include="-fwasm-exceptions" Condition="'$(WasmEnableExceptionHandling)' == 'true'" /> + <_EmccCommonFlags Include="-msimd128" Condition="'$(WasmEnableSIMD)' == 'true'" /> <_EmccIncludePaths Include="$(_WasmIntermediateOutputPath.TrimEnd('\/'))" /> <_EmccIncludePaths Include="$(_WasmRuntimePackIncludeDir)mono-2.0" /> @@ -227,7 +226,6 @@ <_EmccLDFlags Include="-s ASSERTIONS=$(_EmccAssertionLevelDefault)" Condition="'$(_WasmDevel)' == 'true'" /> <_EmccLDFlags Include="@(_EmccCommonFlags)" /> <_EmccLDFlags Include="-Wl,--allow-undefined" /> - <_EmccLDSFlags Include="-s INITIAL_MEMORY=$(EmccInitialHeapSize)" /> <_EmccLDSFlags Include="-s ERROR_ON_UNDEFINED_SYMBOLS=0" Condition="'$(WasmBuildingForNestedPublish)' != 'true'" /> @@ -272,7 +270,6 @@ <_WasmPInvokeModules Include="libSystem.Native" /> <_WasmPInvokeModules Include="libSystem.IO.Compression.Native" /> <_WasmPInvokeModules Include="libSystem.Globalization.Native" /> - <_WasmPInvokeModules Include="libSystem.Security.Cryptography.Native.Browser" /> @@ -371,13 +368,16 @@ - <_WasmEHLib Condition="'$(WasmExceptionHandling)' == 'true'">libmono-wasm-eh-wasm.a - <_WasmEHLib Condition="'$(WasmExceptionHandling)' != 'true'">libmono-wasm-eh-js.a - <_WasmEHLibToExclude Condition="'$(WasmExceptionHandling)' == 'true'">libmono-wasm-eh-js.a - <_WasmEHLibToExclude Condition="'$(WasmExceptionHandling)' != 'true'">libmono-wasm-eh-wasm.a + <_WasmEHLib Condition="'$(WasmEnableExceptionHandling)' == 'true'">libmono-wasm-eh-wasm.a + <_WasmEHLib Condition="'$(WasmEnableExceptionHandling)' != 'true'">libmono-wasm-eh-js.a + <_WasmEHLibToExclude Condition="'$(WasmEnableExceptionHandling)' == 'true'">libmono-wasm-eh-js.a + <_WasmEHLibToExclude Condition="'$(WasmEnableExceptionHandling)' != 'true'">libmono-wasm-eh-wasm.a + 16777216 + 134217728 + <_EmccLDSFlags Include="-s INITIAL_MEMORY=$(EmccInitialHeapSize)" /> <_WasmNativeFileForLinking Include="%(_BitcodeFile.ObjectFile)" /> <_WasmNativeFileForLinking Include="%(_WasmSourceFileToCompile.ObjectFile)" /> @@ -528,8 +528,8 @@ - - + + diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 602896b55da0f0..856b017d10cb82 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -65,8 +65,8 @@ - $(RunAOTCompilationAfterBuild) - Run AOT compilation even after Build. By default, it is run only for publish. Defaults to false. - $(WasmAotProfilePath) - Path to an AOT profile file. - - $(WasmExceptionHandling) - Enable support for the WASM Exception Handling feature. - - $(WasmSIMD) - Enable support for the WASM SIMD feature. + - $(WasmEnableExceptionHandling) - Enable support for the WASM Exception Handling feature. + - $(WasmEnableSIMD) - Enable support for the WASM SIMD feature. Public items: - @(WasmExtraFilesToDeploy) - Files to copy to $(WasmAppDir). @@ -86,8 +86,8 @@ false - false - false + false + false @@ -113,20 +113,42 @@ -1 + + + + + + + + + + + + + <_AppBundleDirForRunCommand Condition="'$(WasmAppDir)' != ''">$(WasmAppDir) - - <_AppBundleDirForRunCommand Condition="Exists('$(WasmAppDir)/$(AssemblyName).runtimeconfig.json')">$(WasmAppDir) - <_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' == '' and Exists('$(OutputPath)/AppBundle')">$([MSBuild]::NormalizeDirectory($(OutputPath), 'AppBundle')) - <_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' == '' and Exists('$(OutputPath)/browser-wasm/AppBundle')">$([MSBuild]::NormalizeDirectory($(OutputPath), 'browser-wasm', 'AppBundle')) - <_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' == ''">OutputPath=$(OutputPath), OutDir=$(OutDir) + + <_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' == ''">$([System.IO.Path]::Combine($(OutputPath), 'browser-wasm', 'AppBundle')) + + + <_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' != '' and !$([System.IO.Path]::IsPathRooted($(_AppBundleDirForRunCommand)))">$([System.IO.Path]::Combine($(MSBuildProjectDirectory), $(_AppBundleDirForRunCommand))) $(DOTNET_HOST_PATH) dotnet - exec $([MSBuild]::NormalizePath($(WasmAppHostDir), 'WasmAppHost.dll')) --runtime-config $(_AppBundleDirForRunCommand)/$(AssemblyName).runtimeconfig.json $(WasmHostArguments) + + <_RuntimeConfigJsonPath>$([MSBuild]::NormalizePath($(_AppBundleDirForRunCommand), '$(AssemblyName).runtimeconfig.json')) + exec "$([MSBuild]::NormalizePath($(WasmAppHostDir), 'WasmAppHost.dll'))" --runtime-config "$(_RuntimeConfigJsonPath)" $(WasmHostArguments) $(_AppBundleDirForRunCommand) @@ -286,7 +308,6 @@ <_HasDotnetWasm Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.wasm'">true <_HasDotnetJsWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.worker.js'">true - <_HasDotnetJsCryptoWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet-crypto-worker.js'">true <_HasDotnetJsSymbols Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js.symbols'">true <_HasDotnetJs Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'">true @@ -296,7 +317,6 @@ - (); + } + } + + public bool ContainsAsyncScope(int oneBasedIdx, int offset) + { + int arrIdx = oneBasedIdx - 1; + return arrIdx >= 0 && arrIdx < _asyncScopes.Length && + offset >= _asyncScopes[arrIdx].StartOffset && offset <= _asyncScopes[arrIdx].EndOffset; } public ParameterInfo[] GetParametersInfo() @@ -617,6 +642,8 @@ public override int GetHashCode(MethodInfo loc) return loc.Source.Id; } } + + private record struct AsyncScopeDebugInformation(int StartOffset, int EndOffset); } internal sealed class ParameterInfo @@ -701,7 +728,9 @@ internal sealed class TypeInfo internal int Token { get; } internal string Namespace { get; } internal bool IsCompilerGenerated { get; } + private bool NonUserCode { get; } public string FullName { get; } + internal bool IsNonUserCode => assembly.pdbMetadataReader == null || NonUserCode; public List Methods { get; } = new(); public Dictionary DebuggerBrowsableFields = new(); public Dictionary DebuggerBrowsableProperties = new(); @@ -762,15 +791,19 @@ internal TypeInfo(AssemblyInfo assembly, TypeDefinitionHandle typeHandle, TypeDe } } - foreach (var cattr in type.GetCustomAttributes()) + foreach (CustomAttributeHandle cattr in type.GetCustomAttributes()) { - var ctorHandle = metadataReader.GetCustomAttribute(cattr).Constructor; - if (ctorHandle.Kind != HandleKind.MemberReference) + if (!assembly.TryGetCustomAttributeName(cattr, metadataReader, out string attributeName)) continue; - var container = metadataReader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent; - var attributeName = assembly.EnCGetString(metadataReader.GetTypeReference((TypeReferenceHandle)container).Name); - if (attributeName == nameof(CompilerGeneratedAttribute)) - IsCompilerGenerated = true; + switch (attributeName) + { + case nameof(CompilerGeneratedAttribute): + IsCompilerGenerated = true; + break; + case nameof(DebuggerNonUserCodeAttribute): + NonUserCode = true; + break; + } } void AppendToBrowsable(Dictionary dict, CustomAttributeHandleCollection customAttrs, string fieldName) @@ -881,6 +914,33 @@ public unsafe AssemblyInfo(MonoProxy monoProxy, SessionId sessionId, string url, Populate(); } + public bool TryGetCustomAttributeName(CustomAttributeHandle customAttribute, MetadataReader metadataReader, out string name) + { + name = ""; + EntityHandle ctorHandle = metadataReader.GetCustomAttribute(customAttribute).Constructor; + if (ctorHandle.Kind != HandleKind.MemberReference) + return false; + EntityHandle? container = ctorHandle.Kind switch + { + HandleKind.MethodDefinition => metadataReader.GetMethodDefinition((MethodDefinitionHandle)ctorHandle).GetDeclaringType(), + HandleKind.MemberReference => metadataReader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent, + _ => null, + }; + if (container == null) + return false; + StringHandle? attributeTypeNameHandle = container.Value.Kind switch + { + HandleKind.TypeDefinition => metadataReader.GetTypeDefinition((TypeDefinitionHandle)container.Value).Name, + HandleKind.TypeReference => metadataReader.GetTypeReference((TypeReferenceHandle)container.Value).Name, + HandleKind.TypeSpecification => null, // custom generic attributes, TypeSpecification does not keep the attribute name for them + _ => null, + }; + if (attributeTypeNameHandle == null) + return false; + name = EnCGetString(attributeTypeNameHandle.Value); + return true; + } + public async Task GetDebugId(MonoSDBHelper sdbAgent, CancellationToken token) { if (debugId > 0) @@ -987,7 +1047,7 @@ private void PopulateEnC(MetadataReader asmMetadataReaderParm, MetadataReader pd { var document = pdbMetadataReaderParm.GetDocument(methodDebugInformation.Document); var documentName = pdbMetadataReaderParm.GetString(document.Name); - source = GetOrAddSourceFile(methodDebugInformation.Document, asmMetadataReaderParm.GetRowNumber(methodDebugInformation.Document), documentName); + source = GetOrAddSourceFile(methodDebugInformation.Document, documentName); } var methodInfo = new MethodInfo(this, MetadataTokens.MethodDefinitionHandle(methodIdxAsm), entryRow, source, typeInfo, asmMetadataReaderParm, pdbMetadataReaderParm); methods[entryRow] = methodInfo; @@ -1010,13 +1070,13 @@ private void PopulateEnC(MetadataReader asmMetadataReaderParm, MetadataReader pd } } } - private SourceFile GetOrAddSourceFile(DocumentHandle doc, int rowid, string documentName) + private SourceFile GetOrAddSourceFile(DocumentHandle doc, string documentName) { - if (_documentIdToSourceFileTable.TryGetValue(rowid, out SourceFile source)) + if (_documentIdToSourceFileTable.TryGetValue(documentName.GetHashCode(), out SourceFile source)) return source; var src = new SourceFile(this, _documentIdToSourceFileTable.Count, doc, GetSourceLinkUrl(documentName), documentName); - _documentIdToSourceFileTable[rowid] = src; + _documentIdToSourceFileTable[documentName.GetHashCode()] = src; return src; } @@ -1046,7 +1106,7 @@ private void Populate() { var document = pdbMetadataReader.GetDocument(methodDebugInformation.Document); var documentName = pdbMetadataReader.GetString(document.Name); - source = GetOrAddSourceFile(methodDebugInformation.Document, asmMetadataReader.GetRowNumber(methodDebugInformation.Document), documentName); + source = GetOrAddSourceFile(methodDebugInformation.Document, documentName); } } var methodInfo = new MethodInfo(this, method, asmMetadataReader.GetRowNumber(method), source, typeInfo, asmMetadataReader, pdbMetadataReader); @@ -1198,11 +1258,9 @@ private static string EscapeAscii(string path) { switch (c) { - case var _ when c >= 'a' && c <= 'z': - case var _ when c >= 'A' && c <= 'Z': - case var _ when char.IsDigit(c): + case var _ when char.IsLetterOrDigit(c): case var _ when c > 255: - case var _ when c == '+' || c == ':' || c == '.' || c == '-' || c == '_' || c == '~': + case var _ when c == '+' || c == ':' || c == '.' || c == '-' || c == '_' || c == '~' || c == '´' || c == '`' || c == '^' || c == '¨': builder.Append(c); break; case var _ when c == Path.DirectorySeparatorChar: @@ -1211,13 +1269,14 @@ private static string EscapeAscii(string path) builder.Append(c); break; default: - builder.Append(string.Format($"%{((int)c):X2}")); + builder.AppendFormat("%{0:X2}", (int)c); break; } } return builder.ToString(); } + internal void AddMethod(MethodInfo mi) { if (!this.methods.ContainsKey(mi.Token)) @@ -1416,60 +1475,56 @@ public IEnumerable Add(SessionId id, string name, byte[] assembly_da } } - public async IAsyncEnumerable Load(SessionId id, string[] loaded_files, ExecutionContext context, bool useDebuggerProtocol, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable Load(SessionId id, string[] loaded_files, ExecutionContext context, bool tryUseDebuggerProtocol, [EnumeratorCancellation] CancellationToken token) { var asm_files = new List(); List steps = new List(); - if (!useDebuggerProtocol) + var pdb_files = new List(); + foreach (string file_name in loaded_files) { - var pdb_files = new List(); - foreach (string file_name in loaded_files) - { - if (file_name.EndsWith(".pdb", StringComparison.OrdinalIgnoreCase)) - pdb_files.Add(file_name); - else - asm_files.Add(file_name); - } + if (file_name.EndsWith(".pdb", StringComparison.OrdinalIgnoreCase)) + pdb_files.Add(file_name); + else + asm_files.Add(file_name); + } - foreach (string url in asm_files) + foreach (string url in asm_files) + { + try { - try - { - string candidate_pdb = Path.ChangeExtension(url, "pdb"); - string pdb = pdb_files.FirstOrDefault(n => n == candidate_pdb); + string candidate_pdb = Path.ChangeExtension(url, "pdb"); + string pdb = pdb_files.FirstOrDefault(n => n == candidate_pdb); - steps.Add( - new DebugItem - { - Url = url, - Data = Task.WhenAll(MonoProxy.HttpClient.GetByteArrayAsync(url, token), pdb != null ? MonoProxy.HttpClient.GetByteArrayAsync(pdb, token) : Task.FromResult(null)) - }); - } - catch (Exception e) - { - logger.LogDebug($"Failed to read {url} ({e.Message})"); - } + steps.Add( + new DebugItem + { + Url = url, + Data = Task.WhenAll(MonoProxy.HttpClient.GetByteArrayAsync(url, token), pdb != null ? MonoProxy.HttpClient.GetByteArrayAsync(pdb, token) : Task.FromResult(null)) + }); } - } - else - { - foreach (string file_name in loaded_files) + catch (Exception e) { - if (file_name.EndsWith(".pdb", StringComparison.OrdinalIgnoreCase)) - continue; - try + if (tryUseDebuggerProtocol) { - steps.Add( + try + { + string unescapedFileName = Uri.UnescapeDataString(url); + steps.Add( new DebugItem { - Url = file_name, - Data = context.SdbAgent.GetBytesFromAssemblyAndPdb(Path.GetFileName(file_name), token) + Url = url, + Data = context.SdbAgent.GetBytesFromAssemblyAndPdb(Path.GetFileName(unescapedFileName), token) }); + } + catch (Exception ex) + { + logger.LogDebug($"Failed to get bytes using debugger protocol {url} ({ex.Message})"); + } } - catch (Exception e) + else { - logger.LogDebug($"Failed to read {file_name} ({e.Message})"); + logger.LogDebug($"Failed to read {url} ({e.Message})"); } } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs index a7c75a428e97a3..4ab32b55a5669b 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -407,6 +408,7 @@ public ExecutionContext(MonoSDBHelper sdbAgent, int id, object auxData, PauseOnE AuxData = auxData; SdbAgent = sdbAgent; PauseOnExceptions = pauseOnExceptions; + Destroyed = false; } public string DebugId { get; set; } @@ -440,6 +442,8 @@ public ExecutionContext(MonoSDBHelper sdbAgent, int id, object auxData, PauseOnE internal int TempBreakpointForSetNextIP { get; set; } internal bool FirstBreakpoint { get; set; } + internal bool Destroyed { get; set; } + public DebugStore Store { get @@ -486,4 +490,70 @@ public PerScopeCache() { } } + + internal sealed class ConcurrentExecutionContextDictionary + { + private ConcurrentDictionary> contexts = new (); + public ExecutionContext GetCurrentContext(SessionId sessionId) + => TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context) + ? context + : throw new KeyNotFoundException($"No execution context found for session {sessionId}"); + + public bool TryGetCurrentExecutionContextValue(SessionId id, out ExecutionContext executionContext, bool ignoreDestroyedContext = true) + { + executionContext = null; + if (!contexts.TryGetValue(id, out ConcurrentBag contextBag)) + return false; + if (contextBag.IsEmpty) + return false; + IEnumerable validContexts = null; + if (ignoreDestroyedContext) + validContexts = contextBag.Where(context => context.Destroyed == false); + else + validContexts = contextBag; + if (!validContexts.Any()) + return false; + int maxId = validContexts.Max(context => context.Id); + executionContext = contextBag.FirstOrDefault(context => context.Id == maxId); + return executionContext != null; + } + + public void OnDefaultContextUpdate(SessionId sessionId, ExecutionContext newContext) + { + if (TryGetAndAddContext(sessionId, newContext, out ExecutionContext previousContext)) + { + foreach (KeyValuePair kvp in previousContext.BreakpointRequests) + { + newContext.BreakpointRequests[kvp.Key] = kvp.Value.Clone(); + } + newContext.PauseOnExceptions = previousContext.PauseOnExceptions; + } + } + + public bool TryGetAndAddContext(SessionId sessionId, ExecutionContext newExecutionContext, out ExecutionContext previousExecutionContext) + { + bool hasExisting = TryGetCurrentExecutionContextValue(sessionId, out previousExecutionContext, ignoreDestroyedContext: false); + ConcurrentBag bag = contexts.GetOrAdd(sessionId, _ => new ConcurrentBag()); + bag.Add(newExecutionContext); + return hasExisting; + } + + public void DestroyContext(SessionId sessionId, int id) + { + if (!contexts.TryGetValue(sessionId, out ConcurrentBag contextBag)) + return; + foreach (ExecutionContext context in contextBag.Where(x => x.Id == id).ToList()) + context.Destroyed = true; + } + + public void ClearContexts(SessionId sessionId) + { + if (!contexts.TryGetValue(sessionId, out ConcurrentBag contextBag)) + return; + foreach (ExecutionContext context in contextBag) + context.Destroyed = true; + } + + public bool ContainsKey(SessionId sessionId) => contexts.ContainsKey(sessionId); + } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs index deb9761130b3cc..f9e7cf6640e14f 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs @@ -23,7 +23,7 @@ public FirefoxMonoProxy(ILogger logger, string loggerId = null, ProxyOptions opt public FirefoxExecutionContext GetContextFixefox(SessionId sessionId) { - if (contexts.TryGetValue(sessionId, out ExecutionContext context)) + if (Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context)) return context as FirefoxExecutionContext; throw new ArgumentException($"Invalid Session: \"{sessionId}\"", nameof(sessionId)); } @@ -254,7 +254,7 @@ protected override async Task AcceptEvent(SessionId sessionId, JObject arg } if (args["frame"] != null && args["type"] == null) { - OnDefaultContextUpdate(sessionId, new FirefoxExecutionContext(new MonoSDBHelper (this, logger, sessionId), 0, args["frame"]["consoleActor"].Value())); + Contexts.OnDefaultContextUpdate(sessionId, new FirefoxExecutionContext(new MonoSDBHelper (this, logger, sessionId), 0, args["frame"]["consoleActor"].Value())); return false; } @@ -317,7 +317,7 @@ await Task.WhenAll( } case "target-available-form": { - OnDefaultContextUpdate(sessionId, new FirefoxExecutionContext(new MonoSDBHelper (this, logger, sessionId), 0, args["target"]["consoleActor"].Value())); + Contexts.OnDefaultContextUpdate(sessionId, new FirefoxExecutionContext(new MonoSDBHelper (this, logger, sessionId), 0, args["target"]["consoleActor"].Value())); break; } } @@ -334,7 +334,7 @@ protected override async Task AcceptCommand(MessageId sessionId, JObject a { case "resume": { - if (!contexts.TryGetValue(sessionId, out ExecutionContext context)) + if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context)) return false; context.PausedOnWasm = false; if (context.CallStack == null) @@ -396,7 +396,7 @@ protected override async Task AcceptCommand(MessageId sessionId, JObject a } case "setBreakpoint": { - if (!contexts.TryGetValue(sessionId, out ExecutionContext context)) + if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context)) return false; var req = JObject.FromObject(new { @@ -436,7 +436,7 @@ protected override async Task AcceptCommand(MessageId sessionId, JObject a } case "removeBreakpoint": { - if (!contexts.TryGetValue(sessionId, out ExecutionContext context)) + if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context)) return false; Result resp = await SendCommand(sessionId, "", args, token); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs index 6e8049e8666bcf..be514841ca8045 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -68,9 +68,9 @@ private static async Task ReadFieldValue( fieldValue["__section"] = field.Attributes switch { FieldAttributes.Private => "private", - FieldAttributes.Public => "result", - _ => "internal" + _ => "result" }; + if (field.IsBackingField) { fieldValue["__isBackingField"] = true; @@ -237,6 +237,9 @@ public static async Task ExpandFieldValues( JObject fieldValue = await ReadFieldValue(sdbHelper, retDebuggerCmdReader, field, id.Value, typeInfo, valtype, isOwn, parentTypeId, getCommandOptions, token); numFieldsRead++; + if (typeInfo.Info.IsNonUserCode && getCommandOptions.HasFlag(GetObjectCommandOptions.JustMyCode) && field.Attributes.HasFlag(FieldAttributes.Private)) + continue; + if (!Enum.TryParse(fieldValue["__state"].Value(), out DebuggerBrowsableState fieldState) || fieldState == DebuggerBrowsableState.Collapsed) { @@ -310,7 +313,7 @@ public static async Task> ExpandPropertyValues( int typeId, string typeName, ArraySegment getterParamsBuffer, - bool isAutoExpandable, + GetObjectCommandOptions getCommandOptions, DotnetObjectId objectId, bool isValueType, bool isOwn, @@ -346,6 +349,10 @@ public static async Task> ExpandPropertyValues( MethodAttributes getterAttrs = getterInfo.Info.Attributes; MethodAttributes getterMemberAccessAttrs = getterAttrs & MethodAttributes.MemberAccessMask; MethodAttributes vtableLayout = getterAttrs & MethodAttributes.VtableLayoutMask; + + if (typeInfo.Info.IsNonUserCode && getCommandOptions.HasFlag(GetObjectCommandOptions.JustMyCode) && getterMemberAccessAttrs == MethodAttributes.Private) + continue; + bool isNewSlot = (vtableLayout & MethodAttributes.NewSlot) == MethodAttributes.NewSlot; typePropertiesBrowsableInfo.TryGetValue(propName, out DebuggerBrowsableState? state); @@ -424,8 +431,7 @@ async Task UpdateBackingFieldWithPropertyAttributes(JObject backingField, string backingField["__section"] = getterMemberAccessAttrs switch { MethodAttributes.Private => "private", - MethodAttributes.Public => "result", - _ => "internal" + _ => "result" }; backingField["__state"] = state?.ToString(); @@ -453,7 +459,7 @@ async Task AddProperty( { string returnTypeName = await sdbHelper.GetReturnType(getMethodId, token); JObject propRet = null; - if (isAutoExpandable || (state is DebuggerBrowsableState.RootHidden && IsACollectionType(returnTypeName))) + if (getCommandOptions.HasFlag(GetObjectCommandOptions.AutoExpandable) || getCommandOptions.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute) || (state is DebuggerBrowsableState.RootHidden && IsACollectionType(returnTypeName))) { try { @@ -473,8 +479,7 @@ async Task AddProperty( propRet["__section"] = getterAttrs switch { MethodAttributes.Private => "private", - MethodAttributes.Public => "result", - _ => "internal" + _ => "result" }; propRet["__state"] = state?.ToString(); if (parentTypeId != -1) @@ -567,13 +572,16 @@ public static async Task GetObjectMemberValues( for (int i = 0; i < typeIdsCnt; i++) { int typeId = typeIdsIncludingParents[i]; + int parentTypeId = i + 1 < typeIdsCnt ? typeIdsIncludingParents[i + 1] : -1; string typeName = await sdbHelper.GetTypeName(typeId, token); // 0th id is for the object itself, and then its ancestors bool isOwn = i == 0; + IReadOnlyList thisTypeFields = await sdbHelper.GetTypeFields(typeId, token); if (!includeStatic) thisTypeFields = thisTypeFields.Where(f => !f.Attributes.HasFlag(FieldAttributes.Static)).ToList(); + if (thisTypeFields.Count > 0) { var allFields = await ExpandFieldValues( @@ -596,7 +604,7 @@ public static async Task GetObjectMemberValues( typeId, typeName, getPropertiesParamBuffer, - getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute), + getCommandType, id, isValueType: false, isOwn, @@ -648,25 +656,21 @@ static void AddOnlyNewFieldValuesByNameTo(JArray namedValues, IDictionary JObject.FromObject(new { result = Result, - privateProperties = PrivateMembers, - internalProperties = OtherMembers + privateProperties = PrivateMembers }); public GetMembersResult() { Result = new JArray(); PrivateMembers = new JArray(); - OtherMembers = new JArray(); } public GetMembersResult(JArray value, bool sortByAccessLevel) @@ -674,7 +678,6 @@ public GetMembersResult(JArray value, bool sortByAccessLevel) var t = FromValues(value, sortByAccessLevel); Result = t.Result; PrivateMembers = t.PrivateMembers; - OtherMembers = t.OtherMembers; } public static GetMembersResult FromValues(IEnumerable values, bool splitMembersByAccessLevel = false) => @@ -709,9 +712,6 @@ private void Split(JToken member) case "private": PrivateMembers.Add(member); return; - case "internal": - OtherMembers.Add(member); - return; default: Result.Add(member); return; @@ -722,7 +722,6 @@ private void Split(JToken member) { Result = (JArray)Result.DeepClone(), PrivateMembers = (JArray)PrivateMembers.DeepClone(), - OtherMembers = (JArray)OtherMembers.DeepClone() }; public IEnumerable Where(Func predicate) @@ -741,26 +740,17 @@ public IEnumerable Where(Func predicate) yield return item; } } - foreach (var item in OtherMembers) - { - if (predicate(item)) - { - yield return item; - } - } } internal JToken FirstOrDefault(Func p) => Result.FirstOrDefault(p) - ?? PrivateMembers.FirstOrDefault(p) - ?? OtherMembers.FirstOrDefault(p); + ?? PrivateMembers.FirstOrDefault(p); internal JArray Flatten() { var result = new JArray(); result.AddRange(Result); result.AddRange(PrivateMembers); - result.AddRange(OtherMembers); return result; } public override string ToString() => $"{JObject}\n"; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 097452892fceae..aca71e664673cf 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -8,6 +8,8 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using System.IO; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Generic; using System.Net.WebSockets; @@ -409,7 +411,16 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, } elementIdxStr += indexObject["value"].ToString(); } - // FixMe: indexing with expressions, e.g. x[a + 1] + // indexing with expressions, e.g. x[a + 1] + else + { + string expression = arg.ToString(); + indexObject = await ExpressionEvaluator.EvaluateSimpleExpression(this, expression, expression, variableDefinitions, logger, token); + string type = indexObject["type"].Value(); + if (type != "number") + throw new InvalidOperationException($"Cannot index with an object of type '{type}'"); + elementIdxStr += indexObject["value"].ToString(); + } } } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index 1626bb2f9e9d4e..781b4c97ff1f2d 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -20,7 +20,7 @@ internal class MonoProxy : DevToolsProxy { private IList urlSymbolServerList; private HashSet sessions = new HashSet(); - protected Dictionary contexts = new Dictionary(); + internal ConcurrentExecutionContextDictionary Contexts = new (); public static HttpClient HttpClient => new HttpClient(); @@ -39,26 +39,11 @@ public MonoProxy(ILogger logger, IList urlSymbolServerList, int runtimeI _defaultPauseOnExceptions = PauseOnExceptionsKind.Unset; } - internal ExecutionContext GetContext(SessionId sessionId) - { - if (contexts.TryGetValue(sessionId, out ExecutionContext context)) - return context; - - throw new ArgumentException($"Invalid Session: \"{sessionId}\"", nameof(sessionId)); - } - - private bool UpdateContext(SessionId sessionId, ExecutionContext executionContext, out ExecutionContext previousExecutionContext) - { - bool previous = contexts.TryGetValue(sessionId, out previousExecutionContext); - contexts[sessionId] = executionContext; - return previous; - } - internal virtual Task SendMonoCommand(SessionId id, MonoCommands cmd, CancellationToken token) => SendCommand(id, "Runtime.evaluate", JObject.FromObject(cmd), token); internal void SendLog(SessionId sessionId, string message, CancellationToken token, string type = "warning") { - if (!contexts.TryGetValue(sessionId, out ExecutionContext context)) + if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context)) return; /*var o = JObject.FromObject(new { @@ -92,6 +77,9 @@ protected override async Task AcceptEvent(SessionId sessionId, JObject par { case "Runtime.consoleAPICalled": { + // Don't process events from sessions we aren't tracking + if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context)) + return false; string type = args["type"]?.ToString(); if (type == "debug") { @@ -110,7 +98,6 @@ protected override async Task AcceptEvent(SessionId sessionId, JObject par { // The optional 3rd argument is the stringified assembly // list so that we don't have to make more round trips - ExecutionContext context = GetContext(sessionId); string loaded = a[2]?["value"]?.ToString(); if (loaded != null) context.LoadedFiles = JToken.Parse(loaded).ToObject(); @@ -167,10 +154,25 @@ protected override async Task AcceptEvent(SessionId sessionId, JObject par return true; } + case "Runtime.executionContextDestroyed": + { + Contexts.DestroyContext(sessionId, args["executionContextId"].Value()); + return false; + } + + case "Runtime.executionContextsCleared": + { + Contexts.ClearContexts(sessionId); + return false; + } + case "Debugger.paused": { // Don't process events from sessions we aren't tracking - if (!contexts.ContainsKey(sessionId)) + if (!Contexts.ContainsKey(sessionId)) + return false; + + if (args?["callFrames"]?.Value()?.Count == 0) return false; //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack @@ -249,7 +251,7 @@ protected virtual async Task SendResume(SessionId id, CancellationToken token) } protected async Task IsRuntimeAlreadyReadyAlready(SessionId sessionId, CancellationToken token) { - if (contexts.TryGetValue(sessionId, out ExecutionContext context) && context.IsRuntimeReady) + if (Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context) && context.IsRuntimeReady) return true; Result res = await SendMonoCommand(sessionId, MonoCommands.IsRuntimeReady(RuntimeId), token); @@ -272,7 +274,7 @@ protected override async Task AcceptCommand(MessageId id, JObject parms, C if (id == SessionId.Null) await AttachToTarget(id, token); - if (!contexts.TryGetValue(id, out ExecutionContext context)) + if (!Contexts.TryGetCurrentExecutionContextValue(id, out ExecutionContext context)) { if (method == "Debugger.setPauseOnExceptions") { @@ -281,7 +283,11 @@ protected override async Task AcceptCommand(MessageId id, JObject parms, C if (pauseOnException != PauseOnExceptionsKind.Unset) _defaultPauseOnExceptions = pauseOnException; } - return false; + if (method != "DotnetDebugger.setDebuggerProperty") + { + // for Dotnetdebugger.* messages, treat them as handled, thus not passing them on to the browser + return method.StartsWith("DotnetDebugger.", StringComparison.OrdinalIgnoreCase); + } } switch (method) @@ -361,37 +367,44 @@ protected override async Task AcceptCommand(MessageId id, JObject parms, C SendResponse(id, resp, token); return true; } + try + { + string bpid = resp.Value["breakpointId"]?.ToString(); + IEnumerable locations = resp.Value["locations"]?.Values(); + var request = BreakpointRequest.Parse(bpid, args); - string bpid = resp.Value["breakpointId"]?.ToString(); - IEnumerable locations = resp.Value["locations"]?.Values(); - var request = BreakpointRequest.Parse(bpid, args); + // is the store done loading? + bool loaded = context.Source.Task.IsCompleted; + if (!loaded) + { + // Send and empty response immediately if not + // and register the breakpoint for resolution + context.BreakpointRequests[bpid] = request; + SendResponse(id, resp, token); + } - // is the store done loading? - bool loaded = context.Source.Task.IsCompleted; - if (!loaded) - { - // Send and empty response immediately if not - // and register the breakpoint for resolution - context.BreakpointRequests[bpid] = request; - SendResponse(id, resp, token); - } + if (await IsRuntimeAlreadyReadyAlready(id, token)) + { + DebugStore store = await RuntimeReady(id, token); - if (await IsRuntimeAlreadyReadyAlready(id, token)) - { - DebugStore store = await RuntimeReady(id, token); + Log("verbose", $"BP req {args}"); + await SetBreakpoint(id, store, request, !loaded, false, token); + } - Log("verbose", $"BP req {args}"); - await SetBreakpoint(id, store, request, !loaded, false, token); - } + if (loaded) + { + // we were already loaded so we should send a response + // with the locations included and register the request + context.BreakpointRequests[bpid] = request; + var result = Result.OkFromObject(request.AsSetBreakpointByUrlResponse(locations)); + SendResponse(id, result, token); - if (loaded) + } + } + catch (Exception e) { - // we were already loaded so we should send a response - // with the locations included and register the request - context.BreakpointRequests[bpid] = request; - var result = Result.OkFromObject(request.AsSetBreakpointByUrlResponse(locations)); - SendResponse(id, result, token); - + logger.LogDebug($"Debugger.setBreakpointByUrl - {args} - failed with exception: {e}"); + SendResponse(id, Result.Err($"Debugger.setBreakpointByUrl - {args} - failed with exception: {e}"), token); } return true; } @@ -573,13 +586,13 @@ protected override async Task AcceptCommand(MessageId id, JObject parms, C } } } - - return false; + // for Dotnetdebugger.* messages, treat them as handled, thus not passing them on to the browser + return method.StartsWith("DotnetDebugger.", StringComparison.OrdinalIgnoreCase); } private async Task ApplyUpdates(MessageId id, JObject args, CancellationToken token) { - var context = GetContext(id); + var context = Contexts.GetCurrentContext(id); string moduleGUID = args["moduleGUID"]?.Value(); string dmeta = args["dmeta"]?.Value(); string dil = args["dil"]?.Value(); @@ -648,7 +661,7 @@ internal async Task GetMethodLocation(MessageId id, JObject args, Cancel private async Task CallOnFunction(MessageId id, JObject args, CancellationToken token) { - var context = GetContext(id); + var context = Contexts.GetCurrentContext(id); if (!DotnetObjectId.TryParse(args["objectId"], out DotnetObjectId objectId)) { return false; } @@ -712,7 +725,7 @@ private async Task CallOnFunction(MessageId id, JObject args, Cancellation private async Task OnSetVariableValue(MessageId id, int scopeId, string varName, JToken varValue, CancellationToken token) { - ExecutionContext context = GetContext(id); + ExecutionContext context = Contexts.GetCurrentContext(id); Frame scope = context.CallStack.FirstOrDefault(s => s.Id == scopeId); if (scope == null) return false; @@ -732,7 +745,7 @@ private async Task OnSetVariableValue(MessageId id, int scopeId, string va internal async Task> RuntimeGetObjectMembers(SessionId id, DotnetObjectId objectId, JToken args, CancellationToken token, bool sortByAccessLevel = false) { - var context = GetContext(id); + var context = Contexts.GetCurrentContext(id); GetObjectCommandOptions getObjectOptions = GetObjectCommandOptions.WithProperties; if (args != null) { @@ -745,6 +758,8 @@ internal async Task> RuntimeGetObjectMembers(Sess if (args["forDebuggerDisplayAttribute"]?.Value() == true) getObjectOptions |= GetObjectCommandOptions.ForDebuggerDisplayAttribute; } + if (JustMyCode) + getObjectOptions |= GetObjectCommandOptions.JustMyCode; try { switch (objectId.Scheme) @@ -869,7 +884,7 @@ private async Task SendBreakpointsOfMethodUpdated(SessionId sessionId, Exe { var methodId = retDebuggerCmdReader.ReadInt32(); var method = await context.SdbAgent.GetMethodInfo(methodId, token); - if (method == null) + if (method == null || method.Info.Source is null) { return true; } @@ -1054,7 +1069,7 @@ internal async Task OnReceiveDebuggerAgentEvent(SessionId sessionId, JObje if (!res.IsOk) return false; - ExecutionContext context = GetContext(sessionId); + ExecutionContext context = Contexts.GetCurrentContext(sessionId); byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value()); using var retDebuggerCmdReader = new MonoBinaryReader(newBytes); retDebuggerCmdReader.ReadBytes(11); //skip HEADER_LEN @@ -1130,7 +1145,7 @@ internal async Task OnReceiveDebuggerAgentEvent(SessionId sessionId, JObje internal async Task LoadSymbolsOnDemand(AssemblyInfo asm, int method_token, SessionId sessionId, CancellationToken token) { - ExecutionContext context = GetContext(sessionId); + ExecutionContext context = Contexts.GetCurrentContext(sessionId); if (urlSymbolServerList.Count == 0) return null; if (asm.TriedToLoadSymbolsOnDemand || !asm.CodeViewInformationAvailable) @@ -1171,29 +1186,17 @@ internal async Task LoadSymbolsOnDemand(AssemblyInfo asm, int method return null; } - protected void OnDefaultContextUpdate(SessionId sessionId, ExecutionContext context) - { - if (UpdateContext(sessionId, context, out ExecutionContext previousContext)) - { - foreach (KeyValuePair kvp in previousContext.BreakpointRequests) - { - context.BreakpointRequests[kvp.Key] = kvp.Value.Clone(); - } - context.PauseOnExceptions = previousContext.PauseOnExceptions; - } - } - protected async Task OnDefaultContext(SessionId sessionId, ExecutionContext context, CancellationToken token) { Log("verbose", "Default context created, clearing state and sending events"); - OnDefaultContextUpdate(sessionId, context); + Contexts.OnDefaultContextUpdate(sessionId, context); if (await IsRuntimeAlreadyReadyAlready(sessionId, token)) await RuntimeReady(sessionId, token); } protected async Task OnResume(MessageId msg_id, CancellationToken token) { - ExecutionContext context = GetContext(msg_id); + ExecutionContext context = Contexts.GetCurrentContext(msg_id); if (context.CallStack != null) { // Stopped on managed code @@ -1201,13 +1204,13 @@ protected async Task OnResume(MessageId msg_id, CancellationToken token) } //discard managed frames - GetContext(msg_id).ClearState(); + Contexts.GetCurrentContext(msg_id).ClearState(); } protected async Task Step(MessageId msgId, StepKind kind, CancellationToken token) { - ExecutionContext context = GetContext(msgId); + ExecutionContext context = Contexts.GetCurrentContext(msgId); if (context.CallStack == null) return false; @@ -1277,7 +1280,7 @@ private async Task OnAssemblyLoadedJSEvent(SessionId sessionId, JObject ev var assembly_data = Convert.FromBase64String(assembly_b64); var pdb_data = string.IsNullOrEmpty(pdb_b64) ? null : Convert.FromBase64String(pdb_b64); - var context = GetContext(sessionId); + var context = Contexts.GetCurrentContext(sessionId); foreach (var source in store.Add(sessionId, assembly_name, assembly_data, pdb_data, token)) { await OnSourceFileAdded(sessionId, source, context, token); @@ -1296,7 +1299,7 @@ private async Task OnSetEntrypointBreakpoint(SessionId sessionId, JObject args, { try { - ExecutionContext context = GetContext(sessionId); + ExecutionContext context = Contexts.GetCurrentContext(sessionId); var argsNew = JObject.FromObject(new { @@ -1362,7 +1365,7 @@ private async Task OnEvaluateOnCallFrame(MessageId msg_id, int scopeId, st { try { - ExecutionContext context = GetContext(msg_id); + ExecutionContext context = Contexts.GetCurrentContext(msg_id); if (context.CallStack == null) return false; @@ -1408,14 +1411,14 @@ internal async Task GetScopeProperties(SessionId msg_id, int scopeId, Ca { try { - ExecutionContext context = GetContext(msg_id); + ExecutionContext context = Contexts.GetCurrentContext(msg_id); Frame scope = context.CallStack.FirstOrDefault(s => s.Id == scopeId); if (scope == null) return Result.Err(JObject.FromObject(new { message = $"Could not find scope with id #{scopeId}" })); VarInfo[] varIds = scope.Method.Info.GetLiveVarsAt(scope.Location.IlLocation.Offset); - var values = await context.SdbAgent.StackFrameGetValues(scope.Method, context.ThreadId, scopeId, varIds, token); + var values = await context.SdbAgent.StackFrameGetValues(scope.Method, context.ThreadId, scopeId, varIds, scope.Location.IlLocation.Offset, token); if (values != null) { if (values == null || values.Count == 0) @@ -1439,7 +1442,7 @@ internal async Task GetScopeProperties(SessionId msg_id, int scopeId, Ca private async Task SetMonoBreakpoint(SessionId sessionId, string reqId, SourceLocation location, string condition, CancellationToken token) { - var context = GetContext(sessionId); + var context = Contexts.GetCurrentContext(sessionId); var bp = new Breakpoint(reqId, location, condition, BreakpointState.Pending); string asm_name = bp.Location.IlLocation.Method.Assembly.Name; int method_token = bp.Location.IlLocation.Method.Token; @@ -1447,7 +1450,8 @@ private async Task SetMonoBreakpoint(SessionId sessionId, string req var assembly_id = await context.SdbAgent.GetAssemblyId(asm_name, token); var methodId = await context.SdbAgent.GetMethodIdByToken(assembly_id, method_token, token); - var breakpoint_id = await context.SdbAgent.SetBreakpoint(methodId, il_offset, token); + //the breakpoint can be invalid because a race condition between the changes already applied on runtime and not applied yet on debugger side + var breakpoint_id = await context.SdbAgent.SetBreakpointNoThrow(methodId, il_offset, token); if (breakpoint_id > 0) { @@ -1468,14 +1472,23 @@ internal virtual async Task OnSourceFileAdded(SessionId sessionId, SourceFile so { if (req.TryResolve(source)) { - await SetBreakpoint(sessionId, context.store, req, true, false, token); + try + { + await SetBreakpoint(sessionId, context.store, req, true, false, token); + } + catch (DebuggerAgentException e) + { + //it's not a wasm page then the command throws an error + if (!e.Message.Contains("getDotnetRuntime is not defined")) + logger.LogDebug($"Unexpected error on OnSourceFileAdded {e}"); + } } } } internal virtual async Task LoadStore(SessionId sessionId, bool tryUseDebuggerProtocol, CancellationToken token) { - ExecutionContext context = GetContext(sessionId); + ExecutionContext context = Contexts.GetCurrentContext(sessionId); if (Interlocked.CompareExchange(ref context.store, new DebugStore(this, logger), null) != null) return await context.Source.Task; @@ -1534,26 +1547,40 @@ async Task GetLoadedFiles(SessionId sessionId, ExecutionContext contex protected async Task RuntimeReady(SessionId sessionId, CancellationToken token) { - ExecutionContext context = GetContext(sessionId); - if (Interlocked.CompareExchange(ref context.ready, new TaskCompletionSource(), null) != null) - return await context.ready.Task; - - await context.SdbAgent.SendDebuggerAgentCommand(CmdEventRequest.ClearAllBreakpoints, null, token); + try + { + ExecutionContext context = Contexts.GetCurrentContext(sessionId); + if (Interlocked.CompareExchange(ref context.ready, new TaskCompletionSource(), null) != null) + return await context.ready.Task; + await context.SdbAgent.SendDebuggerAgentCommand(CmdEventRequest.ClearAllBreakpoints, null, token); - if (context.PauseOnExceptions != PauseOnExceptionsKind.None && context.PauseOnExceptions != PauseOnExceptionsKind.Unset) - await context.SdbAgent.EnableExceptions(context.PauseOnExceptions, token); + if (context.PauseOnExceptions != PauseOnExceptionsKind.None && context.PauseOnExceptions != PauseOnExceptionsKind.Unset) + await context.SdbAgent.EnableExceptions(context.PauseOnExceptions, token); - await context.SdbAgent.SetProtocolVersion(token); - await context.SdbAgent.EnableReceiveRequests(EventKind.UserBreak, token); - await context.SdbAgent.EnableReceiveRequests(EventKind.EnC, token); - await context.SdbAgent.EnableReceiveRequests(EventKind.MethodUpdate, token); + await context.SdbAgent.SetProtocolVersion(token); + await context.SdbAgent.EnableReceiveRequests(EventKind.UserBreak, token); + await context.SdbAgent.EnableReceiveRequests(EventKind.EnC, token); + await context.SdbAgent.EnableReceiveRequests(EventKind.MethodUpdate, token); - DebugStore store = await LoadStore(sessionId, true, token); - context.ready.SetResult(store); - await SendEvent(sessionId, "Mono.runtimeReady", new JObject(), token); - await SendMonoCommand(sessionId, MonoCommands.SetDebuggerAttached(RuntimeId), token); - context.SdbAgent.ResetStore(store); - return store; + DebugStore store = await LoadStore(sessionId, true, token); + context.ready.SetResult(store); + await SendEvent(sessionId, "Mono.runtimeReady", new JObject(), token); + await SendMonoCommand(sessionId, MonoCommands.SetDebuggerAttached(RuntimeId), token); + context.SdbAgent.ResetStore(store); + return store; + } + catch (DebuggerAgentException e) + { + //it's not a wasm page then the command throws an error + if (!e.Message.Contains("getDotnetRuntime is not defined")) + logger.LogDebug($"Unexpected error on RuntimeReady {e}"); + return null; + } + catch (Exception e) + { + logger.LogDebug($"Unexpected error on RuntimeReady {e}"); + return null; + } } private static IEnumerable> GetBPReqLocations(DebugStore store, BreakpointRequest req, bool ifNoneFoundThenFindNext = false) @@ -1574,7 +1601,7 @@ private static IEnumerable> GetBPReqLocation private async Task ResetBreakpoint(SessionId msg_id, DebugStore store, MethodInfo method, CancellationToken token) { - ExecutionContext context = GetContext(msg_id); + ExecutionContext context = Contexts.GetCurrentContext(msg_id); foreach (var req in context.BreakpointRequests.Values) { if (req.Method != null) @@ -1599,7 +1626,7 @@ protected async Task RemoveBreakpoint(SessionId msg_id, JObject args, bool isEnC { string bpid = args?["breakpointId"]?.Value(); - ExecutionContext context = GetContext(msg_id); + ExecutionContext context = Contexts.GetCurrentContext(msg_id); if (!context.BreakpointRequests.TryGetValue(bpid, out BreakpointRequest breakpointRequest)) return; @@ -1621,7 +1648,7 @@ protected async Task RemoveBreakpoint(SessionId msg_id, JObject args, bool isEnC protected async Task SetBreakpoint(SessionId sessionId, DebugStore store, BreakpointRequest req, bool sendResolvedEvent, bool fromEnC, CancellationToken token) { - ExecutionContext context = GetContext(sessionId); + ExecutionContext context = Contexts.GetCurrentContext(sessionId); if ((!fromEnC && req.Locations.Any()) || (fromEnC && req.Locations.Any(bp => bp.State == BreakpointState.Active))) { if (!fromEnC) @@ -1704,7 +1731,7 @@ private static bool IsNestedMethod(DebugStore store, Frame scope, SourceLocation private async Task OnSetNextIP(MessageId sessionId, SourceLocation targetLocation, CancellationToken token) { DebugStore store = await RuntimeReady(sessionId, token); - ExecutionContext context = GetContext(sessionId); + ExecutionContext context = Contexts.GetCurrentContext(sessionId); Frame scope = context.CallStack.First(); SourceLocation foundLocation = DebugStore.FindBreakpointLocations(targetLocation, targetLocation, scope.Method.Info) @@ -1723,7 +1750,10 @@ private async Task OnSetNextIP(MessageId sessionId, SourceLocation targetL if (!ret) return false; - var breakpointId = await context.SdbAgent.SetBreakpoint(scope.Method.DebugId, ilOffset.Offset, token); + var breakpointId = await context.SdbAgent.SetBreakpointNoThrow(scope.Method.DebugId, ilOffset.Offset, token); + if (breakpointId == -1) + return false; + context.TempBreakpointForSetNextIP = breakpointId; await SendResume(sessionId, token); return true; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 3b3de88f56a9c7..4368c4e8730e82 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -59,7 +59,9 @@ internal enum GetObjectCommandOptions OwnProperties = 4, ForDebuggerProxyAttribute = 8, ForDebuggerDisplayAttribute = 16, - WithProperties = 32 + WithProperties = 32, + JustMyCode = 64, + AutoExpandable = 128 } internal enum CommandSet { @@ -785,8 +787,10 @@ internal sealed class MonoSDBHelper private DebugStore store; private SessionId sessionId; - private readonly ILogger logger; - private static readonly Regex regexForAsyncLocals = new (@"\<([^)]*)\>", RegexOptions.Singleline); + internal readonly ILogger logger; + private static readonly Regex regexForAsyncLocals = new(@"\<([^)]*)\>([^)]*)([_][_])([0-9]*)", RegexOptions.Singleline); //5__1 + private static readonly Regex regexForVBAsyncLocals = new(@"\$VB\$ResumableLocal_([^)]*)\$([0-9]*)", RegexOptions.Singleline); //$VB$ResumableLocal_testVbScope$2 + private static readonly Regex regexForVBAsyncMethodName = new(@"VB\$StateMachine_([0-9]*)_([^)]*)", RegexOptions.Singleline); //VB$StateMachine_2_RunVBScope private static readonly Regex regexForAsyncMethodName = new (@"\<([^>]*)\>([d][_][_])([0-9]*)", RegexOptions.Compiled); private static readonly Regex regexForGenericArgs = new (@"[`][0-9]+", RegexOptions.Compiled); private static readonly Regex regexForNestedLeftRightAngleBrackets = new ("^(((?'Open'<)[^<>]*)+((?'Close-Open'>)[^<>]*)+)*(?(Open)(?!))[^<>]*", RegexOptions.Compiled); @@ -1269,6 +1273,14 @@ public async Task GetPrettyMethodName(int methodId, bool isAnonymous, Ca if (anonymousMethodId.LastIndexOf('_') >= 0) anonymousMethodId = klassName.Substring(klassName.LastIndexOf('_') + 1); } + else if (klassName.StartsWith("VB$")) + { + var match = regexForVBAsyncMethodName.Match(klassName); + if (match.Success) + ret = ret.Insert(0, match.Groups[2].Value); + else + ret = ret.Insert(0, klassName); + } else { var matchOnClassName = regexForNestedLeftRightAngleBrackets.Match(klassName); @@ -1355,7 +1367,7 @@ public async Task GetParameters(int methodId, CancellationToken token) return parameters; } - public async Task SetBreakpoint(int methodId, long il_offset, CancellationToken token) + public async Task SetBreakpointNoThrow(int methodId, long il_offset, CancellationToken token) { using var commandParamsWriter = new MonoBinaryWriter(); commandParamsWriter.Write((byte)EventKind.Breakpoint); @@ -1364,7 +1376,9 @@ public async Task SetBreakpoint(int methodId, long il_offset, CancellationT commandParamsWriter.Write((byte)ModifierKind.LocationOnly); commandParamsWriter.Write(methodId); commandParamsWriter.Write(il_offset); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdEventRequest.Set, commandParamsWriter, token); + using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdEventRequest.Set, commandParamsWriter, token, throwOnError: false); + if (retDebuggerCmdReader.HasError) + return -1; return retDebuggerCmdReader.ReadInt32(); } @@ -1567,7 +1581,7 @@ public async Task GetAssemblyFromType(int type_id, CancellationToken token) public JToken GetEvaluationResultProperties(string id) { - ExecutionContext context = proxy.GetContext(sessionId); + ExecutionContext context = proxy.Contexts.GetCurrentContext(sessionId); var resolver = new MemberReferenceResolver(proxy, context, sessionId, context.CallStack.First().Id, logger); var evaluationResult = resolver.TryGetEvaluationResult(id); return evaluationResult["value"]; @@ -1588,7 +1602,7 @@ public async Task GetValueFromDebuggerDisplayAttribute(DotnetObjectId do var stringId = getCAttrsRetReader.ReadInt32(); var dispAttrStr = await GetStringValue(stringId, token); - ExecutionContext context = proxy.GetContext(sessionId); + ExecutionContext context = proxy.Contexts.GetCurrentContext(sessionId); GetMembersResult members = await GetTypeMemberValues( dotnetObjectId, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerDisplayAttribute, @@ -1611,6 +1625,14 @@ public async Task GetValueFromDebuggerDisplayAttribute(DotnetObjectId do { dispAttrStr = dispAttrStr.Replace(",nq", ""); } + if (dispAttrStr.Contains(", raw")) + { + dispAttrStr = dispAttrStr.Replace(", raw", ""); + } + if (dispAttrStr.Contains(",raw")) + { + dispAttrStr = dispAttrStr.Replace(",raw", ""); + } expr = "$\"" + dispAttrStr + "\""; JObject retValue = await resolver.Resolve(expr, token); if (retValue == null) @@ -1908,7 +1930,7 @@ private static bool IsClosureReferenceField (string fieldName) fieldName.StartsWith ("<>8__", StringComparison.Ordinal); } - public async Task GetHoistedLocalVariables(int objectId, IEnumerable asyncLocals, CancellationToken token) + public async Task GetHoistedLocalVariables(MethodInfoWithDebugInformation method, int objectId, IEnumerable asyncLocals, int offset, CancellationToken token) { JArray asyncLocalsFull = new JArray(); List objectsAlreadyRead = new(); @@ -1919,7 +1941,6 @@ public async Task GetHoistedLocalVariables(int objectId, IEnumerable GetHoistedLocalVariables(int objectId, IEnumerable", StringComparison.Ordinal)) //examples: <>t__builder, <>1__state { @@ -1942,18 +1964,37 @@ public async Task GetHoistedLocalVariables(int objectId, IEnumerable StackFrameGetValues(MethodInfoWithDebugInformation method, int thread_id, int frame_id, VarInfo[] varIds, CancellationToken token) + public async Task StackFrameGetValues(MethodInfoWithDebugInformation method, int thread_id, int frame_id, VarInfo[] varIds, int offset, CancellationToken token) { using var commandParamsWriter = new MonoBinaryWriter(); commandParamsWriter.Write(thread_id); @@ -1970,7 +2011,7 @@ public async Task StackFrameGetValues(MethodInfoWithDebugInformation met retDebuggerCmdReader.ReadByte(); //ignore type var objectId = retDebuggerCmdReader.ReadInt32(); GetMembersResult asyncProxyMembers = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithProperties, token, includeStatic: true); - var asyncLocals = await GetHoistedLocalVariables(objectId, asyncProxyMembers.Flatten(), token); + var asyncLocals = await GetHoistedLocalVariables(method, objectId, asyncProxyMembers.Flatten(), offset, token); return asyncLocals; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs index c2a2513f00ba19..0b28bf082b7d60 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -98,9 +98,7 @@ JObject GetFieldWithMetadata(FieldTypeClass field, JObject fieldValue, bool isSt if (isStatic) fieldValue["name"] = field.Name; FieldAttributes attr = field.Attributes & FieldAttributes.FieldAccessMask; - fieldValue["__section"] = attr == FieldAttributes.Public - ? "public" : - attr == FieldAttributes.Private ? "private" : "internal"; + fieldValue["__section"] = attr == FieldAttributes.Private ? "private" : "result"; if (field.IsBackingField) { @@ -218,7 +216,6 @@ public async Task GetMemberValues( result = _combinedResult.Clone(); RemovePropertiesFrom(result.Result); RemovePropertiesFrom(result.PrivateMembers); - RemovePropertiesFrom(result.OtherMembers); } if (result == null) @@ -293,7 +290,7 @@ public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, bool splitMember typeId, className, Buffer, - autoExpand, + autoExpand ? GetObjectCommandOptions.AutoExpandable : GetObjectCommandOptions.None, Id, isValueType: true, isOwn: i == 0, diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/AsyncTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/AsyncTests.cs index 17d29d62bab844..96dc8f25c07f9e 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/AsyncTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/AsyncTests.cs @@ -81,5 +81,77 @@ public async Task AsyncLocalsInNestedContinueWithStaticBlock() => await CheckIns ncs_dt0 = TDateTime(new DateTime(3412, 4, 6, 8, 0, 2)) }, "locals"); }); + + [Theory] + [InlineData("Run", 246, 16, 252, 16, "RunCSharpScope")] + [InlineData("RunContinueWith", 277, 20, 283, 20, "RunContinueWithSameVariableName")] + [InlineData("RunNestedContinueWith", 309, 24, 315, 24, "RunNestedContinueWithSameVariableName.AnonymousMethod__1")] + [InlineData("RunNonAsyncMethod", 334, 16, 340, 16, "RunNonAsyncMethodSameVariableName")] + public async Task InspectLocalsWithSameNameInDifferentScopesInAsyncMethod_CSharp(string method_to_run, int line1, int col1, int line2, int col2, string func_to_pause) + => await InspectLocalsWithSameNameInDifferentScopesInAsyncMethod( + $"[debugger-test] DebuggerTests.AsyncTests.VariablesWithSameNameDifferentScopes:{method_to_run}", + "dotnet://debugger-test.dll/debugger-async-test.cs", + line1, + col1, + line2, + col2, + $"DebuggerTests.AsyncTests.VariablesWithSameNameDifferentScopes.{func_to_pause}", + "testCSharpScope"); + + [Theory] + [InlineData("[debugger-test-vb] DebuggerTestVB.TestVbScope:Run", 14, 12, 22, 12, "DebuggerTestVB.TestVbScope.RunVBScope", "testVbScope")] + public async Task InspectLocalsWithSameNameInDifferentScopesInAsyncMethod_VB(string method_to_run, int line1, int col1, int line2, int col2, string func_to_pause, string variable_to_inspect) + => await InspectLocalsWithSameNameInDifferentScopesInAsyncMethod( + method_to_run, + "dotnet://debugger-test-vb.dll/debugger-test-vb.vb", + line1, + col1, + line2, + col2, + func_to_pause, + variable_to_inspect); + + private async Task InspectLocalsWithSameNameInDifferentScopesInAsyncMethod(string method_to_run, string source_to_pause, int line1, int col1, int line2, int col2, string func_to_pause, string variable_to_inspect) + { + var expression = $"{{ invoke_static_method('{method_to_run}'); }}"; + + await EvaluateAndCheck( + "window.setTimeout(function() {" + expression + "; }, 1);", + source_to_pause, line1, col1, + func_to_pause, + locals_fn: async (locals) => + { + await CheckString(locals, variable_to_inspect, "hello"); + await CheckString(locals, "onlyInFirstScope", "only-in-first-scope"); + Assert.False(locals.Any(jt => jt["name"]?.Value() == "onlyInSecondScope")); + } + ); + await StepAndCheck(StepKind.Resume, source_to_pause, line2, col2, func_to_pause, + locals_fn: async (locals) => + { + await CheckString(locals, variable_to_inspect, "hi"); + await CheckString(locals, "onlyInSecondScope", "only-in-second-scope"); + Assert.False(locals.Any(jt => jt["name"]?.Value() == "onlyInFirstScope")); + } + ); + } + + [Fact] + public async Task InspectLocalsInAsyncVBMethod() + { + var expression = $"{{ invoke_static_method('[debugger-test-vb] DebuggerTestVB.TestVbScope:Run'); }}"; + + await EvaluateAndCheck( + "window.setTimeout(function() {" + expression + "; }, 1);", + "dotnet://debugger-test-vb.dll/debugger-test-vb.vb", 14, 12, + "DebuggerTestVB.TestVbScope.RunVBScope", + locals_fn: async (locals) => + { + await CheckString(locals, "testVbScope", "hello"); + CheckNumber(locals, "a", 10); + CheckNumber(locals, "data", 10); + } + ); + } } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs index cb21ab4e9f2257..7949ebec263fa6 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs @@ -470,8 +470,10 @@ await SendCommandAndCheck(null, "Debugger.resume", }); } - [ConditionalFact(nameof(RunningOnChrome))] - public async Task CreateGoodBreakpointAndHitGoToNonWasmPageComeBackAndHitAgain() + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData("load_non_wasm_page")] + [InlineData("load_non_wasm_page_forcing_runtime_ready")] //to simulate the same behavior that has when debugging from VS and OnDefaultContextCreated is called + public async Task CreateGoodBreakpointAndHitGoToNonWasmPageComeBackAndHitAgain(string func_name) { var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); var pause_location = await EvaluateAndCheck( @@ -500,7 +502,7 @@ public async Task CreateGoodBreakpointAndHitGoToNonWasmPageComeBackAndHitAgain() var run_method = JObject.FromObject(new { - expression = "window.setTimeout(function() { load_non_wasm_page(); }, 1);" + expression = "window.setTimeout(function() { " + func_name + "(); }, 1);" }); await cli.SendCommand("Runtime.evaluate", run_method, token); await Task.Delay(1000, token); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index 01c3a787e8fb83..b533d39910ac15 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -988,7 +988,7 @@ internal virtual async Task GetProperties(string id, JToken fn_args = nu return locals; } - internal async Task<(JToken, JToken, JToken)> GetPropertiesSortedByProtectionLevels(string id, JToken fn_args = null, bool? own_properties = null, bool? accessors_only = null, bool expect_ok = true) + internal async Task<(JToken, JToken)> GetPropertiesSortedByProtectionLevels(string id, JToken fn_args = null, bool? own_properties = null, bool? accessors_only = null, bool expect_ok = true) { if (UseCallFunctionOnBeforeGetProperties && !id.StartsWith("dotnet:scope:")) { @@ -1004,7 +1004,7 @@ internal virtual async Task GetProperties(string id, JToken fn_args = nu var result = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token); AssertEqual(expect_ok, result.IsOk, $"Runtime.getProperties returned {result.IsOk} instead of {expect_ok}, for {cfo_args.ToString()}, with Result: {result}"); if (!result.IsOk) - return (null, null, null); + return (null, null); id = result.Value["result"]?["objectId"]?.Value(); } @@ -1024,10 +1024,9 @@ internal virtual async Task GetProperties(string id, JToken fn_args = nu var frame_props = await cli.SendCommand("Runtime.getProperties", get_prop_req, token); AssertEqual(expect_ok, frame_props.IsOk, $"Runtime.getProperties returned {frame_props.IsOk} instead of {expect_ok}, for {get_prop_req}, with Result: {frame_props}"); if (!frame_props.IsOk) - return (null, null, null);; + return (null, null);; var locals = frame_props.Value["result"]; - var locals_internal = frame_props.Value["internalProperties"]; var locals_private = frame_props.Value["privateProperties"]; // FIXME: Should be done when generating the list in dotnet.es6.lib.js, but not sure yet @@ -1044,7 +1043,7 @@ internal virtual async Task GetProperties(string id, JToken fn_args = nu } } - return (locals, locals_internal, locals_private); + return (locals, locals_private); } internal virtual async Task<(JToken, Result)> EvaluateOnCallFrame(string id, string expression, bool expect_ok = true) @@ -1322,7 +1321,7 @@ internal async Task LoadAssemblyDynamicallyALCAndRunMethod(string asm_f return await WaitFor(Inspector.PAUSE); } - internal async Task LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges(string asm_file, string pdb_file, string class_name, string method_name, bool expectBpResolvedEvent) + internal async Task LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges(string asm_file, string pdb_file, string class_name, string method_name, bool expectBpResolvedEvent, params string[] sourcesToWait) { byte[] bytes = File.ReadAllBytes(asm_file); string asm_base64 = Convert.ToBase64String(bytes); @@ -1338,7 +1337,7 @@ internal async Task LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( Task eventTask = expectBpResolvedEvent ? WaitForBreakpointResolvedEvent() - : WaitForScriptParsedEventsAsync("MethodBody0.cs", "MethodBody1.cs"); + : WaitForScriptParsedEventsAsync(sourcesToWait); (await cli.SendCommand("Runtime.evaluate", load_assemblies, token)).AssertOk(); await eventTask; @@ -1397,7 +1396,7 @@ internal async Task LoadAssemblyAndTestHotReloadUsingSDB(string asm_fil return await WaitFor(Inspector.PAUSE); } - internal async Task LoadAssemblyAndTestHotReload(string asm_file, string pdb_file, string asm_file_hot_reload, string class_name, string method_name, bool expectBpResolvedEvent) + internal async Task LoadAssemblyAndTestHotReload(string asm_file, string pdb_file, string asm_file_hot_reload, string class_name, string method_name, bool expectBpResolvedEvent, string[] sourcesToWait, string methodName2 = "", string methodName3 = "") { byte[] bytes = File.ReadAllBytes(asm_file); string asm_base64 = Convert.ToBase64String(bytes); @@ -1434,13 +1433,18 @@ internal async Task LoadAssemblyAndTestHotReload(string asm_file, strin Task eventTask = expectBpResolvedEvent ? WaitForBreakpointResolvedEvent() - : WaitForScriptParsedEventsAsync("MethodBody0.cs", "MethodBody1.cs"); + : WaitForScriptParsedEventsAsync(sourcesToWait); (await cli.SendCommand("Runtime.evaluate", load_assemblies, token)).AssertOk(); await eventTask; + if (methodName2 == "") + methodName2 = method_name; + if (methodName3 == "") + methodName3 = method_name; + var run_method = JObject.FromObject(new { - expression = "window.setTimeout(function() { invoke_static_method('[debugger-test] TestHotReload:RunMethod', '" + class_name + "', '" + method_name + "'); }, 1);" + expression = "window.setTimeout(function() { invoke_static_method('[debugger-test] TestHotReload:RunMethod', '" + class_name + "', '" + method_name + "', '" + methodName2 + "', '" + methodName3 + "'); }, 1);" }); await cli.SendCommand("Runtime.evaluate", run_method, token); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 0613570a45dc77..2790e8f131ebae 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -627,6 +627,48 @@ await EvaluateOnCallFrameAndCheck(id, }); + [Fact] + public async Task EvaluateIndexingByExpression() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + await EvaluateOnCallFrameAndCheck(id, + ("f.numList[i + 1]", TNumber(2)), + ("f.textList[(2 * j) - 1]", TString("2")), + ("f.textList[j - 1]", TString("1")), + ("f.numArray[f.numList[j - 1]]", TNumber(2)) + ); + }); + + [Fact] + public async Task EvaluateIndexingByExpressionMultidimensional() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateLocalsWithMultidimensionalIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithMultidimensionalIndexingTests.EvaluateLocals", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithMultidimensionalIndexingTests:EvaluateLocals'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + await EvaluateOnCallFrameAndCheck(id, + ("f.numArray2D[0, j - 1]", TNumber(1)), // 0, 0 + ("f.numArray2D[f.idx1, i + j]", TNumber(4)), // 1, 1 + ("f.numArray2D[(f.idx1 - j) * 5, i + j]", TNumber(2)), // 0, 1 + ("f.numArray2D[i + j, f.idx1 - 1]", TNumber(3)) // 1, 0 + ); + }); + + [ConditionalFact(nameof(RunningOnChrome))] + public async Task EvaluateIndexingByExpressionNegative() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", + $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); 1 }})", + wait_for_event_fn: async (pause_location) => + { + // indexing with expression of a wrong type + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var (_, res) = await EvaluateOnCallFrame(id, "f.numList[\"a\" + 1]", expect_ok: false ); + Assert.Equal("Unable to evaluate element access 'f.numList[\"a\" + 1]': Cannot index with an object of type 'string'", res.Error["message"]?.Value()); + }); + [Fact] public async Task EvaluateIndexingByMemberVariables() => await CheckInspectLocalsAtBreakpointSite( "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs index d9fc2098dc07ec..537c286ad60c6d 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs @@ -449,12 +449,13 @@ private void AssertHasOnlyExpectedProperties(string[] expected_names, IEnumerabl throw new XunitException($"missing or unexpected members found"); } - public static TheoryData, Dictionary, Dictionary, string> GetDataForProtectionLevels() + public static TheoryData, Dictionary, string> GetDataForProtectionLevels() { - var data = new TheoryData, Dictionary, Dictionary, string>(); + var data = new TheoryData, Dictionary, string>(); var public_props = new Dictionary() { + // --------- public ------------: // own: {"BaseBase_PropertyForHidingWithField", TNumber(210)}, {"Base_PropertyForOverridingWithProperty", TGetter("Base_PropertyForOverridingWithProperty", TDateTime(new DateTime(2020, 7, 6, 5, 4, 3)))}, @@ -487,10 +488,8 @@ public static TheoryData, Dictionary(){ + // ---- internal / protected ----: // own: {"BaseBase_AutoPropertyForHidingWithProperty", TGetter("BaseBase_AutoPropertyForHidingWithProperty", TString("Derived#BaseBase_AutoPropertyForHidingWithProperty"))}, {"Base_PropertyForOverridingWithAutoProperty", TDateTime(new DateTime(2022, 7, 6, 5, 4, 3))}, @@ -510,20 +509,19 @@ public static TheoryData, Dictionary() { // own + // public {"a", TNumber(4)}, {"DateTime", TGetter("DateTime")}, {"AutoStringProperty", TString("CloneableStruct#AutoStringProperty")}, {"FirstName", TGetter("FirstName")}, - {"LastName", TGetter("LastName")} - }; - internal_protected_props = new Dictionary() - { + {"LastName", TGetter("LastName")}, + // internal {"b", TBool(true)} }; @@ -533,14 +531,14 @@ public static TheoryData, Dictionary expectedPublic, Dictionary expectedProtInter, Dictionary expectedPriv, string entryMethod) => + Dictionary expectedPublicInternalAndProtected, Dictionary expectedPriv, string entryMethod) => await CheckInspectLocalsAtBreakpointSite( $"DebuggerTests.GetPropertiesTests.{entryMethod}", "InstanceMethod", 1, $"DebuggerTests.GetPropertiesTests.{entryMethod}.InstanceMethod", $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.GetPropertiesTests.{entryMethod}:run'); }})", @@ -548,14 +546,12 @@ await CheckInspectLocalsAtBreakpointSite( { var id = pause_location["callFrames"][0]["callFrameId"].Value(); var (obj, _) = await EvaluateOnCallFrame(id, "this"); - var (pub, internalAndProtected, priv) = await GetPropertiesSortedByProtectionLevels(obj["objectId"]?.Value()); + var (pubInternalAndProtected, priv) = await GetPropertiesSortedByProtectionLevels(obj["objectId"]?.Value()); - AssertHasOnlyExpectedProperties(expectedPublic.Keys.ToArray(), pub.Values()); - AssertHasOnlyExpectedProperties(expectedProtInter.Keys.ToArray(), internalAndProtected.Values()); + AssertHasOnlyExpectedProperties(expectedPublicInternalAndProtected.Keys.ToArray(), pubInternalAndProtected.Values()); AssertHasOnlyExpectedProperties(expectedPriv.Keys.ToArray(), priv.Values()); - await CheckProps(pub, expectedPublic, "public"); - await CheckProps(internalAndProtected, expectedProtInter, "internalAndProtected"); + await CheckProps(pubInternalAndProtected, expectedPublicInternalAndProtected, "result"); await CheckProps(priv, expectedPriv, "private"); }); } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs index 146ecaebac862c..eec98fcbd1902b 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs @@ -22,7 +22,7 @@ public async Task DebugHotReloadMethodChangedUserBreak() Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.dll"), Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.pdb"), Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"), - "MethodBody1", "StaticMethod1", expectBpResolvedEvent: false); + "MethodBody1", "StaticMethod1", expectBpResolvedEvent: false, sourcesToWait: new string [] { "MethodBody0.cs", "MethodBody1.cs" }); 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", 12, 16, "ApplyUpdateReferencedAssembly.MethodBody1.StaticMethod1"); @@ -40,7 +40,7 @@ public async Task DebugHotReloadMethodUnchanged() Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.dll"), Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.pdb"), Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"), - "MethodBody2", "StaticMethod1", expectBpResolvedEvent: false); + "MethodBody2", "StaticMethod1", expectBpResolvedEvent: false, sourcesToWait: new string [] { "MethodBody0.cs", "MethodBody1.cs" }); 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", 21, 12, "ApplyUpdateReferencedAssembly.MethodBody2.StaticMethod1"); @@ -51,35 +51,37 @@ public async Task DebugHotReloadMethodUnchanged() CheckNumber(locals, "a", 10); } - [ConditionalFact(nameof(RunningOnChrome))] - public async Task DebugHotReloadMethodAddBreakpoint() + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData("ApplyUpdateReferencedAssembly")] + [InlineData("ApplyUpdateReferencedAssemblyChineseCharInPathㄨ")] + public async Task DebugHotReloadMethodAddBreakpoint(string assembly_name) { 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", expectBpResolvedEvent: true); + Path.Combine(DebuggerTestAppPath, $"{assembly_name}.dll"), + Path.Combine(DebuggerTestAppPath, $"{assembly_name}.pdb"), + Path.Combine(DebuggerTestAppPath, $"../wasm/{assembly_name}.dll"), + "MethodBody3", "StaticMethod3", expectBpResolvedEvent: true, sourcesToWait: new string [] { "MethodBody0.cs", "MethodBody1.cs" }); 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", 30, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3"); + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", $"dotnet://{assembly_name}.dll/MethodBody1.cs", 30, 12, "ApplyUpdateReferencedAssembly.MethodBody3.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", 30, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3"); + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", $"dotnet://{assembly_name}.dll/MethodBody1.cs", 30, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3"); locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); await CheckBool(locals, "c", true); - await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 31, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", + await StepAndCheck(StepKind.Over, $"dotnet://{assembly_name}.dll/MethodBody1.cs", 31, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", locals_fn: async (locals) => { CheckNumber(locals, "d", 10); await Task.CompletedTask; } ); - await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 32, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", + await StepAndCheck(StepKind.Over, $"dotnet://{assembly_name}.dll/MethodBody1.cs", 32, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", locals_fn: async (locals) => { CheckNumber(locals, "d", 10); @@ -87,7 +89,7 @@ await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/Me await Task.CompletedTask; } ); - await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 33, 8, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", + await StepAndCheck(StepKind.Over, $"dotnet://{assembly_name}.dll/MethodBody1.cs", 33, 8, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", locals_fn: async (locals) => { CheckNumber(locals, "d", 10); @@ -108,7 +110,7 @@ public async Task DebugHotReloadMethodEmpty() Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.dll"), Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.pdb"), Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"), - "MethodBody4", "StaticMethod4", expectBpResolvedEvent: true); + "MethodBody4", "StaticMethod4", expectBpResolvedEvent: true, sourcesToWait: new string [] { "MethodBody0.cs", "MethodBody1.cs" }); var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 38, 12, "ApplyUpdateReferencedAssembly.MethodBody4.StaticMethod4"); @@ -164,7 +166,7 @@ public async Task DebugHotReloadMethodChangedUserBreakUsingSDB() string asm_file_hot_reload = Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody1", "StaticMethod1", expectBpResolvedEvent: false); + asm_file, pdb_file, "MethodBody1", "StaticMethod1", expectBpResolvedEvent: false, sourcesToWait: new string [] { "MethodBody0.cs", "MethodBody1.cs" }); var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); CheckNumber(locals, "a", 10); @@ -196,7 +198,7 @@ public async Task DebugHotReloadMethodUnchangedUsingSDB() string asm_file_hot_reload = Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody2", "StaticMethod1", expectBpResolvedEvent: false); + asm_file, pdb_file, "MethodBody2", "StaticMethod1", expectBpResolvedEvent: false, sourcesToWait: new string [] { "MethodBody0.cs", "MethodBody1.cs" }); var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); CheckNumber(locals, "a", 10); @@ -217,17 +219,19 @@ public async Task DebugHotReloadMethodUnchangedUsingSDB() CheckLocation("dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 21, 12, scripts, top_frame["location"]); } - [ConditionalFact(nameof(RunningOnChrome))] - public async Task DebugHotReloadMethodAddBreakpointUsingSDB() + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData("ApplyUpdateReferencedAssembly")] + [InlineData("ApplyUpdateReferencedAssemblyChineseCharInPathㄨ")] + public async Task DebugHotReloadMethodAddBreakpointUsingSDB(string assembly_name) { - string asm_file = Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.dll"); - string pdb_file = Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.pdb"); - string asm_file_hot_reload = Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"); + string asm_file = Path.Combine(DebuggerTestAppPath, $"{assembly_name}.dll"); + string pdb_file = Path.Combine(DebuggerTestAppPath, $"{assembly_name}.pdb"); + string asm_file_hot_reload = Path.Combine(DebuggerTestAppPath, $"../wasm/{assembly_name}.dll"); int line = 30; await SetBreakpoint(".*/MethodBody1.cs$", line, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody3", "StaticMethod3", expectBpResolvedEvent: true); + asm_file, pdb_file, "MethodBody3", "StaticMethod3", expectBpResolvedEvent: true, sourcesToWait: new string [] { "MethodBody0.cs", "MethodBody1.cs" }); var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); CheckNumber(locals, "a", 10); @@ -238,7 +242,7 @@ public async Task DebugHotReloadMethodAddBreakpointUsingSDB() JToken top_frame = pause_location["callFrames"]?[0]; AssertEqual("ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", top_frame?["functionName"]?.Value(), top_frame?.ToString()); - CheckLocation("dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 30, 12, scripts, top_frame["location"]); + CheckLocation($"dotnet://{assembly_name}.dll/MethodBody1.cs", 30, 12, scripts, top_frame["location"]); locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); CheckNumber(locals, "b", 15); @@ -249,19 +253,19 @@ public async Task DebugHotReloadMethodAddBreakpointUsingSDB() top_frame = pause_location["callFrames"]?[0]; AssertEqual("ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", top_frame?["functionName"]?.Value(), top_frame?.ToString()); - CheckLocation("dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 30, 12, scripts, top_frame["location"]); + CheckLocation($"dotnet://{assembly_name}.dll/MethodBody1.cs", 30, 12, scripts, top_frame["location"]); locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); await CheckBool(locals, "c", true); - await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 31, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", + await StepAndCheck(StepKind.Over, $"dotnet://{assembly_name}.dll/MethodBody1.cs", 31, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", locals_fn: async (locals) => { CheckNumber(locals, "d", 10); await Task.CompletedTask; } ); - await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 32, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", + await StepAndCheck(StepKind.Over, $"dotnet://{assembly_name}.dll/MethodBody1.cs", 32, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", locals_fn: async (locals) => { CheckNumber(locals, "d", 10); @@ -269,7 +273,7 @@ await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/Me await Task.CompletedTask; } ); - await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 33, 8, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", + await StepAndCheck(StepKind.Over, $"dotnet://{assembly_name}.dll/MethodBody1.cs", 33, 8, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", locals_fn: async (locals) => { CheckNumber(locals, "d", 10); @@ -290,7 +294,7 @@ public async Task DebugHotReloadMethodEmptyUsingSDB() int line = 38; await SetBreakpoint(".*/MethodBody1.cs$", line, 0, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody4", "StaticMethod4", expectBpResolvedEvent: true); + asm_file, pdb_file, "MethodBody4", "StaticMethod4", expectBpResolvedEvent: true, sourcesToWait: new string [] { "MethodBody0.cs", "MethodBody1.cs" }); //apply first update pause_location = await LoadAssemblyAndTestHotReloadUsingSDB( @@ -347,7 +351,7 @@ public async Task DebugHotReloadMethod_CheckBreakpointLineUpdated_ByVS_Simulated var bp = await SetBreakpoint(".*/MethodBody1.cs$", 48, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody5", "StaticMethod1", expectBpResolvedEvent: true); + asm_file, pdb_file, "MethodBody5", "StaticMethod1", expectBpResolvedEvent: true, sourcesToWait: new string [] { "MethodBody0.cs", "MethodBody1.cs" }); //apply first update pause_location = await LoadAssemblyAndTestHotReloadUsingSDB( @@ -372,7 +376,7 @@ public async Task DebugHotReloadMethod_CheckBreakpointLineUpdated_ByVS_Simulated var bp = await SetBreakpoint(".*/MethodBody1.cs$", 49, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody5", "StaticMethod1", expectBpResolvedEvent: true); + asm_file, pdb_file, "MethodBody5", "StaticMethod1", expectBpResolvedEvent: true, sourcesToWait: new string [] { "MethodBody0.cs", "MethodBody1.cs" }); //apply first update pause_location = await LoadAssemblyAndTestHotReloadUsingSDB( @@ -399,7 +403,7 @@ public async Task DebugHotReloadMethod_CheckBreakpointLineUpdated_ByVS_Simulated var bp_notchanged = await SetBreakpoint(".*/MethodBody1.cs$", 48, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody5", "StaticMethod1", expectBpResolvedEvent: true); + asm_file, pdb_file, "MethodBody5", "StaticMethod1", expectBpResolvedEvent: true, sourcesToWait: new string [] { "MethodBody0.cs", "MethodBody1.cs" }); CheckLocation("dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 48, 12, scripts, pause_location["callFrames"]?[0]["location"]); //apply first update @@ -422,7 +426,7 @@ public async Task DebugHotReloadMethod_AddingNewMethod() var bp_invalid = await SetBreakpoint(".*/MethodBody1.cs$", 59, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody6", "StaticMethod1", expectBpResolvedEvent: true); + asm_file, pdb_file, "MethodBody6", "StaticMethod1", expectBpResolvedEvent: true, sourcesToWait: new string [] { "MethodBody0.cs", "MethodBody1.cs" }); CheckLocation("dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 55, 12, scripts, pause_location["callFrames"]?[0]["location"]); //apply first update @@ -452,7 +456,7 @@ public async Task DebugHotReloadMethod_AddingNewStaticField() var bp_invalid = await SetBreakpoint(".*/MethodBody1.cs$", 59, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody6", "StaticMethod1", expectBpResolvedEvent: true); + asm_file, pdb_file, "MethodBody6", "StaticMethod1", expectBpResolvedEvent: true, sourcesToWait: new string [] { "MethodBody0.cs", "MethodBody1.cs" }); CheckLocation("dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 55, 12, scripts, pause_location["callFrames"]?[0]["location"]); //apply first update @@ -488,7 +492,7 @@ public async Task DebugHotReloadMethod_AddingNewClass() var bp_invalid2 = await SetBreakpoint(".*/MethodBody1.cs$", 102, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody6", "StaticMethod1", expectBpResolvedEvent: true); + asm_file, pdb_file, "MethodBody6", "StaticMethod1", expectBpResolvedEvent: true, sourcesToWait: new string [] { "MethodBody0.cs", "MethodBody1.cs" }); CheckLocation("dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 55, 12, scripts, pause_location["callFrames"]?[0]["location"]); //apply first update @@ -526,5 +530,36 @@ await EvaluateOnCallFrameAndCheck(pause_location["callFrames"]?[0]["callFrameId" await EvaluateOnCallFrameAndCheck(pause_location["callFrames"]?[0]["callFrameId"].Value(), ("ApplyUpdateReferencedAssembly.MethodBody8.staticField", TNumber(80))); } + + [ConditionalFact(nameof(RunningOnChrome))] + public async Task DebugHotReloadMethod_AddingNewMethodWithoutAnyOtherChange() + { + string asm_file = Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly2.dll"); + string pdb_file = Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly2.pdb"); + string asm_file_hot_reload = Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly2.dll"); + + var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( + asm_file, pdb_file, "AddMethod", "StaticMethod1", expectBpResolvedEvent: false, sourcesToWait: new string [] { "MethodBody2.cs" }); + CheckLocation("dotnet://ApplyUpdateReferencedAssembly2.dll/MethodBody2.cs", 12, 12, scripts, pause_location["callFrames"]?[0]["location"]); + //apply first update + pause_location = await LoadAssemblyAndTestHotReloadUsingSDB( + asm_file_hot_reload, "AddMethod", "StaticMethod2", 1); + + JToken top_frame = pause_location["callFrames"]?[0]; + AssertEqual("ApplyUpdateReferencedAssembly.AddMethod.StaticMethod2", top_frame?["functionName"]?.Value(), top_frame?.ToString()); + CheckLocation("dotnet://ApplyUpdateReferencedAssembly2.dll/MethodBody2.cs", 18, 12, scripts, top_frame["location"]); + } + + [ConditionalFact(nameof(RunningOnChrome))] + public async Task DebugHotReloadMethod_AddingNewMethodWithoutAnyOtherChange_WithoutSDB() + { + var pause_location = await LoadAssemblyAndTestHotReload( + Path.Combine(DebuggerTestAppPath, $"ApplyUpdateReferencedAssembly2.dll"), + Path.Combine(DebuggerTestAppPath, $"ApplyUpdateReferencedAssembly2.pdb"), + Path.Combine(DebuggerTestAppPath, $"../wasm/ApplyUpdateReferencedAssembly2.dll"), + "AddMethod", "StaticMethod1", expectBpResolvedEvent: false, sourcesToWait: new string [] { "MethodBody2.cs" }, "StaticMethod2"); + CheckLocation("dotnet://ApplyUpdateReferencedAssembly2.dll/MethodBody2.cs", 12, 12, scripts, pause_location["callFrames"]?[0]["location"]); + await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", $"dotnet://ApplyUpdateReferencedAssembly2.dll/MethodBody2.cs", 18, 12, "ApplyUpdateReferencedAssembly.AddMethod.StaticMethod2"); + } } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs index 5e0f0d6802c5a9..0c94c171d1d858 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs @@ -735,23 +735,25 @@ JObject FindFrame(JObject pause_location, string function_name) ?.Where(f => f["functionName"]?.Value() == function_name) ?.FirstOrDefault(); - [ConditionalFact(nameof(RunningOnChrome))] - public async Task DebugLazyLoadedAssemblyWithPdb() + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData("lazy-debugger-test")] + [InlineData("lazy-debugger-test-chinese-char-in-path-ㄨ")] + public async Task DebugLazyLoadedAssemblyWithPdb(string assembly_name) { Task bpResolved = WaitForBreakpointResolvedEvent(); int line = 9; await SetBreakpoint(".*/lazy-debugger-test.cs$", line, 0, use_regex: true); await LoadAssemblyDynamically( - Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.dll"), - Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.pdb")); + Path.Combine(DebuggerTestAppPath, $"{assembly_name}.dll"), + Path.Combine(DebuggerTestAppPath, $"{assembly_name}.pdb")); - var source_location = "dotnet://lazy-debugger-test.dll/lazy-debugger-test.cs"; + var source_location = $"dotnet://{assembly_name}.dll/lazy-debugger-test.cs"; Assert.Contains(source_location, scripts.Values); await bpResolved; var pause_location = await EvaluateAndCheck( - "window.setTimeout(function () { invoke_static_method('[lazy-debugger-test] LazyMath:IntAdd', 5, 10); }, 1);", + "window.setTimeout(function () { invoke_static_method('[" + assembly_name + "] LazyMath:IntAdd', 5, 10); }, 1);", source_location, line, 8, "LazyMath.IntAdd"); var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); @@ -962,7 +964,7 @@ await EvaluateAndCheck( "dotnet://debugger-test-special-char-in-path.dll/test#.cs")] [InlineData( "DebuggerTests.CheckSNonAsciiCharactersInPath", - "dotnet://debugger-test-special-char-in-path.dll/non-ascii-test-ął.cs")] + "dotnet://debugger-test-special-char-in-path.dll/non-ascii-test-ąłÅ.cs")] public async Task SetBreakpointInProjectWithSpecialCharactersInPath( string classWithNamespace, string expectedFileLocation) { @@ -1039,5 +1041,99 @@ await EvaluateAndCheck( } ); } + + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData("ClassInheritsFromClassWithoutDebugSymbols", 1287, true)] + [InlineData("ClassInheritsFromClassWithoutDebugSymbols", 1287, false)] + [InlineData("ClassInheritsFromNonUserCodeClass", 1335, true)] + [InlineData("ClassInheritsFromNonUserCodeClass", 1335, false)] + [InlineData("ClassInheritsFromNonUserCodeClassThatInheritsFromNormalClass", 1352, true)] + [InlineData("ClassInheritsFromNonUserCodeClassThatInheritsFromNormalClass", 1352, false)] + [InlineData("GenericCustomAttributeDecoratedClassInheritsFromClassWithoutDebugSymbols", 1390, true)] + [InlineData("GenericCustomAttributeDecoratedClassInheritsFromClassWithoutDebugSymbols", 1390, false)] + [InlineData("GenericCustomAttributeDecoratedClassInheritsFromNonUserCodeClass", 1407, true)] + [InlineData("GenericCustomAttributeDecoratedClassInheritsFromNonUserCodeClass", 1407, false)] + [InlineData("GenericCustomAttributeDecoratedClassInheritsFromNonUserCodeClassThatInheritsFromNormalClass", 1425, true)] + [InlineData("GenericCustomAttributeDecoratedClassInheritsFromNonUserCodeClassThatInheritsFromNormalClass", 1425, false)] + public async Task InspectThisThatInheritsFromClassNonUserCode(string class_name, int line, bool jmc) + { + await SetJustMyCode(jmc); + var expression = "{{ invoke_static_method('[debugger-test] " + class_name + ":Run'); }}"; + + await EvaluateAndCheck( + "window.setTimeout(function() {" + expression + "; }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", line, 8, + $"{class_name}.CallMethod", + locals_fn: async (locals) => + { + var this_props = await GetObjectOnLocals(locals, "this"); + if (jmc) + { + await CheckProps(this_props, new + { + myField = TNumber(0), + myField2 = TNumber(0), + propB = TGetter("propB"), + propC = TGetter("propC"), + e = TNumber(50), + f = TNumber(60), + }, "this_props", num_fields: 6); + } + else + { + await CheckProps(this_props, new + { + propA = TNumber(10), + propB = TNumber(20), + propC = TNumber(30), + d = TNumber(40), + e = TNumber(50), + f = TNumber(60), + G = TGetter("G"), + H = TGetter("H"), + myField = TNumber(0), + myField2 = TNumber(0), + }, "this_props", num_fields: 10); + } + } + ); + } + + [ConditionalFact(nameof(RunningOnChrome))] + public async Task SetBreakpointInProjectWithChineseCharactereInPath() + { + var bp = await SetBreakpointInMethod("debugger-test-chinese-char-in-path-ㄨ.dll", "DebuggerTests.CheckChineseCharacterInPath", "Evaluate", 1); + await EvaluateAndCheck( + $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test-chinese-char-in-path-ㄨ] DebuggerTests.CheckChineseCharacterInPath:Evaluate'); }}, 1);", + "dotnet://debugger-test-chinese-char-in-path-ㄨ.dll/test.cs", + bp.Value["locations"][0]["lineNumber"].Value(), + bp.Value["locations"][0]["columnNumber"].Value(), + $"DebuggerTests.CheckChineseCharacterInPath.Evaluate"); + } + + [Fact] + public async Task InspectReadOnlySpan() + { + var expression = $"{{ invoke_static_method('[debugger-test] ReadOnlySpanTest:Run'); }}"; + + await EvaluateAndCheck( + "window.setTimeout(function() {" + expression + "; }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 1371, 8, + "ReadOnlySpanTest.CheckArguments", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + await EvaluateOnCallFrameAndCheck(id, + ("parameters.ToString()", TString("System.ReadOnlySpan[1]")) + ); + } + ); + await StepAndCheck(StepKind.Resume, "dotnet://debugger-test.dll/debugger-test.cs", 1363, 8, "ReadOnlySpanTest.Run", + locals_fn: async (locals) => + { + await CheckValueType(locals, "var1", "System.ReadOnlySpan", description: "System.ReadOnlySpan[0]"); + } + ); + } } } diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/ApplyUpdateReferencedAssembly2.csproj b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/ApplyUpdateReferencedAssembly2.csproj new file mode 100644 index 00000000000000..f024e0e58b3f6b --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/ApplyUpdateReferencedAssembly2.csproj @@ -0,0 +1,35 @@ + + + true + deltascript.json + library + false + true + + false + true + + false + false + false + true + + + true + + + + + + + + + + + diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/MethodBody2.cs b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/MethodBody2.cs new file mode 100644 index 00000000000000..9ae61a4ee789e5 --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/MethodBody2.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; +//keep the same line number for class in the original file and the updates ones +namespace ApplyUpdateReferencedAssembly +{ + public class AddMethod { + public static string StaticMethod1 () { + Console.WriteLine("original"); + int a = 10; + Debugger.Break(); + return "OLD STRING"; + } + } +} \ No newline at end of file diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/MethodBody2_v1.cs b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/MethodBody2_v1.cs new file mode 100644 index 00000000000000..b8e75a4e39300b --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/MethodBody2_v1.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.Diagnostics; +using System; +//keep the same line number for class in the original file and the updates ones +namespace ApplyUpdateReferencedAssembly +{ + public class AddMethod { + public static string StaticMethod1 () { + Console.WriteLine("original"); + int a = 10; + Debugger.Break(); + return "OLD STRING"; + } + public static string StaticMethod2 () { + Console.WriteLine("original"); + int a = 10; + Debugger.Break(); + return "OLD STRING"; + } + } +} \ No newline at end of file diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/MethodBody2_v2.cs b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/MethodBody2_v2.cs new file mode 100644 index 00000000000000..615e47f2c936b4 --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/MethodBody2_v2.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. + +using System.Diagnostics; +using System; +//keep the same line number for class in the original file and the updates ones +namespace ApplyUpdateReferencedAssembly +{ + public class AddMethod { + public static string StaticMethod1 () { + Console.WriteLine("original"); + int a = 10; + Debugger.Break(); + return "OLD STRING"; + } + public static string StaticMethod2 () { + Console.WriteLine("original"); + int a = 10; + Debugger.Break(); + return "OLD STRING"; + } + public static string StaticMethod3 () { + Console.WriteLine("original"); + int a = 10; + Debugger.Break(); + return "OLD STRING"; + } + } +} \ No newline at end of file diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/deltascript.json b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/deltascript.json new file mode 100644 index 00000000000000..6603dcff23d1f8 --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly2/deltascript.json @@ -0,0 +1,7 @@ +{ + "changes": [ + {"document": "MethodBody2.cs", "update": "MethodBody2_v1.cs"}, + {"document": "MethodBody2.cs", "update": "MethodBody2_v2.cs"} + ] +} + diff --git "a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250.csproj" "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250.csproj" new file mode 100644 index 00000000000000..9b4c7a12d15077 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250.csproj" @@ -0,0 +1,36 @@ + + + true + deltascript.json + library + false + true + + false + true + + false + false + false + true + + + true + + + + + + + + + + + + diff --git "a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody0.cs" "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody0.cs" new file mode 100644 index 00000000000000..833ac95a642f02 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody0.cs" @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System; + +namespace ApplyUpdateReferencedAssembly +{ + public class MethodBodyUnchangedAssembly { + public static string StaticMethod1 () { + Console.WriteLine("original"); + return "ok"; + } + } +} diff --git "a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1.cs" "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1.cs" new file mode 100644 index 00000000000000..3abc1d4b538bb7 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1.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.Diagnostics; +using System; +//keep the same line number for class in the original file and the updates ones +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"; + } + } + + + + public class MethodBody4 { + public static void StaticMethod4 () { + } + } + + + + + + + public class MethodBody5 { + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a line that will not be changed"); + Console.WriteLine("original"); + } + } + + public class MethodBody6 { + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a line that will not be changed"); + Console.WriteLine("original"); + } + } +} diff --git "a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1_v1.cs" "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1_v1.cs" new file mode 100644 index 00000000000000..cef3214d9c89e9 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1_v1.cs" @@ -0,0 +1,87 @@ +// 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; +//keep the same line number for class in the original file and the updates ones +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"; + } + } + + + + public class MethodBody4 { + public static void StaticMethod4 () { + int a = 10; + int b = 20; + Console.WriteLine(a + b); + Console.WriteLine(a + b); + Console.WriteLine(a + b); + } + } + + public class MethodBody5 { + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a line that will not be changed"); + Console.WriteLine("beforeoriginal"); + Console.WriteLine("original"); + } + } + public class MethodBody6 { + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a line that will not be changed"); + Console.WriteLine("original"); + } + public static void NewMethodStatic () { + int i = 20; + newStaticField = 10; + Console.WriteLine($"add a breakpoint in the new static method, look at locals {newStaticField}"); + /*var newvar = new MethodBody6(); + newvar.NewMethodInstance (10);*/ + } + public static int newStaticField; + } + + public class MethodBody7 { + public static int staticField; + int attr1; + string attr2; + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a method in a new class"); + Console.WriteLine("original"); + MethodBody7 newvar = new MethodBody7(); + staticField = 80; + newvar.InstanceMethod(); + } + public void InstanceMethod () { + int aLocal = 50; + attr1 = 15; + attr2 = "20"; + Console.WriteLine($"add a breakpoint the instance method of the new class"); + } + } +} diff --git "a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1_v2.cs" "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1_v2.cs" new file mode 100644 index 00000000000000..48c3b9911e8059 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1_v2.cs" @@ -0,0 +1,106 @@ +// 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; +//keep the same line number for class in the original file and the updates ones +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; + int d = 10; + int e = 20; + int f = 50; + return "NEWEST STRING"; + } + } + + public class MethodBody4 { + public static void StaticMethod4 () { + } + } + + + + + + + public class MethodBody5 { + public static void StaticMethod1 () { + Console.WriteLine("beforeoriginal"); + Console.WriteLine("original"); + } + } + + public class MethodBody6 { + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a line that will not be changed"); + Console.WriteLine("original"); + } + public static void NewMethodStatic () { + int i = 20; + newStaticField = 10; + Console.WriteLine($"add a breakpoint in the new static method, look at locals {newStaticField}"); + /*var newvar = new MethodBody6(); + newvar.NewMethodInstance (10);*/ + } + public static int newStaticField; + } + + public class MethodBody7 { + public static int staticField; + int attr1; + string attr2; + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a method in a new class"); + Console.WriteLine("original"); + MethodBody7 newvar = new MethodBody7(); + staticField = 80; + newvar.InstanceMethod(); + } + public void InstanceMethod () { + int aLocal = 50; + attr1 = 15; + attr2 = "20"; + Console.WriteLine($"add a breakpoint the instance method of the new class"); + } + } + + public class MethodBody8 { + public static int staticField; + int attr1; + string attr2; + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a method in a new class"); + Console.WriteLine("original"); + MethodBody8 newvar = new MethodBody8(); + staticField = 80; + newvar.InstanceMethod(); + } + public void InstanceMethod () { + int aLocal = 50; + attr1 = 15; + attr2 = "20"; + Console.WriteLine($"add a breakpoint the instance method of the new class"); + } + } +} diff --git "a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/deltascript.json" "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/deltascript.json" new file mode 100644 index 00000000000000..8e738364bc7475 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/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-chinese-char-in-path-\343\204\250/debugger-test-chinese-char-in-path-\343\204\250.csproj" "b/src/mono/wasm/debugger/tests/debugger-test-chinese-char-in-path-\343\204\250/debugger-test-chinese-char-in-path-\343\204\250.csproj" new file mode 100644 index 00000000000000..1463b4a398f1f7 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/debugger-test-chinese-char-in-path-\343\204\250/debugger-test-chinese-char-in-path-\343\204\250.csproj" @@ -0,0 +1,6 @@ + + + + false + + diff --git "a/src/mono/wasm/debugger/tests/debugger-test-chinese-char-in-path-\343\204\250/test.cs" "b/src/mono/wasm/debugger/tests/debugger-test-chinese-char-in-path-\343\204\250/test.cs" new file mode 100644 index 00000000000000..6902b401317936 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/debugger-test-chinese-char-in-path-\343\204\250/test.cs" @@ -0,0 +1,10 @@ +namespace DebuggerTests +{ + public class CheckChineseCharacterInPath + { + public static void Evaluate() + { + var a = 123; + } + } +} diff --git "a/src/mono/wasm/debugger/tests/debugger-test-special-char-in-path-#@/non-ascii-test-\304\205\305\202.cs" "b/src/mono/wasm/debugger/tests/debugger-test-special-char-in-path-#@/non-ascii-test-\304\205\305\202\303\205.cs" similarity index 100% rename from "src/mono/wasm/debugger/tests/debugger-test-special-char-in-path-#@/non-ascii-test-\304\205\305\202.cs" rename to "src/mono/wasm/debugger/tests/debugger-test-special-char-in-path-#@/non-ascii-test-\304\205\305\202\303\205.cs" diff --git a/src/mono/wasm/debugger/tests/debugger-test-vb/debugger-test-vb.vb b/src/mono/wasm/debugger/tests/debugger-test-vb/debugger-test-vb.vb new file mode 100644 index 00000000000000..4ca35c3281173e --- /dev/null +++ b/src/mono/wasm/debugger/tests/debugger-test-vb/debugger-test-vb.vb @@ -0,0 +1,30 @@ +Public Class TestVbScope + Public Shared Async Function Run() As Task + Await RunVBScope(10) + Await RunVBScope(1000) + End Function + + Public Shared Async Function RunVBScope(data As Integer) As Task(Of Integer) + Dim a As Integer + a = 10 + If data < 999 Then + Dim testVbScope As String + Dim onlyInFirstScope As String + testVbScope = "hello" + onlyInFirstScope = "only-in-first-scope" + System.Diagnostics.Debugger.Break() + Await Task.Delay(1) + Return data + Else + Dim testVbScope As String + Dim onlyInSecondScope As String + testVbScope = "hi" + onlyInSecondScope = "only-in-second-scope" + System.Diagnostics.Debugger.Break() + Await Task.Delay(1) + Return data + End If + + End Function + +End Class diff --git a/src/mono/wasm/debugger/tests/debugger-test-vb/debugger-test-vb.vbproj b/src/mono/wasm/debugger/tests/debugger-test-vb/debugger-test-vb.vbproj new file mode 100644 index 00000000000000..c54512c75cdfcd --- /dev/null +++ b/src/mono/wasm/debugger/tests/debugger-test-vb/debugger-test-vb.vbproj @@ -0,0 +1,8 @@ + + + + DebuggerTestVB + net7.0 + + + diff --git a/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/debugger-test-with-non-user-code-class.csproj b/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/debugger-test-with-non-user-code-class.csproj new file mode 100644 index 00000000000000..c0d42d7f25cde5 --- /dev/null +++ b/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/debugger-test-with-non-user-code-class.csproj @@ -0,0 +1,4 @@ + + + + diff --git a/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/test.cs b/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/test.cs new file mode 100644 index 00000000000000..1626d47d5d4e1f --- /dev/null +++ b/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/test.cs @@ -0,0 +1,41 @@ +using System; + +namespace DebuggerTests +{ + public class NormalClass + { + public int myField2; + } + + [System.Diagnostics.DebuggerNonUserCode] + public class ClassNonUserCodeToInheritThatInheritsFromNormalClass : NormalClass + { + private int propA {get;} + public int propB {get;} + protected int propC {get;} + private int d; + public int e; + protected int f; + private int G + { + get {return f + 1;} + } + private int H => f; + + public ClassNonUserCodeToInheritThatInheritsFromNormalClass() + { + propA = 10; + propB = 20; + propC = 30; + d = 40; + e = 50; + f = 60; + Console.WriteLine(propA); + Console.WriteLine(propB); + Console.WriteLine(propC); + Console.WriteLine(d); + Console.WriteLine(e); + Console.WriteLine(f); + } + } +} diff --git a/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/debugger-test-without-debug-symbols-to-load.csproj b/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/debugger-test-without-debug-symbols-to-load.csproj new file mode 100644 index 00000000000000..34367db0bff2da --- /dev/null +++ b/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/debugger-test-without-debug-symbols-to-load.csproj @@ -0,0 +1,6 @@ + + + none + false + + diff --git a/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/test.cs b/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/test.cs new file mode 100644 index 00000000000000..4899b8869ef402 --- /dev/null +++ b/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/test.cs @@ -0,0 +1,35 @@ +using System; + +namespace DebuggerTests +{ + public class ClassWithoutDebugSymbolsToInherit + { + private int propA {get;} + public int propB {get;} + protected int propC {get;} + private int d; + public int e; + protected int f; + private int G + { + get {return f + 1;} + } + private int H => f; + + public ClassWithoutDebugSymbolsToInherit() + { + propA = 10; + propB = 20; + propC = 30; + d = 40; + e = 50; + f = 60; + Console.WriteLine(propA); + Console.WriteLine(propB); + Console.WriteLine(propC); + Console.WriteLine(d); + Console.WriteLine(e); + Console.WriteLine(f); + } + } +} diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-async-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-async-test.cs index da30a3132967ed..3c103d3d6f489d 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-async-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-async-test.cs @@ -230,4 +230,118 @@ await Task.Delay(300).ContinueWith(t2 => } } + public class VariablesWithSameNameDifferentScopes + { + public static async Task Run() + { + await RunCSharpScope(10); + await RunCSharpScope(1000); + } + + public static async Task RunCSharpScope(int number) + { + await Task.Delay(1); + if (number < 999) + { + string testCSharpScope = "hello"; string onlyInFirstScope = "only-in-first-scope"; + System.Diagnostics.Debugger.Break(); + return testCSharpScope; + } + else + { + string testCSharpScope = "hi"; string onlyInSecondScope = "only-in-second-scope"; + System.Diagnostics.Debugger.Break(); + return testCSharpScope; + } + } + + public static async Task RunContinueWith() + { + await RunContinueWithSameVariableName(10); + await RunContinueWithSameVariableName(1000); + } + + public static async Task RunNestedContinueWith() + { + await RunNestedContinueWithSameVariableName(10); + await RunNestedContinueWithSameVariableName(1000); + } + + public static async Task RunContinueWithSameVariableName(int number) + { + await Task.Delay(500).ContinueWith(async t => + { + await Task.Delay(1); + if (number < 999) + { + var testCSharpScope = new String("hello"); string onlyInFirstScope = "only-in-first-scope"; + System.Diagnostics.Debugger.Break(); + return testCSharpScope; + } + else + { + var testCSharpScope = new String("hi"); string onlyInSecondScope = "only-in-second-scope"; + System.Diagnostics.Debugger.Break(); + return testCSharpScope; + } + }); + Console.WriteLine ($"done with this method"); + } + + public static async Task RunNestedContinueWithSameVariableName(int number) + { + await Task.Delay(500).ContinueWith(async t => + { + if (number < 999) + { + var testCSharpScope = new String("hello_out"); string onlyInFirstScope = "only-in-first-scope_out"; + Console.WriteLine(testCSharpScope); + } + else + { + var testCSharpScope = new String("hi_out"); string onlyInSecondScope = "only-in-second-scope_out"; + Console.WriteLine(testCSharpScope); + } + await Task.Delay(300).ContinueWith(t2 => + { + if (number < 999) + { + var testCSharpScope = new String("hello"); string onlyInFirstScope = "only-in-first-scope"; + System.Diagnostics.Debugger.Break(); + return testCSharpScope; + } + else + { + var testCSharpScope = new String("hi"); string onlyInSecondScope = "only-in-second-scope"; + System.Diagnostics.Debugger.Break(); + return testCSharpScope; + } + }); + }); + Console.WriteLine ($"done with this method"); + } + + public static void RunNonAsyncMethod() + { + RunNonAsyncMethodSameVariableName(10); + RunNonAsyncMethodSameVariableName(1000); + } + + public static string RunNonAsyncMethodSameVariableName(int number) + { + if (number < 999) + { + var testCSharpScope = new String("hello"); string onlyInFirstScope = "only-in-first-scope"; + System.Diagnostics.Debugger.Break(); + return testCSharpScope; + } + else + { + var testCSharpScope = new String("hi"); string onlyInSecondScope = "only-in-second-scope"; + System.Diagnostics.Debugger.Break(); + return testCSharpScope; + } + } + } + } diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html b/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html index fdbbc640696ac3..b6522cf3cc79a8 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html @@ -92,6 +92,10 @@ console.log("load_wasm_page_without_assets") window.location.replace("http://localhost:9400/wasm-page-without-assets.html"); } + function load_non_wasm_page_forcing_runtime_ready () { + console.log("load_non_wasm_page_forcing_runtime_ready") + window.location.replace("http://localhost:9400/non-wasm-page-forcing-runtime-ready.html"); + } 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 6424ca3d5cdd93..cf7be308718a32 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs @@ -604,7 +604,7 @@ public static void LoadLazyHotReload(string asm_base64, string pdb_base64, strin Console.WriteLine($"Loaded - {loadedAssembly}"); } - public static void RunMethod(string className, string methodName) + public static void RunMethod(string className, string methodName, string methodName2, string methodName3) { var ty = typeof(System.Reflection.Metadata.MetadataUpdater); var mi = ty.GetMethod("GetCapabilities", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static, Array.Empty()); @@ -624,13 +624,13 @@ public static void RunMethod(string className, string methodName) ApplyUpdate(loadedAssembly, 1); myType = loadedAssembly.GetType($"ApplyUpdateReferencedAssembly.{className}"); - myMethod = myType.GetMethod(methodName); + myMethod = myType.GetMethod(methodName2); myMethod.Invoke(null, null); ApplyUpdate(loadedAssembly, 2); myType = loadedAssembly.GetType($"ApplyUpdateReferencedAssembly.{className}"); - myMethod = myType.GetMethod(methodName); + myMethod = myType.GetMethod(methodName3); myMethod.Invoke(null, null); } @@ -1275,3 +1275,156 @@ public static void MethodWithHiddenLinesAtTheEnd3() #line default } +public class ClassInheritsFromClassWithoutDebugSymbols : DebuggerTests.ClassWithoutDebugSymbolsToInherit +{ + public static void Run() + { + var myVar = new ClassInheritsFromClassWithoutDebugSymbols(); + myVar.CallMethod(); + } + + public void CallMethod() + { + System.Diagnostics.Debugger.Break(); + } + public int myField2; + public int myField; +} + +[System.Diagnostics.DebuggerNonUserCode] +public class ClassNonUserCodeToInherit +{ + private int propA {get;} + public int propB {get;} + protected int propC {get;} + private int d; + public int e; + protected int f; + private int G + { + get {return f + 1;} + } + private int H => f; + + public ClassNonUserCodeToInherit() + { + propA = 10; + propB = 20; + propC = 30; + d = 40; + e = 50; + f = 60; + Console.WriteLine(propA); + Console.WriteLine(propB); + Console.WriteLine(propC); + Console.WriteLine(d); + Console.WriteLine(e); + Console.WriteLine(f); + } +} + +public class ClassInheritsFromNonUserCodeClass : ClassNonUserCodeToInherit +{ + public static void Run() + { + var myVar = new ClassInheritsFromNonUserCodeClass(); + myVar.CallMethod(); + } + + public void CallMethod() + { + System.Diagnostics.Debugger.Break(); + } + + public int myField2; + public int myField; +} + +public class ClassInheritsFromNonUserCodeClassThatInheritsFromNormalClass : DebuggerTests.ClassNonUserCodeToInheritThatInheritsFromNormalClass +{ + public static void Run() + { + var myVar = new ClassInheritsFromNonUserCodeClassThatInheritsFromNormalClass(); + myVar.CallMethod(); + } + + public void CallMethod() + { + System.Diagnostics.Debugger.Break(); + } + + public int myField; +} +public class ReadOnlySpanTest +{ + public static void Run() + { + Invoke(new string[] {"TEST"}); + ReadOnlySpan var1 = new ReadOnlySpan(); + System.Diagnostics.Debugger.Break(); + } + public static void Invoke(object[] parameters) + { + CheckArguments(parameters); + } + public static void CheckArguments(ReadOnlySpan parameters) + { + System.Diagnostics.Debugger.Break(); + } +} +[AttributeUsage(AttributeTargets.Class)] +public sealed class CustomAttribute : Attribute +{ +} + +[Custom] +public class GenericCustomAttributeDecoratedClassInheritsFromClassWithoutDebugSymbols : DebuggerTests.ClassWithoutDebugSymbolsToInherit +{ + public static void Run() + { + var myVar = new GenericCustomAttributeDecoratedClassInheritsFromClassWithoutDebugSymbols(); + myVar.CallMethod(); + } + + public void CallMethod() + { + System.Diagnostics.Debugger.Break(); + } + public int myField2; + public int myField; +} + +[Custom] +public class GenericCustomAttributeDecoratedClassInheritsFromNonUserCodeClass : ClassNonUserCodeToInherit +{ + public static void Run() + { + var myVar = new GenericCustomAttributeDecoratedClassInheritsFromNonUserCodeClass(); + myVar.CallMethod(); + } + + public void CallMethod() + { + System.Diagnostics.Debugger.Break(); + } + + public int myField2; + public int myField; +} + +[Custom] +public class GenericCustomAttributeDecoratedClassInheritsFromNonUserCodeClassThatInheritsFromNormalClass : DebuggerTests.ClassNonUserCodeToInheritThatInheritsFromNormalClass +{ + public static void Run() + { + var myVar = new GenericCustomAttributeDecoratedClassInheritsFromNonUserCodeClassThatInheritsFromNormalClass(); + myVar.CallMethod(); + } + + public void CallMethod() + { + System.Diagnostics.Debugger.Break(); + } + + public int myField; +} \ No newline at end of file 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 42edcb663f906a..fb946fd185b227 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj @@ -13,19 +13,27 @@ + + + + + + + <_AssemblyForDynamicLoading Include="lazy-debugger-test" /> + <_AssemblyForDynamicLoading Include="lazy-debugger-test-chinese-char-in-path-ㄨ" /> <_AssemblyForDynamicLoading Include="debugger-test-with-full-debug-type" /> <_AssemblyForDynamicLoading Include="debugger-test-with-pdb-deleted" /> <_AssemblyForDynamicLoading Include="debugger-test-without-debug-symbols" /> @@ -44,14 +52,17 @@ debugger-main.js -1 - true + + + + @@ -63,6 +74,7 @@ + + + + + + + + + diff --git "a/src/mono/wasm/debugger/tests/lazy-debugger-test-chinese-char-in-path-\343\204\250/lazy-debugger-test-chinese-char-in-path-\343\204\250.csproj" "b/src/mono/wasm/debugger/tests/lazy-debugger-test-chinese-char-in-path-\343\204\250/lazy-debugger-test-chinese-char-in-path-\343\204\250.csproj" new file mode 100644 index 00000000000000..35e3d8428b7cfc --- /dev/null +++ "b/src/mono/wasm/debugger/tests/lazy-debugger-test-chinese-char-in-path-\343\204\250/lazy-debugger-test-chinese-char-in-path-\343\204\250.csproj" @@ -0,0 +1,2 @@ + + diff --git "a/src/mono/wasm/debugger/tests/lazy-debugger-test-chinese-char-in-path-\343\204\250/lazy-debugger-test.cs" "b/src/mono/wasm/debugger/tests/lazy-debugger-test-chinese-char-in-path-\343\204\250/lazy-debugger-test.cs" new file mode 100644 index 00000000000000..2da3c708e85acb --- /dev/null +++ "b/src/mono/wasm/debugger/tests/lazy-debugger-test-chinese-char-in-path-\343\204\250/lazy-debugger-test.cs" @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +public partial class LazyMath +{ + public static int IntAdd(int a, int b) + { + int c = a + b; + return c; + } +} + +namespace DebuggerTests +{ + public class ClassToCheckFieldValue + { + public int valueToCheck; + public ClassToCheckFieldValue() + { + valueToCheck = 20; + } + } +} diff --git a/src/mono/wasm/host/BrowserHost.cs b/src/mono/wasm/host/BrowserHost.cs index e7dac0bbb6217f..a592ca7386fff3 100644 --- a/src/mono/wasm/host/BrowserHost.cs +++ b/src/mono/wasm/host/BrowserHost.cs @@ -67,13 +67,17 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke var runArgsJson = new RunArgumentsJson(applicationArguments: _args.AppArgs, runtimeArguments: _args.CommonConfig.RuntimeArguments, environmentVariables: envVars, - forwardConsole: _args.ForwardConsoleOutput ?? false, + forwardConsoleToWS: _args.ForwardConsoleOutput ?? false, debugging: _args.CommonConfig.Debugging); runArgsJson.Save(Path.Combine(_args.CommonConfig.AppPath, "runArgs.json")); + string[] urls = envVars.TryGetValue("ASPNETCORE_URLS", out string? aspnetUrls) + ? aspnetUrls.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries) + : new string[] { $"http://127.0.0.1:{_args.CommonConfig.HostProperties.WebServerPort}", "https://127.0.0.1:0" }; + (ServerURLs serverURLs, IWebHost host) = await StartWebServerAsync(_args.CommonConfig.AppPath, _args.ForwardConsoleOutput ?? false, - _args.CommonConfig.HostProperties.WebServerPort, + urls, token); string[] fullUrls = BuildUrls(serverURLs, _args.AppArgs); @@ -84,7 +88,7 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke await host.WaitForShutdownAsync(token); } - private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(string appPath, bool forwardConsole, int port, CancellationToken token) + private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(string appPath, bool forwardConsole, string[] urls, CancellationToken token) { WasmTestMessagesProcessor? logProcessor = null; if (forwardConsole) @@ -100,7 +104,7 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke ContentRootPath: Path.GetFullPath(appPath), WebServerUseCors: true, WebServerUseCrossOriginPolicy: true, - Port: port + Urls: urls ); (ServerURLs serverURLs, IWebHost host) = await WebServer.StartAsync(options, _logger, token); diff --git a/src/mono/wasm/host/RunArgumentsJson.cs b/src/mono/wasm/host/RunArgumentsJson.cs index eb612a8efd60de..f4573c1c7ff5af 100644 --- a/src/mono/wasm/host/RunArgumentsJson.cs +++ b/src/mono/wasm/host/RunArgumentsJson.cs @@ -14,7 +14,7 @@ internal sealed record RunArgumentsJson( string[] applicationArguments, string[]? runtimeArguments = null, IDictionary? environmentVariables = null, - bool forwardConsole = false, + bool forwardConsoleToWS = false, bool debugging = false ) { diff --git a/src/mono/wasm/host/WasmAppHost.csproj b/src/mono/wasm/host/WasmAppHost.csproj index eb563352f198fd..e55d8a3e28c613 100644 --- a/src/mono/wasm/host/WasmAppHost.csproj +++ b/src/mono/wasm/host/WasmAppHost.csproj @@ -6,6 +6,7 @@ $(NoWarn),CA2007 enable false + Major @@ -17,6 +18,8 @@ <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.CSharp.dll" /> <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.dll" /> <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Newtonsoft.Json.dll" /> + <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.CSharp.Scripting.dll" /> + <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.Scripting.dll" /> diff --git a/src/mono/wasm/host/WebServer.cs b/src/mono/wasm/host/WebServer.cs index 51bda60716740d..1f448edcb98eab 100644 --- a/src/mono/wasm/host/WebServer.cs +++ b/src/mono/wasm/host/WebServer.cs @@ -1,13 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -20,7 +16,8 @@ public class WebServer { internal static async Task<(ServerURLs, IWebHost)> StartAsync(WebServerOptions options, ILogger logger, CancellationToken token) { - string[]? urls = new string[] { $"http://127.0.0.1:{options.Port}", "https://127.0.0.1:0" }; + string[] urls = options.Urls; + TaskCompletionSource realUrlsAvailableTcs = new(); IWebHostBuilder builder = new WebHostBuilder() .UseKestrel() @@ -43,6 +40,7 @@ public class WebServer } services.AddSingleton(logger); services.AddSingleton(Options.Create(options)); + services.AddSingleton(realUrlsAvailableTcs); services.AddRouting(); }) .UseUrls(urls); @@ -53,27 +51,11 @@ public class WebServer IWebHost? host = builder.Build(); await host.StartAsync(token); - ICollection? addresses = host.ServerFeatures - .Get()? - .Addresses; + if (token.CanBeCanceled) + token.Register(async () => await host.StopAsync()); - string? ipAddress = - addresses? - .Where(a => a.StartsWith("http:", StringComparison.InvariantCultureIgnoreCase)) - .Select(a => new Uri(a)) - .Select(uri => uri.ToString()) - .FirstOrDefault(); - - string? ipAddressSecure = - addresses? - .Where(a => a.StartsWith("https:", StringComparison.OrdinalIgnoreCase)) - .Select(a => new Uri(a)) - .Select(uri => uri.ToString()) - .FirstOrDefault(); - - return ipAddress == null || ipAddressSecure == null - ? throw new InvalidOperationException("Failed to determine web server's IP address or port") - : (new ServerURLs(ipAddress, ipAddressSecure), host); + ServerURLs serverUrls = await realUrlsAvailableTcs.Task; + return (serverUrls, host); } } diff --git a/src/mono/wasm/host/WebServerOptions.cs b/src/mono/wasm/host/WebServerOptions.cs index bc8b5f2acee63a..43e05c10b7c82e 100644 --- a/src/mono/wasm/host/WebServerOptions.cs +++ b/src/mono/wasm/host/WebServerOptions.cs @@ -15,6 +15,6 @@ internal sealed record WebServerOptions string? ContentRootPath, bool WebServerUseCors, bool WebServerUseCrossOriginPolicy, - int Port, + string [] Urls, string DefaultFileName = "index.html" ); diff --git a/src/mono/wasm/host/WebServerStartup.cs b/src/mono/wasm/host/WebServerStartup.cs index ae5e906c518a65..933eeb6b6fc196 100644 --- a/src/mono/wasm/host/WebServerStartup.cs +++ b/src/mono/wasm/host/WebServerStartup.cs @@ -1,13 +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; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; using System.Net.WebSockets; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using System.Web; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.WebAssembly.Diagnostics; #nullable enable @@ -16,11 +30,41 @@ namespace Microsoft.WebAssembly.AppHost; internal sealed class WebServerStartup { private readonly IWebHostEnvironment _hostingEnvironment; + private static readonly object LaunchLock = new object(); + private static string LaunchedDebugProxyUrl = ""; + private ILogger? _logger; public WebServerStartup(IWebHostEnvironment hostingEnvironment) => _hostingEnvironment = hostingEnvironment; - public void Configure(IApplicationBuilder app, IOptions optionsContainer) + public static int StartDebugProxy(string devToolsHost) { + //we need to start another process, otherwise it will be running the BrowserDebugProxy in the same process that will be debugged, so pausing in a breakpoint + //on managed code will freeze because it will not be able to continue executing the BrowserDebugProxy to get the locals value + var executablePath = Path.Combine(System.AppContext.BaseDirectory, "BrowserDebugHost.dll"); + var ownerPid = Environment.ProcessId; + var generateRandomPort = new Random().Next(5000, 5300); + var processStartInfo = new ProcessStartInfo + { + FileName = "dotnet" + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ""), + Arguments = $"exec \"{executablePath}\" --OwnerPid {ownerPid} --DevToolsUrl {devToolsHost} --DevToolsProxyPort {generateRandomPort}", + UseShellExecute = false, + RedirectStandardOutput = true, + }; + var debugProxyProcess = Process.Start(processStartInfo); + if (debugProxyProcess is null) + { + throw new InvalidOperationException("Unable to start debug proxy process."); + } + return generateRandomPort; + } + + public void Configure(IApplicationBuilder app, + IOptions optionsContainer, + TaskCompletionSource realUrlsAvailableTcs, + ILogger logger, + IHostApplicationLifetime applicationLifetime) + { + _logger = logger; var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".wasm"] = "application/wasm"; provider.Mappings[".cjs"] = "text/javascript"; @@ -73,9 +117,79 @@ public void Configure(IApplicationBuilder app, IOptions option }); } - // app.UseEndpoints(endpoints => - // { - // endpoints.MapFallbackToFile(options.DefaultFileName); - // }); + app.Map("/debug", app => + { + app.Run(async (context) => + { + //debug from VS + var queryParams = HttpUtility.ParseQueryString(context.Request.QueryString.Value!); + var browserParam = queryParams.Get("browser"); + Uri? browserUrl = null; + var devToolsHost = "http://localhost:9222"; + if (browserParam != null) + { + browserUrl = new Uri(browserParam); + devToolsHost = $"http://{browserUrl.Host}:{browserUrl.Port}"; + } + lock (LaunchLock) + { + if (LaunchedDebugProxyUrl == "") + { + LaunchedDebugProxyUrl = $"http://localhost:{StartDebugProxy(devToolsHost)}"; + } + } + var requestPath = context.Request.Path.ToString(); + if (requestPath == string.Empty) + { + requestPath = "/"; + } + context.Response.Redirect($"{LaunchedDebugProxyUrl}{browserUrl!.PathAndQuery}"); + await Task.FromResult(0); + }); + }); + app.UseEndpoints(endpoints => + { + endpoints.MapGet("/", context => + { + context.Response.Redirect("index.html", permanent: false); + return Task.CompletedTask; + }); + }); + + applicationLifetime.ApplicationStarted.Register(() => + { + try + { + ICollection? addresses = app.ServerFeatures + .Get() + ?.Addresses; + + string? ipAddress = null; + string? ipAddressSecure = null; + if (addresses is not null) + { + ipAddress = GetHttpServerAddress(addresses, secure: false); + ipAddressSecure = GetHttpServerAddress(addresses, secure: true); + } + + if (ipAddress == null) + realUrlsAvailableTcs.SetException(new InvalidOperationException("Failed to determine web server's IP address or port")); + else + realUrlsAvailableTcs.SetResult(new ServerURLs(ipAddress, ipAddressSecure)); + } + catch (Exception ex) + { + _logger?.LogError($"Failed to get urls for the webserver: {ex}"); + realUrlsAvailableTcs.TrySetException(ex); + throw; + } + + static string? GetHttpServerAddress(ICollection addresses, bool secure) + => addresses? + .Where(a => a.StartsWith(secure ? "https:" : "http:", StringComparison.InvariantCultureIgnoreCase)) + .Select(a => new Uri(a)) + .Select(uri => uri.ToString()) + .FirstOrDefault(); + }); } } diff --git a/src/mono/wasm/runtime/CMakeLists.txt b/src/mono/wasm/runtime/CMakeLists.txt index 2eeb255fb41945..c852a7df1df614 100644 --- a/src/mono/wasm/runtime/CMakeLists.txt +++ b/src/mono/wasm/runtime/CMakeLists.txt @@ -19,7 +19,6 @@ target_link_libraries(dotnet ${MONO_ARTIFACTS_DIR}/libmono-component-hot_reload-static.a ${MONO_ARTIFACTS_DIR}/libmono-component-debugger-static.a ${MONO_ARTIFACTS_DIR}/libmono-component-diagnostics_tracing-stub-static.a - ${MONO_ARTIFACTS_DIR}/libmono-component-marshal-ilgen-static.a ${MONO_ARTIFACTS_DIR}/libmono-ee-interp.a ${MONO_ARTIFACTS_DIR}/libmonosgen-2.0.a ${MONO_ARTIFACTS_DIR}/libmono-ilgen.a @@ -27,8 +26,7 @@ target_link_libraries(dotnet ${MONO_ARTIFACTS_DIR}/libmono-wasm-eh-js.a ${MONO_ARTIFACTS_DIR}/libmono-profiler-aot.a ${NATIVE_BIN_DIR}/libSystem.Native.a - ${NATIVE_BIN_DIR}/libSystem.IO.Compression.Native.a - ${NATIVE_BIN_DIR}/libSystem.Security.Cryptography.Native.Browser.a) + ${NATIVE_BIN_DIR}/libSystem.IO.Compression.Native.a) set_target_properties(dotnet PROPERTIES LINK_DEPENDS "${NATIVE_BIN_DIR}/src/emcc-default.rsp;${NATIVE_BIN_DIR}/src/es6/dotnet.es6.pre.js;${NATIVE_BIN_DIR}/src/es6/runtime.es6.iffe.js;${NATIVE_BIN_DIR}/src/es6/dotnet.es6.lib.js;${NATIVE_BIN_DIR}/src/pal_random.lib.js;${NATIVE_BIN_DIR}/src/es6/dotnet.es6.post.js;${NATIVE_BIN_DIR}/src/es6/dotnet.es6.extpost.js;" diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 8d44101617b9c8..8e3630188ddbfb 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -28,7 +28,6 @@ let throttlingPromise: PromiseAndController | undefined; const skipDownloadsByAssetTypes: { [k: string]: boolean } = { - "js-module-crypto": true, "js-module-threads": true, }; @@ -43,7 +42,6 @@ const skipBufferByAssetTypes: { const skipInstantiateByAssetTypes: { [k: string]: boolean } = { - "js-module-crypto": true, "js-module-threads": true, "dotnetwasm": true, }; @@ -78,10 +76,10 @@ export async function mono_download_assets(): Promise { asset.pendingDownloadInternal = asset.pendingDownload; const waitForExternalData: () => Promise = async () => { const response = await asset.pendingDownloadInternal!.response; - ++actual_downloaded_assets_count; if (!headersOnly) { asset.buffer = await response.arrayBuffer(); } + ++actual_downloaded_assets_count; return { asset, buffer: asset.buffer }; }; promises_of_assets_with_buffer.push(waitForExternalData()); @@ -124,6 +122,10 @@ export async function mono_download_assets(): Promise { if (!skipInstantiateByAssetTypes[asset.behavior]) { expected_instantiated_assets_count--; } + } else { + if (skipBufferByAssetTypes[asset.behavior]) { + ++actual_downloaded_assets_count; + } } } })()); @@ -199,7 +201,9 @@ async function start_asset_download_with_throttle(asset: AssetEntry, downloadDat if (!downloadData || !response) { return undefined; } - return await response.arrayBuffer(); + const buffer = await response.arrayBuffer(); + ++actual_downloaded_assets_count; + return buffer; } finally { --parallel_count; @@ -228,7 +232,6 @@ async function start_asset_download_sources(asset: AssetEntryInternal): Promise< } }) as any }; - ++actual_downloaded_assets_count; return asset.pendingDownloadInternal.response; } if (asset.pendingDownloadInternal && asset.pendingDownloadInternal.response) { @@ -264,7 +267,6 @@ async function start_asset_download_sources(asset: AssetEntryInternal): Promise< if (!response.ok) { continue;// next source } - ++actual_downloaded_assets_count; return response; } catch (err) { @@ -295,7 +297,7 @@ function resolve_path(asset: AssetEntry, sourcePrefix: string): string { : asset.name; } else if (asset.behavior === "resource") { - const path = asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name; + const path = asset.culture && asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name; attemptUrl = assemblyRootFolder ? (assemblyRootFolder + "/" + path) : path; @@ -356,7 +358,6 @@ function _instantiate_asset(asset: AssetEntry, url: string, bytes: Uint8Array) { switch (asset.behavior) { case "dotnetwasm": - case "js-module-crypto": case "js-module-threads": // do nothing break; @@ -423,7 +424,7 @@ function _instantiate_asset(asset: AssetEntry, url: string, bytes: Uint8Array) { Module.printErr(`MONO_WASM: Error loading ICU asset ${asset.name}`); } else if (asset.behavior === "resource") { - cwraps.mono_wasm_add_satellite_assembly(virtualName, asset.culture!, offset!, bytes.length); + cwraps.mono_wasm_add_satellite_assembly(virtualName, asset.culture || "", offset!, bytes.length); } ++actual_instantiated_assets_count; } @@ -432,10 +433,10 @@ export async function instantiate_wasm_asset( pendingAsset: AssetEntryInternal, wasmModuleImports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback, -) { - mono_assert(pendingAsset && pendingAsset.pendingDownloadInternal, "Can't load dotnet.wasm"); +): Promise { + mono_assert(pendingAsset && pendingAsset.pendingDownloadInternal && pendingAsset.pendingDownloadInternal.response, "Can't load dotnet.wasm"); const response = await pendingAsset.pendingDownloadInternal.response; - const contentType = response.headers ? response.headers.get("Content-Type") : undefined; + const contentType = response.headers && response.headers.get ? response.headers.get("Content-Type") : undefined; let compiledInstance: WebAssembly.Instance; let compiledModule: WebAssembly.Module; if (typeof WebAssembly.instantiateStreaming === "function" && contentType === "application/wasm") { diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 704b8ab94e230c..66b7dd2ebb7266 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -166,7 +166,7 @@ interface AssetEntry extends ResourceRequest { */ pendingDownload?: LoadingResource; } -declare type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-crypto" | "js-module-threads"; +declare type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-threads"; declare type GlobalizationMode = "icu" | // load ICU globalization data from any runtime assets with behavior "icu". "invariant" | // operate in invariant globalization mode. "auto"; @@ -240,12 +240,7 @@ interface IDisposable { dispose(): void; get isDisposed(): boolean; } -declare const enum MemoryViewType { - Byte = 0, - Int32 = 1, - Double = 2 -} -interface IMemoryView { +interface IMemoryView extends IDisposable { /** * copies elements from provided source to the wasm memory. * target has to have the elements of the same type as the underlying C# array. @@ -269,50 +264,7 @@ declare global { function getDotnetRuntime(runtimeId: number): RuntimeAPI | undefined; } -/** - * Span class is JS wrapper for System.Span. This view doesn't own the memory, nor pin the underlying array. - * It's ideal to be used on call from C# with the buffer pinned there or with unmanaged memory. - * It is disposed at the end of the call to JS. - */ -declare class Span implements IMemoryView, IDisposable { - dispose(): void; - get isDisposed(): boolean; - set(source: TypedArray, targetOffset?: number | undefined): void; - copyTo(target: TypedArray, sourceOffset?: number | undefined): void; - slice(start?: number | undefined, end?: number | undefined): TypedArray; - get length(): number; - get byteLength(): number; -} -/** - * ArraySegment class is JS wrapper for System.ArraySegment. - * This wrapper would also pin the underlying array and hold GCHandleType.Pinned until this JS instance is collected. - * User could dispose it manually. - */ -declare class ArraySegment implements IMemoryView, IDisposable { - dispose(): void; - get isDisposed(): boolean; - set(source: TypedArray, targetOffset?: number | undefined): void; - copyTo(target: TypedArray, sourceOffset?: number | undefined): void; - slice(start?: number | undefined, end?: number | undefined): TypedArray; - get length(): number; - get byteLength(): number; -} -/** - * Represents proxy to the System.Exception - */ -declare class ManagedError extends Error implements IDisposable { - get stack(): string | undefined; - dispose(): void; - get isDisposed(): boolean; - toString(): string; -} -/** - * Represents proxy to the System.Object - */ -declare class ManagedObject implements IDisposable { - dispose(): void; - get isDisposed(): boolean; - toString(): string; -} +declare const dotnet: ModuleAPI["dotnet"]; +declare const exit: ModuleAPI["exit"]; -export { ArraySegment, AssetBehaviours, AssetEntry, CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, IMemoryView, LoadingResource, ManagedError, ManagedObject, MemoryViewType, ModuleAPI, MonoConfig, NativePointer, ResourceRequest, RuntimeAPI, Span, createDotnetRuntime as default }; +export { CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, IMemoryView, ModuleAPI, MonoConfig, RuntimeAPI, createDotnetRuntime as default, dotnet, exit }; diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index e2db123f7ab5f2..5434f62585a064 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -55,7 +55,6 @@ int mono_wasm_register_root (char *start, size_t size, const char *name); void mono_wasm_deregister_root (char *addr); void mono_ee_interp_init (const char *opts); -void mono_marshal_lightweight_init (void); void mono_marshal_ilgen_init (void); void mono_method_builder_ilgen_init (void); void mono_sgen_mono_ilgen_init (void); @@ -595,7 +594,6 @@ mono_wasm_load_runtime (const char *unused, int debug_level) #endif #ifdef NEED_INTERP mono_ee_interp_init (interp_opts); - mono_marshal_lightweight_init (); mono_marshal_ilgen_init(); mono_method_builder_ilgen_init (); mono_sgen_mono_ilgen_init (); diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index 74aed3030e8a86..bde650aea36eca 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -95,13 +95,6 @@ const linked_functions = [ "mono_wasm_load_icu_data", "mono_wasm_get_icudt_name", - // pal_crypto_webworker.c - "dotnet_browser_can_use_subtle_crypto_impl", - "dotnet_browser_simple_digest_hash", - "dotnet_browser_sign", - "dotnet_browser_encrypt_decrypt", - "dotnet_browser_derive_bits", - #if USE_PTHREADS /// mono-threads-wasm.c "mono_wasm_pthread_on_pthread_attached", diff --git a/src/mono/wasm/runtime/export-types.ts b/src/mono/wasm/runtime/export-types.ts index bc368975c17520..a1ad9df1e8ba6c 100644 --- a/src/mono/wasm/runtime/export-types.ts +++ b/src/mono/wasm/runtime/export-types.ts @@ -1,9 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { IDisposable, IMemoryView, MemoryViewType } from "./marshal"; -import { AssetBehaviours, AssetEntry, createDotnetRuntime, CreateDotnetRuntimeType, DotnetModuleConfig, RuntimeAPI, LoadingResource, MonoConfig, ResourceRequest, ModuleAPI } from "./types"; -import { EmscriptenModule, NativePointer, TypedArray } from "./types/emscripten"; +import { IMemoryView } from "./marshal"; +import { createDotnetRuntime, CreateDotnetRuntimeType, DotnetModuleConfig, RuntimeAPI, MonoConfig, ModuleAPI } from "./types"; +import { EmscriptenModule } from "./types/emscripten"; // ----------------------------------------------------------- // this files has all public exports from the dotnet.js module @@ -17,60 +17,11 @@ declare global { export default createDotnetRuntime; - -/** - * Span class is JS wrapper for System.Span. This view doesn't own the memory, nor pin the underlying array. - * It's ideal to be used on call from C# with the buffer pinned there or with unmanaged memory. - * It is disposed at the end of the call to JS. - */ -declare class Span implements IMemoryView, IDisposable { - dispose(): void; - get isDisposed(): boolean; - set(source: TypedArray, targetOffset?: number | undefined): void; - copyTo(target: TypedArray, sourceOffset?: number | undefined): void; - slice(start?: number | undefined, end?: number | undefined): TypedArray; - get length(): number; - get byteLength(): number; -} - -/** - * ArraySegment class is JS wrapper for System.ArraySegment. - * This wrapper would also pin the underlying array and hold GCHandleType.Pinned until this JS instance is collected. - * User could dispose it manually. - */ -declare class ArraySegment implements IMemoryView, IDisposable { - dispose(): void; - get isDisposed(): boolean; - set(source: TypedArray, targetOffset?: number | undefined): void; - copyTo(target: TypedArray, sourceOffset?: number | undefined): void; - slice(start?: number | undefined, end?: number | undefined): TypedArray; - get length(): number; - get byteLength(): number; -} - -/** - * Represents proxy to the System.Exception - */ -declare class ManagedError extends Error implements IDisposable { - get stack(): string | undefined; - dispose(): void; - get isDisposed(): boolean; - toString(): string; -} - -/** - * Represents proxy to the System.Object - */ -declare class ManagedObject implements IDisposable { - dispose(): void; - get isDisposed(): boolean; - toString(): string; -} +declare const dotnet: ModuleAPI["dotnet"]; +declare const exit: ModuleAPI["exit"]; export { - EmscriptenModule, NativePointer, - RuntimeAPI, ModuleAPI, DotnetModuleConfig, CreateDotnetRuntimeType, MonoConfig, - AssetEntry, ResourceRequest, LoadingResource, AssetBehaviours, - IMemoryView, MemoryViewType, ManagedObject, ManagedError, Span, ArraySegment + EmscriptenModule, + RuntimeAPI, ModuleAPI, DotnetModuleConfig, CreateDotnetRuntimeType, MonoConfig, IMemoryView, + dotnet, exit }; - diff --git a/src/mono/wasm/runtime/exports-linker.ts b/src/mono/wasm/runtime/exports-linker.ts index 89dfc822b99d82..22f8a66cdee89f 100644 --- a/src/mono/wasm/runtime/exports-linker.ts +++ b/src/mono/wasm/runtime/exports-linker.ts @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. import MonoWasmThreads from "consts:monoWasmThreads"; -import { dotnet_browser_can_use_subtle_crypto_impl, dotnet_browser_simple_digest_hash, dotnet_browser_sign, dotnet_browser_encrypt_decrypt, dotnet_browser_derive_bits } from "./subtle-crypto"; import { mono_wasm_fire_debugger_agent_message, mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_set_entrypoint_breakpoint } from "./debug"; import { mono_wasm_release_cs_owned_object } from "./gc-handles"; import { mono_wasm_load_icu_data, mono_wasm_get_icudt_name } from "./icu"; @@ -78,14 +77,7 @@ export function export_linker(): any { mono_wasm_load_icu_data, mono_wasm_get_icudt_name, - // pal_crypto_webworker.c - dotnet_browser_can_use_subtle_crypto_impl, - dotnet_browser_simple_digest_hash, - dotnet_browser_sign, - dotnet_browser_encrypt_decrypt, - dotnet_browser_derive_bits, - // threading exports, if threading is enabled ...mono_wasm_threads_exports, }; -} \ No newline at end of file +} diff --git a/src/mono/wasm/runtime/http.ts b/src/mono/wasm/runtime/http.ts index d8249fad0b93e3..ccc24698bea0dc 100644 --- a/src/mono/wasm/runtime/http.ts +++ b/src/mono/wasm/runtime/http.ts @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { wrap_as_cancelable_promise } from "./cancelable-promise"; +import { Module } from "./imports"; import { MemoryViewType, Span } from "./marshal"; import { mono_assert } from "./types"; import { VoidPtr } from "./types/emscripten"; @@ -21,7 +22,12 @@ export function http_wasm_abort_request(abort_controller: AbortController): void export function http_wasm_abort_response(res: ResponseExtension): void { res.__abort_controller.abort(); if (res.__reader) { - res.__reader.cancel(); + res.__reader.cancel().catch((err) => { + if (err && err.name !== "AbortError") { + Module.printErr("MONO_WASM: Error in http_wasm_abort_response: " + err); + } + // otherwise, it's expected + }); } } @@ -100,42 +106,33 @@ export function http_wasm_get_response_bytes(res: ResponseExtension, view: Span) return bytes_read; } -export async function http_wasm_get_streamed_response_bytes(res: ResponseExtension, bufferPtr: VoidPtr, bufferLength: number): Promise { +export function http_wasm_get_streamed_response_bytes(res: ResponseExtension, bufferPtr: VoidPtr, bufferLength: number): Promise { // the bufferPtr is pinned by the caller const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte); return wrap_as_cancelable_promise(async () => { - if (!res.__chunk && res.body) { - res.__reader = res.body.getReader(); + if (!res.__reader) { + res.__reader = res.body!.getReader(); + } + if (!res.__chunk) { res.__chunk = await res.__reader.read(); res.__source_offset = 0; } + if (res.__chunk.done) { + return 0; + } - let target_offset = 0; - let bytes_read = 0; - // loop until end of browser stream or end of C# buffer - while (res.__reader && res.__chunk && !res.__chunk.done) { - const remaining_source = res.__chunk.value.byteLength - res.__source_offset; - if (remaining_source === 0) { - res.__chunk = await res.__reader.read(); - res.__source_offset = 0; - continue;// are we done yet - } - - const remaining_target = view.byteLength - target_offset; - const bytes_copied = Math.min(remaining_source, remaining_target); - const source_view = res.__chunk.value.subarray(res.__source_offset, res.__source_offset + bytes_copied); - - // copy available bytes - view.set(source_view, target_offset); - target_offset += bytes_copied; - bytes_read += bytes_copied; - res.__source_offset += bytes_copied; + const remaining_source = res.__chunk.value.byteLength - res.__source_offset; + mono_assert(remaining_source > 0, "expected remaining_source to be greater than 0"); - if (target_offset == view.byteLength) { - return bytes_read; - } + const bytes_copied = Math.min(remaining_source, view.byteLength); + const source_view = res.__chunk.value.subarray(res.__source_offset, res.__source_offset + bytes_copied); + view.set(source_view, 0); + res.__source_offset += bytes_copied; + if (remaining_source == bytes_copied) { + res.__chunk = undefined; } - return bytes_read; + + return bytes_copied; }); } diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index 7e31a296fdb987..0f6fd9f55361d7 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -12,6 +12,7 @@ import { IMPORTS, INTERNAL, Module, runtimeHelpers } from "./imports"; import { generate_arg_marshal_to_js } from "./marshal-to-js"; import { mono_wasm_new_external_root } from "./roots"; import { mono_wasm_symbolicate_string } from "./logging"; +import { wrap_as_cancelable_promise } from "./cancelable-promise"; export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_name: MonoStringRef, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void { const function_name_root = mono_wasm_new_external_root(function_name), @@ -174,7 +175,7 @@ export function get_global_this(): any { export const importedModulesPromises: Map> = new Map(); export const importedModules: Map> = new Map(); -export async function dynamic_import(module_name: string, module_url: string): Promise { +export function dynamic_import(module_name: string, module_url: string): Promise { mono_assert(module_name, "Invalid module_name"); mono_assert(module_url, "Invalid module_name"); let promise = importedModulesPromises.get(module_name); @@ -185,13 +186,16 @@ export async function dynamic_import(module_name: string, module_url: string): P promise = import(/* webpackIgnore: true */module_url); importedModulesPromises.set(module_name, promise); } - const module = await promise; - if (newPromise) { - importedModules.set(module_name, module); - if (runtimeHelpers.diagnosticTracing) - console.debug(`MONO_WASM: imported ES6 module '${module_name}' from '${module_url}'`); - } - return module; + + return wrap_as_cancelable_promise(async () => { + const module = await promise; + if (newPromise) { + importedModules.set(module_name, module); + if (runtimeHelpers.diagnosticTracing) + console.debug(`MONO_WASM: imported ES6 module '${module_name}' from '${module_url}'`); + } + return module; + }); } diff --git a/src/mono/wasm/runtime/logging.ts b/src/mono/wasm/runtime/logging.ts index 2d40d7c4d20e29..cdb2d00dba723d 100644 --- a/src/mono/wasm/runtime/logging.ts +++ b/src/mono/wasm/runtime/logging.ts @@ -62,8 +62,9 @@ export function mono_wasm_symbolicate_string(message: string): string { export function mono_wasm_stringify_as_error_with_stack(err: Error | string): string { let errObj: any = err; - if (!(err instanceof Error)) - errObj = new Error(err); + if (!(errObj instanceof Error)) { + errObj = new Error(errObj); + } // Error return mono_wasm_symbolicate_string(errObj.stack); diff --git a/src/mono/wasm/runtime/managed-exports.ts b/src/mono/wasm/runtime/managed-exports.ts index bbd48aa9e30140..65186e4c0b270f 100644 --- a/src/mono/wasm/runtime/managed-exports.ts +++ b/src/mono/wasm/runtime/managed-exports.ts @@ -7,7 +7,7 @@ import { Module, runtimeHelpers, ENVIRONMENT_IS_PTHREAD } from "./imports"; import { alloc_stack_frame, get_arg, get_arg_gc_handle, MarshalerType, set_arg_type, set_gc_handle } from "./marshal"; import { invoke_method_and_handle_exception } from "./invoke-cs"; import { marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs } from "./marshal-to-cs"; -import { marshal_int32_to_js, marshal_task_to_js } from "./marshal-to-js"; +import { marshal_int32_to_js, marshal_string_to_js, marshal_task_to_js } from "./marshal-to-js"; export function init_managed_exports(): void { const anyModule = Module as any; @@ -22,8 +22,8 @@ export function init_managed_exports(): void { if (!runtimeHelpers.runtime_interop_exports_class) throw "Can't find " + runtimeHelpers.runtime_interop_namespace + "." + runtimeHelpers.runtime_interop_exports_classname + " class"; - const install_sync_context = get_method("InstallSynchronizationContext"); - mono_assert(install_sync_context, "Can't find InstallSynchronizationContext method"); + const install_sync_context = cwraps.mono_wasm_assembly_find_method(runtimeHelpers.runtime_interop_exports_class, "InstallSynchronizationContext", -1); + // mono_assert(install_sync_context, "Can't find InstallSynchronizationContext method"); const call_entry_point = get_method("CallEntrypoint"); mono_assert(call_entry_point, "Can't find CallEntrypoint method"); const release_js_owned_object_by_gc_handle_method = get_method("ReleaseJSOwnedObjectByGCHandle"); @@ -34,6 +34,9 @@ export function init_managed_exports(): void { mono_assert(complete_task_method, "Can't find CompleteTask method"); const call_delegate_method = get_method("CallDelegate"); mono_assert(call_delegate_method, "Can't find CallDelegate method"); + const get_managed_stack_trace_method = get_method("GetManagedStackTrace"); + mono_assert(get_managed_stack_trace_method, "Can't find GetManagedStackTrace method"); + runtimeHelpers.javaScriptExports.call_entry_point = (entry_point: MonoMethod, program_args?: string[]) => { const sp = anyModule.stackSave(); try { @@ -134,19 +137,38 @@ export function init_managed_exports(): void { anyModule.stackRestore(sp); } }; - runtimeHelpers.javaScriptExports.install_synchronization_context = () => { + runtimeHelpers.javaScriptExports.get_managed_stack_trace = (exception_gc_handle: GCHandle) => { const sp = anyModule.stackSave(); try { - const args = alloc_stack_frame(2); - invoke_method_and_handle_exception(install_sync_context, args); + const args = alloc_stack_frame(3); + + const arg1 = get_arg(args, 2); + set_arg_type(arg1, MarshalerType.Exception); + set_gc_handle(arg1, exception_gc_handle); + + invoke_method_and_handle_exception(get_managed_stack_trace_method, args); + const res = get_arg(args, 1); + return marshal_string_to_js(res); } finally { anyModule.stackRestore(sp); } }; - if (!ENVIRONMENT_IS_PTHREAD) - // Install our sync context so that async continuations will migrate back to this thread (the main thread) automatically - runtimeHelpers.javaScriptExports.install_synchronization_context(); + if (install_sync_context) { + runtimeHelpers.javaScriptExports.install_synchronization_context = () => { + const sp = anyModule.stackSave(); + try { + const args = alloc_stack_frame(2); + invoke_method_and_handle_exception(install_sync_context, args); + } finally { + anyModule.stackRestore(sp); + } + }; + + if (!ENVIRONMENT_IS_PTHREAD) + // Install our sync context so that async continuations will migrate back to this thread (the main thread) automatically + runtimeHelpers.javaScriptExports.install_synchronization_context(); + } } export function get_method(method_name: string): MonoMethod { diff --git a/src/mono/wasm/runtime/marshal-to-cs.ts b/src/mono/wasm/runtime/marshal-to-cs.ts index e7cd7cf7e02342..93943bcd4c2463 100644 --- a/src/mono/wasm/runtime/marshal-to-cs.ts +++ b/src/mono/wasm/runtime/marshal-to-cs.ts @@ -449,9 +449,7 @@ function _marshal_cs_object_to_cs(arg: JSMarshalerArgument, value: any): void { set_arg_date(arg, value); } else if (value instanceof Error) { - set_arg_type(arg, MarshalerType.JSException); - const js_handle = mono_wasm_get_js_handle(value); - set_js_handle(arg, js_handle); + marshal_exception_to_cs(arg, value); } else if (value instanceof Uint8Array) { marshal_array_to_cs_impl(arg, value, MarshalerType.Byte); diff --git a/src/mono/wasm/runtime/marshal-to-js.ts b/src/mono/wasm/runtime/marshal-to-js.ts index 318d10580ffec0..aac0155406c4ec 100644 --- a/src/mono/wasm/runtime/marshal-to-js.ts +++ b/src/mono/wasm/runtime/marshal-to-js.ts @@ -32,7 +32,7 @@ export function initialize_marshalers_to_js(): void { cs_to_js_marshalers.set(MarshalerType.Single, _marshal_float_to_js); cs_to_js_marshalers.set(MarshalerType.IntPtr, _marshal_intptr_to_js); cs_to_js_marshalers.set(MarshalerType.Double, _marshal_double_to_js); - cs_to_js_marshalers.set(MarshalerType.String, _marshal_string_to_js); + cs_to_js_marshalers.set(MarshalerType.String, marshal_string_to_js); cs_to_js_marshalers.set(MarshalerType.Exception, marshal_exception_to_js); cs_to_js_marshalers.set(MarshalerType.JSException, marshal_exception_to_js); cs_to_js_marshalers.set(MarshalerType.JSObject, _marshal_js_object_to_js); @@ -353,7 +353,7 @@ export function mono_wasm_marshal_promise(args: JSMarshalerArguments): void { set_arg_type(exc, MarshalerType.None); } -function _marshal_string_to_js(arg: JSMarshalerArgument): string | null { +export function marshal_string_to_js(arg: JSMarshalerArgument): string | null { const type = get_arg_type(arg); if (type == MarshalerType.None) { return null; @@ -383,7 +383,7 @@ export function marshal_exception_to_js(arg: JSMarshalerArgument): Error | null let result = _lookup_js_owned_object(gc_handle); if (result === null || result === undefined) { // this will create new ManagedError - const message = _marshal_string_to_js(arg); + const message = marshal_string_to_js(arg); result = new ManagedError(message!); setup_managed_proxy(result, gc_handle); @@ -462,7 +462,7 @@ function _marshal_array_to_js_impl(arg: JSMarshalerArgument, element_type: Marsh result = new Array(length); for (let index = 0; index < length; index++) { const element_arg = get_arg(buffer_ptr, index); - result[index] = _marshal_string_to_js(element_arg); + result[index] = marshal_string_to_js(element_arg); } cwraps.mono_wasm_deregister_root(buffer_ptr); } diff --git a/src/mono/wasm/runtime/marshal.ts b/src/mono/wasm/runtime/marshal.ts index 77bec6b6aa762f..380abf5f385599 100644 --- a/src/mono/wasm/runtime/marshal.ts +++ b/src/mono/wasm/runtime/marshal.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { js_owned_gc_handle_symbol, teardown_managed_proxy } from "./gc-handles"; -import { Module } from "./imports"; +import { Module, runtimeHelpers } from "./imports"; import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8 } from "./memory"; import { mono_wasm_new_external_root } from "./roots"; import { mono_assert, GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull, JSMarshalerArguments, JSFunctionSignature, JSMarshalerType, JSMarshalerArgument, MarshalerToJs, MarshalerToCs, WasmRoot } from "./types"; @@ -316,13 +316,34 @@ export class ManagedObject implements IDisposable { } export class ManagedError extends Error implements IDisposable { + private superStack: any; constructor(message: string) { super(message); + this.superStack = Object.getOwnPropertyDescriptor(this, "stack"); // this works on Chrome + Object.defineProperty(this, "stack", { + get: this.getManageStack, + }); } - get stack(): string | undefined { - //todo implement lazy managed stack strace from this[js_owned_gc_handle_symbol]! - return super.stack; + getSuperStack() { + if (this.superStack) { + if (this.superStack.value !== undefined) + return this.superStack.value; + if (this.superStack.get !== undefined) + return this.superStack.get.call(this); + } + return super.stack; // this works on FF + } + + getManageStack() { + const gc_handle = (this)[js_owned_gc_handle_symbol]; + if (gc_handle) { + const managed_stack = runtimeHelpers.javaScriptExports.get_managed_stack_trace(gc_handle); + if (managed_stack) { + return managed_stack + "\n" + this.getSuperStack(); + } + } + return this.getSuperStack(); } dispose(): void { @@ -332,10 +353,6 @@ export class ManagedError extends Error implements IDisposable { get isDisposed(): boolean { return (this)[js_owned_gc_handle_symbol] === GCHandleNull; } - - toString(): string { - return `ManagedError(gc_handle: ${(this)[js_owned_gc_handle_symbol]})`; - } } export function get_signature_marshaler(signature: JSFunctionSignature, index: number): JSHandle { @@ -362,7 +379,7 @@ export const enum MemoryViewType { Double = 2, } -abstract class MemoryView implements IMemoryView, IDisposable { +abstract class MemoryView implements IMemoryView { protected constructor(public _pointer: VoidPtr, public _length: number, public _viewType: MemoryViewType) { } @@ -418,7 +435,7 @@ abstract class MemoryView implements IMemoryView, IDisposable { } } -export interface IMemoryView { +export interface IMemoryView extends IDisposable { /** * copies elements from provided source to the wasm memory. * target has to have the elements of the same type as the underlying C# array. @@ -439,7 +456,7 @@ export interface IMemoryView { get byteLength(): number; } -export class Span extends MemoryView implements IDisposable { +export class Span extends MemoryView { private is_disposed = false; public constructor(pointer: VoidPtr, length: number, viewType: MemoryViewType) { super(pointer, length, viewType); diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index 28d4fa03537016..cd4ec500b9656d 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -54,10 +54,7 @@ function assert_int_in_range(value: Number, min: Number, max: Number) { } export function _zero_region(byteOffset: VoidPtr, sizeBytes: number): void { - if (((byteOffset % 4) === 0) && ((sizeBytes % 4) === 0)) - Module.HEAP32.fill(0, byteOffset >>> 2, sizeBytes >>> 2); - else - Module.HEAP8.fill(0, byteOffset, sizeBytes); + Module.HEAP8.fill(0, byteOffset, sizeBytes + byteOffset); } export function setB32(offset: MemOffset, value: number | boolean): void { diff --git a/src/mono/wasm/runtime/polyfills.ts b/src/mono/wasm/runtime/polyfills.ts index b330f45b8f18e6..c28d343b1b7b41 100644 --- a/src/mono/wasm/runtime/polyfills.ts +++ b/src/mono/wasm/runtime/polyfills.ts @@ -7,6 +7,7 @@ import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, ENVIRONM import { afterUpdateGlobalBufferAndViews } from "./memory"; import { replaceEmscriptenPThreadLibrary } from "./pthreads/shared/emscripten-replacements"; import { DotnetModuleConfigImports, EarlyReplacements } from "./types"; +import { TypedArray } from "./types/emscripten"; let node_fs: any | undefined = undefined; let node_url: any | undefined = undefined; @@ -195,6 +196,32 @@ export async function init_polyfills_async(): Promise { const { performance } = INTERNAL.require("perf_hooks"); globalThis.performance = performance; } + + if (!globalThis.crypto) { + globalThis.crypto = {}; + } + if (!globalThis.crypto.getRandomValues) { + let nodeCrypto: any = undefined; + try { + nodeCrypto = INTERNAL.require("node:crypto"); + } catch (err: any) { + // Noop, error throwing polyfill provided bellow + } + + if (!nodeCrypto) { + globalThis.crypto.getRandomValues = () => { + throw new Error("Using node without crypto support. To enable current operation, either provide polyfill for 'globalThis.crypto.getRandomValues' or enable 'node:crypto' module."); + }; + } else if (nodeCrypto.webcrypto) { + globalThis.crypto = nodeCrypto.webcrypto; + } else if (nodeCrypto.randomBytes) { + globalThis.crypto.getRandomValues = (buffer: TypedArray) => { + if (buffer) { + buffer.set(nodeCrypto.randomBytes(buffer.length)); + } + }; + } + } } } diff --git a/src/mono/wasm/runtime/pthreads/browser/index.ts b/src/mono/wasm/runtime/pthreads/browser/index.ts index 021bc6c70a666d..b7c62e9599303c 100644 --- a/src/mono/wasm/runtime/pthreads/browser/index.ts +++ b/src/mono/wasm/runtime/pthreads/browser/index.ts @@ -135,12 +135,14 @@ export function preAllocatePThreadWorkerPool(defaultPthreadPoolSize: number, con export async function instantiateWasmPThreadWorkerPool(): Promise { // this is largely copied from emscripten's "receiveInstance" in "createWasm" in "src/preamble.js" const workers = Internals.getUnusedWorkerPool(); - const allLoaded = createPromiseController(); - let leftToLoad = workers.length; - workers.forEach((w) => { - Internals.loadWasmModuleToWorker(w, function () { - if (!--leftToLoad) allLoaded.promise_control.resolve(); + if (workers.length > 0) { + const allLoaded = createPromiseController(); + let leftToLoad = workers.length; + workers.forEach((w) => { + Internals.loadWasmModuleToWorker(w, function () { + if (!--leftToLoad) allLoaded.promise_control.resolve(); + }); }); - }); - await allLoaded.promise; + await allLoaded.promise; + } } diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 68996b266d28ee..efc843fc7cd00b 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -13,7 +13,6 @@ import { mono_wasm_init_aot_profiler, mono_wasm_init_coverage_profiler } from ". import { mono_on_abort, mono_exit } from "./run"; import { initialize_marshalers_to_cs } from "./marshal-to-cs"; import { initialize_marshalers_to_js } from "./marshal-to-js"; -import { init_crypto } from "./subtle-crypto"; import { init_polyfills_async } from "./polyfills"; import * as pthreads_worker from "./pthreads/worker"; import { createPromiseController } from "./promise-controller"; @@ -32,6 +31,7 @@ import { preAllocatePThreadWorkerPool, instantiateWasmPThreadWorkerPool } from " let config: MonoConfigInternal = undefined as any; let configLoaded = false; let isCustomStartup = false; +export const dotnetReady = createPromiseController(); export const afterConfigLoaded = createPromiseController(); export const afterInstantiateWasm = createPromiseController(); export const beforePreInit = createPromiseController(); @@ -70,13 +70,17 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: // execution order == [5] == module.postRun = [() => postRunAsync(userpostRun)]; // execution order == [6] == - module.ready = module.ready.then(async () => { + + module.ready.then(async () => { // wait for previous stage await afterPostRun.promise; // - here we resolve the promise returned by createDotnetRuntime export - return exportedAPI; // - any code after createDotnetRuntime is executed now + dotnetReady.promise_control.resolve(exportedAPI); + }).catch(err => { + dotnetReady.promise_control.reject(err); }); + module.ready = dotnetReady.promise; // execution order == [*] == if (!module.onAbort) { module.onAbort = () => mono_on_abort; @@ -221,6 +225,7 @@ async function postRunAsync(userpostRun: (() => void)[]) { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function abort_startup(reason: any, should_exit: boolean): void { if (runtimeHelpers.diagnosticTracing) console.trace("MONO_WASM: abort_startup"); + dotnetReady.promise_control.reject(reason); afterInstantiateWasm.promise_control.reject(reason); beforePreInit.promise_control.reject(reason); afterPreInit.promise_control.reject(reason); @@ -255,7 +260,6 @@ async function mono_wasm_pre_init_essential_async(): Promise { await init_polyfills_async(); await mono_wasm_load_config(Module.configSrc); - init_crypto(); if (MonoWasmThreads) { preAllocatePThreadWorkerPool(MONO_PTHREAD_POOL_SIZE, config); diff --git a/src/mono/wasm/runtime/subtle-crypto.ts b/src/mono/wasm/runtime/subtle-crypto.ts deleted file mode 100644 index 8a60cc5ef93865..00000000000000 --- a/src/mono/wasm/runtime/subtle-crypto.ts +++ /dev/null @@ -1,412 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { resolve_asset_path } from "./assets"; -import { Module, runtimeHelpers } from "./imports"; -import { mono_assert } from "./types"; - -class OperationFailedError extends Error { } - -const ERR_ARGS = -1; -const ERR_WORKER_FAILED = -2; -const ERR_OP_FAILED = -3; -const ERR_UNKNOWN = -100; - -let mono_wasm_crypto: { - channel: LibraryChannel - worker: Worker -} | null = null; - -export function dotnet_browser_can_use_subtle_crypto_impl(): number { - return mono_wasm_crypto === null ? 0 : 1; -} - -export function dotnet_browser_simple_digest_hash(ver: number, input_buffer: number, input_len: number, output_buffer: number, output_len: number): number { - const msg = { - func: "digest", - type: ver, - data: Array.from(Module.HEAPU8.subarray(input_buffer, input_buffer + input_len)) - }; - - return _send_simple_msg(msg, "DIGEST HASH", output_buffer, output_len); -} - -export function dotnet_browser_sign(hashAlgorithm: number, key_buffer: number, key_len: number, input_buffer: number, input_len: number, output_buffer: number, output_len: number): number { - const msg = { - func: "sign", - type: hashAlgorithm, - key: Array.from(Module.HEAPU8.subarray(key_buffer, key_buffer + key_len)), - data: Array.from(Module.HEAPU8.subarray(input_buffer, input_buffer + input_len)) - }; - - return _send_simple_msg(msg, "SIGN HASH", output_buffer, output_len); -} - -const AesBlockSizeBytes = 16; // 128 bits - -export function dotnet_browser_encrypt_decrypt(isEncrypting: boolean, key_buffer: number, key_len: number, iv_buffer: number, iv_len: number, input_buffer: number, input_len: number, output_buffer: number, output_len: number): number { - if (input_len <= 0 || input_len % AesBlockSizeBytes !== 0) { - throw "ENCRYPT DECRYPT: data was not a full block: " + input_len; - } - - const msg = { - func: "encrypt_decrypt", - isEncrypting: isEncrypting, - key: Array.from(Module.HEAPU8.subarray(key_buffer, key_buffer + key_len)), - iv: Array.from(Module.HEAPU8.subarray(iv_buffer, iv_buffer + iv_len)), - data: Array.from(Module.HEAPU8.subarray(input_buffer, input_buffer + input_len)) - }; - - const result = _send_msg_worker(msg); - if (typeof result === "number") { - return result; - } - - if (result.length > output_len) { - console.error(`MONO_WASM_ENCRYPT_DECRYPT: Encrypt/Decrypt length exceeds output length: ${result.length} > ${output_len}`); - return ERR_ARGS; - } - - Module.HEAPU8.set(result, output_buffer); - return result.length; -} - -export function dotnet_browser_derive_bits(password_buffer: number, password_len: number, salt_buffer: number, salt_len: number, iterations: number, hashAlgorithm: number, output_buffer: number, output_len: number): number { - const msg = { - func: "derive_bits", - password: Array.from(Module.HEAPU8.subarray(password_buffer, password_buffer + password_len)), - salt: Array.from(Module.HEAPU8.subarray(salt_buffer, salt_buffer + salt_len)), - iterations: iterations, - hashAlgorithm: hashAlgorithm, - lengthInBytes: output_len - }; - - return _send_simple_msg(msg, "DERIVE BITS", output_buffer, output_len); -} - -function _send_simple_msg(msg: any, prefix: string, output_buffer: number, output_len: number): number { - const result = _send_msg_worker(msg); - - if (typeof result === "number") { - return result; - } - - if (result.length > output_len) { - console.error(`MONO_WASM_ENCRYPT_DECRYPT: ${prefix}: Result length exceeds output length: ${result.length} > ${output_len}`); - return ERR_ARGS; - } - - Module.HEAPU8.set(result, output_buffer); - return 0; -} - -export function init_crypto(): void { - if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined" - && typeof SharedArrayBuffer !== "undefined" - && typeof Worker !== "undefined" - ) { - console.debug("MONO_WASM: Initializing Crypto WebWorker"); - - const chan = LibraryChannel.create(1024); // 1024 is the buffer size in char units. - const asset = resolve_asset_path("js-module-crypto"); - mono_assert(asset && asset.resolvedUrl, "Can't find js-module-crypto"); - const worker = new Worker(asset.resolvedUrl); - mono_wasm_crypto = { - channel: chan, - worker: worker, - }; - const messageData: InitCryptoMessageData = { - config: JSON.stringify(runtimeHelpers.config),// there could be things in config which could not be cloned to worker - comm_buf: chan.get_comm_buffer(), - msg_buf: chan.get_msg_buffer(), - msg_char_len: chan.get_msg_len() - }; - worker.onerror = event => { - console.warn(`MONO_WASM: Error in Crypto WebWorker. Cryptography digest calls will fallback to managed implementation. Error: ${event.message}`); - mono_wasm_crypto = null; - }; - worker.postMessage(messageData); - } -} - -function _send_msg_worker(msg: any): number | any { - mono_assert(!!mono_wasm_crypto, "subtle crypto not initialized"); - - try { - const response = mono_wasm_crypto.channel.send_msg(JSON.stringify(msg)); - const responseJson = JSON.parse(response); - - if (responseJson.error !== undefined) { - console.error(`MONO_WASM_ENCRYPT_DECRYPT: Worker failed with: ${responseJson.error}`); - if (responseJson.error_type == "ArgumentsError") - return ERR_ARGS; - if (responseJson.error_type == "WorkerFailedError") - return ERR_WORKER_FAILED; - - return ERR_UNKNOWN; - } - - return responseJson.result; - } catch (err) { - if (err instanceof Error && err.stack !== undefined) - console.error(`MONO_WASM_ENCRYPT_DECRYPT: ${err.stack}`); - else - console.error(`MONO_WASM_ENCRYPT_DECRYPT: _send_msg_worker failed: ${err}`); - return ERR_OP_FAILED; - } -} - -class LibraryChannel { - private msg_char_len: number; - private comm_buf: SharedArrayBuffer; - private msg_buf: SharedArrayBuffer; - private comm: Int32Array; - private msg: Uint16Array; - - // LOCK states - private get LOCK_UNLOCKED(): number { return 0; } // 0 means the lock is unlocked - private get LOCK_OWNED(): number { return 1; } // 1 means the LibraryChannel owns the lock - - // Index constants for the communication buffer. - private get STATE_IDX(): number { return 0; } - private get MSG_SIZE_IDX(): number { return 1; } - private get LOCK_IDX(): number { return 2; } - private get COMM_LAST_IDX(): number { return this.LOCK_IDX; } - - // Communication states. - private get STATE_SHUTDOWN(): number { return -1; } // Shutdown - private get STATE_IDLE(): number { return 0; } - private get STATE_REQ(): number { return 1; } - private get STATE_RESP(): number { return 2; } - private get STATE_REQ_P(): number { return 3; } // Request has multiple parts - private get STATE_RESP_P(): number { return 4; } // Response has multiple parts - private get STATE_AWAIT(): number { return 5; } // Awaiting the next part - private get STATE_REQ_FAILED(): number { return 6; } // The Request failed - private get STATE_RESET(): number { return 7; } // Reset to a known state - - private constructor(msg_char_len: number) { - this.msg_char_len = msg_char_len; - - const int_bytes = 4; - const comm_byte_len = int_bytes * (this.COMM_LAST_IDX + 1); - this.comm_buf = new SharedArrayBuffer(comm_byte_len); - - // JavaScript character encoding is UTF-16. - const char_bytes = 2; - const msg_byte_len = char_bytes * this.msg_char_len; - this.msg_buf = new SharedArrayBuffer(msg_byte_len); - - // Create the local arrays to use. - this.comm = new Int32Array(this.comm_buf); - this.msg = new Uint16Array(this.msg_buf); - } - - public get_msg_len(): number { return this.msg_char_len; } - public get_msg_buffer(): SharedArrayBuffer { return this.msg_buf; } - public get_comm_buffer(): SharedArrayBuffer { return this.comm_buf; } - - public send_msg(msg: string): string { - try { - this.wait_for_state_change_to(pstate => pstate == this.STATE_IDLE, "waiting"); - this.send_request(msg); - return this.read_response(); - } catch (err) { - this.reset(LibraryChannel.stringify_err(err)); - throw err; - } - } - - public shutdown(): void { - console.debug("MONO_WASM_ENCRYPT_DECRYPT: Shutting down crypto"); - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state !== this.STATE_IDLE) - throw new Error(`OWNER: Invalid sync communication channel state: ${state}`); - - this.using_lock(() => { - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - this.change_state_locked(this.STATE_SHUTDOWN); - }); - // Notify webworker - Atomics.notify(this.comm, this.STATE_IDX); - } - - private reset(reason: string): void { - console.debug(`MONO_WASM_ENCRYPT_DECRYPT: reset: ${reason}`); - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state === this.STATE_SHUTDOWN) - return; - - if (state === this.STATE_RESET || state === this.STATE_IDLE) { - console.debug(`MONO_WASM_ENCRYPT_DECRYPT: state is already RESET or idle: ${state}`); - return; - } - - this.using_lock(() => { - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - this.change_state_locked(this.STATE_RESET); - }); - // Notify webworker - Atomics.notify(this.comm, this.STATE_IDX); - } - - private send_request(msg: string): void { - let state; - const msg_len = msg.length; - let msg_written = 0; - - for (; ;) { - this.using_lock(() => { - // Write the message and return how much was written. - const wrote = this.write_to_msg(msg, msg_written, msg_len); - msg_written += wrote; - - // Indicate how much was written to the this.msg buffer. - Atomics.store(this.comm, this.MSG_SIZE_IDX, wrote); - - // Indicate if this was the whole message or part of it. - state = msg_written === msg_len ? this.STATE_REQ : this.STATE_REQ_P; - - this.change_state_locked(state); - }); - // Notify webworker - Atomics.notify(this.comm, this.STATE_IDX); - - // The send message is complete. - if (state === this.STATE_REQ) { - break; - } - else if (state !== this.STATE_REQ_P) { - throw new Error(`Unexpected state ${state}`); - } - - this.wait_for_state_change_to(state => state == this.STATE_AWAIT, "send_request"); - } - } - - private write_to_msg(input: string, start: number, input_len: number): number { - let mi = 0; - let ii = start; - while (mi < this.msg_char_len && ii < input_len) { - this.msg[mi] = input.charCodeAt(ii); - ii++; // Next character - mi++; // Next buffer index - } - return ii - start; - } - - private read_response(): string { - let response = ""; - for (; ;) { - this.wait_for_state_change_to(state => state == this.STATE_RESP || state == this.STATE_RESP_P, "read_response"); - const done = this.using_lock(() => { - const size_to_read = Atomics.load(this.comm, this.MSG_SIZE_IDX); - - // Append the latest part of the message. - response += this.read_from_msg(0, size_to_read); - - // The response is complete. - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state === this.STATE_RESP) { - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - return true; - } else if (state !== this.STATE_RESP_P) { - throw new Error(`Unexpected state ${state}`); - } - - // Reset the size and transition to await state. - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - this.change_state_locked(this.STATE_AWAIT); - return false; - }); - - // Notify webworker - Atomics.notify(this.comm, this.STATE_IDX); - - if (done) { - break; - } - } - - // Reset the communication channel's state and let the - // webworker know we are done. - this.using_lock(() => { - this.change_state_locked(this.STATE_IDLE); - }); - Atomics.notify(this.comm, this.STATE_IDX); - - return response; - } - - private change_state_locked(newState: number): void { - Atomics.store(this.comm, this.STATE_IDX, newState); - } - - private wait_for_state_change_to(is_ready: (state: number) => boolean, msg: string): void { - // Wait for webworker - // - Atomics.wait() is not permissible on the main thread. - for (; ;) { - const done = this.using_lock(() => { - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state == this.STATE_REQ_FAILED) - throw new OperationFailedError(`Worker failed during ${msg} with state=${state}`); - - if (is_ready(state)) - return true; - }); - if (done) return; - } - } - - private read_from_msg(begin: number, end: number): string { - const slicedMessage: number[] = []; - this.msg.slice(begin, end).forEach((value, index) => slicedMessage[index] = value); - return String.fromCharCode.apply(null, slicedMessage); - } - - private using_lock(callback: Function) { - try { - this.acquire_lock(); - return callback(); - } finally { - this.release_lock(); - } - } - - private acquire_lock() { - for (; ;) { - const lock_state = Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_UNLOCKED, this.LOCK_OWNED); - - if (lock_state === this.LOCK_UNLOCKED) { - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state === this.STATE_REQ_FAILED) - throw new OperationFailedError("Worker failed"); - return; - } - } - } - - private release_lock() { - const result = Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_OWNED, this.LOCK_UNLOCKED); - if (result !== this.LOCK_OWNED) { - throw new Error("CRYPTO: LibraryChannel tried to release a lock that wasn't acquired: " + result); - } - } - - private static stringify_err(err: any) { - return (err instanceof Error && err.stack !== undefined) ? err.stack : err; - } - - public static create(msg_char_len: number): LibraryChannel { - if (msg_char_len === undefined) { - msg_char_len = 1024; // Default size is arbitrary but is in 'char' units (i.e. UTF-16 code points). - } - return new LibraryChannel(msg_char_len); - } -} - -export type InitCryptoMessageData = { - config: string,// serialized to avoid passing non-clonable objects - comm_buf: SharedArrayBuffer, - msg_buf: SharedArrayBuffer, - msg_char_len: number -} diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index ac6256b56799be..3664532e54c5e9 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -195,7 +195,6 @@ export type AssetBehaviours = | "icu" // load asset as an ICU data archive | "vfs" // load asset into the virtual filesystem (for fopen, File.Open, etc) | "dotnetwasm" // the binary of the dotnet runtime - | "js-module-crypto" // the javascript module for subtle crypto | "js-module-threads" // the javascript module for threads export type RuntimeHelpers = { @@ -412,6 +411,9 @@ export interface JavaScriptExports { // the marshaled signature is: void InstallSynchronizationContext() install_synchronization_context(): void; + + // the marshaled signature is: string GetManagedStackTrace(GCHandle exception) + get_managed_stack_trace(exception_gc_handle: GCHandle): string | null } export type MarshalerToJs = (arg: JSMarshalerArgument, sig?: JSMarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs) => any; diff --git a/src/mono/wasm/runtime/workers/dotnet-crypto-worker.ts b/src/mono/wasm/runtime/workers/dotnet-crypto-worker.ts deleted file mode 100644 index 47aeba44882bf4..00000000000000 --- a/src/mono/wasm/runtime/workers/dotnet-crypto-worker.ts +++ /dev/null @@ -1,404 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { setup_proxy_console } from "../logging"; -import type { InitCryptoMessageData } from "../subtle-crypto"; -import type { MonoConfig } from "../types"; - -class FailedOrStoppedLoopError extends Error { } -class ArgumentsError extends Error { } -class WorkerFailedError extends Error { } - -class ChannelWorker { - // LOCK states - get LOCK_UNLOCKED() { return 0; } // 0 means the lock is unlocked - get LOCK_OWNED() { return 2; } // 2 means the ChannelWorker owns the lock - - // BEGIN ChannelOwner contract - shared constants. - get STATE_IDX() { return 0; } - get MSG_SIZE_IDX() { return 1; } - get LOCK_IDX() { return 2; } - - // Communication states. - get STATE_SHUTDOWN() { return -1; } // Shutdown - get STATE_IDLE() { return 0; } - get STATE_REQ() { return 1; } - get STATE_RESP() { return 2; } - get STATE_REQ_P() { return 3; } // Request has multiple parts - get STATE_RESP_P() { return 4; } // Response has multiple parts - get STATE_AWAIT() { return 5; } // Awaiting the next part - get STATE_REQ_FAILED() { return 6; } // The Request failed - get STATE_RESET() { return 7; } // Reset to a known state - // END ChannelOwner contract - shared constants. - - private comm: Int32Array; - private msg: Uint16Array; - private msg_char_len: number; - - constructor(comm_buf: SharedArrayBuffer, msg_buf: SharedArrayBuffer, msg_char_len: number) { - this.comm = new Int32Array(comm_buf); - this.msg = new Uint16Array(msg_buf); - this.msg_char_len = msg_char_len; - } - - async run_message_loop(async_op: (jsonRequest: string) => Promise) { - for (; ;) { - try { - // Wait for signal to perform operation - let state; - do { - this.wait_for_state_to_change_from(this.STATE_IDLE); - state = Atomics.load(this.comm, this.STATE_IDX); - } while (state !== this.STATE_REQ && state !== this.STATE_REQ_P && state !== this.STATE_SHUTDOWN && state !== this.STATE_REQ_FAILED && state !== this.STATE_RESET); - - this.throw_if_reset_or_shutdown(); - - // Read in request - const request_json = this.read_request(); - const response: any = {}; - try { - // Perform async action based on request - response.result = await async_op(request_json); - } - catch (err) { - response.error_type = typeof err; - response.error = _stringify_err(err); - console.error(`MONO_WASM: Request error: ${response.error}. req was: ${request_json}`); - } - - // Send response - this.send_response(JSON.stringify(response)); - } catch (err) { - if (err instanceof FailedOrStoppedLoopError) { - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state === this.STATE_SHUTDOWN) - break; - if (state === this.STATE_RESET) - console.debug("MONO_WASM: caller failed, resetting worker"); - } else { - console.error(`MONO_WASM: Worker failed to handle the request: ${_stringify_err(err)}`); - this.using_lock(() => { - this.change_state_locked(this.STATE_REQ_FAILED); - }); - - console.debug("MONO_WASM: set state to failed, now waiting to get RESET"); - Atomics.wait(this.comm, this.STATE_IDX, this.STATE_REQ_FAILED); - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state !== this.STATE_RESET) { - throw new WorkerFailedError(`expected to RESET, but got ${state}`); - } - } - - this.using_lock(() => { - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - Atomics.store(this.comm, this.LOCK_IDX, this.LOCK_UNLOCKED); - this.change_state_locked(this.STATE_IDLE); - }); - } - - const state = Atomics.load(this.comm, this.STATE_IDX); - const lock_state = Atomics.load(this.comm, this.LOCK_IDX); - - if (state !== this.STATE_IDLE && state !== this.STATE_REQ && state !== this.STATE_REQ_P) - console.error(`MONO_WASM: -- state is not idle at the top of the loop: ${state}, and lock_state: ${lock_state}`); - if (lock_state !== this.LOCK_UNLOCKED && state !== this.STATE_REQ && state !== this.STATE_REQ_P && state !== this.STATE_IDLE) - console.error(`MONO_WASM: -- lock is not unlocked at the top of the loop: ${lock_state}, and state: ${state}`); - } - - this.using_lock(() => { - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - this.change_state_locked(this.STATE_SHUTDOWN); - }); - console.debug("MONO_WASM: ******* run_message_loop ending"); - } - - private read_request(): string { - let request = ""; - for (; ;) { - const done = this.using_lock(() => { - this.throw_if_reset_or_shutdown(); - - // Get the current state and message size - const state = Atomics.load(this.comm, this.STATE_IDX); - const size_to_read = Atomics.load(this.comm, this.MSG_SIZE_IDX); - - const view = this.msg.subarray(0, size_to_read); - const part = String.fromCharCode(...view); - // Append the latest part of the message. - request += part; - - // The request is complete. - if (state === this.STATE_REQ) { - return true; - } else if (state !== this.STATE_REQ_P) { - throw new Error(`Unexpected state ${state}`); - } - - // Shutdown the worker. - this.throw_if_reset_or_shutdown(); - - // Reset the size and transition to await state. - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - this.change_state_locked(this.STATE_AWAIT); - }); - if (done) { - break; - } - - this.wait_for_state_to_change_from(this.STATE_AWAIT); - } - - return request; - } - - private send_response(msg: string) { - if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_REQ) - throw new WorkerFailedError("WORKER: Invalid sync communication channel state."); - - const msg_len = msg.length; - let msg_written = 0; - - for (; ;) { - const state = this.using_lock(() => { - // Write the message and return how much was written. - const wrote = this.write_to_msg(msg, msg_written, msg_len); - msg_written += wrote; - - // Indicate how much was written to the this.msg buffer. - Atomics.store(this.comm, this.MSG_SIZE_IDX, wrote); - - // Indicate if this was the whole message or part of it. - const state = msg_written === msg_len ? this.STATE_RESP : this.STATE_RESP_P; - - // Update the state - this.change_state_locked(state); - - return state; - }); - - // Wait for the transition to know the main thread has - // received the response by moving onto a new state. - this.wait_for_state_to_change_from(state); - - // Done sending response. - if (state === this.STATE_RESP) { - break; - } else if (state !== this.STATE_RESP_P) { - throw new Error(`Unexpected state ${state}`); - } - } - } - - private write_to_msg(input: string, start: number, input_len: number) { - let mi = 0; - let ii = start; - while (mi < this.msg_char_len && ii < input_len) { - this.msg[mi] = input.charCodeAt(ii); - ii++; // Next character - mi++; // Next buffer index - } - return ii - start; - } - - private change_state_locked(newState: number) { - Atomics.store(this.comm, this.STATE_IDX, newState); - } - - private using_lock(callback: Function) { - try { - this.acquire_lock(); - return callback(); - } finally { - this.release_lock(); - } - } - - private acquire_lock() { - for (; ;) { - const lockState = Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_UNLOCKED, this.LOCK_OWNED); - this.throw_if_reset_or_shutdown(); - - if (lockState === this.LOCK_UNLOCKED) - return; - } - } - - private release_lock() { - const result = Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_OWNED, this.LOCK_UNLOCKED); - if (result !== this.LOCK_OWNED) { - throw new WorkerFailedError("CRYPTO: ChannelWorker tried to release a lock that wasn't acquired: " + result); - } - } - - private wait_for_state_to_change_from(expected_state: number) { - Atomics.wait(this.comm, this.STATE_IDX, expected_state); - this.throw_if_reset_or_shutdown(); - } - - private throw_if_reset_or_shutdown() { - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state === this.STATE_RESET || state === this.STATE_SHUTDOWN) - throw new FailedOrStoppedLoopError(); - } -} - -async function call_digest(type: number, data: Uint8Array) { - const digest_type = get_hash_name(type); - - // The 'crypto' API is not available in non-browser - // environments (for example, v8 server). - const digest = await crypto.subtle.digest(digest_type, data); - return Array.from(new Uint8Array(digest)); -} - -async function sign(type: number, key: Uint8Array, data: Uint8Array) { - const hash_name = get_hash_name(type); - - if (key.length === 0) { - // crypto.subtle.importKey will raise an error for an empty key. - // To prevent an error, reset it to a key with just a `0x00` byte. This is equivalent - // since HMAC keys get zero-extended up to the block size of the algorithm. - key = new Uint8Array([0]); - } - - const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: hash_name }, false /* extractable */, ["sign"]); - const signResult = await crypto.subtle.sign("HMAC", cryptoKey, data); - return Array.from(new Uint8Array(signResult)); -} - -async function derive_bits(password: Uint8Array, salt: Uint8Array, iterations: number, hashAlgorithm: number, lengthInBytes: number) { - const hash_name = get_hash_name(hashAlgorithm); - - const passwordKey = await importKey(password, "PBKDF2", ["deriveBits"]); - const result = await crypto.subtle.deriveBits( - { - name: "PBKDF2", - salt: salt, - iterations: iterations, - hash: hash_name - }, - passwordKey, - lengthInBytes * 8 // deriveBits takes number of bits - ); - - return Array.from(new Uint8Array(result)); -} - -function get_hash_name(type: number) { - switch (type) { - case 0: return "SHA-1"; - case 1: return "SHA-256"; - case 2: return "SHA-384"; - case 3: return "SHA-512"; - default: - throw new ArgumentsError("CRYPTO: Unknown digest: " + type); - } -} - -const AesBlockSizeBytes = 16; // 128 bits - -async function encrypt_decrypt(isEncrypting: boolean, key: number[], iv: number[], data: number[]) { - const algorithmName = "AES-CBC"; - const keyUsage: KeyUsage[] = isEncrypting ? ["encrypt"] : ["encrypt", "decrypt"]; - const cryptoKey = await importKey(new Uint8Array(key), algorithmName, keyUsage); - const algorithm = { - name: algorithmName, - iv: new Uint8Array(iv) - }; - - const result = await (isEncrypting ? - crypto.subtle.encrypt( - algorithm, - cryptoKey, - new Uint8Array(data)) : - decrypt( - algorithm, - cryptoKey, - data)); - - let resultByteArray = new Uint8Array(result); - if (isEncrypting) { - // trim off the last block, which is always a padding block. - resultByteArray = resultByteArray.slice(0, resultByteArray.length - AesBlockSizeBytes); - } - return Array.from(resultByteArray); -} - -async function decrypt(algorithm: Algorithm, cryptoKey: CryptoKey, data: number[]) { - // crypto.subtle AES-CBC will only allow a PaddingMode of PKCS7, but we need to use - // PaddingMode None. To simulate this, we only decrypt full blocks of data, with an extra full - // padding block of 0x10 (16) bytes appended to data. crypto.subtle will see that padding block and return - // the fully decrypted message. To create the encrypted padding block, we encrypt an empty array using the - // last block of the cipher text as the IV. This will create a full block of padding bytes. - - const paddingBlockIV = new Uint8Array(data).slice(data.length - AesBlockSizeBytes); - const empty = new Uint8Array(); - const encryptedPaddingBlockResult = await crypto.subtle.encrypt( - { - name: algorithm.name, - iv: paddingBlockIV - }, - cryptoKey, - empty - ); - - const encryptedPaddingBlock = new Uint8Array(encryptedPaddingBlockResult); - for (let i = 0; i < encryptedPaddingBlock.length; i++) { - data.push(encryptedPaddingBlock[i]); - } - - return await crypto.subtle.decrypt( - algorithm, - cryptoKey, - new Uint8Array(data)); -} - -function importKey(key: ArrayBuffer, algorithmName: string, keyUsage: KeyUsage[]) { - return crypto.subtle.importKey( - "raw", - key, - { - name: algorithmName - }, - false /* extractable */, - keyUsage); -} - -// Operation to perform. -async function handle_req_async(jsonRequest: string): Promise { - const req = JSON.parse(jsonRequest); - - if (req.func === "digest") { - return await call_digest(req.type, new Uint8Array(req.data)); - } - else if (req.func === "sign") { - return await sign(req.type, new Uint8Array(req.key), new Uint8Array(req.data)); - } - else if (req.func === "encrypt_decrypt") { - return await encrypt_decrypt(req.isEncrypting, req.key, req.iv, req.data); - } - else if (req.func === "derive_bits") { - return await derive_bits(new Uint8Array(req.password), new Uint8Array(req.salt), req.iterations, req.hashAlgorithm, req.lengthInBytes); - } - else { - throw new ArgumentsError("CRYPTO: Unknown request: " + req.func); - } -} - -function _stringify_err(err: any) { - return (err instanceof Error && err.stack !== undefined) ? err.stack : err; -} - -let s_channel; -let config: MonoConfig = null; - -// Initialize WebWorker -self.addEventListener("message", (event: MessageEvent) => { - const data = event.data as InitCryptoMessageData; - config = data && data.config ? JSON.parse(data.config) : {}; - if (config.diagnosticTracing) { - setup_proxy_console("crypto-worker", console, self.location.origin); - } - s_channel = new ChannelWorker(data.comm_buf, data.msg_buf, data.msg_char_len); - s_channel.run_message_loop(handle_req_async); -}); diff --git a/src/mono/wasm/templates/templates/browser/.template.config/template.json b/src/mono/wasm/templates/templates/browser/.template.config/template.json index 4209d083adbcc5..ff7164a96a3965 100644 --- a/src/mono/wasm/templates/templates/browser/.template.config/template.json +++ b/src/mono/wasm/templates/templates/browser/.template.config/template.json @@ -2,12 +2,51 @@ "$schema": "http://json.schemastore.org/template", "author": "Microsoft", "classifications": [ "Web", "WebAssembly", "Browser" ], - "identity": "WebAssembly.Browser", + "generatorVersions": "[1.0.0.0-*)", + "groupIdentity": "WebAssembly.Browser", + "precedence": 7000, + "identity": "WebAssembly.Browser.7.0", "name": "WebAssembly Browser App", + "description": "A project template for creating a .NET app that runs on WebAssembly in a browser", "shortName": "wasmbrowser", "sourceName": "browser.0", + "preferNameDirectory": true, "tags": { "language": "C#", "type": "project" + }, + "symbols": { + "kestrelHttpPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 5000, + "high": 5300 + }, + "replaces": "5000" + }, + "kestrelHttpsPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 7000, + "high": 7300 + }, + "replaces": "5001" + }, + "framework": { + "type": "parameter", + "description": "The target framework for the project.", + "datatype": "choice", + "choices": [ + { + "choice": "net7.0", + "description": "Target net7.0", + "displayName": ".NET 7.0" + } + ], + "defaultValue": "net7.0", + "displayName": "framework" + } } } diff --git a/src/mono/wasm/templates/templates/browser/Properties/AssemblyInfo.cs b/src/mono/wasm/templates/templates/browser/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000000..9ad9b578f20649 --- /dev/null +++ b/src/mono/wasm/templates/templates/browser/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +[assembly:System.Runtime.Versioning.SupportedOSPlatform("browser")] diff --git a/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json b/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json new file mode 100644 index 00000000000000..0e5b784b708843 --- /dev/null +++ b/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "browser.0": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/debug?browser={browserInspectUri}" + } + } +} diff --git a/src/mono/wasm/templates/templates/browser/README.md b/src/mono/wasm/templates/templates/browser/README.md index f3c4ddce82ffc9..7ddf4fd1bce9bd 100644 --- a/src/mono/wasm/templates/templates/browser/README.md +++ b/src/mono/wasm/templates/templates/browser/README.md @@ -1,25 +1,26 @@ -## Browser application +## .NET WebAssembly Browser app ## Build -You can build the applcation from Visual Studio or by dotnet cli +You can build the app from Visual Studio or from the command-line: ``` -dotnet build -c Debug/Release -r browser-wasm +dotnet build -c Debug/Release ``` -After building the application, the result is in the `bin/$(Configuration)/net7.0/browser-wasm/AppBundle` directory. +After building the app, the result is in the `bin/$(Configuration)/net7.0/browser-wasm/AppBundle` directory. ## Run -You can build the applcation from Visual Studio or by dotnet cli +You can build the app from Visual Studio or the command-line: ``` -dotnet run -c Debug/Release -r browser-wasm +dotnet run -c Debug/Release ``` -Or you can start any static file server from the AppBundle directory +Or you can start any static file server from the AppBundle directory: ``` +dotnet tool install dotnet-serve dotnet serve -d:bin/$(Configuration)/net7.0/browser-wasm/AppBundle ``` \ No newline at end of file diff --git a/src/mono/wasm/templates/templates/browser/main.js b/src/mono/wasm/templates/templates/browser/main.js index 32c1599749d1db..6d9bd43f7eb7b3 100644 --- a/src/mono/wasm/templates/templates/browser/main.js +++ b/src/mono/wasm/templates/templates/browser/main.js @@ -3,15 +3,12 @@ import { dotnet } from './dotnet.js' -const is_browser = typeof window != "undefined"; -if (!is_browser) throw new Error(`Expected to be running in a browser`); - -const { setModuleImports, getAssemblyExports, getConfig, runMainAndExit } = await dotnet +const { setModuleImports, getAssemblyExports, getConfig } = await dotnet .withDiagnosticTracing(false) .withApplicationArgumentsFromQuery() .create(); -setModuleImports("main.js", { +setModuleImports('main.js', { window: { location: { href: () => globalThis.window.location.href @@ -24,5 +21,5 @@ const exports = await getAssemblyExports(config.mainAssemblyName); const text = exports.MyClass.Greeting(); console.log(text); -document.getElementById("out").innerHTML = `${text}`; -await runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]); \ No newline at end of file +document.getElementById('out').innerHTML = text; +await dotnet.run(); \ No newline at end of file diff --git a/src/mono/wasm/templates/templates/console/.template.config/template.json b/src/mono/wasm/templates/templates/console/.template.config/template.json index 15f0c9041de09f..c5e89c0b80dc9d 100644 --- a/src/mono/wasm/templates/templates/console/.template.config/template.json +++ b/src/mono/wasm/templates/templates/console/.template.config/template.json @@ -2,12 +2,32 @@ "$schema": "http://json.schemastore.org/template", "author": "Microsoft", "classifications": [ "Web", "WebAssembly", "Console" ], - "identity": "WebAssembly.Console", + "groupIdentity": "WebAssembly.Console", + "precedence": 7000, + "identity": "WebAssembly.Console.7.0", "name": "WebAssembly Console App", + "description": "A project template for creating a .NET app that runs on WebAssembly on Node JS or V8", "shortName": "wasmconsole", "sourceName": "console.0", + "preferNameDirectory": true, "tags": { "language": "C#", "type": "project" + }, + "symbols": { + "framework": { + "type": "parameter", + "description": "The target framework for the project.", + "datatype": "choice", + "choices": [ + { + "choice": "net7.0", + "description": "Target net7.0", + "displayName": ".NET 7.0" + } + ], + "defaultValue": "net7.0", + "displayName": "framework" + } } } diff --git a/src/mono/wasm/templates/templates/console/Properties/AssemblyInfo.cs b/src/mono/wasm/templates/templates/console/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000000..9ad9b578f20649 --- /dev/null +++ b/src/mono/wasm/templates/templates/console/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +[assembly:System.Runtime.Versioning.SupportedOSPlatform("browser")] diff --git a/src/mono/wasm/templates/templates/console/README.md b/src/mono/wasm/templates/templates/console/README.md index a3b8dbc660e5d0..3adce359ae1638 100644 --- a/src/mono/wasm/templates/templates/console/README.md +++ b/src/mono/wasm/templates/templates/console/README.md @@ -1,24 +1,24 @@ -## Node application +## .NET WebAssembly Node app ## Build -You can build the applcation from Visual Studio or by dotnet cli +You can build the app from Visual Studio or from the command-line: ``` -dotnet build -c Debug/Release -r browser-wasm +dotnet build -c Debug/Release ``` -After building the application, the result is in the `bin/$(Configuration)/net7.0/browser-wasm/AppBundle` directory. +After building the app, the result is in the `bin/$(Configuration)/net7.0/browser-wasm/AppBundle` directory. ## Run -You can build the applcation from Visual Studio or by dotnet cli +You can build the app from Visual Studio or the command-line: ``` -dotnet run -c Debug/Release -r browser-wasm -h=nodejs +dotnet run -c Debug/Release ``` -Or you can start any static file server from the AppBundle directory +Or directly start node from the AppBundle directory: ``` node bin/$(Configuration)/net7.0/browser-wasm/AppBundle/main.mjs diff --git a/src/mono/wasm/templates/templates/console/main.mjs b/src/mono/wasm/templates/templates/console/main.mjs index 1072711c59c6dc..6dd163a3741b32 100644 --- a/src/mono/wasm/templates/templates/console/main.mjs +++ b/src/mono/wasm/templates/templates/console/main.mjs @@ -3,14 +3,11 @@ import { dotnet } from './dotnet.js' -const is_node = typeof process === 'object' && typeof process.versions === 'object' && typeof process.versions.node === 'string'; -if (!is_node) throw new Error(`This file only supports nodejs`); - -const { setModuleImports, getAssemblyExports, getConfig, runMainAndExit } = await dotnet +const { setModuleImports, getAssemblyExports, getConfig } = await dotnet .withDiagnosticTracing(false) .create(); -setModuleImports("main.mjs", { +setModuleImports('main.mjs', { node: { process: { version: () => globalThis.process.version @@ -23,4 +20,4 @@ const exports = await getAssemblyExports(config.mainAssemblyName); const text = exports.MyClass.Greeting(); console.log(text); -await runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]); +await dotnet.run(); diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index bac3f856160c57..24f5817ef19bad 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -23,7 +23,7 @@ if (is_node && process.versions.node.split(".")[0] < 14) { throw new Error(`NodeJS at '${process.execPath}' has too low version '${process.versions.node}'`); } -if (typeof globalThis.crypto === 'undefined') { +if (!is_node && !is_browser && typeof globalThis.crypto === 'undefined') { // **NOTE** this is a simple insecure polyfill for testing purposes only // /dev/random doesn't work on js shells, so define our own // See library_fs.js:createDefaultDevices () diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 8ba45094fb98e2..cac849c58181ef 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -26,7 +26,6 @@ <_EmccCompileRspPath>$(NativeBinDir)src\emcc-compile.rsp <_EmccLinkRspPath>$(NativeBinDir)src\emcc-link.rsp false - $(RepoRoot)\src\native\libs\System.Security.Cryptography.Native.Browser @@ -62,7 +61,6 @@ - @@ -295,7 +293,6 @@ $(NativeBinDir)dotnet-legacy.d.ts; $(NativeBinDir)package.json; $(NativeBinDir)dotnet.wasm; - $(NativeBinDir)\src\dotnet-crypto-worker.js; $(NativeBinDir)dotnet.timezones.blat" DestinationFolder="$(MicrosoftNetCoreAppRuntimePackNativeDir)" SkipUnchangedFiles="true" /> diff --git a/src/native/corehost/apphost/static/CMakeLists.txt b/src/native/corehost/apphost/static/CMakeLists.txt index c37885bf5691ce..57681e9c54bd30 100644 --- a/src/native/corehost/apphost/static/CMakeLists.txt +++ b/src/native/corehost/apphost/static/CMakeLists.txt @@ -55,7 +55,9 @@ endif() if(CLR_CMAKE_TARGET_WIN32) list(APPEND SOURCES - ../apphost.windows.cpp) + ../apphost.windows.cpp + ${CLR_SRC_NATIVE_DIR}/libs/Common/delayloadhook_windows.cpp + ) list(APPEND HEADERS ../apphost.windows.h) @@ -65,8 +67,8 @@ if(CLR_CMAKE_TARGET_WIN32) add_linker_flag("/DEF:${CMAKE_CURRENT_SOURCE_DIR}/singlefilehost.def") else() - if(CLR_CMAKE_TARGET_OSX) - set(DEF_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/singlefilehost_OSXexports.src) + if(CLR_CMAKE_TARGET_FREEBSD) + set(DEF_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/singlefilehost_freebsdexports.src) else() set(DEF_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/singlefilehost_unixexports.src) endif() @@ -105,6 +107,9 @@ if(CLR_CMAKE_TARGET_WIN32) # Delay load libraries required for WinRT as that is not supported on all platforms add_linker_flag("/DELAYLOAD:api-ms-win-core-winrt-l1-1-0.dll") + + # Delay load version.dll so that we can specify how to search when loading it as it is not part of Windows' known DLLs + add_linker_flag("/DELAYLOAD:version.dll") endif() if(CLR_CMAKE_TARGET_WIN32) diff --git a/src/native/corehost/apphost/static/singlefilehost_OSXexports.src b/src/native/corehost/apphost/static/singlefilehost_freebsdexports.src similarity index 81% rename from src/native/corehost/apphost/static/singlefilehost_OSXexports.src rename to src/native/corehost/apphost/static/singlefilehost_freebsdexports.src index 18d5697e84580e..1f9c5178218555 100644 --- a/src/native/corehost/apphost/static/singlefilehost_OSXexports.src +++ b/src/native/corehost/apphost/static/singlefilehost_freebsdexports.src @@ -9,3 +9,7 @@ g_dacTable ; Used by profilers MetaDataGetDispenser + +; FreeBSD needs to reexport these +__progname +environ diff --git a/src/native/corehost/apphost/static/singlefilehost_unixexports.src b/src/native/corehost/apphost/static/singlefilehost_unixexports.src index 1f9c5178218555..18d5697e84580e 100644 --- a/src/native/corehost/apphost/static/singlefilehost_unixexports.src +++ b/src/native/corehost/apphost/static/singlefilehost_unixexports.src @@ -9,7 +9,3 @@ g_dacTable ; Used by profilers MetaDataGetDispenser - -; FreeBSD needs to reexport these -__progname -environ diff --git a/src/native/corehost/build.sh b/src/native/corehost/build.sh index ae44091c548926..f300b10f672f51 100755 --- a/src/native/corehost/build.sh +++ b/src/native/corehost/build.sh @@ -74,14 +74,13 @@ __LogsDir="$__RootBinDir/log" __MsbuildDebugLogsDir="$__LogsDir/MsbuildDebugLogs" # Set the remaining variables based upon the determined build configuration -__DistroRidLower="$(echo $__DistroRid | tr '[:upper:]' '[:lower:]')" -__BinDir="$__RootBinDir/bin/$__DistroRidLower.$__BuildType" -__IntermediatesDir="$__RootBinDir/obj/$__DistroRidLower.$__BuildType" +__BinDir="$__RootBinDir/bin/$__OutputRid.$__BuildType" +__IntermediatesDir="$__RootBinDir/obj/$__OutputRid.$__BuildType" export __BinDir __IntermediatesDir __RuntimeFlavor __CMakeArgs="-DCLI_CMAKE_HOST_VER=\"$__host_ver\" -DCLI_CMAKE_COMMON_HOST_VER=\"$__apphost_ver\" -DCLI_CMAKE_HOST_FXR_VER=\"$__fxr_ver\" $__CMakeArgs" -__CMakeArgs="-DCLI_CMAKE_HOST_POLICY_VER=\"$__policy_ver\" -DCLI_CMAKE_PKG_RID=\"$__DistroRid\" -DCLI_CMAKE_COMMIT_HASH=\"$__commit_hash\" $__CMakeArgs" +__CMakeArgs="-DCLI_CMAKE_HOST_POLICY_VER=\"$__policy_ver\" -DCLI_CMAKE_PKG_RID=\"$__OutputRid\" -DCLI_CMAKE_COMMIT_HASH=\"$__commit_hash\" $__CMakeArgs" __CMakeArgs="-DRUNTIME_FLAVOR=\"$__RuntimeFlavor\" $__CMakeArgs" __CMakeArgs="-DFEATURE_DISTRO_AGNOSTIC_SSL=$__PortableBuild $__CMakeArgs" diff --git a/src/native/corehost/coreclr_resolver.h b/src/native/corehost/coreclr_resolver.h index dc7ad889ff8480..81d6403c2879d6 100644 --- a/src/native/corehost/coreclr_resolver.h +++ b/src/native/corehost/coreclr_resolver.h @@ -9,6 +9,8 @@ using host_handle_t = void*; +typedef void (*coreclr_error_writer_callback_fn)(const char* line); + // Prototype of the coreclr_initialize function from coreclr.dll using coreclr_initialize_fn = pal::hresult_t(STDMETHODCALLTYPE*)( const char* exePath, @@ -19,6 +21,10 @@ using coreclr_initialize_fn = pal::hresult_t(STDMETHODCALLTYPE*)( host_handle_t* hostHandle, unsigned int* domainId); +// Prototype of the coreclr_set_error_writer function from coreclr.dll +using coreclr_set_error_writer_fn = pal::hresult_t(STDMETHODCALLTYPE*)( + coreclr_error_writer_callback_fn callBack); + // Prototype of the coreclr_shutdown function from coreclr.dll using coreclr_shutdown_fn = pal::hresult_t(STDMETHODCALLTYPE*)( host_handle_t hostHandle, @@ -46,6 +52,7 @@ using coreclr_create_delegate_fn = pal::hresult_t(STDMETHODCALLTYPE*)( struct coreclr_resolver_contract_t { pal::dll_t coreclr; + coreclr_set_error_writer_fn coreclr_set_error_writer; coreclr_shutdown_fn coreclr_shutdown; coreclr_initialize_fn coreclr_initialize; coreclr_execute_assembly_fn coreclr_execute_assembly; diff --git a/src/native/corehost/corehost.proj b/src/native/corehost/corehost.proj index 9ce1a640eadaa1..a320ab4d814aba 100644 --- a/src/native/corehost/corehost.proj +++ b/src/native/corehost/corehost.proj @@ -81,6 +81,7 @@ $(BuildArgs) -ninja $(BuildArgs) -runtimeflavor $(RuntimeFlavor) $(BuildArgs) /p:OfficialBuildId="$(OfficialBuildId)" + $(BuildArgs) -outputrid $(OutputRid) - diff --git a/src/tasks/WasmAppBuilder/EmccCompile.cs b/src/tasks/WasmAppBuilder/EmccCompile.cs index cec8460e6251fc..c09633303da203 100644 --- a/src/tasks/WasmAppBuilder/EmccCompile.cs +++ b/src/tasks/WasmAppBuilder/EmccCompile.cs @@ -244,7 +244,7 @@ ITaskItem CreateOutputItemFor(string srcFile, string objFile) } } - private bool ShouldCompile(string srcFile, string objFile, string[] depFiles, out string reason) + private static bool ShouldCompile(string srcFile, string objFile, string[] depFiles, out string reason) { if (!File.Exists(srcFile)) throw new LogAsErrorException($"Could not find source file {srcFile}"); @@ -271,8 +271,7 @@ bool IsNewerThanOutput(string inFile, string outFile, out string reason) { if (!File.Exists(inFile)) { - reason = $"Could not find dependency file {inFile} needed for compiling {srcFile} to {outFile}"; - Log.LogWarning(reason); + reason = $"the dependency file {inFile} needed for compiling {srcFile} to {outFile} could not be found."; return true; } diff --git a/src/tasks/WasmAppBuilder/IcallTableGenerator.cs b/src/tasks/WasmAppBuilder/IcallTableGenerator.cs index db530bd57bf826..802ec18190057c 100644 --- a/src/tasks/WasmAppBuilder/IcallTableGenerator.cs +++ b/src/tasks/WasmAppBuilder/IcallTableGenerator.cs @@ -3,9 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; @@ -23,8 +20,13 @@ internal sealed class IcallTableGenerator private Dictionary _runtimeIcalls = new Dictionary(); private TaskLoggingHelper Log { get; set; } + private readonly Func _fixupSymbolName; - public IcallTableGenerator(TaskLoggingHelper log) => Log = log; + public IcallTableGenerator(Func fixupSymbolName, TaskLoggingHelper log) + { + Log = log; + _fixupSymbolName = fixupSymbolName; + } // // Given the runtime generated icall table, and a set of assemblies, generate @@ -32,7 +34,7 @@ internal sealed class IcallTableGenerator // The runtime icall table should be generated using // mono --print-icall-table // - public IEnumerable Generate(string? runtimeIcallTableFile, string[] assemblies, string? outputPath) + public IEnumerable Generate(string? runtimeIcallTableFile, IEnumerable assemblies, string? outputPath) { _icalls.Clear(); _signatures.Clear(); @@ -42,9 +44,13 @@ public IEnumerable Generate(string? runtimeIcallTableFile, string[] asse var resolver = new PathAssemblyResolver(assemblies); using var mlc = new MetadataLoadContext(resolver, "System.Private.CoreLib"); - foreach (var aname in assemblies) + foreach (var asmPath in assemblies) { - var a = mlc.LoadFromAssemblyPath(aname); + if (!File.Exists(asmPath)) + throw new LogAsErrorException($"Cannot find assembly {asmPath}"); + + Log.LogMessage(MessageImportance.Low, $"Loading {asmPath} to scan for icalls"); + var a = mlc.LoadFromAssemblyPath(asmPath); foreach (var type in a.GetTypes()) ProcessType(type); } @@ -86,7 +92,7 @@ private void EmitTable(StreamWriter w) if (assembly == "System.Private.CoreLib") aname = "corlib"; else - aname = assembly.Replace(".", "_"); + aname = _fixupSymbolName(assembly); w.WriteLine($"#define ICALL_TABLE_{aname} 1\n"); w.WriteLine($"static int {aname}_icall_indexes [] = {{"); @@ -203,7 +209,7 @@ private void ProcessType(Type type) } catch (NotImplementedException nie) { - Log.LogWarning($"Failed to generate icall function for method '[{method.DeclaringType!.Assembly.GetName().Name}] {className}::{method.Name}'" + + Log.LogWarning(null, "WASM0001", "", "", 0, 0, 0, 0, $"Failed to generate icall function for method '[{method.DeclaringType!.Assembly.GetName().Name}] {className}::{method.Name}'" + $" because type '{nie.Message}' is not supported for parameter named '{par.Name}'. Ignoring."); return null; } diff --git a/src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs b/src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs index df48afaa52f84a..9b04d02c670f99 100644 --- a/src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs +++ b/src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs @@ -3,23 +3,18 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Reflection.PortableExecutable; using System.Text; -using System.Text.Json; -using System.Reflection; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -#nullable enable - public class ManagedToNativeGenerator : Task { [Required] - public string[]? Assemblies { get; set; } + public string[] Assemblies { get; set; } = Array.Empty(); public string? RuntimeIcallTableFile { get; set; } @@ -37,6 +32,11 @@ public class ManagedToNativeGenerator : Task [Output] public string[]? FileWrites { get; private set; } + private static readonly char[] s_charsToReplace = new[] { '.', '-', '+' }; + + // Avoid sharing this cache with all the invocations of this task throughout the build + private readonly Dictionary _symbolNameFixups = new(); + public override bool Execute() { if (Assemblies!.Length == 0) @@ -65,12 +65,14 @@ public override bool Execute() private void ExecuteInternal() { - var pinvoke = new PInvokeTableGenerator(Log); - var icall = new IcallTableGenerator(Log); + List managedAssemblies = FilterOutUnmanagedBinaries(Assemblies); + + var pinvoke = new PInvokeTableGenerator(FixupSymbolName, Log); + var icall = new IcallTableGenerator(FixupSymbolName, Log); IEnumerable cookies = Enumerable.Concat( - pinvoke.Generate(PInvokeModules, Assemblies!, PInvokeOutputPath!), - icall.Generate(RuntimeIcallTableFile, Assemblies!, IcallOutputPath) + pinvoke.Generate(PInvokeModules, managedAssemblies, PInvokeOutputPath!), + icall.Generate(RuntimeIcallTableFile, managedAssemblies, IcallOutputPath) ); var m2n = new InterpToNativeGenerator(Log); @@ -80,4 +82,76 @@ private void ExecuteInternal() ? new string[] { PInvokeOutputPath, IcallOutputPath, InterpToNativeOutputPath } : new string[] { PInvokeOutputPath, InterpToNativeOutputPath }; } + + public string FixupSymbolName(string name) + { + if (_symbolNameFixups.TryGetValue(name, out string? fixedName)) + return fixedName; + + UTF8Encoding utf8 = new(); + byte[] bytes = utf8.GetBytes(name); + StringBuilder sb = new(); + + foreach (byte b in bytes) + { + if ((b >= (byte)'0' && b <= (byte)'9') || + (b >= (byte)'a' && b <= (byte)'z') || + (b >= (byte)'A' && b <= (byte)'Z') || + (b == (byte)'_')) + { + sb.Append((char)b); + } + else if (s_charsToReplace.Contains((char)b)) + { + sb.Append('_'); + } + else + { + sb.Append($"_{b:X}_"); + } + } + + fixedName = sb.ToString(); + _symbolNameFixups[name] = fixedName; + return fixedName; + } + + private List FilterOutUnmanagedBinaries(string[] assemblies) + { + List managedAssemblies = new(assemblies.Length); + foreach (string asmPath in Assemblies) + { + if (!File.Exists(asmPath)) + throw new LogAsErrorException($"Cannot find assembly {asmPath}"); + + try + { + if (!IsManagedAssembly(asmPath)) + { + Log.LogMessage(MessageImportance.Low, $"Skipping unmanaged {asmPath}."); + continue; + } + } + catch (Exception ex) + { + Log.LogMessage(MessageImportance.Low, $"Failed to read assembly {asmPath}: {ex}"); + throw new LogAsErrorException($"Failed to read assembly {asmPath}: {ex.Message}"); + } + + managedAssemblies.Add(asmPath); + } + + return managedAssemblies; + } + + private static bool IsManagedAssembly(string filePath) + { + if (!File.Exists(filePath)) + return false; + + using FileStream fileStream = File.OpenRead(filePath); + using PEReader reader = new(fileStream, PEStreamOptions.Default); + return reader.HasMetadata; + } + } diff --git a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs index 254969f201233b..12836d3d5a71e5 100644 --- a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs +++ b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs @@ -13,14 +13,18 @@ internal sealed class PInvokeTableGenerator { - private static readonly char[] s_charsToReplace = new[] { '.', '-', '+' }; private readonly Dictionary _assemblyDisableRuntimeMarshallingAttributeCache = new(); private TaskLoggingHelper Log { get; set; } + private readonly Func _fixupSymbolName; - public PInvokeTableGenerator(TaskLoggingHelper log) => Log = log; + public PInvokeTableGenerator(Func fixupSymbolName, TaskLoggingHelper log) + { + Log = log; + _fixupSymbolName = fixupSymbolName; + } - public IEnumerable Generate(string[] pinvokeModules, string[] assemblies, string outputPath) + public IEnumerable Generate(string[] pinvokeModules, IEnumerable assemblies, string outputPath) { var modules = new Dictionary(); foreach (var module in pinvokeModules) @@ -33,9 +37,14 @@ public IEnumerable Generate(string[] pinvokeModules, string[] assemblies var resolver = new PathAssemblyResolver(assemblies); using var mlc = new MetadataLoadContext(resolver, "System.Private.CoreLib"); - foreach (var aname in assemblies) + + foreach (var asmPath in assemblies) { - var a = mlc.LoadFromAssemblyPath(aname); + if (!File.Exists(asmPath)) + throw new LogAsErrorException($"Cannot find assembly {asmPath}"); + + Log.LogMessage(MessageImportance.Low, $"Loading {asmPath} to scan for pinvokes"); + var a = mlc.LoadFromAssemblyPath(asmPath); foreach (var type in a.GetTypes()) CollectPInvokes(pinvokes, callbacks, signatures, type); } @@ -79,6 +88,22 @@ private void CollectPInvokes(List pinvokes, List callb } } + if (HasAttribute(type, "System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute")) + { + var method = type.GetMethod("Invoke"); + + if (method != null) + { + string? signature = SignatureMapper.MethodToSignature(method!); + if (signature == null) + throw new NotSupportedException($"Unsupported parameter type in method '{type.FullName}.{method.Name}'"); + + + Log.LogMessage(MessageImportance.Low, $"Adding pinvoke signature {signature} for method '{type.FullName}.{method.Name}'"); + signatures.Add(signature); + } + } + void CollectPInvokesForMethod(MethodInfo method) { if ((method.Attributes & MethodAttributes.PinvokeImpl) != 0) @@ -151,6 +176,29 @@ static bool MethodHasCallbackAttributes(MethodInfo method) } } + private static bool HasAttribute(MemberInfo element, params string[] attributeNames) + { + foreach (CustomAttributeData cattr in CustomAttributeData.GetCustomAttributes(element)) + { + try + { + for (int i = 0; i < attributeNames.Length; ++i) + { + if (cattr.AttributeType.FullName == attributeNames [i] || + cattr.AttributeType.Name == attributeNames[i]) + { + return true; + } + } + } + catch + { + // Assembly not found, ignore + } + } + return false; + } + private void EmitPInvokeTable(StreamWriter w, Dictionary modules, List pinvokes) { w.WriteLine("// GENERATED FILE, DO NOT MODIFY"); @@ -171,7 +219,7 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary modules string imports = string.Join(Environment.NewLine, candidates.Select( p => $" {p.Method} (in [{p.Method.DeclaringType?.Assembly.GetName().Name}] {p.Method.DeclaringType})")); - Log.LogWarning($"Found a native function ({first.EntryPoint}) with varargs in {first.Module}." + + Log.LogWarning(null, "WASM0001", "", "", 0, 0, 0, 0, $"Found a native function ({first.EntryPoint}) with varargs in {first.Module}." + " Calling such functions is not supported, and will fail at runtime." + $" Managed DllImports: {Environment.NewLine}{imports}"); @@ -195,14 +243,14 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary modules foreach (var module in modules.Keys) { - string symbol = ModuleNameToId(module) + "_imports"; + string symbol = _fixupSymbolName(module) + "_imports"; w.WriteLine("static PinvokeImport " + symbol + " [] = {"); var assemblies_pinvokes = pinvokes. Where(l => l.Module == module && !l.Skip). OrderBy(l => l.EntryPoint). GroupBy(d => d.EntryPoint). - Select(l => "{\"" + FixupSymbolName(l.Key) + "\", " + FixupSymbolName(l.Key) + "}, " + + Select(l => "{\"" + _fixupSymbolName(l.Key) + "\", " + _fixupSymbolName(l.Key) + "}, " + "// " + string.Join(", ", l.Select(c => c.Method.DeclaringType!.Module!.Assembly!.GetName()!.Name!).Distinct().OrderBy(n => n))); foreach (var pinvoke in assemblies_pinvokes) @@ -216,7 +264,7 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary modules w.Write("static void *pinvoke_tables[] = { "); foreach (var module in modules.Keys) { - string symbol = ModuleNameToId(module) + "_imports"; + string symbol = _fixupSymbolName(module) + "_imports"; w.Write(symbol + ","); } w.WriteLine("};"); @@ -227,18 +275,6 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary modules } w.WriteLine("};"); - static string ModuleNameToId(string name) - { - if (name.IndexOfAny(s_charsToReplace) < 0) - return name; - - string fixedName = name; - foreach (char c in s_charsToReplace) - fixedName = fixedName.Replace(c, '_'); - - return fixedName; - } - static bool ShouldTreatAsVariadic(PInvoke[] candidates) { if (candidates.Length < 2) @@ -256,35 +292,7 @@ static bool ShouldTreatAsVariadic(PInvoke[] candidates) } } - private static string FixupSymbolName(string name) - { - UTF8Encoding utf8 = new(); - byte[] bytes = utf8.GetBytes(name); - StringBuilder sb = new(); - - foreach (byte b in bytes) - { - if ((b >= (byte)'0' && b <= (byte)'9') || - (b >= (byte)'a' && b <= (byte)'z') || - (b >= (byte)'A' && b <= (byte)'Z') || - (b == (byte)'_')) - { - sb.Append((char)b); - } - else if (s_charsToReplace.Contains((char)b)) - { - sb.Append('_'); - } - else - { - sb.Append($"_{b:X}_"); - } - } - - return sb.ToString(); - } - - private static string SymbolNameForMethod(MethodInfo method) + private string SymbolNameForMethod(MethodInfo method) { StringBuilder sb = new(); Type? type = method.DeclaringType; @@ -292,7 +300,7 @@ private static string SymbolNameForMethod(MethodInfo method) sb.Append($"{(type!.IsNested ? type!.FullName : type!.Name)}_"); sb.Append(method.Name); - return FixupSymbolName(sb.ToString()); + return _fixupSymbolName(sb.ToString()); } private static string MapType(Type t) => t.Name switch @@ -335,7 +343,7 @@ private static bool TryIsMethodGetParametersUnsupported(MethodInfo method, [NotN { // FIXME: System.Reflection.MetadataLoadContext can't decode function pointer types // https://github.com/dotnet/runtime/issues/43791 - sb.Append($"int {FixupSymbolName(pinvoke.EntryPoint)} (int, int, int, int, int);"); + sb.Append($"int {_fixupSymbolName(pinvoke.EntryPoint)} (int, int, int, int, int);"); return sb.ToString(); } @@ -351,7 +359,7 @@ private static bool TryIsMethodGetParametersUnsupported(MethodInfo method, [NotN } sb.Append(MapType(method.ReturnType)); - sb.Append($" {FixupSymbolName(pinvoke.EntryPoint)} ("); + sb.Append($" {_fixupSymbolName(pinvoke.EntryPoint)} ("); int pindex = 0; var pars = method.GetParameters(); foreach (var p in pars) @@ -365,7 +373,7 @@ private static bool TryIsMethodGetParametersUnsupported(MethodInfo method, [NotN return sb.ToString(); } - private static void EmitNativeToInterp(StreamWriter w, ref List callbacks) + private void EmitNativeToInterp(StreamWriter w, ref List callbacks) { // Generate native->interp entry functions // These are called by native code, so they need to obtain @@ -411,7 +419,7 @@ private static void EmitNativeToInterp(StreamWriter w, ref List bool is_void = method.ReturnType.Name == "Void"; - string module_symbol = method.DeclaringType!.Module!.Assembly!.GetName()!.Name!.Replace(".", "_"); + string module_symbol = _fixupSymbolName(method.DeclaringType!.Module!.Assembly!.GetName()!.Name!); uint token = (uint)method.MetadataToken; string class_name = method.DeclaringType.Name; string method_name = method.Name; @@ -478,7 +486,7 @@ private static void EmitNativeToInterp(StreamWriter w, ref List foreach (var cb in callbacks) { var method = cb.Method; - string module_symbol = method.DeclaringType!.Module!.Assembly!.GetName()!.Name!.Replace(".", "_"); + string module_symbol = _fixupSymbolName(method.DeclaringType!.Module!.Assembly!.GetName()!.Name!); string class_name = method.DeclaringType.Name; string method_name = method.Name; w.WriteLine($"\"{module_symbol}_{class_name}_{method_name}\","); diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index de43aee1d97361..2fead7b9860bfd 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -109,11 +109,6 @@ private sealed class WasmEntry : AssetEntry public WasmEntry(string name) : base(name, "dotnetwasm") { } } - private sealed class CryptoWorkerEntry : AssetEntry - { - public CryptoWorkerEntry(string name) : base(name, "js-module-crypto") { } - } - private sealed class ThreadsWorkerEntry : AssetEntry { public ThreadsWorkerEntry(string name) : base(name, "js-module-threads") { } @@ -258,7 +253,7 @@ private bool ExecuteInternal () string fullPath = assembly.GetMetadata("Identity"); if (string.IsNullOrEmpty(culture)) { - Log.LogWarning($"Missing CultureName metadata for satellite assembly {fullPath}"); + Log.LogWarning(null, "WASM0002", "", "", 0, 0, 0, 0, $"Missing CultureName metadata for satellite assembly {fullPath}"); continue; } // FIXME: validate the culture? @@ -295,7 +290,7 @@ private bool ExecuteInternal () if (firstPath == secondPath) { - Log.LogWarning($"Found identical vfs mappings for target path: {targetPath}, source file: {firstPath}. Ignoring."); + Log.LogWarning(null, "WASM0003", "", "", 0, 0, 0, 0, $"Found identical vfs mappings for target path: {targetPath}, source file: {firstPath}. Ignoring."); continue; } @@ -320,7 +315,6 @@ private bool ExecuteInternal () config.Assets.Add(new VfsEntry ("dotnet.timezones.blat") { VirtualPath = "/usr/share/zoneinfo/"}); config.Assets.Add(new WasmEntry ("dotnet.wasm") ); - config.Assets.Add(new CryptoWorkerEntry ("dotnet-crypto-worker.js") ); if (IncludeThreadsWorker) config.Assets.Add(new ThreadsWorkerEntry ("dotnet.worker.js") ); diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj index 4deea6aa434e08..c18167fcd21494 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj @@ -20,12 +20,6 @@ - - - - - diff --git a/src/tasks/WasmAppBuilder/WasmLoadAssembliesAndReferences.cs b/src/tasks/WasmAppBuilder/WasmLoadAssembliesAndReferences.cs index de765e54093aee..e124835b45a99a 100644 --- a/src/tasks/WasmAppBuilder/WasmLoadAssembliesAndReferences.cs +++ b/src/tasks/WasmAppBuilder/WasmLoadAssembliesAndReferences.cs @@ -76,7 +76,7 @@ private bool AddAssemblyAndReferences(MetadataLoadContext mlc, Assembly assembly { if (SkipMissingAssemblies) { - Log.LogWarning($"Loading assembly reference '{aname}' for '{assembly.GetName()}' failed: {ex.Message} Skipping."); + Log.LogWarning(null, "WASM0004", "", "", 0, 0, 0, 0, $"Loading assembly reference '{aname}' for '{assembly.GetName()}' failed: {ex.Message} Skipping."); } else { diff --git a/src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs b/src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs index 2fd3597425e343..18dce4143225a6 100644 --- a/src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs +++ b/src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs @@ -20,7 +20,10 @@ namespace Microsoft.Workload.Build.Tasks public class InstallWorkloadFromArtifacts : Task { [Required, NotNull] - public ITaskItem[] WorkloadIds { get; set; } = Array.Empty(); + public ITaskItem[] WorkloadIds { get; set; } = Array.Empty(); + + [Required, NotNull] + public ITaskItem[] InstallTargets { get; set; } = Array.Empty(); [Required, NotNull] public string? VersionBand { get; set; } @@ -32,22 +35,73 @@ public class InstallWorkloadFromArtifacts : Task public string? TemplateNuGetConfigPath { get; set; } [Required, NotNull] - public string? SdkDir { get; set; } + public string SdkWithNoWorkloadInstalledPath { get; set; } = string.Empty; public bool OnlyUpdateManifests{ get; set; } private const string s_nugetInsertionTag = ""; + private string AllManifestsStampPath => Path.Combine(SdkWithNoWorkloadInstalledPath, ".all-manifests.stamp"); public override bool Execute() { try { - foreach (var workloadIdItem in WorkloadIds) + if (!Directory.Exists(SdkWithNoWorkloadInstalledPath)) + throw new LogAsErrorException($"Cannot find {nameof(SdkWithNoWorkloadInstalledPath)}={SdkWithNoWorkloadInstalledPath}"); + + if (!Directory.Exists(LocalNuGetsPath)) + throw new LogAsErrorException($"Cannot find {nameof(LocalNuGetsPath)}={LocalNuGetsPath} . " + + "Set it to the Shipping packages directory in artifacts."); + + if (!InstallAllManifests()) + return false; + + if (OnlyUpdateManifests) + return !Log.HasLoggedErrors; + + InstallWorkloadRequest[] selectedRequests = InstallTargets + .SelectMany(workloadToInstall => + { + if (!HasMetadata(workloadToInstall, nameof(workloadToInstall), "Variants", Log)) + throw new LogAsErrorException($"Missing Variants metadata on item '{workloadToInstall.ItemSpec}'"); + + return workloadToInstall + .GetMetadata("Variants") + .Split(";", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(v => (variant: v, target: workloadToInstall)); + }) + .SelectMany(w => + { + IEnumerable workloads = WorkloadIds.Where(wi => wi.GetMetadata("Variant") == w.variant) + .Select(wi => new InstallWorkloadRequest(wi, w.target)); + return workloads.Any() + ? workloads + : throw new LogAsErrorException($"Could not find any workload variant named '{w.variant}'"); + }).ToArray(); + + foreach (InstallWorkloadRequest req in selectedRequests) { - if (!ExecuteInternal(workloadIdItem)) + if (Directory.Exists(req.TargetPath)) + { + Log.LogMessage(MessageImportance.Low, $"Deleting directory {req.TargetPath}"); + Directory.Delete(req.TargetPath, recursive: true); + } + } + + string cachePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + foreach (InstallWorkloadRequest req in selectedRequests) + { + Log.LogMessage(MessageImportance.High, $"** Installing workload {req.WorkloadId} in {req.TargetPath} **"); + if (!req.Validate(Log)) return false; + + if (!ExecuteInternal(req) && !req.IgnoreErrors) + return false; + + File.WriteAllText(req.StampPath, string.Empty); } - return true; + + return !Log.HasLoggedErrors; } catch (LogAsErrorException laee) { @@ -56,73 +110,111 @@ public override bool Execute() } } - private bool ExecuteInternal(ITaskItem workloadId) + private bool ExecuteInternal(InstallWorkloadRequest req) { - if (!HasMetadata(workloadId, nameof(workloadId), "Version") || - !HasMetadata(workloadId, nameof(workloadId), "ManifestName")) + if (!File.Exists(TemplateNuGetConfigPath)) { + Log.LogError($"Cannot find TemplateNuGetConfigPath={TemplateNuGetConfigPath}"); return false; } - if (!Directory.Exists(SdkDir)) - { - Log.LogError($"Cannot find SdkDir={SdkDir}"); + Log.LogMessage(MessageImportance.Low, $"Duplicating {SdkWithNoWorkloadInstalledPath} into {req.TargetPath}"); + Utils.DirectoryCopy(SdkWithNoWorkloadInstalledPath, req.TargetPath); + + string nugetConfigContents = GetNuGetConfig(); + if (!InstallPacks(req, nugetConfigContents)) return false; - } - if (!File.Exists(TemplateNuGetConfigPath)) + UpdateAppRef(req.TargetPath, req.Version); + + return !Log.HasLoggedErrors; + } + + private bool InstallAllManifests() + { + var allManifestPkgs = Directory.EnumerateFiles(LocalNuGetsPath, "*Manifest*nupkg"); + if (!AnyInputsNewerThanOutput(AllManifestsStampPath, allManifestPkgs)) { - Log.LogError($"Cannot find TemplateNuGetConfigPath={TemplateNuGetConfigPath}"); - return false; + Log.LogMessage(MessageImportance.Low, + $"Skipping installing manifests because the {AllManifestsStampPath} " + + $"is newer than packages {string.Join(',', allManifestPkgs)}."); + return true; } - Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** Installing workload manifest {workloadId.ItemSpec} **{Environment.NewLine}"); - string nugetConfigContents = GetNuGetConfig(); - if (!InstallWorkloadManifest(workloadId, workloadId.GetMetadata("ManifestName"), workloadId.GetMetadata("Version"), nugetConfigContents, stopOnMissing: true)) - return false; + HashSet manifestsInstalled = new(); + foreach (ITaskItem workload in WorkloadIds) + { + InstallWorkloadRequest req = new(workload, new TaskItem()); - if (OnlyUpdateManifests) - return !Log.HasLoggedErrors; + if (manifestsInstalled.Contains(req.ManifestName)) + { + Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** Manifests for workload {req.WorkloadId} are already installed **{Environment.NewLine}"); + continue; + } - if (!InstallPacks(workloadId, nugetConfigContents)) - return false; + if (string.IsNullOrEmpty(req.Version)) + { + Log.LogError($"No Version set for workload manifest {req.ManifestName} in workload install requests."); + return false; + } - UpdateAppRef(workloadId.GetMetadata("Version")); + Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** Installing manifests for workload {req.WorkloadId} **"); + if (!InstallWorkloadManifest(workload, + req.ManifestName, + req.Version, + SdkWithNoWorkloadInstalledPath, + nugetConfigContents, + stopOnMissing: true)) + { + return false; + } - return !Log.HasLoggedErrors; + manifestsInstalled.Add(req.ManifestName); + } + + File.WriteAllText(AllManifestsStampPath, string.Empty); + + return true; } - private bool InstallPacks(ITaskItem workloadId, string nugetConfigContents) + private bool InstallPacks(InstallWorkloadRequest req, string nugetConfigContents) { string nugetConfigPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); File.WriteAllText(nugetConfigPath, nugetConfigContents); - Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** workload install **{Environment.NewLine}"); + // Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** dotnet workload install {req.WorkloadId} **{Environment.NewLine}"); (int exitCode, string output) = Utils.TryRunProcess( Log, - Path.Combine(SdkDir, "dotnet"), - $"workload install --skip-manifest-update --no-cache --configfile \"{nugetConfigPath}\" {workloadId.ItemSpec}", + Path.Combine(req.TargetPath, "dotnet"), + $"workload install --skip-sign-check --skip-manifest-update --no-cache --configfile \"{nugetConfigPath}\" {req.WorkloadId}", workingDir: Path.GetTempPath(), silent: false, + logStdErrAsMessage: req.IgnoreErrors, debugMessageImportance: MessageImportance.High); if (exitCode != 0) { - Log.LogError($"workload install failed with exit code {exitCode}: {output}"); - - foreach (string dir in Directory.EnumerateDirectories(Path.Combine(SdkDir, "sdk-manifests"), "*", SearchOption.AllDirectories)) - Log.LogMessage(MessageImportance.Low, $"\t{Path.Combine(SdkDir, "sdk-manifests", dir)}"); + if (req.IgnoreErrors) + { + Log.LogMessage(MessageImportance.High, + $"{Environment.NewLine} ** Ignoring workload installation failure exit code {exitCode}. **{Environment.NewLine}"); + } + else + { + Log.LogError($"workload install failed with exit code {exitCode}: {output}"); + } - foreach (string dir in Directory.EnumerateDirectories(Path.Combine(SdkDir, "packs"), "*", SearchOption.AllDirectories)) - Log.LogMessage(MessageImportance.Low, $"\t{Path.Combine(SdkDir, "packs", dir)}"); + foreach (string dir in Directory.EnumerateDirectories(Path.Combine(req.TargetPath, "sdk-manifests"), "*", SearchOption.AllDirectories)) + Log.LogMessage(MessageImportance.Low, $"\t{Path.Combine(req.TargetPath, "sdk-manifests", dir)}"); - return false; + foreach (string dir in Directory.EnumerateDirectories(Path.Combine(req.TargetPath, "packs"), "*", SearchOption.AllDirectories)) + Log.LogMessage(MessageImportance.Low, $"\t{Path.Combine(req.TargetPath, "packs", dir)}"); } return !Log.HasLoggedErrors; } - private void UpdateAppRef(string version) + private void UpdateAppRef(string sdkPath, string version) { Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** Updating Targeting pack **{Environment.NewLine}"); @@ -131,7 +223,7 @@ private void UpdateAppRef(string version) throw new LogAsErrorException($"Could not find {pkgPath} needed to update the targeting pack to the newly built one." + " Make sure to build the subset `packs`, like `./build.sh -os browser -s mono+libs+packs`."); - string packDir = Path.Combine(SdkDir, "packs", "Microsoft.NETCore.App.Ref"); + string packDir = Path.Combine(sdkPath, "packs", "Microsoft.NETCore.App.Ref"); string[] dirs = Directory.EnumerateDirectories(packDir).ToArray(); if (dirs.Length != 1) throw new LogAsErrorException($"Expected to find exactly one versioned directory under {packDir}, but got " + @@ -150,28 +242,28 @@ private void UpdateAppRef(string version) private string GetNuGetConfig() { string contents = File.ReadAllText(TemplateNuGetConfigPath); - if (contents.IndexOf(s_nugetInsertionTag) < 0) + if (contents.IndexOf(s_nugetInsertionTag, StringComparison.InvariantCultureIgnoreCase) < 0) throw new LogAsErrorException($"Could not find {s_nugetInsertionTag} in {TemplateNuGetConfigPath}"); return contents.Replace(s_nugetInsertionTag, $@""); } - private bool InstallWorkloadManifest(ITaskItem workloadId, string name, string version, string nugetConfigContents, bool stopOnMissing) + private bool InstallWorkloadManifest(ITaskItem workloadId, string name, string version, string sdkDir, string nugetConfigContents, bool stopOnMissing) { - Log.LogMessage(MessageImportance.High, $"Installing workload manifest for {name}/{version}"); + Log.LogMessage(MessageImportance.High, $" ** Installing manifest: {name}/{version}"); // Find any existing directory with the manifest name, ignoring the case // Multiple directories for a manifest, differing only in case causes // workload install to fail due to duplicate manifests! // This is applicable only on case-sensitive filesystems - string outputDir = FindSubDirIgnoringCase(Path.Combine(SdkDir, "sdk-manifests", VersionBand), name); + string outputDir = FindSubDirIgnoringCase(Path.Combine(sdkDir, "sdk-manifests", VersionBand), name); PackageReference pkgRef = new(Name: $"{name}.Manifest-{VersionBand}", Version: version, OutputDir: outputDir, relativeSourceDir: "data"); - if (!PackageInstaller.Install(new[]{ pkgRef }, nugetConfigContents, Log, stopOnMissing)) + if (!PackageInstaller.Install(new[] { pkgRef }, nugetConfigContents, Log, stopOnMissing)) return false; string manifestDir = pkgRef.OutputDir; @@ -209,7 +301,7 @@ private bool InstallWorkloadManifest(ITaskItem workloadId, string name, string v { foreach ((string depName, string depVersion) in manifest.DependsOn) { - if (!InstallWorkloadManifest(workloadId, depName, depVersion, nugetConfigContents, stopOnMissing: false)) + if (!InstallWorkloadManifest(workloadId, depName, depVersion, sdkDir, nugetConfigContents, stopOnMissing: false)) { Log.LogWarning($"Could not install manifest {depName}/{depVersion}. This can be ignored if the workload {workloadId.ItemSpec} doesn't depend on it."); continue; @@ -220,31 +312,35 @@ private bool InstallWorkloadManifest(ITaskItem workloadId, string name, string v return true; } - private bool HasMetadata(ITaskItem item, string itemName, string metadataName) + private static bool HasMetadata(ITaskItem item, string itemName, string metadataName, TaskLoggingHelper log) { if (!string.IsNullOrEmpty(item.GetMetadata(metadataName))) return true; - Log.LogError($"{itemName} item ({item.ItemSpec}) is missing Name metadata"); + log.LogError($"{itemName} item ({item.ItemSpec}) is missing {metadataName} metadata"); return false; } private string FindSubDirIgnoringCase(string parentDir, string dirName) { - IEnumerable matchingDirs = Directory.EnumerateDirectories(parentDir, + string[] matchingDirs = Directory.EnumerateDirectories(parentDir, dirName, - new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }); + new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }) + .ToArray(); string? first = matchingDirs.FirstOrDefault(); - if (matchingDirs.Count() > 1) + if (matchingDirs.Length > 1) { - Log.LogWarning($"Found multiple directories with names that differ only in case. {string.Join(", ", matchingDirs.ToArray())}" + Log.LogWarning($"Found multiple directories with names that differ only in case. {string.Join(", ", matchingDirs)}" + $"{Environment.NewLine}Using the first one: {first}"); } return first ?? Path.Combine(parentDir, dirName.ToLower(CultureInfo.InvariantCulture)); } + private static bool AnyInputsNewerThanOutput(string output, IEnumerable inputs) + => inputs.Any(i => Utils.IsNewerThan(i, output)); + private sealed record ManifestInformation( object Version, string Description, @@ -272,6 +368,36 @@ private sealed record PackVersionInformation( [property: JsonPropertyName("alias-to")] Dictionary AliasTo ); + + internal sealed record InstallWorkloadRequest( + ITaskItem Workload, + ITaskItem Target) + { + public string ManifestName => Workload.GetMetadata("ManifestName"); + public string Version => Workload.GetMetadata("Version"); + public string TargetPath => Target.GetMetadata("InstallPath"); + public string StampPath => Target.GetMetadata("StampPath"); + public bool IgnoreErrors => Workload.GetMetadata("IgnoreErrors").ToLowerInvariant() == "true"; + public string WorkloadId => Workload.ItemSpec; + + public bool Validate(TaskLoggingHelper log) + { + if (!HasMetadata(Workload, nameof(Workload), "Version", log) || + !HasMetadata(Workload, nameof(Workload), "ManifestName", log) || + !HasMetadata(Target, nameof(Target), "InstallPath", log)) + { + return false; + } + + if (string.IsNullOrEmpty(TargetPath)) + { + log.LogError($"InstallPath is empty for workload {Workload.ItemSpec}"); + return false; + } + + return true; + } + } } internal sealed record PackageReference(string Name, diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs index f034d2fcc40a8f..09bbcf8e1d0245 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs @@ -21,12 +21,12 @@ public BlazorWasmBuildPublishTests(ITestOutputHelper output, SharedBuildPerTestC _enablePerTestCleanup = true; } - [Theory] + [Theory, TestCategory("no-workload")] [InlineData("Debug")] [InlineData("Release")] public void DefaultTemplate_WithoutWorkload(string config) { - string id = $"blz_no_workload_{config}"; + string id = $"blz_no_workload_{config}{s_unicodeChar}"; CreateBlazorWasmTemplateProject(id); // Build @@ -41,6 +41,7 @@ public void DefaultTemplate_WithoutWorkload(string config) [Theory] [InlineData("Debug")] [InlineData("Release")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/77740")] public void DefaultTemplate_NoAOT_WithWorkload(string config) { string id = $"blz_no_aot_{config}"; @@ -83,6 +84,7 @@ public void DefaultTemplate_AOT_InProjectFile(string config) [InlineData("Release", true)] [InlineData("Release", false)] [ActiveIssue("https://github.com/dotnet/runtime/issues/70985", TestPlatforms.Linux)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/77740")] public void NativeBuild_WithDeployOnBuild_UsedByVS(string config, bool nativeRelink) { string id = $"blz_deploy_on_build_{config}_{nativeRelink}"; @@ -163,6 +165,7 @@ public void WithNativeReference_AOTInProjectFile(string config) [InlineData("Debug")] [InlineData("Release")] [ActiveIssue("https://github.com/dotnet/runtime/issues/70985", TestPlatforms.Linux)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/77740")] public void WithNativeReference_AOTOnCommandLine(string config) { string id = $"blz_nativeref_aot_{config}"; @@ -179,6 +182,7 @@ public void WithNativeReference_AOTOnCommandLine(string config) [Theory] [InlineData("Debug")] [InlineData("Release")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/77740")] public void WithDllImportInMainAssembly(string config) { // Based on https://github.com/dotnet/runtime/issues/59255 @@ -231,6 +235,7 @@ void CheckNativeFileLinked(bool forPublish) } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/77740")] public void BugRegression_60479_WithRazorClassLib() { string id = "blz_razor_lib_top"; diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs index 1641c7860d5b67..1cba4abf637876 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs @@ -19,6 +19,7 @@ public BlazorWasmTests(ITestOutputHelper output, SharedBuildPerTestClassFixture [Theory, TestCategory("no-workload")] [InlineData("Debug")] [InlineData("Release")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/77740")] public void NativeRef_EmitsWarningBecauseItRequiresWorkload(string config) { CommandResult res = PublishForRequiresWorkloadTest(config, extraItems: ""); diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BrowserRunner.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BrowserRunner.cs index c39818f033d66b..13a4ff96ab761a 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BrowserRunner.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BrowserRunner.cs @@ -77,12 +77,12 @@ public async Task RunAsync(ToolCommand cmd, string args, bool headless = var url = new Uri(urlAvailable.Task.Result); Playwright = await Microsoft.Playwright.Playwright.CreateAsync(); + string[] chromeArgs = new[] { $"--explicitly-allowed-ports={url.Port}" }; + Console.WriteLine($"Launching chrome ('{s_chromePath.Value}') via playwright with args = {string.Join(',', chromeArgs)}"); Browser = await Playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions{ ExecutablePath = s_chromePath.Value, Headless = headless, - Args = OperatingSystem.IsWindows() - ? new[] { $"--explicitly-allowed-ports={url.Port}" } - : Array.Empty() + Args = chromeArgs }); IPage page = await Browser.NewPageAsync(); diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs index c119db17c282cf..8ce212003ab122 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; using System.Runtime.InteropServices; #nullable enable @@ -25,6 +24,7 @@ public class BuildEnvironment public string LogRootPath { get; init; } public string WorkloadPacksDir { get; init; } + public string BuiltNuGetsPath { get; init; } public static readonly string RelativeTestAssetsPath = @"..\testassets\"; public static readonly string TestAssetsPath = Path.Combine(AppContext.BaseDirectory, "testassets"); @@ -51,7 +51,7 @@ public BuildEnvironment() "..", "..", "..", - "dotnet-workload"); + "dotnet-net7"); if (Directory.Exists(probePath)) sdkForWorkloadPath = Path.GetFullPath(probePath); else @@ -99,6 +99,11 @@ public BuildEnvironment() DirectoryBuildTargetsContents = s_directoryBuildTargetsForLocal; } + if (EnvironmentVariables.BuiltNuGetsPath is null || !Directory.Exists(EnvironmentVariables.BuiltNuGetsPath)) + throw new Exception($"Cannot find 'BUILT_NUGETS_PATH={EnvironmentVariables.BuiltNuGetsPath}'"); + + BuiltNuGetsPath = EnvironmentVariables.BuiltNuGetsPath; + // `runtime` repo's build environment sets these, and they // mess up the build for the test project, which is using a different // dotnet @@ -112,6 +117,12 @@ public BuildEnvironment() // helps with debugging EnvVars["WasmNativeStrip"] = "false"; + // Works around an issue in msbuild due to which + // second, and subsequent builds fail without any details + // in the logs + EnvVars["DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER"] = "1"; + DefaultBuildArgs += " /nr:false"; + if (OperatingSystem.IsWindows()) { EnvVars["WasmCachePath"] = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildPublishTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildPublishTests.cs index c798c79cd041fc..66fed9a630d16b 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildPublishTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildPublishTests.cs @@ -25,7 +25,7 @@ public BuildPublishTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur [BuildAndRun(host: RunHost.Chrome, aot: false, config: "Debug")] public void BuildThenPublishNoAOT(BuildArgs buildArgs, RunHost host, string id) { - string projectName = $"build_publish_{buildArgs.Config}"; + string projectName = $"build_publish_{buildArgs.Config}_{s_unicodeChar}"; buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs); diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs index a2fd1c3c247cea..4805396442a6c8 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs @@ -29,6 +29,7 @@ public abstract class BuildTestBase : IClassFixture? envVars = null, string targetFramework = DefaultTargetFramework, string? extraXHarnessMonoArgs = null, + string? extraXHarnessArgs = null, string jsRelativePath = "test-main.js") { buildDir ??= _projectDir; @@ -158,13 +161,15 @@ protected string RunAndTestWasmApp(BuildArgs buildArgs, throw new InvalidOperationException("Running tests with V8 on windows isn't supported"); // Use wasm-console.log to get the xharness output for non-browser cases - (string testCommand, string extraXHarnessArgs, bool useWasmConsoleOutput) = host switch + (string testCommand, string xharnessArgs, bool useWasmConsoleOutput) = host switch { RunHost.V8 => ("wasm test", $"--js-file={jsRelativePath} --engine=V8 -v trace", true), RunHost.NodeJS => ("wasm test", $"--js-file={jsRelativePath} --engine=NodeJS -v trace", true), _ => ("wasm test-browser", $"-v trace -b {host} --web-server-use-cop", false) }; + extraXHarnessArgs += " " + xharnessArgs; + string testLogPath = Path.Combine(_logPath, host.ToString()); string output = RunWithXHarness( testCommand, @@ -213,6 +218,16 @@ protected static string RunWithXHarness(string testCommand, string testLogPath, args.Append($" --expected-exit-code={expectedAppExitCode}"); args.Append($" {extraXHarnessArgs ?? string.Empty}"); + if (File.Exists("/.dockerenv")) + args.Append(" --browser-arg=--no-sandbox"); + + if (!string.IsNullOrEmpty(EnvironmentVariables.BrowserPathForTests)) + { + if (!File.Exists(EnvironmentVariables.BrowserPathForTests)) + throw new Exception($"Cannot find BROWSER_PATH_FOR_TESTS={EnvironmentVariables.BrowserPathForTests}"); + args.Append($" --browser-path=\"{EnvironmentVariables.BrowserPathForTests}\""); + } + args.Append(" -- "); if (extraXHarnessMonoArgs != null) { @@ -325,7 +340,8 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp { _testOutput.WriteLine ($"Using existing build found at {product.ProjectDir}, with build log at {product.LogFile}"); - Assert.True(product.Result, $"Found existing build at {product.ProjectDir}, but it had failed. Check build log at {product.LogFile}"); + if (!product.Result) + throw new XunitException($"Found existing build at {product.ProjectDir}, but it had failed. Check build log at {product.LogFile}"); _projectDir = product.ProjectDir; // use this test's id for the run logs @@ -359,7 +375,6 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp string logFilePath = Path.Combine(_logPath, $"{buildArgs.ProjectName}{logFileSuffix}.binlog"); _testOutput.WriteLine($"-------- Building ---------"); _testOutput.WriteLine($"Binlog path: {logFilePath}"); - _testOutput.WriteLine($"Binlog path: {logFilePath}"); sb.Append($" /bl:\"{logFilePath}\" /nologo"); sb.Append($" /v:{options.Verbosity ?? "minimal"}"); if (buildArgs.ExtraBuildArgs != null) @@ -410,12 +425,25 @@ public void InitBlazorWasmProjectDir(string id) Directory.CreateDirectory(_projectDir); Directory.CreateDirectory(Path.Combine(_projectDir, ".nuget")); - File.Copy(Path.Combine(BuildEnvironment.TestDataPath, NuGetConfigFileNameForDefaultFramework), Path.Combine(_projectDir, "nuget.config")); + File.WriteAllText(Path.Combine(_projectDir, "nuget.config"), + GetNuGetConfigWithLocalPackagesPath( + Path.Combine(BuildEnvironment.TestDataPath, NuGetConfigFileNameForDefaultFramework), + s_buildEnv.BuiltNuGetsPath)); + File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Directory.Build.props"), Path.Combine(_projectDir, "Directory.Build.props")); File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Directory.Build.targets"), Path.Combine(_projectDir, "Directory.Build.targets")); } - public string CreateWasmTemplateProject(string id, string template = "wasmbrowser") + private static string GetNuGetConfigWithLocalPackagesPath(string templatePath, string localNuGetsPath) + { + string contents = File.ReadAllText(templatePath); + if (contents.IndexOf(s_nugetInsertionTag, StringComparison.InvariantCultureIgnoreCase) < 0) + throw new Exception($"Could not find {s_nugetInsertionTag} in {templatePath}"); + + return contents.Replace(s_nugetInsertionTag, $@""); + } + + public string CreateWasmTemplateProject(string id, string template = "wasmbrowser", string extraArgs = "") { InitPaths(id); InitProjectDir(id); @@ -432,7 +460,7 @@ public string CreateWasmTemplateProject(string id, string template = "wasmbrowse new DotNetCommand(s_buildEnv, _testOutput, useDefaultArgs: false) .WithWorkingDirectory(_projectDir!) - .ExecuteWithCapturedOutput($"new {template}") + .ExecuteWithCapturedOutput($"new {template} {extraArgs}") .EnsureSuccessful(); return Path.Combine(_projectDir!, $"{id}.csproj"); @@ -488,6 +516,7 @@ public string CreateBlazorWasmTemplateProject(string id) $"-bl:{logPath}", $"-p:Configuration={config}", "-p:BlazorEnableCompression=false", + "-nr:false", setWasmDevel ? "-p:_WasmDevel=true" : string.Empty }.Concat(extraArgs).ToArray(); @@ -552,8 +581,7 @@ protected static void AssertBasicAppBundle(string bundleDir, string projectName, "dotnet.timezones.blat", "dotnet.wasm", "mono-config.json", - "dotnet.js", - "dotnet-crypto-worker.js" + "dotnet.js" }); AssertFilesExist(bundleDir, new[] { "run-v8.sh" }, expectToExist: hasV8Script); @@ -636,10 +664,10 @@ protected static void AssertFile(string file0, string file1, string? label=null, protected (int exitCode, string buildOutput) AssertBuild(string args, string label="build", bool expectSuccess=true, IDictionary? envVars=null, int? timeoutMs=null) { var result = RunProcess(s_buildEnv.DotNet, _testOutput, args, workingDir: _projectDir, label: label, envVars: envVars, timeoutMs: timeoutMs ?? s_defaultPerTestTimeoutMs); - if (expectSuccess) - Assert.True(0 == result.exitCode, $"Build process exited with non-zero exit code: {result.exitCode}"); - else - Assert.True(0 != result.exitCode, $"Build should have failed, but it didn't. Process exited with exitCode : {result.exitCode}"); + if (expectSuccess && result.exitCode != 0) + throw new XunitException($"Build process exited with non-zero exit code: {result.exitCode}"); + if (!expectSuccess && result.exitCode == 0) + throw new XunitException($"Build should have failed, but it didn't. Process exited with exitCode : {result.exitCode}"); return result; } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/CleanTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/CleanTests.cs index bbd0f0106e89bd..51ca3316872410 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/CleanTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/CleanTests.cs @@ -23,6 +23,7 @@ public CleanTests(ITestOutputHelper output, SharedBuildPerTestClassFixture build [Theory] [InlineData("Debug")] [InlineData("Release")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/77740")] public void Blazor_BuildThenClean_NativeRelinking(string config) { string id = Path.GetRandomFileName(); @@ -51,12 +52,14 @@ public void Blazor_BuildThenClean_NativeRelinking(string config) [Theory] [InlineData("Debug")] [InlineData("Release")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/77740")] public void Blazor_BuildNoNative_ThenBuildNative_ThenClean(string config) => Blazor_BuildNativeNonNative_ThenCleanTest(config, firstBuildNative: false); [Theory] [InlineData("Debug")] [InlineData("Release")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/77740")] public void Blazor_BuildNative_ThenBuildNonNative_ThenClean(string config) => Blazor_BuildNativeNonNative_ThenCleanTest(config, firstBuildNative: true); diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/DotNetCommand.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/DotNetCommand.cs index 84ccf2fa23ab1c..44f58f5cc45e03 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/DotNetCommand.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/DotNetCommand.cs @@ -16,6 +16,8 @@ public DotNetCommand(BuildEnvironment buildEnv, ITestOutputHelper _testOutput, b _useDefaultArgs = useDefaultArgs; if (useDefaultArgs) WithEnvironmentVariables(buildEnv.EnvVars); + // workaround msbuild issue - https://github.com/dotnet/runtime/issues/74328 + WithEnvironmentVariable("DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER", "1"); } protected override string GetFullArgs(params string[] args) diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/EnvironmentVariables.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/EnvironmentVariables.cs index a2439797566848..87589af53f491b 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/EnvironmentVariables.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/EnvironmentVariables.cs @@ -16,5 +16,7 @@ internal static class EnvironmentVariables internal static readonly string? TestLogPath = Environment.GetEnvironmentVariable("TEST_LOG_PATH"); internal static readonly string? SkipProjectCleanup = Environment.GetEnvironmentVariable("SKIP_PROJECT_CLEANUP"); internal static readonly string? XHarnessCliPath = Environment.GetEnvironmentVariable("XHARNESS_CLI_PATH"); + internal static readonly string? BuiltNuGetsPath = Environment.GetEnvironmentVariable("BUILT_NUGETS_PATH"); + internal static readonly string? BrowserPathForTests = Environment.GetEnvironmentVariable("BROWSER_PATH_FOR_TESTS"); } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs index d5f0d0f59755a7..278f5872125e1e 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs @@ -140,7 +140,7 @@ public static int Main() output); string cryptoInitMsg = "MONO_WASM: Initializing Crypto WebWorker"; - Assert.Contains(cryptoInitMsg, output); + Assert.DoesNotContain(cryptoInitMsg, output); } } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs index 1cc525391ad77b..5da728a17c1613 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs @@ -38,6 +38,7 @@ public void NoOpRebuildForNativeBuilds(BuildArgs buildArgs, bool nativeRelink, b [Theory] [InlineData("Debug")] [InlineData("Release")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/77740")] public void BlazorNoopRebuild(string config) { string id = $"blz_rebuild_{config}"; @@ -65,6 +66,7 @@ public void BlazorNoopRebuild(string config) [Theory] [InlineData("Debug")] [InlineData("Release")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/77740")] public void BlazorOnlyLinkRebuild(string config) { string id = $"blz_relink_{config}"; diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs index 3d4aee4ae15821..a45e04ea3deefd 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Text; using Xunit; using Xunit.Abstractions; @@ -538,6 +540,72 @@ public void BuildNativeInNonEnglishCulture(BuildArgs buildArgs, string culture, Assert.Contains("square: 25", output); } + [Theory] + [BuildAndRun(host: RunHost.Chrome, parameters: new object[] { new object[] { + "with-hyphen", + "with#hash-and-hyphen", + "with.per.iod", + "with🚀unicode#" + } })] + + public void CallIntoLibrariesWithNonAlphanumericCharactersInTheirNames(BuildArgs buildArgs, string[] libraryNames, RunHost host, string id) + { + buildArgs = ExpandBuildArgs(buildArgs, + extraItems: @$"", + extraProperties: buildArgs.AOT + ? string.Empty + : "true"); + + int baseArg = 10; + (_, string output) = BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => GenerateSourceFiles(_projectDir!, baseArg), + Publish: buildArgs.AOT, + DotnetWasmFromRuntimePack: false + )); + + output = RunAndTestWasmApp(buildArgs, + buildDir: _projectDir, + expectedExitCode: 42, + host: host, + id: id); + + for (int i = 0; i < libraryNames.Length; i ++) + { + Assert.Contains($"square_{i}: {(i + baseArg) * (i + baseArg)}", output); + } + + void GenerateSourceFiles(string outputPath, int baseArg) + { + StringBuilder csBuilder = new($@" + using System; + using System.Runtime.InteropServices; + "); + + StringBuilder dllImportsBuilder = new(); + for (int i = 0; i < libraryNames.Length; i ++) + { + dllImportsBuilder.AppendLine($"[DllImport(\"{libraryNames[i]}\")] static extern int square_{i}(int x);"); + csBuilder.AppendLine($@"Console.WriteLine($""square_{i}: {{square_{i}({i + baseArg})}}"");"); + + string nativeCode = $@" + #include + + int square_{i}(int x) + {{ + return x * x; + }}"; + File.WriteAllText(Path.Combine(outputPath, $"{libraryNames[i]}.c"), nativeCode); + } + + csBuilder.AppendLine("return 42;"); + csBuilder.Append(dllImportsBuilder); + + File.WriteAllText(Path.Combine(outputPath, "Program.cs"), csBuilder.ToString()); + } + } + private (BuildArgs, string) BuildForVariadicFunctionTests(string programText, BuildArgs buildArgs, string id, string? verbosity = null, string extraProperties = "") { extraProperties += "true<_WasmDevel>true"; diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/README.md b/src/tests/BuildWasmApps/Wasm.Build.Tests/README.md index 79a0a8945a89d9..a42e30078c8dff 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/README.md +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/README.md @@ -38,7 +38,7 @@ For this, the builds get cached using `BuildArgs` as the key. - when running locally, the default is to test with workloads. For this, sdk with `$(SdkVersionForWorkloadTesting)` is installed in - `artifacts/bin/dotnet-workload`. And the workload packs are installed there + `artifacts/bin/dotnet-net7`. And the workload packs are installed there using packages in `artifacts/packages/$(Configuration)/Shipping`. - If the packages get updated, then the workload will get installed again. diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/RunCommand.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/RunCommand.cs index 01f1e1efacd21c..33759316d1e34f 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/RunCommand.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/RunCommand.cs @@ -14,5 +14,7 @@ public RunCommand(BuildEnvironment buildEnv, ITestOutputHelper _testOutput, stri WithEnvironmentVariable("DOTNET_INSTALL_DIR", Path.GetDirectoryName(buildEnv.DotNet)!); WithEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0"); WithEnvironmentVariable("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "1"); + // workaround msbuild issue - https://github.com/dotnet/runtime/issues/74328 + WithEnvironmentVariable("DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER", "1"); } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj index a56ba59191d845..7d87809ec741db 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj @@ -38,10 +38,13 @@ - + + + - - + + @@ -56,7 +59,7 @@ - + <_XUnitTraitArg Condition="'$(TestUsingWorkloads)' == 'true'">-notrait category=no-workload <_XUnitTraitArg Condition="'$(TestUsingWorkloads)' != 'true'">-trait category=no-workload @@ -69,6 +72,9 @@ + + + diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmBuildAppTest.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmBuildAppTest.cs index 8a2eaa9f8b0b1c..a1aeee5933cf92 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmBuildAppTest.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmBuildAppTest.cs @@ -55,6 +55,18 @@ public static int Main() } }", buildArgs, host, id); + [Theory] + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] + public void ExceptionFromMain(BuildArgs buildArgs, RunHost host, string id) + => TestMain("main_exception", """ + using System; + using System.Threading.Tasks; + + public class TestClass { + public static int Main() => throw new Exception("MessageFromMyException"); + } + """, buildArgs, host, id, expectedExitCode: 71, expectedOutput: "Error: MessageFromMyException"); + private static string s_bug49588_ProgramCS = @" using System; public class TestClass { @@ -165,7 +177,9 @@ protected void TestMain(string projectName, RunHost host, string id, string extraProperties = "", - bool? dotnetWasmFromRuntimePack = null) + bool? dotnetWasmFromRuntimePack = null, + int expectedExitCode = 42, + string expectedOutput = "Hello, World!") { buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs, extraProperties); @@ -179,8 +193,8 @@ protected void TestMain(string projectName, InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack)); - RunAndTestWasmApp(buildArgs, expectedExitCode: 42, - test: output => Assert.Contains("Hello, World!", output), host: host, id: id); + RunAndTestWasmApp(buildArgs, expectedExitCode: expectedExitCode, + test: output => Assert.Contains(expectedOutput, output), host: host, id: id); } } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmSIMDTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmSIMDTests.cs index 8c5c397b82c3d0..26aa0a7e62ce43 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmSIMDTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmSIMDTests.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.IO; using Xunit; using Xunit.Abstractions; @@ -16,25 +17,114 @@ public WasmSIMDTests(ITestOutputHelper output, SharedBuildPerTestClassFixture bu } [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void BuildWithSIMD(BuildArgs buildArgs, RunHost host, string id) - => TestMain("main_simd_aot", - @" - using System; - using System.Runtime.Intrinsics; - - public class TestClass { - public static int Main() - { - var v1 = Vector128.Create(0x12345678); - var v2 = Vector128.Create(0x23456789); - var v3 = v1*v2; - Console.WriteLine(v3); - Console.WriteLine(""Hello, World!""); - - return 42; - } - }", - buildArgs, host, id, extraProperties: "true"); + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] + public void BuildWithSIMD_NoAOT_ShouldRelink(BuildArgs buildArgs, RunHost host, string id) + { + string projectName = $"sim_with_workload_no_aot"; + buildArgs = buildArgs with { ProjectName = projectName }; + buildArgs = ExpandBuildArgs(buildArgs, "true"); + + (_, string output) = BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_simdProgramText), + Publish: false, + DotnetWasmFromRuntimePack: false)); + + if (!_buildContext.TryGetBuildFor(buildArgs, out _)) + { + // Check if this is not a cached build + Assert.Contains("Compiling native assets with excc", output); + } + + RunAndTestWasmApp(buildArgs, + extraXHarnessArgs: host == RunHost.NodeJS ? "--engine-arg=--experimental-wasm-simd" : "", + expectedExitCode: 42, + test: output => + { + Assert.Contains("<-2094756296, -2094756296, -2094756296, -2094756296>", output); + Assert.Contains("Hello, World!", output); + }, host: host, id: id); + } + + [Theory] + // https://github.com/dotnet/runtime/issues/75044 - disabled for V8, and NodeJS + //[MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.Chrome })] + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] + public void PublishWithSIMD_AOT(BuildArgs buildArgs, RunHost host, string id) + { + string projectName = $"sim_with_workload_aot"; + buildArgs = buildArgs with { ProjectName = projectName }; + buildArgs = ExpandBuildArgs(buildArgs, "true"); + + BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_simdProgramText), + DotnetWasmFromRuntimePack: false)); + + RunAndTestWasmApp(buildArgs, + extraXHarnessArgs: host == RunHost.NodeJS ? "--engine-arg=--experimental-wasm-simd" : "", + expectedExitCode: 42, + test: output => + { + Assert.Contains("<-2094756296, -2094756296, -2094756296, -2094756296>", output); + Assert.Contains("Hello, World!", output); + }, host: host, id: id); + } + + [Theory, TestCategory("no-workload")] + [InlineData("Debug", /*aot*/true, /*publish*/true)] + [InlineData("Debug", /*aot*/false, /*publish*/false)] + [InlineData("Debug", /*aot*/false, /*publish*/true)] + [InlineData("Release", /*aot*/true, /*publish*/true)] + [InlineData("Release", /*aot*/false, /*publish*/false)] + [InlineData("Release", /*aot*/false, /*publish*/true)] + public void BuildWithSIMDNeedsWorkload(string config, bool aot, bool publish) + { + string id = Path.GetRandomFileName(); + string projectName = $"simd_no_workload_{config}_aot_{aot}"; + BuildArgs buildArgs = new + ( + ProjectName: projectName, + Config: config, + AOT: aot, + ProjectFileContents: "placeholder", + ExtraBuildArgs: string.Empty + ); + + string extraProperties = """ + browser-wasm + true + """; + buildArgs = ExpandBuildArgs(buildArgs, extraProperties); + + (_, string output) = BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_simdProgramText), + Publish: publish, + ExpectSuccess: false, + UseCache: false)); + Assert.Contains("following workloads must be installed: wasm-tools", output); + } + + private static string s_simdProgramText = @" + using System; + using System.Runtime.Intrinsics; + + public class TestClass { + public static int Main() + { + var v1 = Vector128.Create(0x12345678); + var v2 = Vector128.Create(0x23456789); + var v3 = v1*v2; + Console.WriteLine(v3); + Console.WriteLine(""Hello, World!""); + + return 42; + } + }"; } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmTemplateTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmTemplateTests.cs index 08fc1180c849bd..1d73b75894cc7c 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmTemplateTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmTemplateTests.cs @@ -21,7 +21,7 @@ public WasmTemplateTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur { } - private void updateProgramCS() + private void UpdateProgramCS() { string programText = """ Console.WriteLine("Hello, Console!"); @@ -51,8 +51,7 @@ private void UpdateConsoleMainJs() string mainJsContent = File.ReadAllText(mainJsPath); mainJsContent = mainJsContent - .Replace(".create()", ".withConsoleForwarding().create()") - .Replace("[\"dotnet\", \"is\", \"great!\"]", "(await import(/* webpackIgnore: true */\"process\")).argv.slice(2)"); + .Replace(".create()", ".withConsoleForwarding().create()"); File.WriteAllText(mainJsPath, mainJsContent); } @@ -182,13 +181,21 @@ public void ConsoleBuildThenPublish(string config) [InlineData("Debug", true)] [InlineData("Release", false)] [InlineData("Release", true)] - public void ConsoleBuildAndRun(string config, bool relinking) + public void ConsoleBuildAndRunDefault(string config, bool relinking) + => ConsoleBuildAndRun(config, relinking, string.Empty); + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [InlineData("Debug", "-f net7.0")] + public void ConsoleBuildAndRunForSpecificTFM(string config, string extraNewArgs) + => ConsoleBuildAndRun(config, false, extraNewArgs); + + private void ConsoleBuildAndRun(string config, bool relinking, string extraNewArgs) { string id = $"{config}_{Path.GetRandomFileName()}"; - string projectFile = CreateWasmTemplateProject(id, "wasmconsole"); + string projectFile = CreateWasmTemplateProject(id, "wasmconsole", extraNewArgs); string projectName = Path.GetFileNameWithoutExtension(projectFile); - updateProgramCS(); + UpdateProgramCS(); UpdateConsoleMainJs(); if (relinking) AddItemsPropertiesToProject(projectFile, "true"); @@ -216,6 +223,112 @@ public void ConsoleBuildAndRun(string config, bool relinking) Assert.Contains("args[2] = z", output); } + public static TheoryData TestDataForAppBundleDir() + { + var data = new TheoryData(); + AddTestData(forConsole: true, runOutsideProjectDirectory: false); + AddTestData(forConsole: true, runOutsideProjectDirectory: true); + + AddTestData(forConsole: false, runOutsideProjectDirectory: false); + AddTestData(forConsole: false, runOutsideProjectDirectory: true); + + void AddTestData(bool forConsole, bool runOutsideProjectDirectory) + { + data.Add(runOutsideProjectDirectory, forConsole, string.Empty); + + data.Add(runOutsideProjectDirectory, forConsole, + $"{Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())}"); + data.Add(runOutsideProjectDirectory, forConsole, + $"{Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())}"); + } + + return data; + } + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [MemberData(nameof(TestDataForAppBundleDir))] + public async Task RunWithDifferentAppBundleLocations(bool forConsole, bool runOutsideProjectDirectory, string extraProperties) + => await (forConsole + ? ConsoleRunWithAndThenWithoutBuildAsync("Release", extraProperties, runOutsideProjectDirectory) + : BrowserRunTwiceWithAndThenWithoutBuildAsync("Release", extraProperties, runOutsideProjectDirectory)); + + private async Task BrowserRunTwiceWithAndThenWithoutBuildAsync(string config, string extraProperties = "", bool runOutsideProjectDirectory = false) + { + string id = $"browser_{config}_{Path.GetRandomFileName()}"; + string projectFile = CreateWasmTemplateProject(id, "wasmbrowser"); + + UpdateBrowserMainJs(); + + if (!string.IsNullOrEmpty(extraProperties)) + AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); + + string workingDir = runOutsideProjectDirectory ? Path.GetTempPath() : _projectDir!; + + { + using var runCommand = new RunCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(workingDir); + + await using var runner = new BrowserRunner(); + var page = await runner.RunAsync(runCommand, $"run -c {config} --project {projectFile} --forward-console"); + await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2)); + Assert.Contains("Hello, Browser!", string.Join(Environment.NewLine, runner.OutputLines)); + } + + { + using var runCommand = new RunCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(workingDir); + + await using var runner = new BrowserRunner(); + var page = await runner.RunAsync(runCommand, $"run -c {config} --no-build --project {projectFile} --forward-console"); + await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2)); + Assert.Contains("Hello, Browser!", string.Join(Environment.NewLine, runner.OutputLines)); + } + } + + private Task ConsoleRunWithAndThenWithoutBuildAsync(string config, string extraProperties = "", bool runOutsideProjectDirectory = false) + { + string id = $"console_{config}_{Path.GetRandomFileName()}"; + string projectFile = CreateWasmTemplateProject(id, "wasmconsole"); + + UpdateProgramCS(); + UpdateConsoleMainJs(); + + if (!string.IsNullOrEmpty(extraProperties)) + AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); + + string workingDir = runOutsideProjectDirectory ? Path.GetTempPath() : _projectDir!; + + { + string runArgs = $"run -c {config} --project {projectFile}"; + runArgs += " x y z"; + using var cmd = new RunCommand(s_buildEnv, _testOutput, label: id) + .WithWorkingDirectory(workingDir) + .WithEnvironmentVariables(s_buildEnv.EnvVars); + var res = cmd.ExecuteWithCapturedOutput(runArgs).EnsureExitCode(42); + + Assert.Contains("args[0] = x", res.Output); + Assert.Contains("args[1] = y", res.Output); + Assert.Contains("args[2] = z", res.Output); + } + + _testOutput.WriteLine($"{Environment.NewLine}[{id}] Running again with --no-build{Environment.NewLine}"); + + { + // Run with --no-build + string runArgs = $"run -c {config} --project {projectFile} --no-build"; + runArgs += " x y z"; + using var cmd = new RunCommand(s_buildEnv, _testOutput, label: id) + .WithWorkingDirectory(workingDir); + var res = cmd.ExecuteWithCapturedOutput(runArgs).EnsureExitCode(42); + + Assert.Contains("args[0] = x", res.Output); + Assert.Contains("args[1] = y", res.Output); + Assert.Contains("args[2] = z", res.Output); + } + + return Task.CompletedTask; + } + public static TheoryData TestDataForConsolePublishAndRun() { var data = new TheoryData(); @@ -242,7 +355,7 @@ public void ConsolePublishAndRun(string config, bool aot, bool relinking) string projectFile = CreateWasmTemplateProject(id, "wasmconsole"); string projectName = Path.GetFileNameWithoutExtension(projectFile); - updateProgramCS(); + UpdateProgramCS(); UpdateConsoleMainJs(); if (aot) @@ -326,12 +439,14 @@ public async Task BlazorRunTest() Assert.Equal("Current count: 1", txt); } - [ConditionalFact(typeof(BuildTestBase), nameof(IsUsingWorkloads))] - public async Task BrowserTest() + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [InlineData("")] + [InlineData("-f net7.0")] + public async Task BrowserBuildAndRun(string extraNewArgs) { string config = "Debug"; string id = $"browser_{config}_{Path.GetRandomFileName()}"; - CreateWasmTemplateProject(id, "wasmbrowser"); + CreateWasmTemplateProject(id, "wasmbrowser", extraNewArgs); // var buildArgs = new BuildArgs(projectName, config, false, id, null); // buildArgs = ExpandBuildArgs(buildArgs); diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/WorkloadTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/WorkloadTests.cs index 55df68a8987b9b..dc625e457f7001 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/WorkloadTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/WorkloadTests.cs @@ -59,11 +59,11 @@ public void FilesInUnixFilesPermissionsXmlExist() // Expect just the emscripten ones here for now // linux doesn't have Emscripten.Python package, so only 2 there - int expectedPermFileCount = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? 3 : 4; + int expectedPermFileCount = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? 4 : 5; int permFileCount = unixPermFiles.Count(); if (permFileCount != expectedPermFileCount) - throw new XunitException($"Expected to find 3 UnixFilePermissions.xml files, but got {permFileCount}." + throw new XunitException($"Expected to find 4 UnixFilePermissions.xml files, but got {permFileCount}." + $"{Environment.NewLine}Files: {string.Join(", ", unixPermFiles)}"); } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets index 5557a97f375cca..419a6d48046ba0 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets @@ -1,75 +1,28 @@ - - Microsoft.NETCore.App - - - - - - - - - - - - + + - - - - - - - - + + - - - - - - + + - - - - <_targetingPackReferenceExclusion Include="$(TargetName)" /> - <_targetingPackReferenceExclusion Include="@(_ResolvedProjectReferencePaths->'%(Filename)')" /> - <_targetingPackReferenceExclusion Include="@(DefaultReferenceExclusion)" /> - + + - - <_targetingPackReferenceWithExclusion Include="@(Reference)"> - %(_targetingPackReferenceExclusion.Identity) - - - + diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.targets b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.targets index 18ef74cead43c5..d18165944fa43c 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.targets +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.targets @@ -2,24 +2,4 @@ - - - - - - - - - diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.cmd b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.cmd index d3c47af35c02ff..75b09e58cb2c6f 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.cmd +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.cmd @@ -94,10 +94,10 @@ exit /b %EXIT_CODE% REM Functions :SetEnvVars if [%TEST_USING_WORKLOADS%] == [true] ( - set _DIR_NAME=dotnet-workload + set _DIR_NAME=dotnet-net7 set SDK_HAS_WORKLOAD_INSTALLED=true ) else ( - set _DIR_NAME=sdk-no-workload + set _DIR_NAME=dotnet-none set SDK_HAS_WORKLOAD_INSTALLED=false ) diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.sh b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.sh index 0c3be485422f90..e27d280a23c2bf 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.sh +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.sh @@ -68,10 +68,10 @@ function set_env_vars() { local _DIR_NAME= if [ "x$TEST_USING_WORKLOADS" = "xtrue" ]; then - _DIR_NAME=dotnet-workload + _DIR_NAME=dotnet-net7 export SDK_HAS_WORKLOAD_INSTALLED=true else - _DIR_NAME=sdk-no-workload + _DIR_NAME=dotnet-none export SDK_HAS_WORKLOAD_INSTALLED=false fi diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets index 2274fe83598bf2..b2b875fd8449ce 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets @@ -6,74 +6,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_targetingPackReferenceExclusion Include="$(TargetName)" /> - <_targetingPackReferenceExclusion Include="@(_ResolvedProjectReferencePaths->'%(Filename)')" /> - <_targetingPackReferenceExclusion Include="@(DefaultReferenceExclusion)" /> - - - - <_targetingPackReferenceWithExclusion Include="@(Reference)"> - %(_targetingPackReferenceExclusion.Identity) - - - - diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/nuget7.config b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/nuget7.config index d6de05ad43dddc..e7ad45f78b8cab 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/nuget7.config +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/nuget7.config @@ -7,6 +7,7 @@ + diff --git a/src/tests/Common/CLRTest.Execute.Bash.targets b/src/tests/Common/CLRTest.Execute.Bash.targets index 82eb602eadddc0..bb353c172dfcd8 100644 --- a/src/tests/Common/CLRTest.Execute.Bash.targets +++ b/src/tests/Common/CLRTest.Execute.Bash.targets @@ -286,6 +286,9 @@ else __Command+=" dotnet" fi +# workaround msbuild issue - https://github.com/dotnet/runtime/issues/74328 +export DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER=1 + $__Command msbuild $CORE_ROOT/wasm-test-runner/WasmTestRunner.proj /p:NetCoreAppCurrent=$(NetCoreAppCurrent) /p:TestAssemblyFileName=$(MsBuildProjectName).dll /p:TestBinDir=`pwd` $(CLRTestMSBuildArgs) || exit $? ]]> diff --git a/src/tests/Common/CoreCLRTestLibrary/PlatformDetection.cs b/src/tests/Common/CoreCLRTestLibrary/PlatformDetection.cs index 429d637b28de79..5d15c0825d532f 100644 --- a/src/tests/Common/CoreCLRTestLibrary/PlatformDetection.cs +++ b/src/tests/Common/CoreCLRTestLibrary/PlatformDetection.cs @@ -10,8 +10,12 @@ public static class PlatformDetection { public static bool Is32BitProcess => IntPtr.Size == 4; public static bool Is64BitProcess => IntPtr.Size == 8; - + public static bool IsX86Process => RuntimeInformation.ProcessArchitecture == Architecture.X86; public static bool IsNotX86Process => !IsX86Process; + + private static string _variant = Environment.GetEnvironmentVariable("DOTNET_RUNTIME_VARIANT"); + + public static bool IsMonoLLVMFULLAOT => _variant == "llvmfullaot"; } } diff --git a/src/tests/Common/testenvironment.proj b/src/tests/Common/testenvironment.proj index f60eb9b8394ac7..b601b280a1ed4a 100644 --- a/src/tests/Common/testenvironment.proj +++ b/src/tests/Common/testenvironment.proj @@ -66,6 +66,7 @@ COMPlus_JitRandomGuardedDevirtualization; COMPlus_JitRandomEdgeCounts; COMPlus_JitRandomOnStackReplacement; + COMPlus_JitRandomlyCollect64BitCounts; COMPlus_JitForceControlFlowGuard; COMPlus_JitCFGUseDispatcher; RunningIlasmRoundTrip @@ -209,10 +210,10 @@ - - - - + + + + @@ -257,6 +258,8 @@ <_TestEnvFileLine Condition="'$(RuntimeVariant)' == 'monointerpreter'" Include="set MONO_ENV_OPTIONS=--interpreter" /> + <_TestEnvFileLine Condition="'$(RuntimeVariant)' != ''" Include="set DOTNET_RUNTIME_VARIANT=$(RuntimeVariant)" /> + <_TestEnvFileLine Condition="'$(Scenario)' == 'clrinterpreter'" Include="set COMPlus_Interpret=%2A" /> <_TestEnvFileLine Condition="'$(Scenario)' == 'clrinterpreter'" Include="set COMPlus_InterpreterHWIntrinsicsIsSupportedFalse=1" /> @@ -272,6 +275,8 @@ <_TestEnvFileLine Condition="'$(RuntimeVariant)' == 'monointerpreter'" Include="export MONO_ENV_OPTIONS=--interpreter" /> + <_TestEnvFileLine Condition="'$(RuntimeVariant)' != ''" Include="export DOTNET_RUNTIME_VARIANT=$(RuntimeVariant)" /> + <_TestEnvFileLine Condition="'$(RuntimeVariant)' == 'llvmaot'" Include="export MONO_ENV_OPTIONS=--llvm" /> diff --git a/src/tests/Common/wasm-test-runner/WasmTestRunner.proj b/src/tests/Common/wasm-test-runner/WasmTestRunner.proj index 3c604b39a4a850..c653f98c47bff3 100644 --- a/src/tests/Common/wasm-test-runner/WasmTestRunner.proj +++ b/src/tests/Common/wasm-test-runner/WasmTestRunner.proj @@ -1,6 +1,8 @@ - - + + + $(TestBinDir)\obj\ + + false diff --git a/src/tests/Directory.Build.targets b/src/tests/Directory.Build.targets index 000af600e2a616..7008a3c4e0ce55 100644 --- a/src/tests/Directory.Build.targets +++ b/src/tests/Directory.Build.targets @@ -537,13 +537,17 @@ <_UsingDefaultForHasRuntimeOutput>false + + + + diff --git a/src/tests/Interop/DisabledRuntimeMarshalling/Native_DisabledMarshalling/DisabledRuntimeMarshallingNative.cs b/src/tests/Interop/DisabledRuntimeMarshalling/Native_DisabledMarshalling/DisabledRuntimeMarshallingNative.cs index ed9b121bef6a23..8a0408768a346d 100644 --- a/src/tests/Interop/DisabledRuntimeMarshalling/Native_DisabledMarshalling/DisabledRuntimeMarshallingNative.cs +++ b/src/tests/Interop/DisabledRuntimeMarshalling/Native_DisabledMarshalling/DisabledRuntimeMarshallingNative.cs @@ -156,6 +156,9 @@ public enum ByteEnum : byte [DllImport(nameof(DisabledRuntimeMarshallingNative), EntryPoint = "Invalid")] public static extern void CallWithVarargs(__arglist); + [DllImport(nameof(DisabledRuntimeMarshallingNative), EntryPoint = "Invalid")] + public static extern void CallWithInt128(Int128 i); + [DllImport(nameof(DisabledRuntimeMarshallingNative))] public static extern delegate* unmanaged GetStructWithShortAndBoolCallback(); diff --git a/src/tests/Interop/DisabledRuntimeMarshalling/PInvokeAssemblyMarshallingDisabled/PInvokes.cs b/src/tests/Interop/DisabledRuntimeMarshalling/PInvokeAssemblyMarshallingDisabled/PInvokes.cs index 4fa7315d43063d..21b0716948d20b 100644 --- a/src/tests/Interop/DisabledRuntimeMarshalling/PInvokeAssemblyMarshallingDisabled/PInvokes.cs +++ b/src/tests/Interop/DisabledRuntimeMarshalling/PInvokeAssemblyMarshallingDisabled/PInvokes.cs @@ -148,4 +148,11 @@ public static void CanUseEnumsWithDisabledMarshalling() { Assert.Equal((byte)ByteEnum.Value, DisabledRuntimeMarshallingNative.GetEnumUnderlyingValue(ByteEnum.Value)); } + + [Fact] + [SkipOnMono("Blocking this on CoreCLR should be good enough.")] + public static void Int128_NotSupported() + { + Assert.Throws(() => DisabledRuntimeMarshallingNative.CallWithInt128(default(Int128))); + } } diff --git a/src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.cs b/src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.cs new file mode 100644 index 00000000000000..04e5a4e9ae0682 --- /dev/null +++ b/src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using Xunit; + +public class DllImportSearchPathsTest +{ + private static string Subdirectory => Path.Combine(NativeLibraryToLoad.GetDirectory(), "subdirectory"); + + [Fact] + public static void AssemblyDirectory_NotFound() + { + // Library should not be found in the assembly directory + Assert.Throws(() => NativeLibraryPInvoke.Sum(1, 2)); + } + + public static bool CanLoadAssemblyInSubdirectory => + !TestLibrary.Utilities.IsNativeAot && + !TestLibrary.PlatformDetection.IsMonoLLVMFULLAOT && + !OperatingSystem.IsAndroid() && + !OperatingSystem.IsIOS() && + !OperatingSystem.IsTvOS() && + !OperatingSystem.IsBrowser(); + + [ConditionalFact(nameof(CanLoadAssemblyInSubdirectory))] + public static void AssemblyDirectory_Found() + { + // Library should be found in the assembly directory + var assembly = Assembly.LoadFile(Path.Combine(Subdirectory, $"{nameof(DllImportSearchPathsTest)}.dll")); + var type = assembly.GetType(nameof(NativeLibraryPInvoke)); + var method = type.GetMethod(nameof(NativeLibraryPInvoke.Sum)); + + int sum = (int)method.Invoke(null, new object[] { 1, 2 }); + Assert.Equal(3, sum); + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public static void AssemblyDirectory_Fallback_Found() + { + string currentDirectory = Environment.CurrentDirectory; + try + { + Environment.CurrentDirectory = Subdirectory; + + // Library should not be found in the assembly directory, but should fall back to the default OS search which includes CWD on Windows + int sum = NativeLibraryPInvoke.Sum(1, 2); + Assert.Equal(3, sum); + } + finally + { + Environment.CurrentDirectory = currentDirectory; + } + } +} + +public class NativeLibraryPInvoke +{ + public static int Sum(int a, int b) + { + return NativeSum(a, b); + } + + [DllImport(NativeLibraryToLoad.Name)] + [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] + static extern int NativeSum(int arg1, int arg2); +} diff --git a/src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.csproj b/src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.csproj new file mode 100644 index 00000000000000..2456564dbb9304 --- /dev/null +++ b/src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.csproj @@ -0,0 +1,24 @@ + + + true + + + + + + + + + + $(OutDir)/subdirectory + -in-subdirectory + + + <_FilesToCopy Include="$(OutDir)/$(TargetName).dll" /> + <_FilesToMove Include="$(OutDir)/libNativeLibrary.*" /> + <_FilesToMove Include="$(OutDir)/NativeLibrary.*" /> + + + + + diff --git a/src/tests/Interop/IJW/CopyConstructorMarshaler/CMakeLists.txt b/src/tests/Interop/IJW/CopyConstructorMarshaler/CMakeLists.txt index 92085fcc778acd..735be8ce9d327e 100644 --- a/src/tests/Interop/IJW/CopyConstructorMarshaler/CMakeLists.txt +++ b/src/tests/Interop/IJW/CopyConstructorMarshaler/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES IjwCopyConstructorMarshaler.cpp) # add the shared library add_library (IjwCopyConstructorMarshaler SHARED ${SOURCES}) target_link_libraries(IjwCopyConstructorMarshaler ${LINK_LIBRARIES_ADDITIONAL}) +add_ijw_msbuild_project_properties(IjwCopyConstructorMarshaler ijwhost) # add the install targets install (TARGETS IjwCopyConstructorMarshaler DESTINATION bin) diff --git a/src/tests/Interop/IJW/IjwNativeDll/CMakeLists.txt b/src/tests/Interop/IJW/IjwNativeDll/CMakeLists.txt index 93d8a676af254f..27f9bad93f10b3 100644 --- a/src/tests/Interop/IJW/IjwNativeDll/CMakeLists.txt +++ b/src/tests/Interop/IJW/IjwNativeDll/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES IjwNativeDll.cpp) # add the shared library add_library (IjwNativeDll SHARED ${SOURCES}) target_link_libraries(IjwNativeDll ${LINK_LIBRARIES_ADDITIONAL}) +add_ijw_msbuild_project_properties(IjwNativeDll ijwhost) # add the install targets install (TARGETS IjwNativeDll DESTINATION bin) diff --git a/src/tests/Interop/IJW/NativeVarargs/CMakeLists.txt b/src/tests/Interop/IJW/NativeVarargs/CMakeLists.txt index 5427431a31a381..588f8b2625592b 100644 --- a/src/tests/Interop/IJW/NativeVarargs/CMakeLists.txt +++ b/src/tests/Interop/IJW/NativeVarargs/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES IjwNativeVarargs.cpp) # add the shared library add_library (IjwNativeVarargs SHARED ${SOURCES}) target_link_libraries(IjwNativeVarargs ${LINK_LIBRARIES_ADDITIONAL}) +add_ijw_msbuild_project_properties(IjwNativeVarargs ijwhost) # add the install targets install (TARGETS IjwNativeVarargs DESTINATION bin) diff --git a/src/tests/Interop/NativeLibrary/API/GetMainProgramHandleTests.cs b/src/tests/Interop/NativeLibrary/API/GetMainProgramHandleTests.cs index 58ec31f5d60b91..edefe6e32f446a 100644 --- a/src/tests/Interop/NativeLibrary/API/GetMainProgramHandleTests.cs +++ b/src/tests/Interop/NativeLibrary/API/GetMainProgramHandleTests.cs @@ -45,7 +45,7 @@ public static void GloballyLoadedLibrarySymbolsVisibleFromMainProgramHandle() // On non-Windows platforms, symbols from globally loaded shared libraries will also be discoverable. // Globally loading symbols is not the .NET default, so we use a call to dlopen in native code // with the right flags to test the scenario. - IntPtr handle = LoadLibraryGlobally(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary"))); + IntPtr handle = LoadLibraryGlobally(Path.Combine(NativeLibraryToLoad.GetDirectory(), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary"))); try { @@ -64,7 +64,7 @@ public static void InvalidSymbolName_Fails() // On non-Windows platforms, symbols from globally loaded shared libraries will also be discoverable. // Globally loading symbols is not the .NET default, so we use a call to dlopen in native code // with the right flags to test the scenario. - IntPtr handle = LoadLibraryGlobally(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary"))); + IntPtr handle = LoadLibraryGlobally(Path.Combine(NativeLibraryToLoad.GetDirectory(), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary"))); try { @@ -83,7 +83,7 @@ public static void GloballyLoadedLibrarySymbolsVisibleFromMainProgramHandle_Mang // On non-Windows platforms, symbols from globally loaded shared libraries will also be discoverable. // Globally loading symbols is not the .NET default, so we use a call to dlopen in native code // with the right flags to test the scenario. - IntPtr handle = LoadLibraryGlobally(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary"))); + IntPtr handle = LoadLibraryGlobally(Path.Combine(NativeLibraryToLoad.GetDirectory(), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary"))); try { diff --git a/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.cs b/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.cs index 2d5f1c3734557c..f3115f5f4e4207 100644 --- a/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.cs +++ b/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.cs @@ -17,7 +17,7 @@ public class NativeLibraryTests : IDisposable public NativeLibraryTests() { assembly = System.Reflection.Assembly.GetExecutingAssembly(); - testBinDir = Path.GetDirectoryName(assembly.Location); + testBinDir = NativeLibraryToLoad.GetDirectory(); libFullPath = NativeLibraryToLoad.GetFullPath(); } @@ -133,11 +133,19 @@ public void LoadLibraryFullPathWithoutNativePrefixOrSuffix_WithAssembly_Failure( public void LoadSystemLibrary_WithSearchPath() { string libName = "url.dll"; - // Calls on a valid library from System32 directory + // Library should be found in the system directory EXPECT(LoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.System32)); EXPECT(TryLoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.System32)); - // Calls on a valid library from application directory + // Library should not be found in the assembly directory and should be found in the system directory + EXPECT(LoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.System32)); + EXPECT(TryLoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.System32)); + + // Library should not be found in the assembly directory, but should fall back to the default OS search which includes CWD on Windows + EXPECT(LoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.AssemblyDirectory)); + EXPECT(TryLoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.AssemblyDirectory)); + + // Library should not be found in application directory EXPECT(LoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.ApplicationDirectory), TestResult.DllNotFound); EXPECT(TryLoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.ApplicationDirectory), TestResult.ReturnFailure); } @@ -165,6 +173,40 @@ public void LoadLibrary_UsesFullPath_EvenWhen_AssemblyDirectory_Specified() EXPECT(TryLoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.AssemblyDirectory), TestResult.ReturnFailure); } + [Fact] + public void LoadLibrary_AssemblyDirectory() + { + string suffix = "-in-subdirectory"; + string libName = $"{NativeLibraryToLoad.Name}{suffix}"; + + string subdirectory = Path.Combine(testBinDir, "subdirectory"); + + if (!TestLibrary.Utilities.IsNativeAot && !TestLibrary.PlatformDetection.IsMonoLLVMFULLAOT) + { + // Library should be found in the assembly directory + Assembly assemblyInSubdirectory = Assembly.LoadFile(Path.Combine(subdirectory, $"{Path.GetFileNameWithoutExtension(assembly.Location)}{suffix}.dll")); + EXPECT(LoadLibrary_WithAssembly(libName, assemblyInSubdirectory, DllImportSearchPath.AssemblyDirectory)); + EXPECT(TryLoadLibrary_WithAssembly(libName, assemblyInSubdirectory, DllImportSearchPath.AssemblyDirectory)); + } + + if (OperatingSystem.IsWindows()) + { + string currentDirectory = Environment.CurrentDirectory; + try + { + Environment.CurrentDirectory = subdirectory; + + // Library should not be found in the assembly directory, but should fall back to the default OS search which includes CWD on Windows + EXPECT(LoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.AssemblyDirectory)); + EXPECT(TryLoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.AssemblyDirectory)); + } + finally + { + Environment.CurrentDirectory = currentDirectory; + } + } + } + [Fact] public void Free() { diff --git a/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.csproj b/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.csproj index 50c9834e83a168..f6dd733af6d438 100644 --- a/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.csproj +++ b/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.csproj @@ -15,4 +15,17 @@ + + + + $(OutDir)/subdirectory + -in-subdirectory + + + + + + + + diff --git a/src/tests/Interop/NativeLibrary/Callback/CallbackTests.cs b/src/tests/Interop/NativeLibrary/Callback/CallbackTests.cs index c77a187e5c0874..351b8b4d1a5a09 100644 --- a/src/tests/Interop/NativeLibrary/Callback/CallbackTests.cs +++ b/src/tests/Interop/NativeLibrary/Callback/CallbackTests.cs @@ -99,7 +99,7 @@ private IntPtr ResolveDllImport(string libraryName, Assembly asm, DllImportSearc if (string.Equals(libraryName, NativeLibraryToLoad.InvalidName)) { Assert.Equal(DllImportSearchPath.System32, dllImportSearchPath); - return NativeLibrary.Load(NativeLibraryToLoad.Name, asm, null); + return NativeLibrary.Load(NativeLibraryToLoad.GetFullPath(), asm, null); } return IntPtr.Zero; diff --git a/src/tests/Interop/NativeLibrary/MainProgramHandle/MainProgramHandleTests.cs b/src/tests/Interop/NativeLibrary/MainProgramHandle/MainProgramHandleTests.cs new file mode 100644 index 00000000000000..c526c2a8d2a0a4 --- /dev/null +++ b/src/tests/Interop/NativeLibrary/MainProgramHandle/MainProgramHandleTests.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +using Xunit; + +public static class MainProgramHandleTests +{ + private static IntPtr s_handle; + + static MainProgramHandleTests() => NativeLibrary.SetDllImportResolver(typeof(MainProgramHandleTests).Assembly, + (string libraryName, Assembly asm, DllImportSearchPath? dllImportSearchPath) => + { + if (libraryName == "Self") + { + s_handle = NativeLibrary.GetMainProgramHandle(); + Assert.NotEqual(IntPtr.Zero, s_handle); + return s_handle; + } + + return IntPtr.Zero; + }); + + public static int Main() + { + try + { + free(s_handle); + } + catch (Exception e) + { + Console.WriteLine($"Test Failure: {e}"); + return 101; + } + + return 100; + } + + [DllImport("Self")] + private static extern void free(IntPtr arg); +} diff --git a/src/tests/Interop/NativeLibrary/MainProgramHandle/MainProgramHandleTests.csproj b/src/tests/Interop/NativeLibrary/MainProgramHandle/MainProgramHandleTests.csproj new file mode 100644 index 00000000000000..1ce6225d7853cd --- /dev/null +++ b/src/tests/Interop/NativeLibrary/MainProgramHandle/MainProgramHandleTests.csproj @@ -0,0 +1,13 @@ + + + Exe + true + true + + + + + + + + diff --git a/src/tests/Interop/NativeLibrary/NativeLibraryToLoad/NativeLibraryToLoad.cs b/src/tests/Interop/NativeLibrary/NativeLibraryToLoad/NativeLibraryToLoad.cs index 21dc1ca1c9c986..3acc65116eab28 100644 --- a/src/tests/Interop/NativeLibrary/NativeLibraryToLoad/NativeLibraryToLoad.cs +++ b/src/tests/Interop/NativeLibrary/NativeLibraryToLoad/NativeLibraryToLoad.cs @@ -32,8 +32,23 @@ public static string GetLibraryFileName(string name) public static string GetFullPath() { - Assembly assembly = Assembly.GetExecutingAssembly(); - string directory = Path.GetDirectoryName(assembly.Location); - return Path.Combine(directory, GetFileName()); + return Path.Combine(GetDirectory(), GetFileName()); + } + + public static string GetDirectory() + { + string directory; + if (TestLibrary.Utilities.IsNativeAot) + { + // NativeAOT test is put in a native/ subdirectory, so we want the parent + // directory that contains the native library to load + directory = new DirectoryInfo(AppContext.BaseDirectory).Parent.FullName; + } + else + { + directory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + } + + return directory; } } diff --git a/src/tests/Interop/PInvoke/Int128/Int128Native.cpp b/src/tests/Interop/PInvoke/Int128/Int128Native.cpp index 28f70bca06fabd..5b7e298b1e45f7 100644 --- a/src/tests/Interop/PInvoke/Int128/Int128Native.cpp +++ b/src/tests/Interop/PInvoke/Int128/Int128Native.cpp @@ -11,14 +11,29 @@ #elif defined(__SIZEOF_INT128__) typedef __int128 Int128; #else - typedef struct { +struct +#if defined(_M_ARM64) || defined(_M_AMD64) || defined(_M_IX86) +alignas(16) +#endif +Int128 { uint64_t lower; uint64_t upper; - } Int128; + }; #endif static Int128 Int128Value = { }; +struct StructWithInt128 +{ + int8_t messUpPadding; + Int128 value; +}; + +struct StructJustInt128 +{ + Int128 value; +}; + extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE GetInt128(uint64_t upper, uint64_t lower) { Int128 result; @@ -35,15 +50,34 @@ extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE GetInt128(uint64_t upper, uint64_ return result; } -extern "C" DLL_EXPORT void STDMETHODCALLTYPE GetInt128Out(uint64_t upper, uint64_t lower, Int128* pValue) + +extern "C" DLL_EXPORT void STDMETHODCALLTYPE GetInt128Out(uint64_t upper, uint64_t lower, char* pValue /* This is a char*, as .NET does not currently guarantee that Int128 values are aligned */) { Int128 value = GetInt128(upper, lower); - *pValue = value; + memcpy(pValue, &value, sizeof(value)); // Perform unaligned write +} + +extern "C" DLL_EXPORT uint64_t STDMETHODCALLTYPE GetInt128Lower(Int128 value) +{ +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + return (uint64_t)value; +#else + return value.lower; +#endif +} + +extern "C" DLL_EXPORT uint64_t STDMETHODCALLTYPE GetInt128Lower_S(StructJustInt128 value) +{ +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + return (uint64_t)value.value; +#else + return value.value.lower; +#endif } extern "C" DLL_EXPORT const Int128* STDMETHODCALLTYPE GetInt128Ptr(uint64_t upper, uint64_t lower) { - GetInt128Out(upper, lower, &Int128Value); + GetInt128Out(upper, lower, (char*)&Int128Value); return &Int128Value; } @@ -62,13 +96,221 @@ extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128(Int128 lhs, Int128 rhs) return result; } -extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128s(const Int128* pValues, uint32_t count) +// Test that struct alignment behavior matches with the standard OS compiler +extern "C" DLL_EXPORT void STDMETHODCALLTYPE AddStructWithInt128_ByRef(char *pLhs, char *pRhs) /* These are char*, as .NET does not currently guarantee that Int128 values are aligned */ +{ + StructWithInt128 result = {}; + StructWithInt128 lhs; + memcpy(&lhs, pLhs, sizeof(lhs)); // Perform unaligned read + StructWithInt128 rhs; + memcpy(&rhs, pRhs, sizeof(rhs)); // Perform unaligned read + + result.messUpPadding = lhs.messUpPadding; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result.value = lhs.value + rhs.value; +#else + result.value.lower = lhs.value.lower + rhs.value.lower; + uint64_t carry = (result.value.lower < lhs.value.lower) ? 1 : 0; + result.value.upper = lhs.value.upper + rhs.value.upper + carry; +#endif + + memcpy(pLhs, &result, sizeof(result)); // Perform unaligned write +} + +extern "C" DLL_EXPORT StructWithInt128 STDMETHODCALLTYPE AddStructWithInt128(StructWithInt128 lhs, StructWithInt128 rhs) +{ + StructWithInt128 result = {}; + result.messUpPadding = lhs.messUpPadding; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result.value = lhs.value + rhs.value; +#else + result.value.lower = lhs.value.lower + rhs.value.lower; + uint64_t carry = (result.value.lower < lhs.value.lower) ? 1 : 0; + result.value.upper = lhs.value.upper + rhs.value.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT StructWithInt128 STDMETHODCALLTYPE AddStructWithInt128_1(int64_t dummy1, StructWithInt128 lhs, StructWithInt128 rhs) +{ + StructWithInt128 result = {}; + result.messUpPadding = lhs.messUpPadding; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result.value = lhs.value + rhs.value; +#else + result.value.lower = lhs.value.lower + rhs.value.lower; + uint64_t carry = (result.value.lower < lhs.value.lower) ? 1 : 0; + result.value.upper = lhs.value.upper + rhs.value.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT StructWithInt128 STDMETHODCALLTYPE AddStructWithInt128_9(int64_t dummy1, int64_t dummy2, int64_t dummy3, int64_t dummy4, int64_t dummy5, int64_t dummy6, int64_t dummy7, int64_t dummy8, int64_t dummy9, StructWithInt128 lhs, StructWithInt128 rhs) +{ + StructWithInt128 result = {}; + result.messUpPadding = lhs.messUpPadding; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result.value = lhs.value + rhs.value; +#else + result.value.lower = lhs.value.lower + rhs.value.lower; + uint64_t carry = (result.value.lower < lhs.value.lower) ? 1 : 0; + result.value.upper = lhs.value.upper + rhs.value.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_1(int64_t dummy1, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_2(int64_t dummy1, int64_t dummy2, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_3(int64_t dummy1, int64_t dummy2, int64_t dummy3, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_4(int64_t dummy1, int64_t dummy2, int64_t dummy3, int64_t dummy4, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_5(int64_t dummy1, int64_t dummy2, int64_t dummy3, int64_t dummy4, int64_t dummy5, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_6(int64_t dummy1, int64_t dummy2, int64_t dummy3, int64_t dummy4, int64_t dummy5, int64_t dummy6, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_7(int64_t dummy1, int64_t dummy2, int64_t dummy3, int64_t dummy4, int64_t dummy5, int64_t dummy6, int64_t dummy7, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_8(int64_t dummy1, int64_t dummy2, int64_t dummy3, int64_t dummy4, int64_t dummy5, int64_t dummy6, int64_t dummy7, int64_t dummy8, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_9(int64_t dummy1, int64_t dummy2, int64_t dummy3, int64_t dummy4, int64_t dummy5, int64_t dummy6, int64_t dummy7, int64_t dummy8, int64_t dummy9, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128s(const char* pValues /* These are char*, as .NET does not currently guarantee that Int128 values are aligned */, uint32_t count) { Int128 result = {}; for (uint32_t i = 0; i < count; i++) { - result = AddInt128(result, pValues[i]); + Int128 input; + memcpy(&input, pValues + (sizeof(Int128) * i), sizeof(Int128)); // Perform unaligned read + result = AddInt128(result, input); } return result; diff --git a/src/tests/Interop/PInvoke/Int128/Int128Test.cs b/src/tests/Interop/PInvoke/Int128/Int128Test.cs index 5a9ddd5bb2135d..c6a8c378d6f25e 100644 --- a/src/tests/Interop/PInvoke/Int128/Int128Test.cs +++ b/src/tests/Interop/PInvoke/Int128/Int128Test.cs @@ -5,6 +5,20 @@ using System.Runtime.InteropServices; using Xunit; + +struct StructJustInt128 +{ + public StructJustInt128(Int128 val) { value = val; } + public Int128 value; +} + +struct StructWithInt128 +{ + public StructWithInt128(Int128 val) { value = val; messUpPadding = 0x10; } + public byte messUpPadding; + public Int128 value; +} + unsafe partial class Int128Native { [DllImport(nameof(Int128Native))] @@ -16,6 +30,15 @@ unsafe partial class Int128Native [DllImport(nameof(Int128Native))] public static extern void GetInt128Out(ulong upper, ulong lower, out Int128 value); + [DllImport(nameof(Int128Native))] + public static extern void GetInt128Out(ulong upper, ulong lower, out StructJustInt128 value); + + [DllImport(nameof(Int128Native))] + public static extern ulong GetInt128Lower_S(StructJustInt128 value); + + [DllImport(nameof(Int128Native))] + public static extern ulong GetInt128Lower(Int128 value); + [DllImport(nameof(Int128Native))] public static extern Int128* GetInt128Ptr(ulong upper, ulong lower); @@ -25,6 +48,47 @@ unsafe partial class Int128Native [DllImport(nameof(Int128Native))] public static extern Int128 AddInt128(Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern void AddStructWithInt128_ByRef(ref StructWithInt128 lhs, ref StructWithInt128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern StructWithInt128 AddStructWithInt128(StructWithInt128 lhs, StructWithInt128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern StructWithInt128 AddStructWithInt128_1(long dummy1, StructWithInt128 lhs, StructWithInt128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern StructWithInt128 AddStructWithInt128_9(long dummy1, long dummy2, long dummy3, long dummy4, long dummy5, long dummy6, long dummy7, long dummy8, long dummy9, StructWithInt128 lhs, StructWithInt128 rhs); + + // Test alignment and proper register usage for Int128 with various amounts of other registers in use. These tests are designed to stress the calling convention of Arm64 and Unix X64. + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_1(long dummy1, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_2(long dummy1, long dummy2, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_3(long dummy1, long dummy2, long dummy3, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_4(long dummy1, long dummy2, long dummy3, long dummy4, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_5(long dummy1, long dummy2, long dummy3, long dummy4, long dummy5, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_6(long dummy1, long dummy2, long dummy3, long dummy4, long dummy5, long dummy6, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_7(long dummy1, long dummy2, long dummy3, long dummy4, long dummy5, long dummy6, long dummy7, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_8(long dummy1, long dummy2, long dummy3, long dummy4, long dummy5, long dummy6, long dummy7, long dummy8, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_9(long dummy1, long dummy2, long dummy3, long dummy4, long dummy5, long dummy6, long dummy7, long dummy8, long dummy9, Int128 lhs, Int128 rhs); + [DllImport(nameof(Int128Native))] public static extern Int128 AddInt128s(Int128* pValues, int count); @@ -37,10 +101,14 @@ unsafe partial class Int128Native unsafe partial class Int128Native { - private static void TestInt128() + public static void TestInt128FieldLayout() { - Int128 value1 = Int128Native.GetInt128(1, 2); - Assert.Equal(new Int128(1, 2), value1); + // This test checks that the alignment rules of Int128 structs match the native compiler + StructWithInt128 lhs = new StructWithInt128(new Int128(11, 12)); + StructWithInt128 rhs = new StructWithInt128(new Int128(13, 14)); + + Int128Native.AddStructWithInt128_ByRef(ref lhs, ref rhs); + Assert.Equal(new StructWithInt128(new Int128(24, 26)), lhs); Int128 value2; Int128Native.GetInt128Out(3, 4, &value2); @@ -49,6 +117,28 @@ private static void TestInt128() Int128Native.GetInt128Out(5, 6, out Int128 value3); Assert.Equal(new Int128(5, 6), value3); + StructJustInt128 value4; + Int128Native.GetInt128Out(7, 8, out value4); + Assert.Equal(new StructJustInt128(new Int128(7, 8)), value4); + + // Until we implement the correct abi for Int128, validate that we don't marshal to native + + // Checking return value + Assert.Throws(() => GetInt128(0, 1)); + + // Checking input value as Int128 itself + Assert.Throws(() => GetInt128Lower(default(Int128))); + + // Checking input value as structure wrapping Int128 + Assert.Throws(() => GetInt128Lower_S(default(StructJustInt128))); + } + + private static void TestInt128() + { + Int128 value1 = Int128Native.GetInt128(1, 2); + Assert.Equal(new Int128(1, 2), value1); + + Int128* value4 = Int128Native.GetInt128Ptr(7, 8); Assert.Equal(new Int128(7, 8), *value4); @@ -77,5 +167,45 @@ private static void TestInt128() Int128 value9 = Int128Native.AddInt128s(in values[0], values.Length); Assert.Equal(new Int128(95, 100), value9); + + // Test ABI alignment issues on Arm64 and Unix X64, both enregistered and while spilled to the stack + Int128 value10 = Int128Native.AddInt128_1(1, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value10); + + Int128 value11 = Int128Native.AddInt128_2(1, 2, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value11); + + Int128 value12 = Int128Native.AddInt128_3(1, 2, 3, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value12); + + Int128 value13 = Int128Native.AddInt128_4(1, 2, 3, 4, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value13); + + Int128 value14 = Int128Native.AddInt128_5(1, 2, 3, 4, 5, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value14); + + Int128 value15 = Int128Native.AddInt128_6(1, 2, 3, 4, 5, 6, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value15); + + Int128 value16 = Int128Native.AddInt128_7(1, 2, 3, 4, 5, 6, 7, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value16); + + Int128 value17 = Int128Native.AddInt128_8(1, 2, 3, 4, 5, 6, 7, 8, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value17); + + Int128 value18 = Int128Native.AddInt128_9(1, 2, 3, 4, 5, 6, 7, 8, 9, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value18); + + // Test alignment within a structure + StructWithInt128 value19 = Int128Native.AddStructWithInt128(new StructWithInt128(new Int128(29, 30)), new StructWithInt128(new Int128(31, 32))); + Assert.Equal(new StructWithInt128(new Int128(60, 62)), value19); + + // Test abi register alignment within a structure + StructWithInt128 value20 = Int128Native.AddStructWithInt128_1(1, new StructWithInt128(new Int128(29, 30)), new StructWithInt128(new Int128(31, 32))); + Assert.Equal(new StructWithInt128(new Int128(60, 62)), value20); + + // Test abi alignment when spilled to the stack + StructWithInt128 value21 = Int128Native.AddStructWithInt128_9(1, 2, 3, 4, 5, 6, 7, 8, 9, new StructWithInt128(new Int128(29, 30)), new StructWithInt128(new Int128(31, 32))); + Assert.Equal(new StructWithInt128(new Int128(60, 62)), value21); } } diff --git a/src/tests/Interop/PInvoke/Int128/Int128Test.csproj b/src/tests/Interop/PInvoke/Int128/Int128Test.csproj index 42fc09c10b7185..e13c49fe94c3f7 100644 --- a/src/tests/Interop/PInvoke/Int128/Int128Test.csproj +++ b/src/tests/Interop/PInvoke/Int128/Int128Test.csproj @@ -6,7 +6,9 @@ exe - + + + diff --git a/src/tests/Interop/PInvoke/Int128/Int128TestFieldLayout.csproj b/src/tests/Interop/PInvoke/Int128/Int128TestFieldLayout.csproj new file mode 100644 index 00000000000000..449dc00c211eff --- /dev/null +++ b/src/tests/Interop/PInvoke/Int128/Int128TestFieldLayout.csproj @@ -0,0 +1,15 @@ + + + + true + embedded + exe + + + + + + + + + diff --git a/src/tests/Interop/PInvoke/Int128/ProgramFieldLayout.cs b/src/tests/Interop/PInvoke/Int128/ProgramFieldLayout.cs new file mode 100644 index 00000000000000..5a31288c5d3266 --- /dev/null +++ b/src/tests/Interop/PInvoke/Int128/ProgramFieldLayout.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; +using System.Runtime.InteropServices; + +unsafe partial class Int128NativeFieldLayout +{ + public static int Main(string[] args) + { + try + { + Console.WriteLine("Testing Int128"); + Int128Native.TestInt128FieldLayout(); + } + catch (System.Exception ex) + { + Console.WriteLine(ex); + return 0; + } + return 100; + } +} diff --git a/src/tests/Interop/PInvoke/Int128/UInt128Native.cpp b/src/tests/Interop/PInvoke/Int128/UInt128Native.cpp index 6d561cf4243439..d07d9d8f5ceb32 100644 --- a/src/tests/Interop/PInvoke/Int128/UInt128Native.cpp +++ b/src/tests/Interop/PInvoke/Int128/UInt128Native.cpp @@ -35,15 +35,15 @@ extern "C" DLL_EXPORT UInt128 STDMETHODCALLTYPE GetUInt128(uint64_t upper, uint6 return result; } -extern "C" DLL_EXPORT void STDMETHODCALLTYPE GetUInt128Out(uint64_t upper, uint64_t lower, UInt128* pValue) +extern "C" DLL_EXPORT void STDMETHODCALLTYPE GetUInt128Out(uint64_t upper, uint64_t lower, char* pValue /* This is a char*, as .NET does not currently guarantee that Int128 values are aligned */) { UInt128 value = GetUInt128(upper, lower); - *pValue = value; + memcpy(pValue, &value, sizeof(value)); // Perform unaligned write } extern "C" DLL_EXPORT const UInt128* STDMETHODCALLTYPE GetUInt128Ptr(uint64_t upper, uint64_t lower) { - GetUInt128Out(upper, lower, &UInt128Value); + GetUInt128Out(upper, lower, (char*)&UInt128Value); return &UInt128Value; } @@ -62,13 +62,15 @@ extern "C" DLL_EXPORT UInt128 STDMETHODCALLTYPE AddUInt128(UInt128 lhs, UInt128 return result; } -extern "C" DLL_EXPORT UInt128 STDMETHODCALLTYPE AddUInt128s(const UInt128* pValues, uint32_t count) +extern "C" DLL_EXPORT UInt128 STDMETHODCALLTYPE AddUInt128s(const char* pValues /* These are char*, as .NET does not currently guarantee that Int128 values are aligned */, uint32_t count) { UInt128 result = {}; for (uint32_t i = 0; i < count; i++) { - result = AddUInt128(result, pValues[i]); + UInt128 input; + memcpy(&input, pValues + (sizeof(UInt128) * i), sizeof(UInt128)); // Perform unaligned read + result = AddUInt128(result, input); } return result; diff --git a/src/tests/JIT/Regression/JitBlue/GitHub_85129/GitHub_85129.cs b/src/tests/JIT/Regression/JitBlue/GitHub_85129/GitHub_85129.cs new file mode 100644 index 00000000000000..07607dfaf56011 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/GitHub_85129/GitHub_85129.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; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using Xunit; + +public class Program_85129 +{ + public static int Main() + { + Vector256 v256Shuffle = Vector256.Create(100, 101, 102, 103, 104, 105, 106, 107); + Vector256 v256ShuffleExpectedResult = Vector256.Create(107, 105, 0, 101, 106, 104, 0, 100); + Vector256 v256ShuffleActualResult = Vector256Shuffle(v256Shuffle); + if(v256ShuffleExpectedResult != v256ShuffleActualResult) + { + return 1; + } + + return 100; + } + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector256 Vector256Shuffle(Vector256 v1) + { + return Vector256.Shuffle(v1, Vector256.Create(7, 5, 132, 1, 6, 4, -3, 0)); + } +} diff --git a/src/tests/JIT/Regression/JitBlue/GitHub_85129/GitHub_85129.csproj b/src/tests/JIT/Regression/JitBlue/GitHub_85129/GitHub_85129.csproj new file mode 100644 index 00000000000000..ebb70d021ed152 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/GitHub_85129/GitHub_85129.csproj @@ -0,0 +1,11 @@ + + + Exe + + + PdbOnly + + + + + diff --git a/src/tests/JIT/Regression/JitBlue/ImageSharp_2117/ImageSharp_2117.cs b/src/tests/JIT/Regression/JitBlue/ImageSharp_2117/ImageSharp_2117.cs new file mode 100644 index 00000000000000..415d9b00f5c8a6 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/ImageSharp_2117/ImageSharp_2117.cs @@ -0,0 +1,37 @@ +// 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.Intrinsics; +using System.Runtime.Intrinsics.X86; + +public unsafe class ImageSharp_2117 +{ + public static int Main(string[] args) + { + if (Sse.IsSupported) + { + Vector128 fnan = Vector128.Create(float.NaN); + Vector128 res1 = Sse.Max(Sse.LoadVector128((float*)(&fnan)), Vector128.Zero); + Vector128 res2 = Sse.Min(Sse.LoadVector128((float*)(&fnan)), Vector128.Zero); + + if (float.IsNaN(res1[0]) || float.IsNaN(res2[0])) + { + return 0; + } + } + + if (Sse2.IsSupported) + { + Vector128 dnan = Vector128.Create(double.NaN); + Vector128 res3 = Sse2.Max(Sse2.LoadVector128((double*)(&dnan)), Vector128.Zero); + Vector128 res4 = Sse2.Min(Sse2.LoadVector128((double*)(&dnan)), Vector128.Zero); + + if (double.IsNaN(res3[0]) || double.IsNaN(res4[0])) + { + return 0; + } + } + + return 100; + } +} diff --git a/src/tests/JIT/Regression/JitBlue/ImageSharp_2117/ImageSharp_2117.csproj b/src/tests/JIT/Regression/JitBlue/ImageSharp_2117/ImageSharp_2117.csproj new file mode 100644 index 00000000000000..e7284d26ab2f18 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/ImageSharp_2117/ImageSharp_2117.csproj @@ -0,0 +1,13 @@ + + + Exe + True + + + None + True + + + + + diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_34170/Runtime_34170.cs b/src/tests/JIT/Regression/JitBlue/Runtime_34170/Runtime_34170.cs index 19fd90aff05ef6..622bbc1d04fd2a 100644 --- a/src/tests/JIT/Regression/JitBlue/Runtime_34170/Runtime_34170.cs +++ b/src/tests/JIT/Regression/JitBlue/Runtime_34170/Runtime_34170.cs @@ -21,7 +21,7 @@ public FloatNonAlignedFieldWithSmallOffset(float a) [StructLayout(LayoutKind.Explicit)] internal struct FloatNonAlignedFieldWithLargeOffset { - [FieldOffset(1021)] + [FieldOffset(0x10001)] public float field; public FloatNonAlignedFieldWithLargeOffset(float a) @@ -45,7 +45,7 @@ public DoubleNonAlignedFieldWithSmallOffset(float a) [StructLayout(LayoutKind.Explicit)] internal struct DoubleNonAlignedFieldWithLargeOffset { - [FieldOffset(1021)] + [FieldOffset(0x10001)] public double field; public DoubleNonAlignedFieldWithLargeOffset(float a) diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_73951/Runtime_73951.cs b/src/tests/JIT/Regression/JitBlue/Runtime_73951/Runtime_73951.cs new file mode 100644 index 00000000000000..1f4f5895fdc4c6 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_73951/Runtime_73951.cs @@ -0,0 +1,76 @@ +// 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.Runtime.CompilerServices; + +public class Runtime_73951 +{ + [ThreadStatic] + public static IRuntime s_rt; + [ThreadStatic] + public static S1 s_17; + + public static ushort s_result; + + public static int Main() + { + Problem(new Runtime()); + + return s_result == 0 ? 100 : 101; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Problem(IRuntime rt) + { + s_rt = rt; + S0 vr21 = s_17.F1; + new S1(new object()).M105(vr21); + + var vr22 = new C0(vr21.F3); + s_rt.Capture(vr22.F1); + } + + public class C0 + { + public ushort F1; + public C0(ushort f1) + { + F1 = f1; + } + } + + public struct S0 + { + public uint F1; + public int F2; + public byte F3; + } + + public struct S1 + { + public object F0; + public S0 F1; + public S1(object f0) : this() + { + F0 = f0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public S1 M105(S0 arg0) + { + return this; + } + } + + public interface IRuntime + { + void Capture(ushort value); + } + + public class Runtime : IRuntime + { + public void Capture(ushort value) => s_result = value; + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_73951/Runtime_73951.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_73951/Runtime_73951.csproj new file mode 100644 index 00000000000000..f492aeac9d056b --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_73951/Runtime_73951.csproj @@ -0,0 +1,9 @@ + + + Exe + True + + + + + \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_74117/Runtime_74117.cs b/src/tests/JIT/Regression/JitBlue/Runtime_74117/Runtime_74117.cs new file mode 100644 index 00000000000000..0bb8fa2d83f72b --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_74117/Runtime_74117.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.Runtime.CompilerServices; + +unsafe class Runtime_74117 +{ + public unsafe static int Main(string[] args) + { + byte a = 5; + Problem(ref a, 0); + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static byte GetByte() => 1; + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Problem(ref byte x, int a) + { + JitUse(&a); + Unsafe.Add(ref x, a) = GetByte(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void JitUse(T* arg) where T : unmanaged { } +} \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_74117/Runtime_74117.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_74117/Runtime_74117.csproj new file mode 100644 index 00000000000000..cf94135633b19a --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_74117/Runtime_74117.csproj @@ -0,0 +1,10 @@ + + + Exe + True + true + + + + + \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_74126/Runtime_74126.cs b/src/tests/JIT/Regression/JitBlue/Runtime_74126/Runtime_74126.cs new file mode 100644 index 00000000000000..dd21dce550479d --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_74126/Runtime_74126.cs @@ -0,0 +1,82 @@ +// 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.Numerics; +using System.Runtime.Intrinsics; +using System.Runtime.CompilerServices; + +public class Runtime_74126 +{ + public static int Main() + { + if (GetVtor(GetVtor2()) != GetVtor2()) + { + return 101; + } + if (GetVtor(GetVtor3()) != GetVtor3()) + { + return 102; + } + if (GetVtor(GetVtor4()) != GetVtor4()) + { + return 103; + } + if (GetVtor(GetVtor64()) != GetVtor64()) + { + return 104; + } + if (GetVtor(GetVtor128()) != GetVtor128()) + { + return 105; + } + if (GetVtor(GetVtor256()) != GetVtor256()) + { + return 106; + } + + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Vector2 GetVtor2() + { + return new Vector2(1, 2); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Vector3 GetVtor3() + { + return new Vector3(1, 2, 3); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Vector4 GetVtor4() + { + return new Vector4(1, 2, 3, 4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Vector64 GetVtor64() + { + return Vector64.Create(1, 2); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Vector128 GetVtor128() + { + return Vector128.Create(1, 2, 3, 4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Vector256 GetVtor256() + { + return Vector256.Create(1, 2, 3, 4, 5, 6, 7, 8); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static T GetVtor(T vtor) + { + return vtor; + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_74126/Runtime_74126.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_74126/Runtime_74126.csproj new file mode 100644 index 00000000000000..f492aeac9d056b --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_74126/Runtime_74126.csproj @@ -0,0 +1,9 @@ + + + Exe + True + + + + + \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_74373/Runtime_74373.cs b/src/tests/JIT/Regression/JitBlue/Runtime_74373/Runtime_74373.cs new file mode 100644 index 00000000000000..c06276960e1075 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_74373/Runtime_74373.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. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +public class _74373 +{ + public static int Main(string[] args) + { + Problem(10); + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static unsafe float Problem(long x) + { + var y = BitConverter.Int32BitsToSingle((int)x); + Use(&x); + JitUse(0); + return y; + } + + public static unsafe void Use(long* arg) { } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void JitUse(T arg) { } +} \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_74373/Runtime_74373.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_74373/Runtime_74373.csproj new file mode 100644 index 00000000000000..cf94135633b19a --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_74373/Runtime_74373.csproj @@ -0,0 +1,10 @@ + + + Exe + True + true + + + + + \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_75312/Runtime_75312.il b/src/tests/JIT/Regression/JitBlue/Runtime_75312/Runtime_75312.il new file mode 100644 index 00000000000000..ef3f9b67f82934 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_75312/Runtime_75312.il @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +.assembly extern System.Runtime {} +.assembly extern System.Runtime.Extensions {} +.assembly extern System.Console {} +.assembly Runtime_75312 {} + +.class public abstract auto ansi sealed beforefieldinit Runtime_75312 + extends [System.Runtime]System.Object +{ + .method private hidebysig static + int32 Main () cil managed + { + .entrypoint + .maxstack 2 + .locals init ( + [0] int64 a + ) + ldc.i8 1234605616436508552 + stloc.0 + ldc.i4 1146447579 + ldloca.s 0 + conv.u + newobj instance void [System.Runtime]System.IntPtr::.ctor(void*) + call int32 [System.Runtime]System.IntPtr::op_Explicit(native int) + call int32 Runtime_75312::Test(int32) + sub + ret + } + + .method private hidebysig static int32 Test (int32 lcl) cil managed noinlining nooptimization + { + .maxstack 8 + + // return *(int*)(arg0 + ((3 * 0) << 2) + 1); + // to avoid constant folding in Roslyn (even for Debug) it's written in IL + + ldarg.0 + ldc.i4.3 + ldc.i4.0 + mul + ldc.i4.2 + shl + add + ldc.i4.1 + add + conv.i + ldind.i4 + ret + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_75312/Runtime_75312.ilproj b/src/tests/JIT/Regression/JitBlue/Runtime_75312/Runtime_75312.ilproj new file mode 100644 index 00000000000000..5e9fc16ea3a67c --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_75312/Runtime_75312.ilproj @@ -0,0 +1,11 @@ + + + Exe + true + None + True + + + + + diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_75828/Runtime_75828.cs b/src/tests/JIT/Regression/JitBlue/Runtime_75828/Runtime_75828.cs new file mode 100644 index 00000000000000..714359398eb1e8 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_75828/Runtime_75828.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.Runtime.InteropServices; + +// Verify that the Jit_Patchpoint helper inserted for OSR preserves last error + +class Runtime_75828 +{ + public static int Main() + { + Marshal.SetLastSystemError(42); + + int expected = 5_000_000 + 42; + + int result = 0; + for (int i = 0; i < 10_000_000; i++) + { + result += i % 2; + } + + result += Marshal.GetLastSystemError(); + + Console.WriteLine($"got {result} expected {expected}"); + + return result == expected ? 100 : -1; + } +} + diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_75828/Runtime_75828.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_75828/Runtime_75828.csproj new file mode 100644 index 00000000000000..f492aeac9d056b --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_75828/Runtime_75828.csproj @@ -0,0 +1,9 @@ + + + Exe + True + + + + + \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_76051/Runtime_76051.cs b/src/tests/JIT/Regression/JitBlue/Runtime_76051/Runtime_76051.cs new file mode 100644 index 00000000000000..c95666b0f834c4 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_76051/Runtime_76051.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +public class Runtime_76051 +{ + public static int Main(string[] args) + { + GetIndex(1); + return 100; + } + + // This tests an assertion failure (debug)/segfault (release) in + // fgMorphModToSubMulDiv due to the use of gtClone that fails for the + // complex tree seen for the address-of expression. + [MethodImpl(MethodImplOptions.NoInlining)] + static unsafe int GetIndex(uint cellCount) => (int)((ulong)&cellCount % cellCount); +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_76051/Runtime_76051.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_76051/Runtime_76051.csproj new file mode 100644 index 00000000000000..0f9eec3d705fb2 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_76051/Runtime_76051.csproj @@ -0,0 +1,10 @@ + + + Exe + True + True + + + + + \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_79354/Runtime_79354.cs b/src/tests/JIT/Regression/JitBlue/Runtime_79354/Runtime_79354.cs new file mode 100644 index 00000000000000..8114d964526fc0 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_79354/Runtime_79354.cs @@ -0,0 +1,45 @@ +// 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; + +public interface IGetContents { + (string, int, string) GetContents(); +} + +public struct MyStruct : IGetContents { + public string s1; + public int a; + public string s2; + + public (string, int, string) GetContents() + { + return (s1, a, s2); + } +} + +public class Program { + + public delegate (string, int, string) MyDelegate(IGetContents arg); + + public static int Main(string[] args) + { + MyStruct str = new MyStruct(); + str.s1 = "test1"; + str.a = 42; + str.s2 = "test2"; + + MethodInfo mi = typeof(IGetContents).GetMethod("GetContents"); + MyDelegate func = (MyDelegate)mi.CreateDelegate(typeof(MyDelegate)); + + (string c1, int c2, string c3) = func(str); + if (c1 != "test1") + return 1; + if (c2 != 42) + return 2; + if (c3 != "test2") + return 3; + return 100; + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_79354/Runtime_79354.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_79354/Runtime_79354.csproj new file mode 100644 index 00000000000000..75e7d24ec6fd6d --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_79354/Runtime_79354.csproj @@ -0,0 +1,9 @@ + + + Exe + True + + + + + diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_83242/Runtime_83242.cs b/src/tests/JIT/Regression/JitBlue/Runtime_83242/Runtime_83242.cs new file mode 100644 index 00000000000000..9e0c0d9889aa11 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_83242/Runtime_83242.cs @@ -0,0 +1,55 @@ +// 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.CompilerServices; + +class Runtime_83242 +{ + [MethodImpl(MethodImplOptions.NoInlining)] + static int Map(int i) + { + if (i == 5) return -1; + return i; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Setup(int[][] a) + { + for (int i = 0; i < a.Length; i++) + { + a[i] = new int[5]; + + for (int j = 0; j < 5; j++) + { + a[i][j] = j; + } + } + } + + public static int Main() + { + int[][] a = new int[11][]; + int sum = 0; + Setup(a); + + for (int i = 0; i < a.Length; i++) + { + int ii = Map(i); + + // Need to ensure ii >= 0 is in the cloning + // conditions for the following loop + + for (int j = 0; j < 5; j++) + { + if (ii >= 0) + { + sum += a[ii][j]; + } + } + } + + Console.WriteLine($"sum is {sum}\n"); + return sum; + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_83242/Runtime_83242.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_83242/Runtime_83242.csproj new file mode 100644 index 00000000000000..c0a5f3d0484e39 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_83242/Runtime_83242.csproj @@ -0,0 +1,9 @@ + + + Exe + True + + + + + \ No newline at end of file diff --git a/src/tests/JIT/Stress/ABI/ABIs.cs b/src/tests/JIT/Stress/ABI/ABIs.cs index a9c85f864aa88b..3ad1eed8d7c743 100644 --- a/src/tests/JIT/Stress/ABI/ABIs.cs +++ b/src/tests/JIT/Stress/ABI/ABIs.cs @@ -47,14 +47,14 @@ internal class Win86Abi : IAbi new[] { typeof(byte), typeof(short), typeof(int), typeof(long), - typeof(float), typeof(double), + typeof(float), typeof(double), typeof(Int128), typeof(Vector), typeof(Vector128), typeof(Vector256), typeof(S1P), typeof(S2P), typeof(S2U), typeof(S3U), typeof(S4P), typeof(S4U), typeof(S5U), typeof(S6U), typeof(S7U), typeof(S8P), typeof(S8U), typeof(S9U), typeof(S10U), typeof(S11U), typeof(S12U), typeof(S13U), typeof(S14U), typeof(S15U), typeof(S16U), typeof(S17U), - typeof(S31U), typeof(S32U), + typeof(S31U), typeof(S32U), typeof(I128_1), typeof(I128_2) }; public CallingConvention[] PInvokeConventions { get; } = { CallingConvention.Cdecl, CallingConvention.StdCall, }; @@ -107,12 +107,13 @@ internal class SysVAbi : IAbi typeof(byte), typeof(short), typeof(int), typeof(long), typeof(float), typeof(double), typeof(Vector), typeof(Vector128), typeof(Vector256), + typeof(Int128), typeof(S1P), typeof(S2P), typeof(S2U), typeof(S3U), typeof(S4P), typeof(S4U), typeof(S5U), typeof(S6U), typeof(S7U), typeof(S8P), typeof(S8U), typeof(S9U), typeof(S10U), typeof(S11U), typeof(S12U), typeof(S13U), typeof(S14U), typeof(S15U), typeof(S16U), typeof(S17U), - typeof(S31U), typeof(S32U), + typeof(S31U), typeof(S32U), typeof(I128_1), typeof(I128_2) }; public CallingConvention[] PInvokeConventions { get; } = { CallingConvention.Cdecl }; @@ -135,14 +136,14 @@ internal class Arm64Abi : IAbi new[] { typeof(byte), typeof(short), typeof(int), typeof(long), - typeof(float), typeof(double), + typeof(float), typeof(double), typeof(Int128), typeof(Vector), typeof(Vector128), typeof(Vector256), typeof(S1P), typeof(S2P), typeof(S2U), typeof(S3U), typeof(S4P), typeof(S4U), typeof(S5U), typeof(S6U), typeof(S7U), typeof(S8P), typeof(S8U), typeof(S9U), typeof(S10U), typeof(S11U), typeof(S12U), typeof(S13U), typeof(S14U), typeof(S15U), typeof(S16U), - typeof(Hfa1), typeof(Hfa2), + typeof(Hfa1), typeof(Hfa2), typeof(I128_1) }; public CallingConvention[] PInvokeConventions { get; } = { CallingConvention.Cdecl }; diff --git a/src/tests/JIT/Stress/ABI/Gen.cs b/src/tests/JIT/Stress/ABI/Gen.cs index a7d0d0efbe5435..ddb7c1215c87ef 100644 --- a/src/tests/JIT/Stress/ABI/Gen.cs +++ b/src/tests/JIT/Stress/ABI/Gen.cs @@ -48,6 +48,9 @@ internal static object GenConstant(Type type, FieldInfo[] fields, Random rand) if (type == typeof(double)) return (double)rand.Next(); + if (type == typeof(Int128)) + return new Int128((ulong)(long)GenConstant(typeof(long), null, rand), (ulong)(long)GenConstant(typeof(long), null, rand)); + if (type == typeof(Vector)) return GenConstantVector, int>(rand); @@ -173,6 +176,8 @@ internal static void EmitLoadPrimitive(ILGenerator il, object val) il.Emit(OpCodes.Ldc_R4, (float)val); else if (ty == typeof(double)) il.Emit(OpCodes.Ldc_R8, (double)val); + else if (ty == typeof(Int128)) + EmitLoadBlittable(il, (Int128)val); else if (ty == typeof(Vector)) EmitLoadBlittable(il, (Vector)val); else if (ty == typeof(Vector128)) diff --git a/src/tests/JIT/Stress/ABI/Program.cs b/src/tests/JIT/Stress/ABI/Program.cs index 8eab812a5748c3..1c1c00c648309b 100644 --- a/src/tests/JIT/Stress/ABI/Program.cs +++ b/src/tests/JIT/Stress/ABI/Program.cs @@ -344,6 +344,7 @@ public static void EmitDumpValues(string listName, ILGenerator g, IEnumerable), typeof(Vector128), typeof(Vector256), + typeof(Int128), typeof(S1P), typeof(S2P), typeof(S2U), typeof(S3U), typeof(S4P), typeof(S4U), typeof(S5U), typeof(S6U), typeof(S7U), typeof(S8P), typeof(S8U), typeof(S9U), @@ -351,6 +352,7 @@ public static void EmitDumpValues(string listName, ILGenerator g, IEnumerable new TypeEx(t)).ToArray(); private static readonly IAbi s_abi = SelectAbi(); diff --git a/src/tests/JIT/Stress/ABI/Types.cs b/src/tests/JIT/Stress/ABI/Types.cs index a24c47154de6d2..140ae76762b257 100644 --- a/src/tests/JIT/Stress/ABI/Types.cs +++ b/src/tests/JIT/Stress/ABI/Types.cs @@ -61,6 +61,8 @@ public struct S31U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F1 public struct S32U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31; public S32U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6, byte f7, byte f8, byte f9, byte f10, byte f11, byte f12, byte f13, byte f14, byte f15, byte f16, byte f17, byte f18, byte f19, byte f20, byte f21, byte f22, byte f23, byte f24, byte f25, byte f26, byte f27, byte f28, byte f29, byte f30, byte f31) => (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31) = (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, f29, f30, f31); } public struct Hfa1 { public float F0, F1; public Hfa1(float f0, float f1) => (F0, F1) = (f0, f1); } public struct Hfa2 { public double F0, F1, F2, F3; public Hfa2(double f0, double f1, double f2, double f3) => (F0, F1, F2, F3) = (f0, f1, f2, f3); } + public struct I128_1 { public Int128 F0; public I128_1(Int128 f0) => F0 = f0; } + public struct I128_2 { public byte F0; public Int128 F1; public I128_2(byte f0, Int128 f1) => (F0, F1) = (f0, f1); } internal static class TypeExtensions { @@ -78,7 +80,8 @@ public static bool IsOurStructType(this Type t) t == typeof(S14U) || t == typeof(S15U) || t == typeof(S16U) || t == typeof(S17U) || t == typeof(S31U) || t == typeof(S32U) || - t == typeof(Hfa1) || t == typeof(Hfa2); + t == typeof(Hfa1) || t == typeof(Hfa2) || + t == typeof(I128_1) || t == typeof(I128_2); } } } diff --git a/src/tests/JIT/opt/Vectorization/StringEquals.cs b/src/tests/JIT/opt/Vectorization/StringEquals.cs index d5c80c0e411266..86739946fa8acd 100644 --- a/src/tests/JIT/opt/Vectorization/StringEquals.cs +++ b/src/tests/JIT/opt/Vectorization/StringEquals.cs @@ -22,7 +22,7 @@ public static int Main() } Console.WriteLine(testCount); - return testCount == 25920 ? 100 : 0; + return testCount == 27888 ? 100 : 0; } } @@ -195,10 +195,22 @@ static void ValidateEquals(bool result, string left, string right) [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_157(string s) => ValidateEquals(s == "2ж1311a23b2212aЙ21Й11жb3233bb3a1", s, "2ж1311a23b2212aЙ21Й11жb3233bb3a1"); [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_158(string s) => ValidateEquals(s == "01Й11113ь3Й32a3ьЙЙ3Й32b2ab221310", s, "01Й11113ь3Й32a3ьЙЙ3Й32b2ab221310"); [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_159(string s) => ValidateEquals(s == "a120213b11211\0223223312ьь1Й3222Й", s, "a120213b11211\0223223312ьь1Й3222Й"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_160(string s) => ValidateEquals(s == "\u9244", s, "\u9244"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_161(string s) => ValidateEquals(s == "\u9244\u9244", s, "\u9244\u9244"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_162(string s) => ValidateEquals(s == "\u9244\u9244\u9244", s, "\u9244\u9244\u9244"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_163(string s) => ValidateEquals(s == "\uFFFF", s, "\uFFFF"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_164(string s) => ValidateEquals(s == "\uFFFF\uFFFF", s, "\uFFFF\uFFFF"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_165(string s) => ValidateEquals(s == "\uFFFF\uFFFF\uFFFF", s, "\uFFFF\uFFFF\uFFFF"); public static readonly string[] s_TestData = { null, + "\u9244", + "\u9244\u9244", + "\u9244\u9244\u9244", + "\uFFFF", + "\uFFFF\uFFFF", + "\uFFFF\uFFFF\uFFFF", "", "\0", "a", diff --git a/src/tests/JIT/opt/Vectorization/UnrollEqualsStartsWIth.cs b/src/tests/JIT/opt/Vectorization/UnrollEqualsStartsWIth.cs index d50deb26defcf5..f81b96c4eaa245 100644 --- a/src/tests/JIT/opt/Vectorization/UnrollEqualsStartsWIth.cs +++ b/src/tests/JIT/opt/Vectorization/UnrollEqualsStartsWIth.cs @@ -18,7 +18,7 @@ public static int Main() int testCount = 0; foreach (var testType in testTypes) testCount += RunTests(testType); - return testCount == 113652 ? 100 : 0; + return testCount == 127512 ? 100 : 0; } public static int RunTests(Type type) @@ -37,6 +37,12 @@ public static int RunTests(Type type) string[] testData = { + "\u9244", + "\u9244\u9244", + "\u9244\u9244\u9244", + "\uFFFF", + "\uFFFF\uFFFF", + "\uFFFF\uFFFF\uFFFF", "", string.Empty, "a", diff --git a/src/tests/Loader/classloader/Casting/Functionpointer.cs b/src/tests/Loader/classloader/Casting/Functionpointer.cs new file mode 100644 index 00000000000000..95d7bf33b1303c --- /dev/null +++ b/src/tests/Loader/classloader/Casting/Functionpointer.cs @@ -0,0 +1,46 @@ +using System; +using System.Runtime.InteropServices; + +namespace TestFunctionPointer +{ + unsafe class TestThings + { + public static delegate* managed[][] Functions = { + new delegate* managed[] + { + &Function, + }, + }; + + public static int Function() => 100; + + public static delegate* unmanaged[][] Functions1 = { + new delegate* unmanaged[] + { + &Function1, + }, + }; + + [UnmanagedCallersOnly] + public static int Function1() => 100; + + public static delegate* managed[][] Functions2 = { + new delegate* managed[] + { + &Function2, + }, + }; + + public static int Function2(int a) { + return a; + } + } + + unsafe class Program + { + public static int Main() + { + return TestThings.Functions2[0][0](TestThings.Functions[0][0]()); + } + } +} \ No newline at end of file diff --git a/src/tests/Loader/classloader/Casting/Functionpointer.csproj b/src/tests/Loader/classloader/Casting/Functionpointer.csproj new file mode 100644 index 00000000000000..51f7075c49c815 --- /dev/null +++ b/src/tests/Loader/classloader/Casting/Functionpointer.csproj @@ -0,0 +1,9 @@ + + + Exe + true + + + + + \ No newline at end of file diff --git a/src/tests/Loader/classloader/DefaultInterfaceMethods/regressions/github61244.cs b/src/tests/Loader/classloader/DefaultInterfaceMethods/regressions/github61244.cs index ae08369177388d..66783d12a7a164 100644 --- a/src/tests/Loader/classloader/DefaultInterfaceMethods/regressions/github61244.cs +++ b/src/tests/Loader/classloader/DefaultInterfaceMethods/regressions/github61244.cs @@ -14,11 +14,19 @@ // derived interface contexts, but the order is changed (or different.) // When this occurs the generic info is incorrect for the inflated method. +// TestClass2 tests a regression due to the fix for the previous +// regression that caused Mono to incorrectly instantiate generic +// interfaces that appeared in the MethodImpl table + class Program { static int Main(string[] args) { - return new TestClass().DoTest(); + int result = new TestClass().DoTest(); + if (result != 100) + return result; + result = new TestClass2().DoTest(); + return result; } } @@ -78,4 +86,33 @@ public int DoTest () Console.WriteLine("Passed => 100"); return 100; } -} \ No newline at end of file +} + +public interface IA +{ + public int Foo(); +} + +public interface IB : IA +{ + int IA.Foo() { return 104; } +} + +public interface IC : IB

+{ + int IA.Foo() { return 105; } +} + +public class C : IC +{ + int IA.Foo() { return 100; } +} + +public class TestClass2 +{ + public int DoTest() + { + IA c = new C(); + return c.Foo(); + } +} diff --git a/src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/OverrideReabstracted.cs b/src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/OverrideReabstracted.cs new file mode 100644 index 00000000000000..f0f461777e7002 --- /dev/null +++ b/src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/OverrideReabstracted.cs @@ -0,0 +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; + + +namespace ReproMAUI6811; + +public static class Program +{ + public static int Main() + { + Leaf l = new Leaf(); + + if (l.getI().ToString() != "Leaf") + return 1; + if (((Intermediate)l).getI().ToString() != "Leaf") + return 2; + if (((PseudoBase)l).getI().ToString() != "Leaf") + return 3; + if (((Base)l).getI().ToString() != "Leaf") + return 4; + return 100; + } +} + +public abstract class Base { + public abstract I getI(); +} + +public class PseudoBase : Base { + public override I getI() => new C ("PseudoBase"); +} + +public abstract class Intermediate : PseudoBase { + public override abstract I getI(); +} + +public class Leaf : Intermediate { + public Leaf() {} + public override C getI() { return new C ("Leaf"); } +} + +public interface I {} + +public class C : I { + private readonly string _repr; + public C(string s) { _repr = s; } + public override string ToString() => _repr; +} + + diff --git a/src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/OverrideReabstracted.csproj b/src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/OverrideReabstracted.csproj new file mode 100644 index 00000000000000..570644f1dbcb7e --- /dev/null +++ b/src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/OverrideReabstracted.csproj @@ -0,0 +1,8 @@ + + + Exe + + + + + diff --git a/src/tests/Loader/classloader/explicitlayout/objrefandnonobjrefoverlap/case13.cs b/src/tests/Loader/classloader/explicitlayout/objrefandnonobjrefoverlap/case13.cs new file mode 100644 index 00000000000000..99ca59fdd47e7a --- /dev/null +++ b/src/tests/Loader/classloader/explicitlayout/objrefandnonobjrefoverlap/case13.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Xunit; + +public class Test +{ + public struct WithORefs + { + public object F; + } + + public struct WithNoORefs + { + public int F; + } + + public ref struct WithByRefs + { + public ref int F; + } + + [StructLayout(LayoutKind.Explicit)] + public ref struct Explicit1 + { + [FieldOffset(0)] public Inner1 Field1; + public ref struct Inner1 + { + public WithORefs Field2; + } + } + + [StructLayout(LayoutKind.Explicit)] + public ref struct Explicit2 + { + [FieldOffset(0)] public Inner2 Field1; + public ref struct Inner2 + { + public WithNoORefs Field2; + } + } + + [StructLayout(LayoutKind.Explicit)] + public ref struct Explicit3 + { + [FieldOffset(0)] public Inner3 Field1; + public ref struct Inner3 + { + public WithByRefs Field2; + } + } + + [Fact] + public static void Validate_Explicit1() + { + Load1(); + + [MethodImpl(MethodImplOptions.NoInlining)] + static string Load1() + { + return typeof(Explicit1).ToString(); + } + } + + [Fact] + public static void Validate_Explicit2() + { + Load2(); + + [MethodImpl(MethodImplOptions.NoInlining)] + static string Load2() + { + return typeof(Explicit2).ToString(); + } + } + + [Fact] + public static void Validate_Explicit3() + { + Load3(); + + [MethodImpl(MethodImplOptions.NoInlining)] + static string Load3() + { + return typeof(Explicit3).ToString(); + } + } +} diff --git a/src/tests/Loader/classloader/explicitlayout/objrefandnonobjrefoverlap/case13.csproj b/src/tests/Loader/classloader/explicitlayout/objrefandnonobjrefoverlap/case13.csproj new file mode 100644 index 00000000000000..0de2ecf447373d --- /dev/null +++ b/src/tests/Loader/classloader/explicitlayout/objrefandnonobjrefoverlap/case13.csproj @@ -0,0 +1,10 @@ + + + true + Exe + false + + + + + diff --git a/src/tests/Loader/classloader/regressions/GitHub_82187/GitHub_82187.csproj b/src/tests/Loader/classloader/regressions/GitHub_82187/GitHub_82187.csproj new file mode 100644 index 00000000000000..32355f272f9085 --- /dev/null +++ b/src/tests/Loader/classloader/regressions/GitHub_82187/GitHub_82187.csproj @@ -0,0 +1,9 @@ + + + true + Exe + + + + + diff --git a/src/tests/Loader/classloader/regressions/GitHub_82187/repro.cs b/src/tests/Loader/classloader/regressions/GitHub_82187/repro.cs new file mode 100644 index 00000000000000..1ab3e5476a66b0 --- /dev/null +++ b/src/tests/Loader/classloader/regressions/GitHub_82187/repro.cs @@ -0,0 +1,31 @@ +using System; + +/* Regression test for https://github.com/dotnet/runtime/issues/78638 + * and https://github.com/dotnet/runtime/issues/82187 ensure AOT + * cross-compiler and AOT runtime use the same name hashing for names + * that include UTF-8 continuation bytes. + */ + +[MySpecial(typeof(MeineTüre))] +public class Program +{ + public static int Main() + { + var attr = (MySpecialAttribute)Attribute.GetCustomAttribute(typeof (Program), typeof(MySpecialAttribute), false); + if (attr == null) + return 101; + if (attr.Type == null) + return 102; + if (attr.Type.FullName != "MeineTüre") + return 103; + return 100; + } +} + +public class MySpecialAttribute : Attribute +{ + public Type Type {get; private set; } + public MySpecialAttribute(Type t) { Type = t; } +} + +public class MeineTüre {} diff --git a/src/tests/Regressions/coreclr/GitHub_86865/test86865.cs b/src/tests/Regressions/coreclr/GitHub_86865/test86865.cs new file mode 100644 index 00000000000000..d14c8bbd8e167b --- /dev/null +++ b/src/tests/Regressions/coreclr/GitHub_86865/test86865.cs @@ -0,0 +1,71 @@ +using System; +using System.Reflection; + +namespace test86865; + +public class test86865 +{ + public static int Main() + { + + // Regression test for https://github.com/dotnet/runtime/issues/86865 + // Verify that the RuntimeHelpers.GetSpanDataFrom method underlying RuntimeHelpers.CreateSpan + // works correctly with enums. + + ReadOnlySpan myEnums = new[] + { + MyEnum.A, + MyEnum.B, + MyEnum.C, + MyEnum.B, + MyEnum.C, + }; + + if (string.Join(", ", myEnums.ToArray()) != "A, B, C, B, C") + return 1; + + var types = new Type[] { + typeof(RuntimeFieldHandle), + typeof(RuntimeTypeHandle), + typeof(int).MakeByRefType(), + }; + var mi = typeof(System.Runtime.CompilerServices.RuntimeHelpers).GetMethod("GetSpanDataFrom", BindingFlags.Static | BindingFlags.NonPublic, types); + if (mi == null) + return 2; + + var pid = typeof(MyEnum).Assembly.GetType(""); + if (pid == null) + return 3; + + var fi = pid.GetField("0B77DC554B4A81403D62BE25FB5404020AD451151D4203D544BF60E3FEDBD8AE", BindingFlags.Static | BindingFlags.NonPublic); + if (fi == null) + { + Console.WriteLine("Could not find the expected array data in . The available static non-public fields are:"); + foreach (var f in pid.GetFields(BindingFlags.Static | BindingFlags.NonPublic)) { + Console.WriteLine($" - '{f}'"); + } + return 4; + } + + var parms = new object[] { + fi.FieldHandle, + typeof(MyEnum).TypeHandle, + new int() + }; + var result = mi.Invoke(null, parms); + if (result == null) + return 6; + if ((int)parms[2] != myEnums.Length) + return 7; + + return 100; + } +} + +enum MyEnum +{ + A, + B, + C +} + diff --git a/src/tests/Regressions/coreclr/GitHub_86865/test86865.csproj b/src/tests/Regressions/coreclr/GitHub_86865/test86865.csproj new file mode 100644 index 00000000000000..dc5ae5f51c138d --- /dev/null +++ b/src/tests/Regressions/coreclr/GitHub_86865/test86865.csproj @@ -0,0 +1,9 @@ + + + Exe + true + + + + + diff --git a/src/tests/build.proj b/src/tests/build.proj index e2716558b9d8a4..37236bad27175f 100644 --- a/src/tests/build.proj +++ b/src/tests/build.proj @@ -185,7 +185,7 @@ $(BuildDir)\apk $(XUnitTestBinBase)$(CategoryWithSlash)\$(Category).apk False - diagnostics_tracing;marshal-ilgen + diagnostics_tracing 127.0.0.1:9000,nosuspend,listen True $(ArtifactsBinDir)microsoft.netcore.app.runtime.android-$(TargetArchitecture)\$(Configuration)\runtimes\android-$(TargetArchitecture)\ @@ -272,7 +272,7 @@ $(CMDDIR_GrandParent)/$(CategoryWithSlash)/$(XUnitWrapperFileName) $(IntermediateOutputPath)\iOSApps\$(Category) $(XUnitTestBinBase)$(CategoryWithSlash)\$(Category).app - diagnostics_tracing;marshal-ilgen + diagnostics_tracing diff --git a/src/tests/issues.targets b/src/tests/issues.targets index 7caad0c5838b2f..6d1ae189b60f92 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -8,6 +8,9 @@ https://github.com/dotnet/runtime/issues/13703 + + https://github.com/dotnet/runtime/issues/74209 + @@ -916,15 +919,16 @@ https://github.com/dotnet/runtime/issues/62881 - - https://github.com/dotnet/runtime/issues/63856 - - - https://github.com/dotnet/runtime/issues/63856 + + + + + + https://github.com/dotnet/runtime/issues/73299 - + https://github.com/dotnet/runtimelab/issues/155: Compatible TypeLoadException for invalid inputs @@ -1028,9 +1032,6 @@ https://github.com/dotnet/runtimelab/issues/166 - - https://github.com/dotnet/runtimelab/issues/206 - https://github.com/dotnet/runtimelab/issues/176: VARIANT marshalling @@ -1427,6 +1428,9 @@ + + https://github.com/dotnet/runtime/issues/74223 + https://github.com/dotnet/runtime/issues/71656 @@ -1869,6 +1873,9 @@ needs triage + + https://github.com/dotnet/runtime/issues/69832 + needs triage @@ -2019,10 +2026,10 @@ These tests are not supposed to be run with mono. - needs triage + https://github.com/dotnet/runtime/issues/36113 - needs triage + https://github.com/dotnet/runtime/issues/36113 needs triage @@ -2473,9 +2480,6 @@ needs triage - - https://github.com/dotnet/runtime/issues/69832 - https://github.com/dotnet/runtime/issues/54393 @@ -2683,6 +2687,9 @@ needs triage + + needs triage + https://github.com/dotnet/runtime/issues/47624 @@ -2901,6 +2908,9 @@ https://github.com/dotnet/runtime/issues/57350 + + https://github.com/dotnet/runtime/issues/57350 + https://github.com/dotnet/runtime/issues/57350 @@ -3577,6 +3587,9 @@ needs triage + + Loads an assembly from file + Loads an assembly from file @@ -3703,7 +3716,7 @@ needs triage - + https://github.com/dotnet/runtime/issues/73539 @@ -3844,6 +3857,9 @@ https://github.com/dotnet/runtime/issues/67359 + + Loads an assembly from file + diff --git a/src/tests/nativeaot/SmokeTests/PInvoke/PInvoke.cs b/src/tests/nativeaot/SmokeTests/PInvoke/PInvoke.cs index b8e6b8d57db354..52205934d046d3 100644 --- a/src/tests/nativeaot/SmokeTests/PInvoke/PInvoke.cs +++ b/src/tests/nativeaot/SmokeTests/PInvoke/PInvoke.cs @@ -702,6 +702,8 @@ public struct SequentialStruct public float f2; [MarshalAs(UnmanagedType.LPStr)] public String f3; + [MarshalAs(UnmanagedType.LPUTF8Str)] + public String f4; } [StructLayout(LayoutKind.Sequential)] @@ -713,6 +715,8 @@ public class SequentialClass public float f2; [MarshalAs(UnmanagedType.LPStr)] public String f3; + [MarshalAs(UnmanagedType.LPUTF8Str)] + public String f4; } // A second struct with the same name but nested. Regression test against native types being mangled into @@ -824,15 +828,16 @@ private static void TestStruct() ss.f1 = 1; ss.f2 = 10.0f; ss.f3 = "Hello"; + ss.f4 = "Hola"; ThrowIfNotEquals(true, StructTest(ss), "Struct marshalling scenario1 failed."); StructTest_ByRef(ref ss); - ThrowIfNotEquals(true, ss.f1 == 2 && ss.f2 == 11.0 && ss.f3.Equals("Ifmmp"), "Struct marshalling scenario2 failed."); + ThrowIfNotEquals(true, ss.f1 == 2 && ss.f2 == 11.0 && ss.f3.Equals("Ifmmp") && ss.f4.Equals("Ipmb"), "Struct marshalling scenario2 failed."); SequentialStruct ss2 = new SequentialStruct(); StructTest_ByOut(out ss2); - ThrowIfNotEquals(true, ss2.f0 == 1 && ss2.f1 == 1.0 && ss2.f2 == 1.0 && ss2.f3.Equals("0123456"), "Struct marshalling scenario3 failed."); + ThrowIfNotEquals(true, ss2.f0 == 1 && ss2.f1 == 1.0 && ss2.f2 == 1.0 && ss2.f3.Equals("0123456") && ss2.f4.Equals("789"), "Struct marshalling scenario3 failed."); NesterOfSequentialStruct.SequentialStruct ss3 = new NesterOfSequentialStruct.SequentialStruct(); ss3.f1 = 10.0f; @@ -861,6 +866,7 @@ private static void TestStruct() ssa[i].f1 = i; ssa[i].f2 = i*i; ssa[i].f3 = i.LowLevelToString(); + ssa[i].f4 = "u8" + i.LowLevelToString(); } ThrowIfNotEquals(true, StructTest_Array(ssa, ssa.Length), "Array of struct marshalling failed"); @@ -923,9 +929,10 @@ private static void TestLayoutClassPtr() ss.f1 = 1; ss.f2 = 10.0f; ss.f3 = "Hello"; + ss.f4 = "Hola"; ClassTest(ss); - ThrowIfNotEquals(true, ss.f1 == 2 && ss.f2 == 11.0 && ss.f3.Equals("Ifmmp"), "LayoutClassPtr marshalling scenario1 failed."); + ThrowIfNotEquals(true, ss.f1 == 2 && ss.f2 == 11.0 && ss.f3.Equals("Ifmmp") && ss.f4.Equals("Ipmb"), "LayoutClassPtr marshalling scenario1 failed."); } #if OPTIMIZED_MODE_WITHOUT_SCANNER @@ -955,20 +962,22 @@ private static void TestAsAny() sc.f1 = 1; sc.f2 = 10.0f; sc.f3 = "Hello"; + sc.f4 = "Hola"; AsAnyTest(sc); - ThrowIfNotEquals(true, sc.f1 == 2 && sc.f2 == 11.0 && sc.f3.Equals("Ifmmp"), "AsAny marshalling scenario1 failed."); + ThrowIfNotEquals(true, sc.f1 == 2 && sc.f2 == 11.0 && sc.f3.Equals("Ifmmp") && sc.f4.Equals("Ipmb"), "AsAny marshalling scenario1 failed."); SequentialStruct ss = new SequentialStruct(); ss.f0 = 100; ss.f1 = 1; ss.f2 = 10.0f; ss.f3 = "Hello"; + ss.f4 = "Hola"; object o = ss; AsAnyTest(o); ss = (SequentialStruct)o; - ThrowIfNotEquals(true, ss.f1 == 2 && ss.f2 == 11.0 && ss.f3.Equals("Ifmmp"), "AsAny marshalling scenario2 failed."); + ThrowIfNotEquals(true, ss.f1 == 2 && ss.f2 == 11.0 && ss.f3.Equals("Ifmmp") && sc.f4.Equals("Ipmb"), "AsAny marshalling scenario2 failed."); } private static void TestLayoutClass() diff --git a/src/tests/nativeaot/SmokeTests/PInvoke/PInvokeNative.cpp b/src/tests/nativeaot/SmokeTests/PInvoke/PInvokeNative.cpp index 7412274680bcfd..480209d3b2560a 100644 --- a/src/tests/nativeaot/SmokeTests/PInvoke/PInvokeNative.cpp +++ b/src/tests/nativeaot/SmokeTests/PInvoke/PInvokeNative.cpp @@ -471,6 +471,7 @@ struct NativeSequentialStruct int a; float b; char *str; + char *u8str; }; struct NativeSequentialStruct2 @@ -494,6 +495,9 @@ DLL_EXPORT bool __stdcall StructTest(NativeSequentialStruct nss) if (!CompareAnsiString(nss.str, "Hello")) return false; + if (!CompareAnsiString(nss.u8str, "Hola")) + return false; + return true; } @@ -519,6 +523,13 @@ DLL_EXPORT void __stdcall StructTest_ByRef(NativeSequentialStruct *nss) *p = *p + 1; p++; } + + p = nss->u8str; + while (*p != '\0') + { + *p = *p + 1; + p++; + } } DLL_EXPORT void __stdcall StructTest_ByOut(NativeSequentialStruct *nss) @@ -529,7 +540,7 @@ DLL_EXPORT void __stdcall StructTest_ByOut(NativeSequentialStruct *nss) int arrSize = 7; char *p; - p = (char *)MemAlloc(sizeof(char) * arrSize); + p = (char *)MemAlloc(sizeof(char) * arrSize + 1); for (int i = 0; i < arrSize; i++) { @@ -537,6 +548,10 @@ DLL_EXPORT void __stdcall StructTest_ByOut(NativeSequentialStruct *nss) } *(p + arrSize) = '\0'; nss->str = p; + + p = (char *)MemAlloc(sizeof(char) * 4); + strcpy(p, "789"); + nss->u8str = p; } DLL_EXPORT bool __stdcall StructTest_Array(NativeSequentialStruct *nss, int length) @@ -558,6 +573,11 @@ DLL_EXPORT bool __stdcall StructTest_Array(NativeSequentialStruct *nss, int leng if (CompareAnsiString(expected, nss[i].str) == 0) return false; + + sprintf(expected, "u8%d", i); + + if (CompareAnsiString(expected, nss[i].u8str) == 0) + return false; } return true; } diff --git a/src/tests/readytorun/fieldlayout/fieldlayout.csproj b/src/tests/readytorun/fieldlayout/fieldlayout.csproj new file mode 100644 index 00000000000000..a9902bca098f35 --- /dev/null +++ b/src/tests/readytorun/fieldlayout/fieldlayout.csproj @@ -0,0 +1,16 @@ + + + + exe + BuildAndRun + true + true + true + + + + + + + + diff --git a/src/tests/readytorun/fieldlayout/fieldlayouttests.cs b/src/tests/readytorun/fieldlayout/fieldlayouttests.cs new file mode 100644 index 00000000000000..9b421859653648 --- /dev/null +++ b/src/tests/readytorun/fieldlayout/fieldlayouttests.cs @@ -0,0 +1,300 @@ +using System; +using System.Runtime.Intrinsics; + +class Test +{ + // This test uses the same set of types as the type system unittests use, and attempts to validate that the R2R usage of said types works well. + // This is done by touching the various types, and then relying on the verification logic in R2R images to detect failures. + static int Main() + { + ContainsGCPointersFieldsTest.Test(); +// ExplicitTest.Test(); // Explicit layout is known to not quite match the runtime, and if enabled this set of tests will fail. + SequentialTest.Test(); + AutoTest.Test(); + EnumAlignmentTest.Test(); + AutoTestWithVector.Test(); + return 100; + } +} + +class EnumAlignmentTest +{ + static EnumAlignment.LongIntEnumStruct _fld1; + static EnumAlignment.LongIntEnumStructFieldStruct _fld2; + static EnumAlignment.IntShortEnumStruct _fld3; + static EnumAlignment.IntShortEnumStructFieldStruct _fld4; + static EnumAlignment.ShortByteEnumStruct _fld5; + static EnumAlignment.ShortByteEnumStructFieldStruct _fld6; + static EnumAlignment.LongIntEnumStructAuto _fld7; + static EnumAlignment.LongIntEnumStructAutoFieldStruct _fld8; + static EnumAlignment.IntShortEnumStructAuto _fld9; + static EnumAlignment.IntShortEnumStructAutoFieldStruct _fld10; + static EnumAlignment.ShortByteEnumStructAuto _fld11; + static EnumAlignment.ShortByteEnumStructAutoFieldStruct _fld12; + + public static void Test() + { + _fld1._1 = EnumAlignment.LongEnum.Val; + _fld1._2 = EnumAlignment.IntEnum.Val; + _fld1._3 = EnumAlignment.LongEnum.Val; + _fld1._4 = EnumAlignment.IntEnum.Val; + + _fld2._0 = 0; + _fld2._struct = _fld1; + + _fld3._1 = EnumAlignment.IntEnum.Val; + _fld3._2 = EnumAlignment.ShortEnum.Val; + _fld3._3 = EnumAlignment.IntEnum.Val; + _fld3._4 = EnumAlignment.ShortEnum.Val; + + _fld4._0 = 1; + _fld4._struct = _fld3; + + _fld5._1 = EnumAlignment.ShortEnum.Val; + _fld5._2 = EnumAlignment.ByteEnum.Val; + _fld5._3 = EnumAlignment.ShortEnum.Val; + _fld5._4 = EnumAlignment.ByteEnum.Val; + + _fld6._0 = 2; + _fld6._struct = _fld5; + + _fld7._1 = EnumAlignment.LongEnum.Val; + _fld7._2 = EnumAlignment.IntEnum.Val; + _fld7._3 = EnumAlignment.LongEnum.Val; + _fld7._4 = EnumAlignment.IntEnum.Val; + + _fld8._0 = 3; + _fld8._struct = _fld7; + + _fld9._1 = EnumAlignment.IntEnum.Val; + _fld9._2 = EnumAlignment.ShortEnum.Val; + _fld9._3 = EnumAlignment.IntEnum.Val; + _fld9._4 = EnumAlignment.ShortEnum.Val; + + _fld10._0 = 4; + _fld10._struct = _fld9; + + _fld11._1 = EnumAlignment.ShortEnum.Val; + _fld11._2 = EnumAlignment.ByteEnum.Val; + _fld11._3 = EnumAlignment.ShortEnum.Val; + _fld11._4 = EnumAlignment.ByteEnum.Val; + + _fld12._0 = 5; + _fld12._struct = _fld11; + } +} + +class AutoTest +{ + static Auto.StructWithBool _fld1; + static Auto.StructWithIntChar _fld2; + static Auto.StructWithChar _fld3; + static Auto.ClassContainingStructs _fld4 = new Auto.ClassContainingStructs(); + static Auto.BaseClass7BytesRemaining _fld5 = new Auto.BaseClass7BytesRemaining(); + static Auto.BaseClass4BytesRemaining _fld6 = new Auto.BaseClass4BytesRemaining(); + static Auto.BaseClass3BytesRemaining _fld7 = new Auto.BaseClass3BytesRemaining(); + static Auto.OptimizePartial _fld8 = new Auto.OptimizePartial(); + static Auto.Optimize7Bools _fld9 = new Auto.Optimize7Bools(); + static Auto.OptimizeAlignedFields _fld10 = new Auto.OptimizeAlignedFields(); + static Auto.OptimizeLargestField _fld11 = new Auto.OptimizeLargestField(); + static Auto.NoOptimizeMisaligned _fld12 = new Auto.NoOptimizeMisaligned(); + static Auto.NoOptimizeCharAtSize2Alignment _fld13 = new Auto.NoOptimizeCharAtSize2Alignment(); + static Auto.MinPacking _fld14 = new Auto.MinPacking(); + + public static void Test() + { + _fld1.MyStructBool = true; + + _fld2.MyStructInt = 1; + _fld2.MyStructChar = 'A'; + + _fld3.MyStructChar = 'B'; + + _fld4.MyStructWithChar = _fld3; + _fld4.MyStructWithIntChar = _fld2; + _fld4.MyStructWithBool = _fld1; + _fld4.MyString1 = "Str"; + _fld4.MyBool1 = false; + _fld4.MyBool2 = true; + + _fld5.MyBool1 = false; + _fld5.MyLong1 = 2; + _fld5.MyString1 = "Str2"; + _fld5.MyDouble1 = 1.0; + _fld5.MyByteArray1 = new byte[3]; + + _fld6.MyLong1 = 3; + _fld6.MyUint1 = 4; + + _fld7.MyBool1 = true; + _fld7.MyInt1 = 5; + _fld7.MyString1 = "str3"; + + _fld8.OptBool = false; + _fld8.OptChar = 'B'; + _fld8.NoOptLong = 6; + _fld8.NoOptString = "STR4"; + + _fld9.OptBool1 = true; + _fld9.OptBool2 = false; + _fld9.OptBool3 = true; + _fld9.OptBool4 = true; + _fld9.OptBool5 = false; + _fld9.OptBool6 = true; + _fld9.OptBool7 = false; + _fld9.NoOptBool8 = true; + _fld9.NoOptString = "STR5"; + + _fld10.OptBool1 = false; + _fld10.OptBool2 = true; + _fld10.OptBool3 = false; + _fld10.NoOptBool4 = true; + _fld10.OptChar1 = 'C'; + _fld10.OptChar2 = 'D'; + _fld10.NoOptString = "STR6"; + + _fld13.NoOptChar = 'E'; + + _fld14._value = 7; + _fld14._byte = 8; + } +} + +class AutoTestWithVector +{ + static Auto.int8x16x2 _fld1 = new Auto.int8x16x2(); + static Auto.Wrapper_int8x16x2 _fld2 = new Auto.Wrapper_int8x16x2(); + static Auto.Wrapper_int8x16x2_2 _fld3 = new Auto.Wrapper_int8x16x2_2(); + + public static void Test() + { + _fld1._0 = new Vector128(); + _fld1._1 = new Vector128(); + + _fld2.fld = _fld1; + + _fld3.fld1 = true; + _fld3.fld2 = _fld1; + } +} + +class SequentialTest +{ + static Sequential.Class1 _fld1 = new Sequential.Class1(); + static Sequential.Class2 _fld2 = new Sequential.Class2(); + static Sequential.Struct0 _fld3; + static Sequential.Struct1 _fld4; + static Sequential.ClassDoubleBool _fld5 = new Sequential.ClassDoubleBool(); + static Sequential.ClassBoolDoubleBool _fld6 = new Sequential.ClassBoolDoubleBool(); + static Sequential.StructStructByte_StructByteAuto _fld7; + static Sequential.StructStructByte_Struct2BytesAuto _fld8; + static Sequential.StructStructByte_Struct3BytesAuto _fld9; + static Sequential.StructStructByte_Struct4BytesAuto _fld10; + static Sequential.StructStructByte_Struct5BytesAuto _fld11; + static Sequential.StructStructByte_Struct8BytesAuto _fld12; + static Sequential.StructStructByte_Struct9BytesAuto _fld13; + static Sequential.StructStructByte_Int128StructAuto _fld14; + static Sequential.StructStructByte_UInt128StructAuto _fld15; + + public static void Test() + { + _fld1.MyClass1SelfRef = _fld1; + _fld1.MyChar = 'A'; + _fld1.MyInt = 1; + _fld1.MyString = "STR"; + _fld1.MyBool = true; + + _fld2.MyClass1SelfRef = _fld1; + _fld2.MyChar = 'B'; + _fld2.MyInt = 2; + _fld2.MyString = "STR2"; + _fld2.MyBool = false; + _fld2.MyInt2 = 3; + + _fld3.b1 = true; + _fld3.b2 = false; + _fld3.b3 = true; + _fld3.i1 = 4; + _fld3.s1 = "str"; + + _fld4.MyStruct0 = _fld3; + _fld4.MyBool = false; + + _fld5.bool1 = true; + _fld5.double1 = 1.0; + + _fld6.bool1 = false; + _fld6.bool2 = true; + _fld6.double1 = 2.0; + + _fld7.fld2 = default(Auto.StructByte); + _fld8.fld2 = default(Auto.Struct2Bytes); + _fld9.fld2 = default(Auto.Struct3Bytes); + _fld10.fld2 = default(Auto.Struct4Bytes); + _fld11.fld2 = default(Auto.Struct5Bytes); + _fld12.fld2 = default(Auto.Struct8Bytes); + _fld13.fld2 = default(Auto.Struct9Bytes); + _fld14.fld2 = default(Auto.Int128Struct); + _fld15.fld2 = default(Auto.UInt128Struct); + } +} + +class ExplicitTest +{ + static Explicit.Class1 _fld1 = new Explicit.Class1(); + static Explicit.Class2 _fld2 = new Explicit.Class2(); + static Explicit.ExplicitSize _fld3 = new Explicit.ExplicitSize(); + static Explicit.ExplicitEmptyClass _fld4 = new Explicit.ExplicitEmptyClass(); + static Explicit.ExplicitEmptyClassSize0 _fld5 = new Explicit.ExplicitEmptyClassSize0(); + static Explicit.ExplicitEmptyStruct _fld6 = new Explicit.ExplicitEmptyStruct(); + + public static void Test() + { + _fld1.Bar = true; + _fld1.Baz = 'A'; + + _fld2.Baz = 'B'; + _fld2.Bar = false; + _fld2.Lol = 1; + _fld2.Omg = 2; + + _fld3.Omg = 3; + _fld3.Lol = 4; + } +} +class ContainsGCPointersFieldsTest +{ + static ContainsGCPointers.NoPointers _fld1; + static ContainsGCPointers.StillNoPointers _fld2; + static ContainsGCPointers.ClassNoPointers _fld3 = new ContainsGCPointers.ClassNoPointers(); + static ContainsGCPointers.HasPointers _fld4; + static ContainsGCPointers.FieldHasPointers _fld5; + static ContainsGCPointers.ClassHasPointers _fld6 = new ContainsGCPointers.ClassHasPointers(); + static ContainsGCPointers.BaseClassHasPointers _fld7 = new ContainsGCPointers.BaseClassHasPointers(); + static ContainsGCPointers.ClassHasIntArray _fld8 = new ContainsGCPointers.ClassHasIntArray(); + static ContainsGCPointers.ClassHasArrayOfClassType _fld9 = new ContainsGCPointers.ClassHasArrayOfClassType(); + + public static void Test() + { + _fld1.int1 = 1; + _fld1.byte1 = 2; + _fld1.char1 = '0'; + _fld2.bool1 = true; + + _fld2.noPointers1 = _fld1; + + _fld3.char1 = '2'; + + _fld4.string1 = "STR"; + + _fld5.hasPointers1.string1 = "STR2"; + + _fld6.classHasPointers1 = new ContainsGCPointers.ClassHasPointers(); + + _fld7.classHasPointers1 = new ContainsGCPointers.ClassHasPointers(); + + _fld8.intArrayField = new int[1]; + + _fld9.classTypeArray = new ContainsGCPointers.ClassNoPointers[1]; + } +} diff --git a/src/tests/readytorun/tests/main.cs b/src/tests/readytorun/tests/main.cs index 02d94a5939378a..04314860d48ca0 100644 --- a/src/tests/readytorun/tests/main.cs +++ b/src/tests/readytorun/tests/main.cs @@ -414,6 +414,13 @@ static void RVAFieldTest() Assert.AreEqual(value[i], (byte)(9 - i)); } + // public constructor, so we run something when loading from byte array in the test below + public Program() + { + // do something in the constructor to see if it works + TestVirtualMethodCalls(); + } + static void TestLoadR2RImageFromByteArray() { Assembly assembly1 = typeof(Program).Assembly; @@ -422,6 +429,8 @@ static void TestLoadR2RImageFromByteArray() Assembly assembly2 = Assembly.Load(array); Assert.AreEqual(assembly2.FullName, assembly1.FullName); + + assembly2.CreateInstance("Program"); } [MethodImplAttribute(MethodImplOptions.NoInlining)] @@ -513,9 +522,8 @@ static void RunAllTests() Console.WriteLine("RVAFieldTest"); RVAFieldTest(); -// Disable for https://github.com/dotnet/runtime/issues/71507 -// Console.WriteLine("TestLoadR2RImageFromByteArray"); -// TestLoadR2RImageFromByteArray(); + Console.WriteLine("TestLoadR2RImageFromByteArray"); + TestLoadR2RImageFromByteArray(); Console.WriteLine("TestILBodyChange"); TestILBodyChange(); diff --git a/src/workloads/workloads.csproj b/src/workloads/workloads.csproj index e2e96fd572c17e..8f44cad6917641 100644 --- a/src/workloads/workloads.csproj +++ b/src/workloads/workloads.csproj @@ -7,6 +7,7 @@ false $(ArtifactsObjDir)workloads/ + $(WorkloadIntermediateOutputPath)VS/ $(ArtifactsBinDir)workloads/ $(workloadArtifactsPath)/ $(ArtifactsShippingPackagesDir) @@ -28,10 +29,10 @@ - + - + @@ -40,43 +41,52 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + <_ComponentResources Include="microsoft-net-runtime-mono-tooling-net6" Title=".NET 6.0 Shared Mobile Build Tools" + Description="Shared build tasks for mobile platform development."/> + <_ComponentResources Include="wasm-tools-net6" Title=".NET 6.0 WebAssembly Build Tools" + Description="Build tools for net6.0 WebAssembly ahead-of-time (AoT) compilation and native linking."/> + <_ComponentResources Include="microsoft-net-runtime-android-net6" Title=".NET 6.0 Android Build Tools" + Description="Build tools for net6.0 Android compilation and native linking."/> + <_ComponentResources Include="microsoft-net-runtime-android-aot-net6" Title=".NET 6.0 Android Build Tools (AoT)" + Description="Build tools for net6.0 Android ahead-of-time (AoT) compilation and native linking."/> + <_ComponentResources Include="microsoft-net-runtime-ios-net6" Title=".NET 6.0 iOS Build Tools" + Description="Build tools for net6.0 iOS compilation and native linking."/> + <_ComponentResources Include="microsoft-net-runtime-tvos-net" Title=".NET 6.0 tvOS Build Tools" + Description="Build tools for net6.0 tvOS compilation and native linking."/> + <_ComponentResources Include="microsoft-net-runtime-maccatalyst-net6" Title=".NET 6.0 Mac Catalyst Build Tools" + Description="Build tools for net6.0 Mac Catalyst compilation and native linking."/> + <_ComponentResources Include="runtimes-ios-net6" Title=".NET 6.0 iOS Runtimes" + Description=".NET 6.0 runtime components for iOS execution."/> + <_ComponentResources Include="runtimes-tvos-net6" Title=".NET 6.0 tvOS Build Tools" + Description=".NET 6.0 runtime components for tvOS execution."/> + <_ComponentResources Include="runtimes-maccatalyst-net6" Title=".NET 6.0 Mac Catalyst Build Tools" + Description=".NET 6.0 runtime components for Mac Catalyst execution."/> + <_ComponentResources Include="runtimes-windows-net6" Title=".NET 6.0 Windows Runtimes" + Description=".NET 6.0 runtime components for Windows execution."/> + + @@ -86,6 +96,10 @@ + + + Mono. + Microsoft @@ -95,54 +109,65 @@ - + + - - - - - + - + - - - + + + + + + - - - + + + + + + + + + + + + + + - - - + - + + - - + @@ -169,15 +194,7 @@ - - - - - - - - - +